diff --git a/.github/workflows/build-android-libs.yml b/.github/workflows/build-android-libs.yml index b3c3990..116025e 100644 --- a/.github/workflows/build-android-libs.yml +++ b/.github/workflows/build-android-libs.yml @@ -11,18 +11,18 @@ on: ghostty_commit: description: ghostty commit to build (C ABI is not yet stable — always pin) required: true - default: b0947378349eff70f7030dda0e6d022fae1e6fbd + default: 3c1ef5b32fc5ea6b93d28493fabf193f595139cf permissions: contents: write env: # ghostty's requireZig hard-pins the minor version. - ZIG_VERSION: 0.15.2 + ZIG_VERSION: 0.16.0 NDK_VERSION: 27.1.12297006 # Symbols-only Nerd Font bundled as an AAR asset for PUA glyph cells. - NERD_FONTS_VERSION: v3.4.0 - NERD_FONTS_SHA256: 8e617904b980fe3648a4b116808788fe50c99d2d495376cb7c0badbd8a564c47 + NERD_FONTS_VERSION: v3.5.1 + NERD_FONTS_SHA256: fdca3682534f6f65e1ccb2345b0362ccf67d9b8eca7c8025330946e93e2473bc jobs: build: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 000ca1b..b3d6802 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,12 +38,16 @@ jobs: with: node-version: 26 - - uses: actions/setup-java@v5 + - uses: actions/setup-java@v6 with: distribution: temurin java-version: 17 - - uses: gradle/actions/setup-gradle@v5 + - uses: gradle/actions/setup-gradle@v6 + with: + # v6's cache is a separate proprietary component. Keep CI on the + # MIT-licensed action and skip that cache. + cache-disabled: true # postinstall fetches the pinned libghostty-vt vendor tarball the # CMake build links against. diff --git a/CHANGELOG.md b/CHANGELOG.md index aef9d93..fa4cc92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,19 @@ ### 💡 Others +- Dependency bump: libghostty-spm 1.3.1 → 1.5.20260903 (XCFramework + `upstream.c4e16970a803`, ghostty `c4e16970`), MSDisplayLink 2.1.0 → 2.2.0, + libghostty-vt to ghostty `3c1ef5b` (Zig 0.16.0, Nerd Fonts v3.5.1). + Android JNI follows the new vt C API (`ghostty_terminal_new` cols/rows, + mode query via `GHOSTTY_TERMINAL_DATA_MODE`, colors via + `GHOSTTY_RENDER_STATE_DATA_COLORS`). Expo SDK 57.0.19 / React Native + 0.86.3, ESLint 10.9.1 and typescript-eslint 8.69.0. `tsc` is TypeScript + 7.0.2 (`@typescript/native`); the `typescript` package is + `@typescript/typescript6` so ESLint still has a compiler API (TypeScript + 7 has none until 7.1). +- iOS: `clipboard-write = ask` so OSC 52 cannot silently replace the + system pasteboard. User paste still proceeds; program clipboard + read/write is denied until a host supplies confirmation UI. - First automated test suites: vitest covers the TerminalView imperative queue (the 0.8.1 mount-race contract), and JUnit covers the Android snapshot wire format, cell color resolution, and the sticky-modifier state diff --git a/README.md b/README.md index 0afbd98..a73f70c 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ dependencies this layer disappears in favor of the upstream package. On Android, `android/vendor/` holds per-ABI `libghostty-vt.a` static libraries plus the matching C headers, cross-compiled from a pinned ghostty -commit (Zig 0.15.2 + NDK r27); `vendor-manifest.json` pins the tarball +commit (Zig 0.16.0 + NDK r27); `vendor-manifest.json` pins the tarball checksum. A thin JNI shim (`android/src/main/cpp/ghostty_jni.cpp`) exposes the terminal + render-state loop to Kotlin, which paints the grid with Canvas/Skia (`GhosttyTerminalView.kt`). diff --git a/android/src/main/cpp/ghostty_jni.cpp b/android/src/main/cpp/ghostty_jni.cpp index d7a6c18..7141174 100644 --- a/android/src/main/cpp/ghostty_jni.cpp +++ b/android/src/main/cpp/ghostty_jni.cpp @@ -268,12 +268,9 @@ JNIEXPORT jlong JNICALL Java_expo_modules_libghostty_GhosttyVt_nativeCreate( JNIEnv*, jobject, jint cols, jint rows, jlong maxScrollback) { auto* session = new Session(); - GhosttyTerminalOptions opts{}; - opts.cols = static_cast(cols); - opts.rows = static_cast(rows); - opts.max_scrollback = static_cast(maxScrollback); - if (ghostty_terminal_new(nullptr, &session->term, opts) != GHOSTTY_SUCCESS || + if (ghostty_terminal_new(nullptr, &session->term, static_cast(cols), + static_cast(rows)) != GHOSTTY_SUCCESS || ghostty_render_state_new(nullptr, &session->renderState) != GHOSTTY_SUCCESS || ghostty_render_state_row_iterator_new(nullptr, &session->rowIter) != GHOSTTY_SUCCESS || ghostty_render_state_row_cells_new(nullptr, &session->cells) != GHOSTTY_SUCCESS || @@ -284,6 +281,9 @@ Java_expo_modules_libghostty_GhosttyVt_nativeCreate( return 0; } + const size_t maxLines = static_cast(maxScrollback); + ghostty_terminal_set(session->term, GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_LINES, &maxLines); + ghostty_terminal_set(session->term, GHOSTTY_TERMINAL_OPT_USERDATA, session); ghostty_terminal_set(session->term, GHOSTTY_TERMINAL_OPT_WRITE_PTY, reinterpret_cast(&writePtyCallback)); @@ -487,9 +487,8 @@ Java_expo_modules_libghostty_GhosttyVt_nativeSnapshot( ghostty_render_state_get(session->renderState, GHOSTTY_RENDER_STATE_DATA_COLS, &cols); ghostty_render_state_get(session->renderState, GHOSTTY_RENDER_STATE_DATA_ROWS, &rows); - GhosttyRenderStateColors colors{}; - colors.size = sizeof(colors); - ghostty_render_state_colors_get(session->renderState, &colors); + GhosttyRenderStateColors colors = GHOSTTY_INIT_SIZED(GhosttyRenderStateColors); + ghostty_render_state_get(session->renderState, GHOSTTY_RENDER_STATE_DATA_COLORS, &colors); bool cursorVisible = false; bool cursorBlinking = false; @@ -814,8 +813,10 @@ Java_expo_modules_libghostty_GhosttyVt_nativeEncodePaste( env->GetByteArrayRegion(data, 0, len, reinterpret_cast(input.data())); } - bool bracketed = false; - ghostty_terminal_mode_get(session->term, GHOSTTY_MODE_BRACKETED_PASTE, &bracketed); + GhosttyTerminalModeConfig modeConfig{}; + modeConfig.mode = GHOSTTY_MODE_BRACKETED_PASTE; + ghostty_terminal_get(session->term, GHOSTTY_TERMINAL_DATA_MODE, &modeConfig); + const bool bracketed = modeConfig.value; // Bracketed wrapping adds 12 bytes; stripping/CR replacement is 1:1. std::vector out(input.size() + 16); diff --git a/eslint.config.cjs b/eslint.config.cjs index 7c0fd9a..b78765d 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -2,4 +2,11 @@ const { defineConfig } = require('eslint/config'); const universe = require('eslint-config-universe/flat/native'); const universeWeb = require('eslint-config-universe/flat/web'); -module.exports = defineConfig([{ ignores: ['build'] }, ...universe, ...universeWeb]); +module.exports = defineConfig([ + { ignores: ['build'] }, + ...universe, + ...universeWeb, + // eslint-plugin-react 7.37.5 is not ESLint 10-ready. Pinning the React + // version skips detectReactVersion(), which still calls context.getFilename(). + { settings: { react: { version: '19.2' } } }, +]); diff --git a/example/package.json b/example/package.json index d9e05d7..d9701ac 100644 --- a/example/package.json +++ b/example/package.json @@ -3,14 +3,14 @@ "version": "1.0.0", "main": "index.ts", "dependencies": { - "expo": "~57.0.6", - "react": "19.2.3", - "react-native": "0.86.0" + "expo": "~57.0.19", + "react": "19.2.8", + "react-native": "0.86.3" }, "devDependencies": { - "@types/react": "~19.2.2", - "babel-preset-expo": "^57.0.3", - "typescript": "~6.0.3" + "@types/react": "~19.2.18", + "babel-preset-expo": "^57.0.10", + "typescript": "~7.0.2" }, "scripts": { "start": "expo start", diff --git a/ios/ExpoLibghostty.podspec b/ios/ExpoLibghostty.podspec index 184f717..8564d9d 100644 --- a/ios/ExpoLibghostty.podspec +++ b/ios/ExpoLibghostty.podspec @@ -26,5 +26,6 @@ Pod::Spec.new do |s| # Module sources only — the vendored pods own everything under vendor/. s.source_files = '*.{h,m,mm,swift,hpp,cpp}' + s.exclude_files = 'GhosttyTerminalBundle+CocoaPods.swift' s.resource_bundles = { 'ExpoLibghostty_privacy' => 'privacy/ExpoLibghostty/PrivacyInfo.xcprivacy' } end diff --git a/ios/ExpoLibghosttyView.swift b/ios/ExpoLibghosttyView.swift index 4ea081b..613b00d 100644 --- a/ios/ExpoLibghosttyView.swift +++ b/ios/ExpoLibghosttyView.swift @@ -66,6 +66,11 @@ class ExpoLibghosttyView: ExpoView { self.session = session // Without a controller the coordinator never builds a surface // ("surface rebuild skipped: missing controller"). + // OSC 52 writes must ask; the default `clipboard-write = allow` + // would let PTY output silently replace the system pasteboard. + _ = TerminalController.shared.setTerminalConfiguration( + TerminalConfiguration().custom("clipboard-write", "ask") + ) terminalView.controller = TerminalController.shared terminalView.configuration = TerminalSurfaceOptions(backend: .inMemory(session)) terminalView.autoresizingMask = [.flexibleWidth, .flexibleHeight] @@ -96,7 +101,8 @@ class ExpoLibghosttyView: ExpoView { // Terminal effects (OSC escapes) surfaced as component events. extension ExpoLibghosttyView: TerminalSurfaceBellDelegate, TerminalSurfaceTitleDelegate, - TerminalSurfacePwdDelegate { + TerminalSurfacePwdDelegate, TerminalSurfaceClipboardConfirmationDelegate +{ func terminalDidRingBell() { onBell([:]) } @@ -108,4 +114,10 @@ extension ExpoLibghosttyView: TerminalSurfaceBellDelegate, TerminalSurfaceTitleD func terminalDidChangeWorkingDirectory(_ path: String) { onDirectoryChange(["path": path]) } + + func terminalDidRequestClipboardConfirmation(_ request: TerminalClipboardConfirmationRequest) { + // User-started paste can proceed. A program's OSC 52 read/write is + // denied until a host wires its own confirmation UI. + request.respond(allow: request.kind == .paste) + } } diff --git a/ios/GhosttyKit.podspec b/ios/GhosttyKit.podspec index 0107d40..dcec867 100644 --- a/ios/GhosttyKit.podspec +++ b/ios/GhosttyKit.podspec @@ -3,7 +3,7 @@ # scripts/download-xcframework.mjs fetches (checksum-pinned) at install time. Pod::Spec.new do |s| s.name = 'GhosttyKit' - s.version = '1.3.1' + s.version = '1.5.20260903' s.summary = "Ghostty's libghostty C API for Apple platforms (vendored by expo-libghostty)." s.author = { 'Lakr233' => 'https://github.com/Lakr233' } s.homepage = 'https://github.com/Lakr233/libghostty-spm' diff --git a/ios/GhosttyTerminal.podspec b/ios/GhosttyTerminal.podspec index 3de2a6c..76db1aa 100644 --- a/ios/GhosttyTerminal.podspec +++ b/ios/GhosttyTerminal.podspec @@ -2,7 +2,7 @@ # Swift wrapper layer: native terminal views, input/IME handling, display link. Pod::Spec.new do |s| s.name = 'GhosttyTerminal' - s.version = '1.3.1' + s.version = '1.5.20260903' s.summary = 'Ghostty-powered native terminal view (vendored by expo-libghostty).' s.author = { 'Lakr233' => 'https://github.com/Lakr233' } s.homepage = 'https://github.com/Lakr233/libghostty-spm' @@ -16,6 +16,14 @@ Pod::Spec.new do |s| s.dependency 'GhosttyKit' s.dependency 'MSDisplayLink' - s.source_files = 'vendor/GhosttyTerminal/**/*.swift' - s.resource_bundles = { 'GhosttyTerminal_privacy' => 'privacy/GhosttyTerminal/PrivacyInfo.xcprivacy' } + s.source_files = 'vendor/GhosttyTerminal/**/*.swift', 'GhosttyTerminalBundle+CocoaPods.swift' + # SPM generates Bundle.module for Resources/{Ghostty,terminfo}; CocoaPods + # needs an explicit resource bundle plus the Bundle.module shim. + s.resource_bundles = { + 'GhosttyTerminal_privacy' => 'privacy/GhosttyTerminal/PrivacyInfo.xcprivacy', + 'GhosttyTerminal' => [ + 'vendor/GhosttyTerminal/Resources/Ghostty', + 'vendor/GhosttyTerminal/Resources/terminfo', + ], + } end diff --git a/ios/GhosttyTerminalBundle+CocoaPods.swift b/ios/GhosttyTerminalBundle+CocoaPods.swift new file mode 100644 index 0000000..66707e0 --- /dev/null +++ b/ios/GhosttyTerminalBundle+CocoaPods.swift @@ -0,0 +1,19 @@ +import Foundation + +#if !SWIFT_PACKAGE +// SPM synthesizes Bundle.module from Package.swift resources. CocoaPods +// does not, so the vendored GhosttyRuntimeResources lookups need this +// shim. The GhosttyTerminal resource bundle holds Resources/Ghostty and +// Resources/terminfo (shell integration + compiled terminfo). +extension Bundle { + static var module: Bundle { + let host = Bundle(for: TerminalController.self) + if let url = host.url(forResource: "GhosttyTerminal", withExtension: "bundle"), + let bundle = Bundle(url: url) + { + return bundle + } + return host + } +} +#endif diff --git a/ios/MSDisplayLink.podspec b/ios/MSDisplayLink.podspec index c535525..1e66c85 100644 --- a/ios/MSDisplayLink.podspec +++ b/ios/MSDisplayLink.podspec @@ -2,7 +2,7 @@ # Upstream ships SPM only; this pod exists because CocoaPods cannot consume SPM packages. Pod::Spec.new do |s| s.name = 'MSDisplayLink' - s.version = '2.1.0' + s.version = '2.2.0' s.summary = 'Cross-platform DisplayLink (vendored by expo-libghostty).' s.author = { 'Lakr233' => 'https://github.com/Lakr233' } s.homepage = 'https://github.com/Lakr233/MSDisplayLink' diff --git a/ios/vendor/GhosttyTerminal/Configuration/GhosttyRuntimeResources.swift b/ios/vendor/GhosttyTerminal/Configuration/GhosttyRuntimeResources.swift new file mode 100644 index 0000000..659fc21 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Configuration/GhosttyRuntimeResources.swift @@ -0,0 +1,27 @@ +import Darwin +import Foundation + +/// Runtime assets required by Ghostty's exec backend. +/// +/// The package owns these assets and always points libghostty at this immutable +/// bundle location before the C runtime initializes. User-level Ghostty +/// resources and configuration are never consulted. +public enum GhosttyRuntimeResources { + /// The package-bundled Ghostty resource directory. + /// + /// Ghostty expects shell integration below this directory and its compiled + /// terminfo database in a sibling `terminfo` directory. + public static var directoryURL: URL? { + Bundle.module.url(forResource: "Ghostty", withExtension: nil) + } + + /// The compiled terminfo database exported to child shells by Ghostty. + public static var terminfoDirectoryURL: URL? { + Bundle.module.url(forResource: "terminfo", withExtension: nil) + } + + static func configureEnvironment() { + guard let path = directoryURL?.path else { return } + setenv("GHOSTTY_RESOURCES_DIR", path, 1) + } +} diff --git a/ios/vendor/GhosttyTerminal/Configuration/TerminalConfiguration.swift b/ios/vendor/GhosttyTerminal/Configuration/TerminalConfiguration.swift index fafe5b5..f156768 100644 --- a/ios/vendor/GhosttyTerminal/Configuration/TerminalConfiguration.swift +++ b/ios/vendor/GhosttyTerminal/Configuration/TerminalConfiguration.swift @@ -5,6 +5,8 @@ // Created by Lakr233 on 2026/3/17. // +import Foundation + public enum TerminalCursorStyle: String, Sendable, Hashable { case block case bar @@ -51,7 +53,7 @@ public enum TerminalConfigCommand: Sendable, Hashable { "font-family = \(value)" case let .fontSize(value): - "font-size = \(value.formatted(.number.precision(.fractionLength(0 ... 2))))" + "font-size = \(configLiteral(value, maximumFractionDigits: 2))" case let .fontThicken(enabled): "font-thicken = \(enabled)" @@ -72,7 +74,7 @@ public enum TerminalConfigCommand: Sendable, Hashable { "cursor-text = \(value)" case let .cursorOpacity(value): - "cursor-opacity = \(value.formatted(.number.precision(.fractionLength(0 ... 3))))" + "cursor-opacity = \(configLiteral(value, maximumFractionDigits: 3))" case let .background(value): "background = \(value)" @@ -93,10 +95,10 @@ public enum TerminalConfigCommand: Sendable, Hashable { "palette = \(index)=\(color)" case let .minimumContrast(value): - "minimum-contrast = \(value.formatted(.number.precision(.fractionLength(0 ... 2))))" + "minimum-contrast = \(configLiteral(value, maximumFractionDigits: 2))" case let .backgroundOpacity(value): - "background-opacity = \(value.formatted(.number.precision(.fractionLength(0 ... 3))))" + "background-opacity = \(configLiteral(value, maximumFractionDigits: 3))" case let .backgroundBlur(value): "background-blur = \(value)" @@ -111,6 +113,20 @@ public enum TerminalConfigCommand: Sendable, Hashable { "\(key) = \(value)" } } + + // ghostty parses the value with std.fmt.parseFloat, which accepts only a + // `.` separator and ASCII digits; the default style follows the device + // region (`13,5` in de_DE, `١٤` in ar_EG) and the whole config is rejected. + private func configLiteral( + _ value: Value, + maximumFractionDigits: Int + ) -> String { + value.formatted( + FloatingPointFormatStyle(locale: Locale(identifier: "en_US_POSIX")) + .precision(.fractionLength(0 ... maximumFractionDigits)) + .grouping(.never) + ) + } } public struct TerminalConfiguration: Sendable, Hashable { diff --git a/ios/vendor/GhosttyTerminal/Controller/TerminalController+Callbacks.swift b/ios/vendor/GhosttyTerminal/Controller/TerminalController+Callbacks.swift index 3147816..dc2ab7f 100644 --- a/ios/vendor/GhosttyTerminal/Controller/TerminalController+Callbacks.swift +++ b/ios/vendor/GhosttyTerminal/Controller/TerminalController+Callbacks.swift @@ -36,11 +36,18 @@ private enum TerminalCallbacks { let bridge = Unmanaged .fromOpaque(bridgePtr) .takeUnretainedValue() - terminalRunOnMain { + guard Thread.isMainThread else { + terminalRunOnMain { bridge.handleAction(action) } + return false + } + return MainActor.assumeIsolated { bridge.handleAction(action) + // Core spawns /usr/bin/open for an open_url reported unhandled, + // so a host delegate that took the URL is reported as handling + // it. Both open_url emitters run on the main thread. + return action.tag == GHOSTTY_ACTION_OPEN_URL + && bridge.delegate is any TerminalSurfaceOpenURLDelegate } - - return false } static func closeSurface( @@ -56,82 +63,335 @@ private enum TerminalCallbacks { } } + /// A program wrote the clipboard (OSC 52), or a copy binding did. + /// + /// `confirm` is ghostty's `clipboard-write = ask`: the write must not + /// land until the host has asked. That goes through the same + /// confirmation delegate as a protected read; a host without one denies + /// it, as it does the read. The default configuration allows writes + /// outright, and those land immediately. + /// + /// Writes one or more MIME-typed representations to the system + /// pasteboard. `ghostty_clipboard_content_s` is binary-safe with an + /// explicit `len` and is **not** guaranteed NUL-terminated, so every + /// representation must be decoded length-bounded rather than via + /// `String(cString:)` — a NUL-terminated read here would compile clean + /// and read past the end of the buffer. static func writeClipboard( - userdata _: UnsafeMutableRawPointer?, - clipboard _: ghostty_clipboard_e, + userdata: UnsafeMutableRawPointer?, + clipboard: ghostty_clipboard_e, contents: UnsafePointer?, contentsLen: Int, - confirm _: Bool + confirm: Bool ) { - guard contentsLen > 0 else { return } - guard let content = contents?.pointee else { return } - guard let data = content.data else { return } - let string = String(cString: data) + // The selection clipboard is advertised (`supports_selection_clipboard`) + // so that `copy-on-select` writes there and not to the one pasteboard + // the user has — otherwise every drag, and every double-click a tap + // lands within ghostty's click interval, would replace it. Nothing + // exposes a primary selection, so those writes go nowhere. + guard clipboard == GHOSTTY_CLIPBOARD_STANDARD else { return } + + let payloads = copyClipboardContents(contents, count: contentsLen) + guard !payloads.isEmpty else { return } + + TerminalDebugLog.log( + .input, + "clipboard write count=\(payloads.count) mimes=\(payloads.map(\.mime).joined(separator: ",")) confirm=\(confirm)" + ) + + guard confirm else { + terminalRunOnMain { setPasteboardString(payloads) } + return + } + guard let userdata else { return } + let bridge = Unmanaged + .fromOpaque(userdata) + .takeUnretainedValue() + let text = firstTextRepresentation(in: payloads) ?? "" + terminalRunOnMain { + bridge.handleClipboardConfirmation(contents: text, kind: .osc52Write) { allowed in + TerminalDebugLog.log( + .input, + allowed ? "clipboard write allowed" : "clipboard write denied" + ) + guard allowed else { return } + setPasteboardString(payloads) + } + } + } + @MainActor + private static func setPasteboardString(_ payloads: [TerminalClipboardContent]) { #if canImport(UIKit) - UIPasteboard.general.string = string + if let text = payloads.first(where: { isTextLikeMime($0.mime) }) { + UIPasteboard.general.string = String(decoding: text.data, as: UTF8.self) + } #elseif canImport(AppKit) let pasteboard = NSPasteboard.general pasteboard.clearContents() - pasteboard.setString(string, forType: .string) + for payload in payloads { + if isTextLikeMime(payload.mime) { + pasteboard.setString(String(decoding: payload.data, as: UTF8.self), forType: .string) + } else { + pasteboard.setData(payload.data, forType: NSPasteboard.PasteboardType(payload.mime)) + } + } #endif } + /// Reads the system pasteboard on behalf of a clipboard-read request + /// (a keybound paste, an OSC 52 read, or a Kitty clipboard read). This + /// callback only reports facts about the pasteboard — whether the read + /// started, found nothing to serve, or can't be served at all. + /// libghostty decides on its own, from the request's *type* (which this + /// callback is never told), whether the result additionally needs the + /// host's permission; when it does, it calls back through + /// `confirmReadClipboard` before anything reaches the requesting + /// program. static func readClipboard( userdata: UnsafeMutableRawPointer?, clipboard _: ghostty_clipboard_e, - opaquePtr: UnsafeMutableRawPointer? - ) -> Bool { - guard let userdata, let opaquePtr else { return false } + statePtr: UnsafeMutableRawPointer?, + mimes: UnsafePointer?>?, + mimesLen: Int, + listAvailable: Bool + ) -> ghostty_clipboard_read_result_e { + guard let userdata, let statePtr else { return GHOSTTY_CLIPBOARD_READ_UNSUPPORTED } let bridge = Unmanaged .fromOpaque(userdata) .takeUnretainedValue() - guard let surface = bridge.rawSurface else { return false } + guard bridge.rawSurface != nil else { return GHOSTTY_CLIPBOARD_READ_UNSUPPORTED } - #if canImport(UIKit) - let string = UIPasteboard.general.string - #elseif canImport(AppKit) - let string = NSPasteboard.general.string(forType: .string) - #endif + let requestedMimes = copyMimeList(mimes, count: mimesLen) + // A mode 5522 "list" request must not read any clipboard data — it + // only wants the type listing. + let wantsListOnly = mimesLen == 0 && listAvailable + // Everything we can serve is plain text; a caller asking only for + // something else has no representation we can fulfill. + let wantsUnsupportedMime = !wantsListOnly + && !requestedMimes.isEmpty + && !requestedMimes.contains(where: isTextLikeMime) + if wantsUnsupportedMime { + TerminalDebugLog.log(.input, "clipboard paste read unsupported mimes=\(requestedMimes)") + return GHOSTTY_CLIPBOARD_READ_UNSUPPORTED + } + + // Text and file URLs only, through the shared reader: a file copied + // in Finder or Files pastes as its escaped path, not its display + // name. This also serves a program's OSC 52 read, which must not + // write files as a side effect; a host paste that finds image or + // document data materialises it itself + // (`UITerminalView.pasteFromPasteboard`). + let string = TerminalPasteboardContent.text(from: .general) - guard let string else { + let hasText = string.map { !$0.isEmpty } ?? false + let available = listAvailable && hasText ? ["text/plain"] : [] + + guard !wantsListOnly else { + TerminalDebugLog.log(.input, "clipboard paste list available=\(available)") + bridge.completeClipboardRead(statePtr: statePtr, contents: [], available: available) + return GHOSTTY_CLIPBOARD_READ_STARTED + } + + guard let string, hasText else { TerminalDebugLog.log(.input, "clipboard paste read empty") - return false + return GHOSTTY_CLIPBOARD_READ_UNAVAILABLE } + TerminalDebugLog.log( .input, "clipboard paste read bytes=\(string.utf8.count) lines=\(TerminalInputText.lineCount(in: string))" ) - string.withCString { cString in - ghostty_surface_complete_clipboard_request(surface, cString, opaquePtr, false) - } + let content = TerminalClipboardContent(mime: "text/plain", data: Data(string.utf8)) + bridge.completeClipboardRead(statePtr: statePtr, contents: [content], available: available) TerminalDebugLog.log(.input, "clipboard paste complete") - return true + return GHOSTTY_CLIPBOARD_READ_STARTED } + /// libghostty determined the pending read needs the host's explicit + /// permission (OSC 52 / Kitty clipboard protocol) before it can reach + /// the requesting program. `confirm`'s contents are only borrowed for + /// this call — copy everything out before any hop or async work, per + /// the header's doc comment on `ghostty_runtime_confirm_read_clipboard_cb`. static func confirmReadClipboard( userdata: UnsafeMutableRawPointer?, - string: UnsafePointer?, - opaquePtr: UnsafeMutableRawPointer?, + confirm: UnsafePointer?, + statePtr: UnsafeMutableRawPointer?, request: ghostty_clipboard_request_e ) { - guard let userdata, let string, let opaquePtr else { return } + guard let userdata, let confirm, let statePtr else { return } let bridge = Unmanaged .fromOpaque(userdata) .takeUnretainedValue() - guard let surface = bridge.rawSurface else { return } - let text = String(cString: string) + let raw = confirm.pointee + let contents = copyClipboardContents(raw.contents, count: raw.contents_len) + let available = copyMimeList(raw.available, count: raw.available_len) + let text = firstTextRepresentation(in: contents) ?? "" + + // The new clipboard-request enum grew Kitty-clipboard-protocol and + // "list" cases that upstream's `TerminalClipboardRequestKind` (and + // its confirmation delegate) don't model yet. Deny outright rather + // than silently dropping the request unanswered: an unanswered + // request hangs the requesting program indefinitely. + guard let kind = TerminalClipboardRequestKind(request) else { + TerminalDebugLog.log( + .input, + "clipboard confirm denied: unrecognized request kind=\(request.rawValue)" + ) + // Round-tripped through a bit pattern, not captured directly: + // a raw pointer captured as-is by this main-actor-crossing + // closure trips Swift 6's sending-risks-data-race check. + let stateAddress = UInt(bitPattern: statePtr) + terminalRunOnMain { + guard let surface = bridge.rawSurface, + let statePtr = UnsafeMutableRawPointer(bitPattern: stateAddress) + else { return } + ghostty_surface_deny_clipboard_request(surface, statePtr) + } + return + } + TerminalDebugLog.log( .input, "clipboard paste confirm request=\(request.rawValue) bytes=\(text.utf8.count) lines=\(TerminalInputText.lineCount(in: text))" ) - text.withCString { cString in - ghostty_surface_complete_clipboard_request(surface, cString, opaquePtr, true) + + // Registered synchronously, before the hop to main: a surface + // teardown racing this callback must be able to find and deny this + // request the moment it's observable, not only after the hop lands. + let token = bridge.registerPendingClipboardRequest(statePtr) + + terminalRunOnMain { + bridge.handleClipboardConfirmation(contents: text, kind: kind) { allowed in + token.resolve(allowed, contents: contents, available: available) + } + } + } +} + +// MARK: - C interop helpers + +private func isTextLikeMime(_ mime: String) -> Bool { + mime == "text/plain" || mime.hasPrefix("text/plain;") +} + +private func firstTextRepresentation(in contents: [TerminalClipboardContent]) -> String? { + guard let chosen = contents.first(where: { isTextLikeMime($0.mime) }) ?? contents.first else { + return nil + } + return String(decoding: chosen.data, as: UTF8.self) +} + +private func copyClipboardContents( + _ ptr: UnsafePointer?, + count: Int +) -> [TerminalClipboardContent] { + guard let ptr, count > 0 else { return [] } + let buffer = UnsafeBufferPointer(start: ptr, count: count) + return buffer.compactMap { item in + guard let mimePtr = item.mime else { return nil } + let mime = String(cString: mimePtr) + guard let dataPtr = item.data, item.len > 0 else { + return TerminalClipboardContent(mime: mime, data: Data()) + } + let data = Data(bytes: UnsafeRawPointer(dataPtr), count: item.len) + return TerminalClipboardContent(mime: mime, data: data) + } +} + +private func copyMimeList( + _ ptr: UnsafePointer?>?, + count: Int +) -> [String] { + guard let ptr, count > 0 else { return [] } + let buffer = UnsafeBufferPointer(start: ptr, count: count) + return buffer.compactMap { $0.map { String(cString: $0) } } +} + +/// Builds a `ghostty_clipboard_complete_s` over freshly allocated, +/// caller-owned buffers and frees them after `body` returns. libghostty +/// only borrows the payload for the duration of the call (mirroring the +/// contract on `confirmReadClipboard`'s incoming payload), so nothing here +/// needs to outlive it. +func withClipboardCompletePayload( + contents: [TerminalClipboardContent], + available: [String], + confirmed: Bool, + remember: Bool, + _ body: (UnsafePointer) -> R +) -> R { + var mimeCStrings: [UnsafeMutablePointer?] = [] + var dataBuffers: [UnsafeMutableRawPointer] = [] + var availableCStrings: [UnsafeMutablePointer?] = [] + defer { + mimeCStrings.forEach { if let p = $0 { free(p) } } + dataBuffers.forEach { free($0) } + availableCStrings.forEach { if let p = $0 { free(p) } } + } + + let cContents: [ghostty_clipboard_content_s] = contents.map { content in + let mimePtr = strdup(content.mime) + mimeCStrings.append(mimePtr) + let byteCount = content.data.count + let dataPtr = UnsafeMutableRawPointer.allocate( + byteCount: max(byteCount, 1), + alignment: MemoryLayout.alignment + ) + if byteCount > 0 { + content.data.withUnsafeBytes { raw in + if let base = raw.baseAddress { + dataPtr.copyMemory(from: base, byteCount: byteCount) + } + } + } + dataBuffers.append(dataPtr) + return ghostty_clipboard_content_s( + mime: mimePtr.map { UnsafePointer($0) }, + data: dataPtr.assumingMemoryBound(to: CChar.self), + len: byteCount + ) + } + + availableCStrings = available.map { strdup($0) } + let availablePtrs: [UnsafePointer?] = availableCStrings.map { $0.map { UnsafePointer($0) } } + + return cContents.withUnsafeBufferPointer { contentsBuf in + availablePtrs.withUnsafeBufferPointer { availableBuf in + var complete = ghostty_clipboard_complete_s( + contents: contentsBuf.baseAddress, + contents_len: contentsBuf.count, + available: availableBuf.baseAddress, + available_len: availableBuf.count, + confirmed: confirmed, + remember: remember + ) + return withUnsafePointer(to: &complete) { body($0) } + } + } +} + +extension TerminalCallbackBridge { + /// Completes a clipboard read from `readClipboard` itself — always + /// `confirmed: false`, since this callback is never told whether the + /// request's type requires confirmation. libghostty diverts into + /// `confirmReadClipboard` on its own when it does. + nonisolated fileprivate func completeClipboardRead( + statePtr: UnsafeMutableRawPointer, + contents: [TerminalClipboardContent], + available: [String] + ) { + guard let surface = rawSurface else { return } + withClipboardCompletePayload( + contents: contents, + available: available, + confirmed: false, + remember: false + ) { complete in + ghostty_surface_complete_clipboard_request(surface, complete, statePtr) } - TerminalDebugLog.log(.input, "clipboard paste confirmed") } } @@ -173,25 +433,31 @@ func terminalControllerWriteClipboardCallback( func terminalControllerReadClipboardCallback( userdata: UnsafeMutableRawPointer?, clipboard: ghostty_clipboard_e, - opaquePtr: UnsafeMutableRawPointer? -) -> Bool { + statePtr: UnsafeMutableRawPointer?, + mimes: UnsafePointer?>?, + mimesLen: Int, + listAvailable: Bool +) -> ghostty_clipboard_read_result_e { TerminalCallbacks.readClipboard( userdata: userdata, clipboard: clipboard, - opaquePtr: opaquePtr + statePtr: statePtr, + mimes: mimes, + mimesLen: mimesLen, + listAvailable: listAvailable ) } func terminalControllerConfirmReadClipboardCallback( userdata: UnsafeMutableRawPointer?, - string: UnsafePointer?, - opaquePtr: UnsafeMutableRawPointer?, + confirm: UnsafePointer?, + statePtr: UnsafeMutableRawPointer?, request: ghostty_clipboard_request_e ) { TerminalCallbacks.confirmReadClipboard( userdata: userdata, - string: string, - opaquePtr: opaquePtr, + confirm: confirm, + statePtr: statePtr, request: request ) } diff --git a/ios/vendor/GhosttyTerminal/Controller/TerminalController+Config.swift b/ios/vendor/GhosttyTerminal/Controller/TerminalController+Config.swift index 60a35a8..a60cfac 100644 --- a/ios/vendor/GhosttyTerminal/Controller/TerminalController+Config.swift +++ b/ios/vendor/GhosttyTerminal/Controller/TerminalController+Config.swift @@ -9,12 +9,16 @@ import GhosttyKit extension TerminalController { @discardableResult public func updateConfigSource(_ source: ConfigSource) -> Bool { - guard source != configSource else { return true } - + // No same-source short-circuit: a host re-giving `.file(path)` is + // asking for the file to be read again. switch Self.prepareConfig(source: source) { case let .success(value): + // The new source becomes the base, the same way init loads + // it: applied bare, then theme and overrides on top. applyPreparedConfigToRuntime(value, source: source) - return true + baseConfigSource = source + baseConfigTemplate = value.renderedContents + return reconfigure() case let .failure(issue): lastConfigurationIssue = issue.description @@ -91,6 +95,8 @@ extension TerminalController { return } applyPreparedConfig(fallback, source: .none) + // The fallback loading is not the requested source loading. + lastConfigurationIssue = issue.description } } @@ -141,13 +147,17 @@ extension TerminalController { } case let .file(path): + // ghostty_config_load_file requires an absolute path: it takes + // dirname(path) as the base for relative includes, which is + // null for a bare filename. + let absolutePath = URL(fileURLWithPath: path).standardizedFileURL.path do { - resolvedContents = try String(contentsOfFile: path, encoding: .utf8) + resolvedContents = try String(contentsOfFile: absolutePath, encoding: .utf8) } catch { return .failure(ConfigurationIssue("failed to load ghostty config template: \(error)")) } managedConfigURL = nil - configPath = path + configPath = absolutePath } guard let rawValue = ghostty_config_new() else { diff --git a/ios/vendor/GhosttyTerminal/Controller/TerminalController+Surface.swift b/ios/vendor/GhosttyTerminal/Controller/TerminalController+Surface.swift index bf6f260..1d6f466 100644 --- a/ios/vendor/GhosttyTerminal/Controller/TerminalController+Surface.swift +++ b/ios/vendor/GhosttyTerminal/Controller/TerminalController+Surface.swift @@ -28,6 +28,10 @@ extension TerminalController { surfaceConfig.font_size = fontSize } + if let waitAfterCommand = configuration.waitAfterCommand { + surfaceConfig.wait_after_command = waitAfterCommand + } + // Like `working_directory` below, the pointers only need to outlive // `ghostty_surface_new`, which copies the values during surface init. return withEnvVarEntries(configuration.envVars) { entries, count in @@ -39,6 +43,7 @@ extension TerminalController { configuration: configuration, config: &surfaceConfig, workingDirectory: configuration.workingDirectory, + command: configuration.command, platformSetup: platformSetup ) } @@ -98,20 +103,53 @@ extension TerminalController { configuration: TerminalSurfaceOptions, config: inout ghostty_surface_config_s, workingDirectory: String?, + command: String?, platformSetup: (inout ghostty_surface_config_s) -> Void ) -> ghostty_surface_t? { guard let workingDirectory else { - return buildSurface( + return finalizeCommand( app: app, bridge: bridge, configuration: configuration, config: &config, + command: command, platformSetup: platformSetup ) } return workingDirectory.withCString { ptr in config.working_directory = ptr + return finalizeCommand( + app: app, + bridge: bridge, + configuration: configuration, + config: &config, + command: command, + platformSetup: platformSetup + ) + } + } + + private func finalizeCommand( + app: ghostty_app_t, + bridge: TerminalCallbackBridge, + configuration: TerminalSurfaceOptions, + config: inout ghostty_surface_config_s, + command: String?, + platformSetup: (inout ghostty_surface_config_s) -> Void + ) -> ghostty_surface_t? { + guard let command else { + return buildSurface( + app: app, + bridge: bridge, + configuration: configuration, + config: &config, + platformSetup: platformSetup + ) + } + + return command.withCString { ptr in + config.command = ptr return buildSurface( app: app, bridge: bridge, diff --git a/ios/vendor/GhosttyTerminal/Controller/TerminalController.swift b/ios/vendor/GhosttyTerminal/Controller/TerminalController.swift index 8079ab0..942ed4d 100644 --- a/ios/vendor/GhosttyTerminal/Controller/TerminalController.swift +++ b/ios/vendor/GhosttyTerminal/Controller/TerminalController.swift @@ -56,14 +56,33 @@ public final class TerminalController { var renderedConfigContents: String = TerminalController.defaultRenderedConfig public internal(set) var lastConfigurationIssue: String? - var onWakeup: (() -> Void)? - var shouldProcessWakeup: (() -> Bool)? + /// One surface's interest in a wakeup. Every surface shares this + /// controller, so a single handler is not enough. + struct WakeupObserver { + let shouldProcess: () -> Bool + let onWakeup: () -> Void + } + + private var wakeupObservers: [ObjectIdentifier: WakeupObserver] = [:] + + func addWakeupObserver( + _ key: ObjectIdentifier, + shouldProcess: @escaping () -> Bool, + onWakeup: @escaping () -> Void + ) { + wakeupObservers[key] = WakeupObserver(shouldProcess: shouldProcess, onWakeup: onWakeup) + } + + func removeWakeupObserver(_ key: ObjectIdentifier) { + wakeupObservers.removeValue(forKey: key) + } // MARK: - Config Resolution State - /// The base config before theme/colorScheme are applied. - private let baseConfigSource: ConfigSource - private var baseConfigTemplate: String = "" + /// The base config before theme/colorScheme are applied: what actually + /// loaded, so `.none` after init fell back from a rejected file. + var baseConfigSource: ConfigSource = .none + var baseConfigTemplate: String = "" /// Per-session configuration overrides (e.g. font size changes). public private(set) var terminalConfiguration: TerminalConfiguration @@ -142,17 +161,21 @@ public final class TerminalController { ) { Self.initializeRuntimeIfNeeded() - baseConfigSource = configSource self.theme = theme self.terminalConfiguration = terminalConfiguration self.configSource = configSource // Load the base config (without theme) so ghostty validates it. applyInitialConfig(source: configSource) + baseConfigSource = self.configSource baseConfigTemplate = renderedConfigContents + let baseIssue = lastConfigurationIssue // Now apply theme on top and push to ghostty. reconfigure() + // The theme pass loading does not make a rejected base config + // load; the fallback is what a host reads here after init. + if let baseIssue { lastConfigurationIssue = baseIssue } createApp() } @@ -245,7 +268,7 @@ public final class TerminalController { // MARK: - Config Resolution @discardableResult - private func reconfigure() -> Bool { + func reconfigure() -> Bool { applyResolvedConfig(resolveEffectiveConfig(), willChange: nil) } @@ -288,18 +311,21 @@ public final class TerminalController { } func handleWakeup() { - guard shouldProcessWakeup?() ?? true else { + let observers = Array(wakeupObservers.values) + // One detached surface must not stop the tick for the others. + guard observers.isEmpty || observers.contains(where: { $0.shouldProcess() }) else { TerminalDebugLog.log(.lifecycle, "wakeup suspended") return } tick() - onWakeup?() + for observer in observers { observer.onWakeup() } } private static func initializeRuntimeIfNeeded() { guard !runtimeInitialized else { return } runtimeInitialized = true + GhosttyRuntimeResources.configureEnvironment() ghostty_init(0, nil) } diff --git a/ios/vendor/GhosttyTerminal/Debug/TerminalDebugLog.swift b/ios/vendor/GhosttyTerminal/Debug/TerminalDebugLog.swift index 3bba827..c954f5c 100644 --- a/ios/vendor/GhosttyTerminal/Debug/TerminalDebugLog.swift +++ b/ios/vendor/GhosttyTerminal/Debug/TerminalDebugLog.swift @@ -291,14 +291,3 @@ extension TerminalSurfaceContext { } } } - -extension TerminalHardwareKeyDelivery { - var debugSummary: String { - switch self { - case let .ghostty(key): - "ghostty(\(key.rawValue))" - case let .data(data): - "data(\(TerminalDebugLog.describe(data)))" - } - } -} diff --git a/ios/vendor/GhosttyTerminal/InMemory/InMemoryTerminalSession.swift b/ios/vendor/GhosttyTerminal/InMemory/InMemoryTerminalSession.swift index 3dd574c..914a195 100644 --- a/ios/vendor/GhosttyTerminal/InMemory/InMemoryTerminalSession.swift +++ b/ios/vendor/GhosttyTerminal/InMemory/InMemoryTerminalSession.swift @@ -17,30 +17,53 @@ public final class InMemoryTerminalSession: @unchecked Sendable { private let writeHandler: @Sendable (Data) -> Void private let resizeHandler: @Sendable (InMemoryTerminalViewport) -> Void + /// Skip resize dispatches whose grid is unchanged and only the pixel + /// metrics moved. + /// + /// Off by default: the resize closure is a lossless contract, and a host + /// that reads `widthPixels`/`heightPixels` would otherwise stop seeing + /// sub-cell changes — permanently, if the grid never changes again. + /// + /// Worth enabling for a host that only consumes columns and rows and + /// repaints on every dispatch. A live divider drag produces mostly + /// pixel-only updates (measured at ~78% of metric updates across one + /// session's drags), and each one asks the terminal app for a full + /// repaint that re-wraps its content. + public let suppressesPixelOnlyResizes: Bool + public init( write: @escaping @Sendable (Data) -> Void, - resize: @escaping @Sendable (InMemoryTerminalViewport) -> Void + resize: @escaping @Sendable (InMemoryTerminalViewport) -> Void, + suppressesPixelOnlyResizes: Bool = false ) { writeHandler = write resizeHandler = resize + self.suppressesPixelOnlyResizes = suppressesPixelOnlyResizes surfaceAccess = InMemoryTerminalSurfaceAccess( write: Self.writeToSurface, - processExit: Self.reportProcessExit + processExit: Self.reportProcessExit, + tick: Self.tickApp ) } + /// Test seam: the surface handed to `setSurface` is a stand-in, so every + /// C call on it is injected, and the tick defaults to a no-op. init( write: @escaping @Sendable (Data) -> Void, resize: @escaping @Sendable (InMemoryTerminalViewport) -> Void, + suppressesPixelOnlyResizes: Bool = false, surfaceWrite: @escaping InMemoryTerminalSurfaceAccess.Write, processExit: @escaping InMemoryTerminalSurfaceAccess.ProcessExit = - InMemoryTerminalSession.reportProcessExit + InMemoryTerminalSession.reportProcessExit, + tick: @escaping InMemoryTerminalSurfaceAccess.Tick = { _ in } ) { writeHandler = write resizeHandler = resize + self.suppressesPixelOnlyResizes = suppressesPixelOnlyResizes surfaceAccess = InMemoryTerminalSurfaceAccess( write: surfaceWrite, - processExit: processExit + processExit: processExit, + tick: tick ) } @@ -73,49 +96,57 @@ public final class InMemoryTerminalSession: @unchecked Sendable { // MARK: - Viewport Read /// Returns the active viewport as a UTF-8 string, or `nil` if no surface - /// is attached. Lines are separated by `\n`. The `ghostty_text_s` - /// lifecycle (allocate via `ghostty_surface_read_text`, free via - /// `ghostty_surface_free_text`) is fully encapsulated — callers never - /// touch the C buffer. + /// is attached: one line per viewport row, joined with `\n`. The + /// `ghostty_text_s` lifecycle (allocate via `ghostty_surface_read_text`, + /// free via `ghostty_surface_free_text`) is fully encapsulated — callers + /// never touch the C buffer. /// - /// Selection grammar: `(VIEWPORT, TOP_LEFT)` to `(VIEWPORT, BOTTOM_RIGHT)` - /// with `rectangle: false` (linear flow). This reads exactly the visible - /// rows and ignores scrollback. Empty viewports return an empty string. + /// Each row is its own read, `(VIEWPORT, EXACT (0, y))` to + /// `(VIEWPORT, EXACT (columns - 1, y))`: a single read over the whole + /// viewport unwraps a soft-wrapped row into its neighbour's line, and + /// `TerminalSelectionAnchor` indexes these lines by viewport row. This + /// reads exactly the visible rows and ignores scrollback. Empty viewports + /// return an empty string. /// /// Thread-safe: keeps the surface alive for the duration of the read, /// preventing access against a surface mid-replacement. public func readViewportText() -> String? { - surfaceAccess.withCurrentSurface { surface in - let topLeft = ghostty_point_s( - tag: GHOSTTY_POINT_VIEWPORT, - coord: GHOSTTY_POINT_COORD_TOP_LEFT, - x: 0, - y: 0 - ) - let bottomRight = ghostty_point_s( - tag: GHOSTTY_POINT_VIEWPORT, - coord: GHOSTTY_POINT_COORD_BOTTOM_RIGHT, - x: 0, - y: 0 - ) - let selection = ghostty_selection_s( - top_left: topLeft, - bottom_right: bottomRight, - rectangle: false - ) - - var out = ghostty_text_s() - guard ghostty_surface_read_text(surface, selection, &out) else { - return nil - } - defer { ghostty_surface_free_text(surface, &out) } + surfaceAccess.withCurrentSurface { surface -> String? in + let size = ghostty_surface_size(surface) + guard size.columns > 0 else { return "" } + var lines: [String] = [] + for row in 0.. 0 else { - return "" + var out = ghostty_text_s() + guard ghostty_surface_read_text(surface, selection, &out) else { + return nil + } + defer { ghostty_surface_free_text(surface, &out) } + + guard let textPtr = out.text, out.text_len > 0 else { + lines.append("") + continue + } + let bytes = UnsafeBufferPointer(start: textPtr, count: Int(out.text_len)) + .map { UInt8(bitPattern: $0) } + lines.append(String(decoding: bytes, as: UTF8.self)) } - let bytes = UnsafeBufferPointer(start: textPtr, count: Int(out.text_len)) - .map { UInt8(bitPattern: $0) } - return String(decoding: bytes, as: UTF8.self) + return lines.joined(separator: "\n") } ?? nil } @@ -136,16 +167,12 @@ public final class InMemoryTerminalSession: @unchecked Sendable { /// Enqueue data for the terminal from the host backend. /// /// Writes are processed in order on a per-session serial queue so parsing - /// cannot block the caller or the main thread. + /// cannot block the caller or the main thread. Bytes that arrive before a + /// surface attaches are buffered (oldest dropped past a 1 MiB cap) and + /// flushed on attach — hosts do not need to hold their connection until + /// the first viewport report. public func receive(_ data: Data) { - guard surfaceAccess.enqueueWrite(data) else { - TerminalDebugLog.log( - .output, - "terminal <- host dropped \(TerminalDebugLog.describe(data))" - ) - return - } - + surfaceAccess.enqueueWrite(data) TerminalDebugLog.log( .output, "terminal <- host \(TerminalDebugLog.describe(data))" @@ -173,18 +200,13 @@ public final class InMemoryTerminalSession: @unchecked Sendable { // MARK: - Process Exit /// Enqueue a host-managed process exit after all previously received data. + /// Like that data, an exit that arrives before a surface attaches waits + /// for the next one and is delivered after the buffered bytes. public func finish(exitCode: UInt32, runtimeMilliseconds: UInt64) { - guard surfaceAccess.enqueueProcessExit( + surfaceAccess.enqueueProcessExit( exitCode: exitCode, runtimeMilliseconds: runtimeMilliseconds - ) else { - TerminalDebugLog.log( - .lifecycle, - "process exit ignored: missing surface exitCode=\(exitCode) runtimeMs=\(runtimeMilliseconds)" - ) - return - } - + ) TerminalDebugLog.log( .lifecycle, "process exit exitCode=\(exitCode) runtimeMs=\(runtimeMilliseconds)" @@ -234,7 +256,26 @@ public final class InMemoryTerminalSession: @unchecked Sendable { ) return } + // Opt-in (`suppressesPixelOnlyResizes`): only a change in grid size + // changes what a terminal app has to draw, so a host that repaints on + // every dispatch can skip the sub-cell ones. The latest pixel metrics + // are still recorded, so a later grid change carries them — but a host + // that consumes pixels must leave this off, because without a further + // grid change that update is never delivered. + let gridChanged = lastResize.map { + $0.columns != mergedResize.columns || $0.rows != mergedResize.rows + } ?? true lastResize = mergedResize + if suppressesPixelOnlyResizes, !gridChanged { + resizeLock.unlock() + TerminalDebugLog.log( + .metrics, + "resize sub-cell skipped cols=\(mergedResize.columns) rows=\(mergedResize.rows) pixels=\(mergedResize.widthPixels)x\(mergedResize.heightPixels)" + ) + + return + } + resizeLock.unlock() TerminalDebugLog.log( @@ -257,7 +298,18 @@ public final class InMemoryTerminalSession: @unchecked Sendable { ) } - func waitForPendingOutput() { + /// Blocks until every `receive(_:)` call made so far has been fully parsed by the + /// terminal engine's internal serial queue, including any resulting writeback (e.g. + /// DECRPM/DA/OSC query responses the engine generates while parsing). + /// + /// `receive(_:)` only enqueues — it returns before parsing happens. A host that feeds + /// buffered/replayed history and then flips some "replay done" flag on its own signal + /// (rather than on this call returning) can observe writeback for that history arrive + /// late, after the flag already says replay is over. + /// + /// Safe on the main thread: parsing fills ghostty's app mailbox, which only the main + /// thread drains, so a main-thread caller ticks the app while it waits. + public func waitForPendingOutput() { surfaceAccess.waitForPendingOutput() } @@ -288,4 +340,8 @@ public final class InMemoryTerminalSession: @unchecked Sendable { ) { ghostty_surface_process_exit(surface, exitCode, runtimeMilliseconds) } + + private static func tickApp(_ surface: ghostty_surface_t) { + ghostty_app_tick(ghostty_surface_app(surface)) + } } diff --git a/ios/vendor/GhosttyTerminal/InMemory/InMemoryTerminalSurfaceAccess.swift b/ios/vendor/GhosttyTerminal/InMemory/InMemoryTerminalSurfaceAccess.swift index 721087b..8ad9fe9 100644 --- a/ios/vendor/GhosttyTerminal/InMemory/InMemoryTerminalSurfaceAccess.swift +++ b/ios/vendor/GhosttyTerminal/InMemory/InMemoryTerminalSurfaceAccess.swift @@ -5,6 +5,7 @@ import GhosttyKit final class InMemoryTerminalSurfaceAccess: @unchecked Sendable { typealias Write = @Sendable (ghostty_surface_t, Data) -> Void typealias ProcessExit = @Sendable (ghostty_surface_t, UInt32, UInt64) -> Void + typealias Tick = @Sendable (ghostty_surface_t) -> Void private let condition = NSCondition() private let outputQueue = DispatchQueue( @@ -13,27 +14,73 @@ final class InMemoryTerminalSurfaceAccess: @unchecked Sendable { ) private let write: Write private let processExit: ProcessExit + private let tick: Tick private var surface: ghostty_surface_t? /// Invalidates work that was enqueued for a surface that has been replaced. private var generation: UInt64 = 0 /// Prevents the caller from freeing a surface while a C operation uses it. private var activeOperations = 0 + /// Bytes received while no surface is attached, replayed into the next + /// one. The host's transport does not pause while a view (re)builds its + /// surface — a reattach replay that lands in that gap used to be dropped + /// wholesale, leaving the restored session showing only whatever the + /// shell printed afterwards. Bounded: oldest bytes go first, matching + /// what a terminal scrollback would have forgotten anyway. + private var pendingWrites = Data() + private static let pendingWriteByteLimit = 1 << 20 + /// A process exit received while no surface is attached, delivered to + /// the next one after the pending bytes — the host's shell ends in the + /// same gap its output lands in. + private var pendingExit: (exitCode: UInt32, runtimeMilliseconds: UInt64)? + /// Parsing on the output queue pushes titles, pwd and command marks into + /// ghostty's 64-slot app mailbox, which only `ghostty_app_tick` drains, + /// and this package ticks on the main thread alone. A main-thread caller + /// that blocks on the queue outright therefore waits forever on a write + /// that is itself waiting for the tick, so the main thread waits in + /// slices and ticks between them. + private static let mainThreadPollInterval: TimeInterval = 0.01 init( write: @escaping Write, - processExit: @escaping ProcessExit + processExit: @escaping ProcessExit, + tick: @escaping Tick ) { self.write = write self.processExit = processExit + self.tick = tick } func setSurface(_ surface: ghostty_surface_t?) { condition.lock() generation &+= 1 + let previous = self.surface self.surface = nil - waitForActiveOperations() + waitForActiveOperations(ticking: previous) self.surface = surface + // Flush what arrived surfaceless, ahead of anything received after + // this call: both ride the same serial queue, so enqueueing while the + // lock still excludes `enqueueWrite` preserves stream order. + if surface != nil { + let flushGeneration = generation + if !pendingWrites.isEmpty { + let flush = pendingWrites + pendingWrites = Data() + outputQueue.async { [self] in + withSurface(generation: flushGeneration) { surface in + write(surface, flush) + } + } + } + if let exit = pendingExit { + pendingExit = nil + outputQueue.async { [self] in + withSurface(generation: flushGeneration) { surface in + processExit(surface, exit.exitCode, exit.runtimeMilliseconds) + } + } + } + } condition.unlock() } @@ -47,7 +94,7 @@ final class InMemoryTerminalSurfaceAccess: @unchecked Sendable { generation &+= 1 surface = nil - waitForActiveOperations() + waitForActiveOperations(ticking: expectedSurface) condition.unlock() return true } @@ -60,27 +107,43 @@ final class InMemoryTerminalSurfaceAccess: @unchecked Sendable { @discardableResult func enqueueWrite(_ data: Data) -> Bool { - guard let generation = currentGeneration else { return false } + condition.lock() + guard surface != nil else { + pendingWrites.append(data) + let excess = pendingWrites.count - Self.pendingWriteByteLimit + if excess > 0 { + pendingWrites.removeFirst(excess) + } + condition.unlock() + return true + } + let writeGeneration = generation + condition.unlock() outputQueue.async { [self] in - withSurface(generation: generation) { surface in + withSurface(generation: writeGeneration) { surface in write(surface, data) } } return true } - @discardableResult func enqueueProcessExit( exitCode: UInt32, runtimeMilliseconds: UInt64 - ) -> Bool { - guard let generation = currentGeneration else { return false } + ) { + condition.lock() + guard surface != nil else { + pendingExit = (exitCode, runtimeMilliseconds) + condition.unlock() + return + } + let exitGeneration = generation + condition.unlock() outputQueue.async { [self] in - withSurface(generation: generation) { surface in + withSurface(generation: exitGeneration) { surface in processExit(surface, exitCode, runtimeMilliseconds) } } - return true } func withCurrentSurface( @@ -99,13 +162,28 @@ final class InMemoryTerminalSurfaceAccess: @unchecked Sendable { } func waitForPendingOutput() { - outputQueue.sync {} + guard Thread.isMainThread else { + outputQueue.sync {} + return + } + let drained = DispatchSemaphore(value: 0) + outputQueue.async { drained.signal() } + while drained.wait(timeout: .now() + Self.mainThreadPollInterval) == .timedOut { + tickCurrentSurface() + } } - private var currentGeneration: UInt64? { + /// Ticks outside the lock and without counting an operation: the tick can + /// deliver a close, and a host that tears the surface down from that + /// callback re-enters `clearSurface` on this thread. Teardown is + /// main-actor work, so the pointer stays valid across a main-thread tick. + private func tickCurrentSurface() { condition.lock() - defer { condition.unlock() } - return surface == nil ? nil : generation + let current = surface + condition.unlock() + if let current { + tick(current) + } } private func withSurface( @@ -133,9 +211,19 @@ final class InMemoryTerminalSurfaceAccess: @unchecked Sendable { condition.unlock() } - private func waitForActiveOperations() { + /// Called with the lock held. `previous` is the surface the in-flight + /// operations use; the caller frees it only after this returns. + private func waitForActiveOperations(ticking previous: ghostty_surface_t?) { while activeOperations > 0 { - condition.wait() + guard Thread.isMainThread, let previous else { + condition.wait() + continue + } + _ = condition.wait(until: Date(timeIntervalSinceNow: Self.mainThreadPollInterval)) + guard activeOperations > 0 else { return } + condition.unlock() + tick(previous) + condition.lock() } } } diff --git a/ios/vendor/GhosttyTerminal/InMemory/TerminalCallbackBridge.swift b/ios/vendor/GhosttyTerminal/InMemory/TerminalCallbackBridge.swift index d90375a..2e0f822 100644 --- a/ios/vendor/GhosttyTerminal/InMemory/TerminalCallbackBridge.swift +++ b/ios/vendor/GhosttyTerminal/InMemory/TerminalCallbackBridge.swift @@ -20,6 +20,7 @@ final class TerminalCallbackBridge { nonisolated(unsafe) var rawSurface: ghostty_surface_t? var onCellSizeChange: ((UInt32, UInt32) -> Void)? var onRenderRequest: (() -> Void)? + var onMouseShape: ((ghostty_action_mouse_shape_e) -> Void)? init(delegate: (any TerminalSurfaceViewDelegate)? = nil) { self.delegate = delegate @@ -116,6 +117,16 @@ final class TerminalCallbackBridge { (delegate as? any TerminalSurfaceOpenURLDelegate)? .terminalDidRequestOpenURL(url, kind: kind) + case GHOSTTY_ACTION_MOUSE_SHAPE: + let shape = action.action.mouse_shape + TerminalDebugLog.log( + .actions, + "callback action=mouse_shape value=\(shape.rawValue)" + ) + onMouseShape?(shape) + (delegate as? any TerminalSurfaceMouseShapeDelegate)? + .terminalDidChangeMouseShape(TerminalMouseShape(shape)) + case GHOSTTY_ACTION_MOUSE_OVER_LINK: let payload = action.action.mouse_over_link let url: String? = { @@ -142,6 +153,21 @@ final class TerminalCallbackBridge { .terminalDidChangeWorkingDirectory(pwd) } + case GHOSTTY_ACTION_SCROLLBAR: + let payload = action.action.scrollbar + TerminalDebugLog.log( + .actions, + "callback action=scrollbar total=\(payload.total) offset=\(payload.offset) len=\(payload.len)" + ) + (delegate as? any TerminalSurfaceScrollbarDelegate)? + .terminalDidUpdateScrollbar( + TerminalScrollbar( + total: payload.total, + offset: payload.offset, + len: payload.len + ) + ) + default: TerminalDebugLog.log( .actions, @@ -158,4 +184,193 @@ final class TerminalCallbackBridge { (delegate as? any TerminalSurfaceCloseDelegate)? .terminalDidClose(processAlive: processAlive) } + + func handleClipboardConfirmation( + contents: String, + kind: TerminalClipboardRequestKind, + completion: @escaping (Bool) -> Void + ) { + guard let delegate = delegate as? any TerminalSurfaceClipboardConfirmationDelegate else { + completion(false) + return + } + delegate.terminalDidRequestClipboardConfirmation( + TerminalClipboardConfirmationRequest( + contents: contents, + kind: kind, + completion: completion + ) + ) + } + + // MARK: - Clipboard read confirmation bookkeeping + + /// Guards `pendingClipboardRequests`. Two independent callers can reach + /// for the same entry — the host's confirmation UI answering + /// (``PendingClipboardRequest/resolve(_:contents:available:)``, normally + /// main-actor via `terminalRunOnMain`) and surface teardown denying + /// everything outstanding (``denyAllPendingClipboardRequests()``, called + /// from `TerminalSurfaceCoordinator.deinit`, which — a `@MainActor` + /// class's deinit is `nonisolated` by default — only *assumes* main-actor + /// isolation rather than the runtime enforcing it, and that assumption + /// silently no-ops in a release build if it's ever wrong). Without a + /// real lock, "exactly one of complete/deny fires for every request" was + /// a single unsynchronized `Bool` read-then-write on + /// ``PendingClipboardRequest``, which is a genuine data race, not just a + /// theoretical one, whenever those two callers overlap: both could pass + /// the check and both call into libghostty for the same, already-freed + /// `apprt.ClipboardRequest*`, corrupting the heap. The lock is held only + /// across the map mutation, never across the libghostty call itself. + private let pendingClipboardRequestsLock = NSLock() + + /// Requests awaiting the host's answer, keyed by the opaque + /// `apprt.ClipboardRequest` pointer libghostty gave us, guarded by + /// `pendingClipboardRequestsLock`. Guarantees "exactly one of + /// complete/deny fires for every request" even when the host's + /// confirmation UI never answers — see + /// ``denyAllPendingClipboardRequests()``. + nonisolated(unsafe) private var pendingClipboardRequests: [Int: PendingClipboardRequest] = [:] + + /// Registers a newly received confirm request and returns the token the + /// caller resolves once the host answers. + nonisolated func registerPendingClipboardRequest(_ statePtr: UnsafeMutableRawPointer) -> PendingClipboardRequest { + let token = PendingClipboardRequest(statePtr: statePtr, bridge: self) + pendingClipboardRequestsLock.lock() + pendingClipboardRequests[Int(bitPattern: statePtr)] = token + pendingClipboardRequestsLock.unlock() + return token + } + + /// Atomically removes and returns the pending request for `statePtr`, if + /// it is still outstanding. Whichever caller receives the non-nil result + /// is the *only* one allowed to answer it — this is what makes "exactly + /// one of complete/deny fires" hold even when + /// ``PendingClipboardRequest/resolve(_:contents:available:)`` and + /// ``denyAllPendingClipboardRequests()`` race for the same request: the + /// loser finds nothing left to take and no-ops. + nonisolated fileprivate func takePendingClipboardRequest( + _ statePtr: UnsafeMutableRawPointer + ) -> PendingClipboardRequest? { + pendingClipboardRequestsLock.lock() + defer { pendingClipboardRequestsLock.unlock() } + return pendingClipboardRequests.removeValue(forKey: Int(bitPattern: statePtr)) + } + + /// Denies every clipboard-read confirmation still waiting on the host's + /// answer. **Must run while `rawSurface` is still the live surface** — + /// call before nil-ing it out and before `TerminalSurface.free()`. This + /// is the wrapper-level backstop for "exactly one of complete/deny + /// fires for every request": a Pane/Session/Window tearing down while a + /// confirmation prompt is still open must not leave the requesting + /// program hanging forever, regardless of whether the host's own UI + /// (``TerminalClipboardConfirmationRequest``) ever answers or is + /// released. The map is drained under the lock before any request is + /// answered, so a concurrent `resolve` either wins the race for a given + /// entry (and this loop never sees it) or loses it (and its own + /// `takePendingClipboardRequest` finds nothing) — never both. + nonisolated func denyAllPendingClipboardRequests() { + pendingClipboardRequestsLock.lock() + guard !pendingClipboardRequests.isEmpty else { + pendingClipboardRequestsLock.unlock() + return + } + let tokens = Array(pendingClipboardRequests.values) + pendingClipboardRequests.removeAll() + pendingClipboardRequestsLock.unlock() + for token in tokens { + token.forceDeny() + } + } + + #if DEBUG + /// Test-only count of ``finishClipboardRequest`` invocations, guarded + /// by `pendingClipboardRequestsLock`. Exists because a live + /// `ghostty_surface_t` isn't available in unit tests, so + /// "exactly one of complete/deny fires per request" — including + /// under the ``resolve(_:contents:available:)`` / + /// ``denyAllPendingClipboardRequests()`` race this file's locking + /// exists to prevent — needs some observable signal other than the + /// (untestable) libghostty call itself. + nonisolated(unsafe) private var _testHooks_clipboardAnswerCount = 0 + var testHooks_clipboardAnswerCount: Int { + pendingClipboardRequestsLock.lock() + defer { pendingClipboardRequestsLock.unlock() } + return _testHooks_clipboardAnswerCount + } + #endif + + nonisolated fileprivate func finishClipboardRequest( + _ statePtr: UnsafeMutableRawPointer, + allowed: Bool, + contents: [TerminalClipboardContent], + available: [String] + ) { + #if DEBUG + pendingClipboardRequestsLock.lock() + _testHooks_clipboardAnswerCount += 1 + pendingClipboardRequestsLock.unlock() + #endif + + guard let surface = rawSurface else { + TerminalDebugLog.log(.input, "clipboard confirm resolve skipped: missing surface") + return + } + + guard allowed else { + ghostty_surface_deny_clipboard_request(surface, statePtr) + TerminalDebugLog.log(.input, "clipboard confirm denied") + return + } + + withClipboardCompletePayload( + contents: contents, + available: available, + confirmed: true, + remember: false + ) { complete in + ghostty_surface_complete_clipboard_request(surface, complete, statePtr) + } + TerminalDebugLog.log(.input, "clipboard confirm allowed") + } +} + +/// Tracks one clipboard-read confirmation from request to answer. Resolves +/// at most once: a second `resolve`/`forceDeny` call (e.g. the host answers +/// after the wrapper already denied at teardown) is a documented no-op, not +/// a double free of libghostty's request state. "At most once" is enforced +/// by `TerminalCallbackBridge.takePendingClipboardRequest(_:)` atomically +/// removing this token's entry from the bridge's pending map — whichever +/// caller performs that removal is the only one that proceeds — not by a +/// flag on this instance, since `resolve` (normally main-actor, via +/// `terminalRunOnMain`) and `forceDeny` (called from +/// `TerminalSurfaceCoordinator.deinit`, which only *assumes* main-actor +/// isolation) can genuinely run concurrently on different threads. +/// +/// `@unchecked Sendable` so a token created on the (nonisolated) clipboard +/// callback thread can be captured by the main-actor-isolated closure that +/// answers it later. +final class PendingClipboardRequest: @unchecked Sendable { + private let statePtr: UnsafeMutableRawPointer + private weak var bridge: TerminalCallbackBridge? + + fileprivate init(statePtr: UnsafeMutableRawPointer, bridge: TerminalCallbackBridge) { + self.statePtr = statePtr + self.bridge = bridge + } + + /// Answers the request with the host's decision. Intended to be used as + /// the completion passed to a ``TerminalClipboardConfirmationRequest``. + func resolve(_ allowed: Bool, contents: [TerminalClipboardContent], available: [String]) { + guard let bridge, bridge.takePendingClipboardRequest(statePtr) != nil else { return } + bridge.finishClipboardRequest(statePtr, allowed: allowed, contents: contents, available: available) + } + + /// Called by `TerminalCallbackBridge.denyAllPendingClipboardRequests()` + /// at teardown, once per token *after* that method has already atomically + /// drained the whole pending map under its lock — so, unlike `resolve`, + /// this never needs to re-take the entry; a concurrent `resolve` for this + /// same token already lost the race the moment the map was cleared. + fileprivate func forceDeny() { + bridge?.finishClipboardRequest(statePtr, allowed: false, contents: [], available: []) + } } diff --git a/ios/vendor/GhosttyTerminal/Metrics/TerminalInputModifiers.swift b/ios/vendor/GhosttyTerminal/Metrics/TerminalInputModifiers.swift index 70a0e4b..03fa0e4 100644 --- a/ios/vendor/GhosttyTerminal/Metrics/TerminalInputModifiers.swift +++ b/ios/vendor/GhosttyTerminal/Metrics/TerminalInputModifiers.swift @@ -13,7 +13,7 @@ import GhosttyKit import AppKit #endif -public struct TerminalInputModifiers: OptionSet, Sendable { +public struct TerminalInputModifiers: OptionSet, Hashable, Sendable { public let rawValue: UInt32 public init(rawValue: UInt32) { diff --git a/ios/vendor/GhosttyTerminal/Metrics/TerminalScrollModifiers.swift b/ios/vendor/GhosttyTerminal/Metrics/TerminalScrollModifiers.swift index 04b2162..0326623 100644 --- a/ios/vendor/GhosttyTerminal/Metrics/TerminalScrollModifiers.swift +++ b/ios/vendor/GhosttyTerminal/Metrics/TerminalScrollModifiers.swift @@ -7,7 +7,7 @@ import GhosttyKit -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit #endif @@ -40,7 +40,7 @@ public struct TerminalScrollModifiers: Sendable { case changed = 3 } - #if canImport(AppKit) && !canImport(UIKit) + #if !canImport(UIKit) && canImport(AppKit) static func momentumFrom(phase: NSEvent.Phase) -> Momentum { if phase.contains(.began) { return .began } if phase.contains(.stationary) { return .stationary } diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Input.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Input.swift index b4bc404..9104f1b 100644 --- a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Input.swift +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Input.swift @@ -5,10 +5,20 @@ // Created by Lakr233 on 2026/3/17. // -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit import GhosttyKit + /// Cmd/Ctrl key-equivalent echo dedup state; behavior lives in +Input. + struct KeyEchoState { + var lastPerformKeyEvent: TimeInterval? + } + + /// Mouse selection state; behavior lives in +Input. + struct PointerSelectionState { + var pendingSelectionMenuPoint: CGPoint? + } + extension AppTerminalView { override open func keyDown(with event: NSEvent) { inputHandler?.handleKeyDown(with: event) @@ -48,19 +58,19 @@ if !event.modifierFlags.contains(.command), !event.modifierFlags.contains(.control) { - lastPerformKeyEvent = nil + keyEcho.lastPerformKeyEvent = nil return false } - if let lastPerformKeyEvent, - lastPerformKeyEvent == event.timestamp + if let last = keyEcho.lastPerformKeyEvent, + last == event.timestamp { - self.lastPerformKeyEvent = nil + keyEcho.lastPerformKeyEvent = nil equivalent = event.characters ?? "" break } - lastPerformKeyEvent = event.timestamp + keyEcho.lastPerformKeyEvent = event.timestamp return false } @@ -92,9 +102,9 @@ } override open func doCommand(by selector: Selector) { - if let lastPerformKeyEvent, + if let last = keyEcho.lastPerformKeyEvent, let current = NSApp.currentEvent, - lastPerformKeyEvent == current.timestamp + last == current.timestamp { NSApp.sendEvent(current) return @@ -132,8 +142,7 @@ window?.makeFirstResponder(self) let (x, y) = mousePoint(from: event) let mods = TerminalInputModifiers(from: event.modifierFlags) - pointerSelectionStartPoint = CGPoint(x: x, y: y) - pendingSelectionMenuPoint = nil + pointer.pendingSelectionMenuPoint = nil surface?.sendMousePos(x: x, y: y, mods: mods.ghosttyMods) surface?.sendMouseButton( state: GHOSTTY_MOUSE_PRESS, @@ -151,7 +160,6 @@ button: GHOSTTY_MOUSE_LEFT, mods: mods.ghosttyMods ) - finishPointerSelection(at: CGPoint(x: x, y: y)) } override open func rightMouseDown(with event: NSEvent) { @@ -160,7 +168,7 @@ let mods = TerminalInputModifiers(from: event.modifierFlags) surface?.sendMousePos(x: x, y: y, mods: mods.ghosttyMods) if let menuPoint = selectionMenuPoint(at: CGPoint(x: x, y: y)) { - pendingSelectionMenuPoint = menuPoint + pointer.pendingSelectionMenuPoint = menuPoint return } surface?.sendMouseButton( @@ -174,8 +182,8 @@ let (x, y) = mousePoint(from: event) let mods = TerminalInputModifiers(from: event.modifierFlags) surface?.sendMousePos(x: x, y: y, mods: mods.ghosttyMods) - if pendingSelectionMenuPoint != nil { - pendingSelectionMenuPoint = nil + if pointer.pendingSelectionMenuPoint != nil { + pointer.pendingSelectionMenuPoint = nil showSelectionCopyMenu(with: event) return } @@ -223,9 +231,16 @@ surface?.sendMousePos(x: x, y: y, mods: mods.ghosttyMods) } + // ghostty clears link hover only on a negative position; the + // tracking area stops delivering mouseMoved outside the view. Skipped + // during a drag, where mouseDragged keeps reporting real positions. + override open func mouseExited(with event: NSEvent) { + guard NSEvent.pressedMouseButtons == 0 else { return } + let mods = TerminalInputModifiers(from: event.modifierFlags) + surface?.sendMousePos(x: -1, y: -1, mods: mods.ghosttyMods) + } + override open func mouseDragged(with event: NSEvent) { - let (x, y) = mousePoint(from: event) - updatePointerSelectionRect(to: CGPoint(x: x, y: y)) mouseMoved(with: event) } @@ -249,27 +264,6 @@ ) } - private func updatePointerSelectionRect(to point: CGPoint) { - guard let start = pointerSelectionStartPoint else { return } - lastPointerSelectionRect = CGRect( - x: min(start.x, point.x), - y: min(start.y, point.y), - width: abs(start.x - point.x), - height: abs(start.y - point.y) - ).insetBy(dx: -2, dy: -2) - } - - private func finishPointerSelection(at point: CGPoint) { - defer { pointerSelectionStartPoint = nil } - guard let start = pointerSelectionStartPoint else { return } - let dragDistance = hypot(point.x - start.x, point.y - start.y) - if dragDistance < 2 { - lastPointerSelectionRect = nil - } else { - updatePointerSelectionRect(to: point) - } - } - private func showSelectionCopyMenu(with event: NSEvent) { let menu = selectionContextMenu() NSMenu.popUpContextMenu(menu, with: event, for: self) diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Lifecycle.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Lifecycle.swift index 3800e2e..2b5c32f 100644 --- a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Lifecycle.swift +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Lifecycle.swift @@ -5,9 +5,14 @@ // Created by Lakr233 on 2026/3/17. // -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit + /// SwiftUI focus-bridge hooks; behavior lives in +Lifecycle. + struct FocusBridgeState { + var onFocusChange: ((Bool) -> Void)? + } + extension AppTerminalView { func setupTrackingArea() { let options: NSTrackingArea.Options = [ @@ -38,14 +43,14 @@ override open func becomeFirstResponder() -> Bool { let result = super.becomeFirstResponder() core.setFocus(true) - onFocusChange?(true) + focusBridge.onFocusChange?(true) return result } override open func resignFirstResponder() -> Bool { let result = super.resignFirstResponder() core.setFocus(false) - onFocusChange?(false) + focusBridge.onFocusChange?(false) return result } @@ -93,22 +98,30 @@ name: NSWindow.didChangeScreenNotification, object: window ) + // Same runloop hop as `requestFocus`: attaching can happen + // mid SwiftUI update, where the first-responder dance must + // not mutate focus state. + DispatchQueue.main.async { [weak self] in + guard let self else { return } + (delegate as? TerminalViewState)?.replayPendingFocusIfNeeded() + } } else { core.stopDisplayLink() core.setFocus(false) } } + // Window key state is not a first-responder change: reporting it + // through the focus bridge flips the host's FocusState, whose + // synchronizeFocus then resigns a view that is still first responder. @objc func windowDidBecomeKey(_: Notification) { let focused = window?.isKeyWindow == true && window?.firstResponder === self core.setFocus(focused) - onFocusChange?(focused) } @objc func windowDidResignKey(_: Notification) { core.setFocus(false) - onFocusChange?(false) } @objc func windowDidChangeScreen(_: Notification) { diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+NSTextInputClient.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+NSTextInputClient.swift index fec75ef..7bca6f6 100644 --- a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+NSTextInputClient.swift +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+NSTextInputClient.swift @@ -5,7 +5,7 @@ // Created by Lakr233 on 2026/3/17. // -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit extension AppTerminalView: @preconcurrency NSTextInputClient { @@ -62,10 +62,11 @@ ) -> NSRect { guard let surface else { return .zero } + // ghostty's ime point y is already the cell's bottom edge. let point = surface.imePoint() let viewRect = NSRect( x: point.x, - y: bounds.height - point.y - point.height, + y: bounds.height - point.y, width: point.width, height: point.height ) diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+PublicInput.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+PublicInput.swift index 0b9e78d..209924c 100644 --- a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+PublicInput.swift +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+PublicInput.swift @@ -6,16 +6,61 @@ // inject bytes into the pty without reaching for internal API. // -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit + import GhosttyKit extension AppTerminalView { - /// Send raw UTF-8 text directly to the underlying pty (bypassing - /// key translation). Use this for synthetic input like `\x1b[Z` - /// (Shift+Tab / CSI Z) or multi-line paste-style injections. - /// No-op when the surface has not been created yet. + /// Make this view the window's first responder, reporting whether + /// keyboard focus was actually acquired. Fails (returns false) while + /// the view is not in a window; ``TerminalViewState/requestFocus()`` + /// retries then on window attach. + @discardableResult + public func acquireProgrammaticFocus() -> Bool { + guard let window else { return false } + if window.firstResponder === self { return true } + return window.makeFirstResponder(self) + } + + /// Paste text into the terminal. This is the text path: a program + /// that enabled bracketed paste receives it framed as a paste, so + /// escape sequences and a `\r` in it are pasted characters, not + /// keys. Keystrokes — Shift+Tab, Enter, Ctrl+C — go through + /// ``sendKey(_:)``. False when the surface has not been created yet. + @discardableResult + public func paste(text: String) -> Bool { + surface?.sendText(text) ?? false + } + + /// The old name of ``paste(text:)``. It never bypassed key + /// translation — the text path is a paste, and an escape sequence + /// sent through it is pasted, not pressed. + @available(*, deprecated, renamed: "paste(text:)", message: "The text path is a paste; press keys with sendKey(_:).") public func sendText(_ text: String) { - surface?.sendText(text) + paste(text: text) + } + + /// Presses and releases a key, as if typed on a hardware keyboard — + /// see ``TerminalSurface/sendKey(_:)``. An open IME composition is + /// committed first, as it would be ahead of a hardware key. False + /// with no surface yet. + @discardableResult + public func sendKey(_ press: TerminalKeyPress) -> Bool { + guard let surface else { return false } + if hasMarkedText() { + inputHandler?.inputMethodHandler?.commitMarkedText() + // The input method keeps its own copy of the composition and + // would re-mark it on the next keystroke. + inputContext?.discardMarkedText() + } + return surface.sendKey(press) + } + + /// ``sendKey(_:)`` for a key and its modifiers: `sendKey(.enter)`, + /// `sendKey(.tab, modifiers: .shift)`. + @discardableResult + public func sendKey(_ key: TerminalKey, modifiers: TerminalInputModifiers = []) -> Bool { + sendKey(TerminalKeyPress(key, modifiers: modifiers)) } /// Invoke a named Ghostty binding action (e.g. "copy_to_clipboard", @@ -39,5 +84,39 @@ public func scrollToRow(_ row: UInt) -> Bool { surface?.scrollToRow(row) ?? false } + + /// Whether the application currently owns the mouse. + public var isMouseCaptured: Bool { + surface?.isMouseCaptured ?? false + } + + public func sendMousePos( + x: Double, + y: Double, + modifiers: TerminalInputModifiers = [] + ) { + surface?.sendMousePos(x: x, y: y, modifiers: modifiers) + } + + @discardableResult + public func sendMouseButton( + state: ghostty_input_mouse_state_e, + button: ghostty_input_mouse_button_e, + modifiers: TerminalInputModifiers = [] + ) -> Bool { + surface?.sendMouseButton( + state: state, + button: button, + modifiers: modifiers + ) ?? false + } + + public func sendMouseScroll( + x: Double, + y: Double, + mods: TerminalScrollModifiers = TerminalScrollModifiers(precision: true) + ) { + surface?.sendMouseScroll(x: x, y: y, mods: mods) + } } #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Snapshot.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Snapshot.swift new file mode 100644 index 0000000..a18e262 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView+Snapshot.swift @@ -0,0 +1,24 @@ +// +// AppTerminalView+Snapshot.swift +// libghostty-spm +// + +#if !canImport(UIKit) && canImport(AppKit) + import AppKit + + public extension AppTerminalView { + /// Renders the view into an image via `cacheDisplay`. Best-effort + /// on AppKit: a Metal layer's presented frame may not be included — + /// the UIKit twin uses a render-server snapshot + /// (`drawHierarchy`), which does capture it. + func snapshotImage() -> NSImage? { + guard bounds.width > 0, bounds.height > 0, + let representation = bitmapImageRepForCachingDisplay(in: bounds) + else { return nil } + cacheDisplay(in: bounds, to: representation) + let image = NSImage(size: bounds.size) + image.addRepresentation(representation) + return image + } + } +#endif diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView.swift index e911e91..44652fb 100644 --- a/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView.swift +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/AppTerminalView.swift @@ -5,7 +5,7 @@ // Created by Lakr233 on 2026/3/16. // -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit import GhosttyKit @@ -14,11 +14,14 @@ let core = TerminalSurfaceCoordinator() var metalLayer: CAMetalLayer? var inputHandler: TerminalKeyEventHandler? - var lastPerformKeyEvent: TimeInterval? - var pointerSelectionStartPoint: CGPoint? - var lastPointerSelectionRect: CGRect? - var pendingSelectionMenuPoint: CGPoint? - var onFocusChange: ((Bool) -> Void)? + + // Grouped view state, one struct per concern — same convention as + // the UIKit twin: each state type is defined in the extension file + // that owns the behavior (+Input, +Lifecycle); the storage lives + // here because extensions cannot add stored properties. + var keyEcho: KeyEchoState = .init() + var pointer: PointerSelectionState = .init() + var focusBridge: FocusBridgeState = .init() open weak var delegate: (any TerminalSurfaceViewDelegate)? { get { core.delegate } @@ -39,6 +42,14 @@ core.setDisplayVisible(visible) } + /// Adjusts this surface's resize coalescing window without rebuilding + /// it. Overrides `TerminalSurfaceOptions.resizeThrottleMilliseconds`, + /// which is the declarative home for the same policy and the one every + /// platform can reach; pass `nil` to fall back to it. + open func setResizeThrottle(milliseconds: Double?) { + core.resizeThrottleInterval = milliseconds.map { max(0, $0) / 1000 } + } + var surface: TerminalSurface? { core.surface } @@ -98,6 +109,9 @@ } } + // Upstream's rule: Copy is offered whenever ghostty has a selection. + // A cached drag rect went stale on scroll and select_all, and the + // quicklook-word test misses whitespace inside a selection. open func selectionMenuPoint(at point: CGPoint) -> CGPoint? { guard surface?.hasSelection() == true else { TerminalDebugLog.log( @@ -107,30 +121,6 @@ return nil } - if let rect = lastPointerSelectionRect { - guard rect.insetBy(dx: -4, dy: -4).contains(point) else { - TerminalDebugLog.log( - .input, - "selection menu miss point=\(selectionPointDescription(point)) outside pointer selection" - ) - return nil - } - - TerminalDebugLog.log( - .input, - "selection menu hit point=\(selectionPointDescription(point)) inside pointer selection" - ) - return point - } - - guard surface?.selectionContainsQuicklookWord() == true else { - TerminalDebugLog.log( - .input, - "selection menu miss point=\(selectionPointDescription(point)) outside quicklook word" - ) - return nil - } - TerminalDebugLog.log( .input, "selection menu hit point=\(selectionPointDescription(point))" diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/KeyboardLayout.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/KeyboardLayout.swift index 0fe3c0d..6df0d5b 100644 --- a/ios/vendor/GhosttyTerminal/Platform/AppKit/KeyboardLayout.swift +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/KeyboardLayout.swift @@ -1,4 +1,4 @@ -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import Carbon.HIToolbox enum KeyboardLayout { diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/TerminalKeyEventHandler@AppKit.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/TerminalKeyEventHandler@AppKit.swift index 4728892..f3deb3b 100644 --- a/ios/vendor/GhosttyTerminal/Platform/AppKit/TerminalKeyEventHandler@AppKit.swift +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/TerminalKeyEventHandler@AppKit.swift @@ -11,7 +11,7 @@ // drift from upstream keyboard/IME semantics. // -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit import GhosttyKit @@ -26,12 +26,6 @@ inputMethodHandler = TerminalTextInputHandler(view: view) } - nonisolated static func shouldUseDirectInput( - modifierFlags: NSEvent.ModifierFlags - ) -> Bool { - modifierFlags.intersection([.shift, .control, .option, .command]).isEmpty - } - nonisolated static func shouldReplayInterpretedCommand( _ selector: Selector ) -> Bool { @@ -45,10 +39,6 @@ func handleKeyDown(with event: NSEvent) { guard let view, let surface = view.surface else { return } - if handleDirectInputIfNeeded(event) { - return - } - let action: ghostty_input_action_e = event.isARepeat ? GHOSTTY_ACTION_REPEAT : GHOSTTY_ACTION_PRESS let translationEvent = translatedEvent(for: event, on: surface) @@ -57,7 +47,7 @@ interpretedCommandSelector = nil let markedTextBefore = inputMethodHandler?.hasMarkedText == true let keyboardIdBefore = markedTextBefore ? nil : KeyboardLayout.id - view.lastPerformKeyEvent = nil + view.keyEcho.lastPerformKeyEvent = nil view.interpretKeyEvents([translationEvent]) if !markedTextBefore, keyboardIdBefore != KeyboardLayout.id { _ = inputMethodHandler?.finishCollectingText() @@ -106,9 +96,6 @@ func handleKeyUp(with event: NSEvent) { guard let view, let surface = view.surface else { return } - if shouldBypassGhosttyForDirectInput(event) { - return - } var input = event.buildKeyInput(action: GHOSTTY_ACTION_RELEASE) input.text = nil surface.sendKeyEvent(input) @@ -186,36 +173,6 @@ } } - private func handleDirectInputIfNeeded(_ event: NSEvent) -> Bool { - guard let view else { return false } - // During IME composition, AppKit needs to keep ownership of editing - // commands so marked text can shrink, cancel, and move correctly. - guard inputMethodHandler?.hasMarkedText != true else { return false } - guard Self.shouldUseDirectInput(modifierFlags: event.modifierFlags) else { - return false - } - let delivery = TerminalHardwareKeyRouter.routeAppKit( - keyCode: event.keyCode, - backend: view.configuration.backend - ) - guard case let .data(sequence) = delivery else { return false } - guard case let .inMemory(session) = view.configuration.backend else { return false } - - session.sendInput(sequence) - return true - } - - private func shouldBypassGhosttyForDirectInput(_ event: NSEvent) -> Bool { - guard let view else { return false } - guard Self.shouldUseDirectInput(modifierFlags: event.modifierFlags) else { - return false - } - return TerminalHardwareKeyRouter.routeAppKit( - keyCode: event.keyCode, - backend: view.configuration.backend - ).isDirectInput - } - private func translatedEvent( for event: NSEvent, on surface: TerminalSurface diff --git a/ios/vendor/GhosttyTerminal/Platform/AppKit/TerminalTextInputHandler@AppKit.swift b/ios/vendor/GhosttyTerminal/Platform/AppKit/TerminalTextInputHandler@AppKit.swift index b40809c..032930c 100644 --- a/ios/vendor/GhosttyTerminal/Platform/AppKit/TerminalTextInputHandler@AppKit.swift +++ b/ios/vendor/GhosttyTerminal/Platform/AppKit/TerminalTextInputHandler@AppKit.swift @@ -11,7 +11,7 @@ // filtering, so composition behavior stays consistent with upstream. // -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit import GhosttyKit @@ -89,6 +89,31 @@ syncPreedit() } + /// Commits an open composition as typed text on the key path — what + /// a hardware key would have done through `interpretKeyEvents` — + /// instead of dropping it. The keycode is deliberately outside the + /// AppKit virtual-keycode table so ghostty resolves the key to + /// `.unidentified` and encodes from the text alone, as the UIKit + /// twin's `sendTypedText` does. + func commitMarkedText() { + guard let text = markedTextState.text else { return } + markedTextState.clear() + syncPreedit() + + var event = ghostty_input_key_s() + event.action = GHOSTTY_ACTION_PRESS + event.mods = ghostty_input_mods_e(rawValue: 0) + event.consumed_mods = ghostty_input_mods_e(rawValue: 0) + event.keycode = 0xFFFF + event.composing = false + event.unshifted_codepoint = text.unicodeScalars.first.map(\.value) ?? 0 + + text.withCString { ptr in + event.text = ptr + view?.surface?.sendKeyEvent(event) + } + } + func currentSelectedRange() -> NSRange { markedTextState.currentSelectedRange } diff --git a/ios/vendor/GhosttyTerminal/Platform/PlatformSupport.swift b/ios/vendor/GhosttyTerminal/Platform/PlatformSupport.swift new file mode 100644 index 0000000..8c1a110 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/PlatformSupport.swift @@ -0,0 +1,18 @@ +// +// PlatformSupport.swift +// libghostty-spm +// + +// The library's one platform assertion. +// +// Every other file guards its code with `#if canImport(UIKit)` / +// `#elseif canImport(AppKit)` and stops there — none of them carries an +// `#else` arm of its own. A target with neither framework would quietly +// compile all of them to nothing, so the failure is stated once, here, in a +// file that is always compiled and has no other content to distract from it. +// +// See AGENTS.md, "Platform Guards", for the order the rest of the library +// follows. +#if !canImport(UIKit) && !canImport(AppKit) + #error("Unsupported platform: libghostty-spm requires UIKit or AppKit.") +#endif diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalFileStaging.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalFileStaging.swift new file mode 100644 index 0000000..1f30825 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalFileStaging.swift @@ -0,0 +1,292 @@ +// +// TerminalFileStaging.swift +// libghostty-spm +// + +import Foundation +import UniformTypeIdentifiers + +/// Files the terminal writes on the host's behalf so a program gets a path +/// it can open: image or document data pasted with no path of its own, and +/// anything dropped on the view. The pasteboard and drop readers hand it +/// item providers or bytes; it hands back shell-escaped paths, space-joined, +/// ready for the text path. +/// +/// Lifetime: a staged file belongs to the shell that received its path, and +/// nothing here can know when that shell is done with it. So a file stays +/// until ``staleFileAge`` has passed — swept whenever a new one is written, +/// or on ``removeStaleFiles()`` — and a host that knows nothing can refer to +/// them any more (its last session ended, the app is quitting together with +/// its shells) calls ``removeAllFiles()``. +public enum TerminalFileStaging { + /// Where staged files go. Defaults to a `ghostty-paste` folder in the + /// app's temporary directory; a host whose shell cannot read the app + /// container points it somewhere both can reach, before the first paste + /// or drop. + @MainActor + public static var directory = FileManager.default.temporaryDirectory + .appendingPathComponent("ghostty-paste", isDirectory: true) + + /// How long a staged file stays: 24 hours by default. The clipboard and + /// the drop forget it long before that; the shell that got its path may + /// still be typing it. + @MainActor + public static var staleFileAge: TimeInterval = 24 * 60 * 60 + + /// A provider and the type it will be asked for. `NSItemProvider` is + /// thread-safe by contract but not marked so; its own load completion + /// is the only place it travels to. + struct Item: @unchecked Sendable { + let provider: NSItemProvider + let type: UTType + } + + /// One slot per staged item, filled by whichever load completion runs. + /// A reference type because the completions run concurrently and share + /// it; the lock is what makes the sharing safe. + private final class Paths: @unchecked Sendable { + private let lock = NSLock() + private var values: [String?] + + init(count: Int) { + values = .init(repeating: nil, count: count) + } + + func set(_ path: String?, at index: Int) { + lock.lock() + values[index] = path + lock.unlock() + } + + var resolved: [String] { + lock.lock() + defer { lock.unlock() } + return values.compactMap { $0 } + } + } + + // MARK: - Cleanup + + /// Removes staged files older than ``staleFileAge``. Cheap when the + /// directory does not exist. + @MainActor + public static func removeStaleFiles() { + sweep(directory, olderThan: staleFileAge) + } + + /// Removes every staged file. For the moment the host knows no shell + /// can still refer to one: its last session ended, or the app is + /// quitting and taking its shells with it. + @MainActor + public static func removeAllFiles() { + do { + try FileManager.default.removeItem(at: directory) + } catch CocoaError.fileNoSuchFile { + } catch { + TerminalDebugLog.log(.input, "staged files not removed: \(error)") + } + } + + // MARK: - Staging + + /// Writes every provider's file representation under ``directory`` and + /// completes on the main queue with the escaped, space-joined paths, or + /// `nil` when none could be written. Every load is requested before this + /// returns — a drop session's providers only answer within the + /// `performDrop` call that handed them over — and the copying happens + /// in each load's completion, off the main thread. + @MainActor + static func stage( + _ items: [Item], + completion: @escaping @MainActor (String?) -> Void + ) { + let directory = directory + guard prepareDirectory(directory, staleAge: staleFileAge) else { + completion(nil) + return + } + let group = DispatchGroup() + let paths = Paths(count: items.count) + for (index, item) in items.enumerated() { + group.enter() + // The representation is deleted when the completion + // returns, so it is copied out before then. + item.provider.loadFileRepresentation(forTypeIdentifier: item.type.identifier) { url, error in + defer { group.leave() } + guard let url else { + TerminalDebugLog.log( + .input, + "staging file representation failed type=\(item.type.identifier) error=\(String(describing: error))" + ) + return + } + let (name, fileExtension) = fileName(suggested: item.provider.suggestedName, type: item.type) + let path = store(name: name, extension: fileExtension, in: directory) { + try FileManager.default.copyItem(at: url, to: $0) + } + paths.set(path, at: index) + } + } + // `@Sendable` keeps the block off the main actor; formed here it + // would otherwise inherit `stage`'s isolation and trap on the + // global queue. + group.notify(queue: .global(qos: .userInitiated)) { @Sendable in + let resolved = paths.resolved + TerminalDebugLog.log(.input, "staged \(resolved.count)/\(items.count) file(s)") + terminalRunOnMain { + completion(resolved.isEmpty ? nil : resolved.map(TerminalShellEscape.escape).joined(separator: " ")) + } + } + } + + /// Writes raw bytes as one staged file named for `type` and completes on + /// the main queue with its escaped path, or `nil` when the write failed. + @MainActor + static func stage( + data: Data, + name: String, + type: UTType, + completion: @escaping @MainActor (String?) -> Void + ) { + let directory = directory + let staleAge = staleFileAge + DispatchQueue.global(qos: .userInitiated).async { + guard prepareDirectory(directory, staleAge: staleAge) else { + terminalRunOnMain { completion(nil) } + return + } + let path = store(name: name, extension: type.preferredFilenameExtension ?? "bin", in: directory) { + try data.write(to: $0, options: .atomic) + } + terminalRunOnMain { completion(path.map(TerminalShellEscape.escape)) } + } + } + + // MARK: - Rules + + /// The type worth a file among what one item offers, or `nil` when it + /// carries text, a link, or a folder. Images win over anything else the + /// same item registers (a copied photo also registers its URL); text + /// wins over the rest, since a rich-text or web selection also registers + /// data types (`com.apple.flat-rtfd`, `com.apple.webarchive`) that are + /// not text themselves. + static func fileType(among identifiers: [String]) -> UTType? { + let types = identifiers.compactMap(UTType.init) + if let image = types.first(where: { $0.conforms(to: .image) }) { + return image + } + if types.contains(where: { $0.conforms(to: .text) }) { + return nil + } + // Dynamic types (`dyn.a…`) are pasteboard bookkeeping. + return types.first { type in + !type.isDynamic + && type.conforms(to: .data) + && !type.conforms(to: .text) + && !type.conforms(to: .url) + } + } + + /// The file name a staged item gets: its own when the provider carries + /// one, else `image`/`file`, always with the type's preferred extension + /// unless the name brought its own. + static func fileName(suggested: String?, type: UTType) -> (name: String, extension: String) { + let preferred = type.preferredFilenameExtension ?? "bin" + guard let suggested = suggested.map({ $0 as NSString }), suggested.length > 0 else { + return (type.conforms(to: .image) ? "image" : "file", preferred) + } + let existing = suggested.pathExtension + return ( + existing.isEmpty ? suggested as String : suggested.deletingPathExtension, + existing.isEmpty ? preferred : existing + ) + } + + /// A path under `directory` that no earlier file took: the name, the + /// time in seconds, and a counter only when two share both. The name + /// loses path separators and control characters — the shell escape + /// covers everything else. + static func uniqueURL(name: String, extension fileExtension: String, in directory: URL) -> URL { + let safeName = String(name.map { $0 == "/" || $0.isNewline || $0.asciiValue.map { $0 < 0x20 } == true ? "_" : $0 }) + let stamp = Int(Date().timeIntervalSince1970) + var candidate = directory.appendingPathComponent("\(safeName)-\(stamp).\(fileExtension)") + var counter = 1 + while FileManager.default.fileExists(atPath: candidate.path) { + candidate = directory.appendingPathComponent("\(safeName)-\(stamp)-\(counter).\(fileExtension)") + counter += 1 + } + return candidate + } + + /// Serialises name choice and write: provider completions arrive on + /// concurrent queues, and two items with the same name would otherwise + /// be handed the same path. + private static let storeLock = NSLock() + + /// Writes one item into `directory` — prepared by the caller, once per + /// paste or drop — readable by whoever can reach the directory, and + /// returns its path, or `nil` when the write fails. + static func store( + name: String, + extension fileExtension: String, + in directory: URL, + write: (URL) throws -> Void + ) -> String? { + storeLock.lock() + defer { storeLock.unlock() } + let destination = uniqueURL(name: name, extension: fileExtension, in: directory) + do { + try write(destination) + // A copied representation keeps the provider's mode and an + // atomic write follows the umask; the shell that opens the + // file may not be the app's user. The copy also keeps the + // source's modification date, which the sweep would read as + // age, so the file is stamped with the time its path was + // handed out. + try FileManager.default.setAttributes( + [.posixPermissions: 0o644, .modificationDate: Date()], + ofItemAtPath: destination.path + ) + return destination.path + } catch { + TerminalDebugLog.log(.input, "staged file write failed: \(error)") + return nil + } + } + + /// Creates the staging directory, readable by anyone who can reach it + /// (the shell may not be the app's own user), and sweeps what is stale. + /// Once per paste or drop, before any file is written. + static func prepareDirectory(_ directory: URL, staleAge: TimeInterval) -> Bool { + do { + try FileManager.default.createDirectory( + at: directory, + withIntermediateDirectories: true, + attributes: [.posixPermissions: 0o755] + ) + } catch { + TerminalDebugLog.log(.input, "staging directory unavailable: \(error)") + return false + } + sweep(directory, olderThan: staleAge) + return true + } + + /// Removes the files in `directory` whose modification date is older + /// than `age`. Nothing to do when the directory does not exist. + static func sweep(_ directory: URL, olderThan age: TimeInterval) { + let manager = FileManager.default + let cutoff = Date().addingTimeInterval(-age) + let contents = (try? manager.contentsOfDirectory( + at: directory, + includingPropertiesForKeys: [.contentModificationDateKey] + )) ?? [] + for url in contents { + let modified = (try? url.resourceValues(forKeys: [.contentModificationDateKey]))? + .contentModificationDate ?? .distantPast + if modified < cutoff { + try? manager.removeItem(at: url) + } + } + } +} diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalHardwareKeyRouter.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalHardwareKeyRouter.swift index 727f710..eaa6dfa 100644 --- a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalHardwareKeyRouter.swift +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalHardwareKeyRouter.swift @@ -6,132 +6,30 @@ import Foundation import GhosttyKit -enum TerminalHardwareKeyDelivery: Equatable { - case ghostty(ghostty_input_key_e) - case data(Data) - - var isDirectInput: Bool { - if case .data = self { - return true - } - return false - } -} - enum TerminalHardwareKeyRouter { - static func routeUIKit( - usage: UInt16, - backend: TerminalSessionBackend - ) -> TerminalHardwareKeyDelivery { - if case .inMemory = backend, - let data = directControlInputForUIKit(usage: usage) - { - return .data(data) - } - - return .ghostty(ghosttyKeyForUIKit(usage: usage)) - } - - static func routeUIKit( - usage: UInt16, - backend: TerminalSessionBackend, - modifiers: TerminalInputModifiers - ) -> TerminalHardwareKeyDelivery { - // Raw host-managed bytes only represent the unmodified control key. - // Modified synthetic accessory keys need a real Ghostty key event so - // the backend can emit the correct escape sequence for those modifiers. - guard modifiers.isEmpty else { - return .ghostty(ghosttyKeyForUIKit(usage: usage)) - } - return routeUIKit(usage: usage, backend: backend) - } - - static func routeAppKit( - keyCode: UInt16, - backend: TerminalSessionBackend - ) -> TerminalHardwareKeyDelivery { - if case .inMemory = backend, - let data = directControlInputForAppKit(keyCode: keyCode) - { - return .data(data) - } - - return .ghostty(ghosttyKeyForAppKit(keyCode: keyCode)) - } - - private static func directControlInputForUIKit(usage: UInt16) -> Data? { - switch usage { - case 0x2A: - Data([0x7F]) - case 0x2B: - Data([0x09]) - case 0x4C: - Data("\u{1B}[3~".utf8) - case 0x4A: - Data("\u{1B}[H".utf8) - case 0x4D: - Data("\u{1B}[F".utf8) - case 0x4B: - Data("\u{1B}[5~".utf8) - case 0x4E: - Data("\u{1B}[6~".utf8) - case 0x4F: - Data("\u{1B}[C".utf8) - case 0x50: - Data("\u{1B}[D".utf8) - case 0x51: - Data("\u{1B}[B".utf8) - case 0x52: - Data("\u{1B}[A".utf8) - default: - nil - } - } - - private static func directControlInputForAppKit(keyCode: UInt16) -> Data? { - switch keyCode { - case 0x33: - Data([0x7F]) - case 0x30: - Data([0x09]) - case 0x75: - Data("\u{1B}[3~".utf8) - case 0x73: - Data("\u{1B}[H".utf8) - case 0x77: - Data("\u{1B}[F".utf8) - case 0x74: - Data("\u{1B}[5~".utf8) - case 0x79: - Data("\u{1B}[6~".utf8) - case 0x7B: - Data("\u{1B}[D".utf8) - case 0x7C: - Data("\u{1B}[C".utf8) - case 0x7D: - Data("\u{1B}[B".utf8) - case 0x7E: - Data("\u{1B}[A".utf8) - default: - nil - } - } + // Every key on every backend travels through `ghostty_surface_key`, so + // the core's key encoder owns the bytes: DECCKM application-cursor + // sequences, kitty keyboard protocol, and modifier-aware escapes all + // stay correct. The in-memory backend forwards the encoder's output to + // the host verbatim (termio queueWrite -> host write callback), so it + // needs no raw-byte side channel. - private static func ghosttyKeyForUIKit(usage: UInt16) -> ghostty_input_key_e { + static func ghosttyKey(forUIKitUsage usage: UInt16) -> ghostty_input_key_e { uiKitMap[usage] ?? GHOSTTY_KEY_UNIDENTIFIED } - private static func ghosttyKeyForAppKit(keyCode: UInt16) -> ghostty_input_key_e { + static func ghosttyKey(forAppKitKeyCode keyCode: UInt16) -> ghostty_input_key_e { appKitMap[keyCode] ?? GHOSTTY_KEY_UNIDENTIFIED } /// Sentinel `keycode` value for keys that have no macOS AppKit - /// equivalent (e.g. CUT/COPY/PASTE, media keys, CONTEXT_MENU, INSERT on - /// PC keyboards). Any value outside the 8-bit AppKit virtual keycode - /// range falls out of libghostty's native-keycode lookup and resolves - /// to `.unidentified`. The pinned Ghostty keycode table uses 8-bit macOS - /// keycodes, so `0x1_0000` stays safely outside the native range. Using - /// plain `0` would instead collide with AppKit's keycode for the `A` key. + /// equivalent in the pinned Ghostty keycode table (e.g. CUT/COPY/PASTE, + /// media keys, HELP, FN, NUMPAD_CLEAR). Any value outside the 8-bit + /// AppKit virtual keycode range falls out of libghostty's native-keycode + /// lookup and resolves to `.unidentified`. The pinned Ghostty keycode + /// table uses 8-bit macOS keycodes, so `0x1_0000` stays safely outside + /// the native range. Using plain `0` would instead collide with AppKit's + /// keycode for the `A` key. static let unidentifiedAppKitKeyCode: UInt32 = 0x10000 /// Translate a Ghostty key enum to the macOS AppKit virtual keycode @@ -178,6 +76,8 @@ enum TerminalHardwareKeyRouter { 0x53: 0x47, // keyboardNonUSBackslash -> kVK_ISO_Section 0x64: 0x0A, + // keyboardHelp -> kVK_Help, which libghostty resolves as Insert + 0x75: 0x72, ] private typealias Pair = (UInt16, ghostty_input_key_e) @@ -289,9 +189,11 @@ enum TerminalHardwareKeyRouter { ] ) - /// JIS keyboard entries are still absent from this table: - /// (0x5D, GHOSTTY_KEY_INTL_YEN) // kVK_JIS_Yen - /// (0x5E, GHOSTTY_KEY_INTL_RO) // kVK_JIS_Underscore + /// The mac column of libghostty's `src/input/keycodes.zig` for every + /// code its `code_to_key` names. A row absent there (Help, Fn, + /// NumpadClear, IntlBackslash, IntlYen, IntlRo) stays absent here, or + /// `hasPlatformKeycode` would promise a key libghostty resolves to + /// `.unidentified`. private static let appKitMap = buildMap( literalPairs: [ (0x00, GHOSTTY_KEY_A), (0x01, GHOSTTY_KEY_S), (0x02, GHOSTTY_KEY_D), @@ -315,9 +217,9 @@ enum TerminalHardwareKeyRouter { (0x35, GHOSTTY_KEY_ESCAPE), (0x36, GHOSTTY_KEY_META_RIGHT), (0x37, GHOSTTY_KEY_META_LEFT), (0x38, GHOSTTY_KEY_SHIFT_LEFT), (0x39, GHOSTTY_KEY_CAPS_LOCK), (0x3A, GHOSTTY_KEY_ALT_LEFT), (0x3B, GHOSTTY_KEY_CONTROL_LEFT), (0x3C, GHOSTTY_KEY_SHIFT_RIGHT), (0x3D, GHOSTTY_KEY_ALT_RIGHT), - (0x3E, GHOSTTY_KEY_CONTROL_RIGHT), (0x3F, GHOSTTY_KEY_FN), (0x40, GHOSTTY_KEY_F17), + (0x3E, GHOSTTY_KEY_CONTROL_RIGHT), (0x40, GHOSTTY_KEY_F17), (0x41, GHOSTTY_KEY_NUMPAD_DECIMAL), - (0x43, GHOSTTY_KEY_NUMPAD_MULTIPLY), (0x45, GHOSTTY_KEY_NUMPAD_ADD), (0x47, GHOSTTY_KEY_NUMPAD_CLEAR), + (0x43, GHOSTTY_KEY_NUMPAD_MULTIPLY), (0x45, GHOSTTY_KEY_NUMPAD_ADD), (0x47, GHOSTTY_KEY_NUM_LOCK), (0x48, GHOSTTY_KEY_AUDIO_VOLUME_UP), (0x49, GHOSTTY_KEY_AUDIO_VOLUME_DOWN), (0x4A, GHOSTTY_KEY_AUDIO_VOLUME_MUTE), (0x4B, GHOSTTY_KEY_NUMPAD_DIVIDE), (0x4C, GHOSTTY_KEY_NUMPAD_ENTER), (0x4E, GHOSTTY_KEY_NUMPAD_SUBTRACT), @@ -330,8 +232,9 @@ enum TerminalHardwareKeyRouter { (0x61, GHOSTTY_KEY_F6), (0x62, GHOSTTY_KEY_F7), (0x63, GHOSTTY_KEY_F3), (0x64, GHOSTTY_KEY_F8), (0x65, GHOSTTY_KEY_F9), (0x67, GHOSTTY_KEY_F11), (0x69, GHOSTTY_KEY_F13), (0x6A, GHOSTTY_KEY_F16), (0x6B, GHOSTTY_KEY_F14), - (0x6D, GHOSTTY_KEY_F10), (0x6F, GHOSTTY_KEY_F12), (0x71, GHOSTTY_KEY_F15), - (0x72, GHOSTTY_KEY_HELP), (0x73, GHOSTTY_KEY_HOME), (0x74, GHOSTTY_KEY_PAGE_UP), + (0x6D, GHOSTTY_KEY_F10), (0x6E, GHOSTTY_KEY_CONTEXT_MENU), (0x6F, GHOSTTY_KEY_F12), + (0x71, GHOSTTY_KEY_F15), + (0x72, GHOSTTY_KEY_INSERT), (0x73, GHOSTTY_KEY_HOME), (0x74, GHOSTTY_KEY_PAGE_UP), (0x75, GHOSTTY_KEY_DELETE), (0x76, GHOSTTY_KEY_F4), (0x77, GHOSTTY_KEY_END), (0x78, GHOSTTY_KEY_F2), (0x79, GHOSTTY_KEY_PAGE_DOWN), (0x7A, GHOSTTY_KEY_F1), (0x7B, GHOSTTY_KEY_ARROW_LEFT), (0x7C, GHOSTTY_KEY_ARROW_RIGHT), diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalIMEComposition.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalIMEComposition.swift new file mode 100644 index 0000000..d736e57 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalIMEComposition.swift @@ -0,0 +1,48 @@ +// +// TerminalIMEComposition.swift +// libghostty-spm +// +// Routing decisions for hardware keys while a composition-based input +// method (Chinese, Japanese, Korean) is the active input mode. Pure logic, +// kept platform-free so the macOS test suite can pin it. +// + +import Foundation + +enum TerminalIMEComposition { + /// Whether the input mode identified by `primaryLanguage` composes text + /// through marked-text preedit instead of inserting each keystroke + /// directly. These are the modes where a raw hardware key must not reach + /// the terminal: the input method turns key sequences into text. + static func languageUsesComposition(_ primaryLanguage: String?) -> Bool { + guard let primaryLanguage else { return false } + let language = primaryLanguage.lowercased() + return language.hasPrefix("zh") + || language.hasPrefix("ja") + || language.hasPrefix("ko") + } + + /// Whether a hardware key press belongs to the input method rather than + /// the terminal. + /// + /// With marked text on screen every key is the input method's — it moves + /// the composition caret, picks candidates, commits, or cancels. Before + /// composition starts, only presses that produce printable text can open + /// one; control characters (Return, Tab, Escape…) and function keys keep + /// driving the terminal even while a composition input mode is active. + static func shouldDeferKey( + characters: String?, + hasMarkedText: Bool, + inputModeUsesComposition: Bool + ) -> Bool { + if hasMarkedText { return true } + guard inputModeUsesComposition else { return false } + guard + let text = TerminalInputText.filteredFunctionKeyText(characters), + !text.isEmpty + else { return false } + return !text.unicodeScalars.contains { + $0.value < 0x20 || $0.value == 0x7F + } + } +} diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalInputText.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalInputText.swift index 60eee1d..180d639 100644 --- a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalInputText.swift +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalInputText.swift @@ -43,3 +43,25 @@ enum TerminalInputText { text.hasPrefix("UIKeyInput") } } + +enum TerminalSoftwareKeyCommitRoute: Sendable, Equatable { + case suppressHardwareDuplicate + case semanticEnter + case text +} + +enum TerminalSoftwareKeyCommitRouter { + static func route( + text: String, + hasMarkedText: Bool, + hardwareKeyHandled: Bool + ) -> TerminalSoftwareKeyCommitRoute { + if hardwareKeyHandled { + return .suppressHardwareDuplicate + } + if !hasMarkedText, text == "\n" || text == "\r" { + return .semanticEnter + } + return .text + } +} diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalMainActor.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalMainActor.swift index 41783dd..b95b26e 100644 --- a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalMainActor.swift +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalMainActor.swift @@ -17,3 +17,29 @@ func terminalRunOnMain( } } } + +/// Runs on the main queue's *next* turn — never inline, even when the caller +/// is already on the main thread. +/// +/// This exists for one narrow job: publishing a change that a view layout +/// produced. SwiftUI runs `layoutSubviews` inside its own update pass, so an +/// `@Published` mutation made from a layout-driven delegate callback lands +/// while SwiftUI is mid-update, and it says so — "Publishing changes from +/// within view updates is not allowed, this will cause undefined behavior." +/// Hopping to the next turn puts the mutation after the update pass has +/// finished, which is the only place it is legal. +/// +/// Ordering is preserved: the main queue is FIFO, so two changes scheduled in +/// order are applied in that order. Use ``terminalRunOnMain`` for everything +/// else — work that merely needs *to be* on the main actor should not pay a +/// turn of latency for it. +@inline(__always) +func terminalRunOnMainNextTurn( + _ operation: @escaping @MainActor () -> Void +) { + DispatchQueue.main.async { + MainActor.assumeIsolated { + operation() + } + } +} diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalPasteboardContent.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalPasteboardContent.swift new file mode 100644 index 0000000..9d11b13 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalPasteboardContent.swift @@ -0,0 +1,143 @@ +// +// TerminalPasteboardContent.swift +// libghostty-spm +// +// Reference: +// - ghostty-org/ghostty +// - macos/Sources/Helpers/NSPasteboard+Extension.swift +// (`getOpinionatedStringContents`: URLs first — file URLs paste as +// escaped paths, others verbatim — then the string) +// + +import Foundation +import UniformTypeIdentifiers + +#if canImport(UIKit) + import UIKit +#elseif canImport(AppKit) + import AppKit +#endif + +/// What a paste hands the terminal, read off the general pasteboard the way +/// Ghostty's macOS app reads it: files as shell-escaped paths, text as-is. +/// +/// Two readers, deliberately kept apart: +/// +/// - ``text(from:)`` is what ghostty's `read_clipboard` callback uses. It +/// serves the paste binding *and* a program's OSC 52 read, so it has no +/// side effects and never touches the disk. +/// - ``files(from:completion:)`` (UIKit) is for a host-driven paste when the +/// pasteboard holds raw image or document data with no path at all — a +/// screenshot, a photo, a file copied out of Files. That data is staged as +/// a file first (``TerminalFileStaging``) so the paste lands as a path a +/// program can open; a program asking for "the clipboard" must never +/// trigger that write. +public enum TerminalPasteboardContent { + /// Upstream's `getOpinionatedStringContents`, as the one rule every + /// reader applies: URLs first — a file URL as its shell-escaped path, + /// any other verbatim — then the string. The order matters: a file + /// copied in Finder or Files carries both its URL and its display name + /// as the string, and taking the string first pasted the name. + static func text(string: String?, urls: [URL]) -> String? { + if !urls.isEmpty { + return urls + .map { $0.isFileURL ? TerminalShellEscape.escape($0.path) : $0.absoluteString } + .joined(separator: " ") + } + guard let string, !string.isEmpty else { return nil } + return string + } + + #if canImport(UIKit) + /// Where pasted data with no path of its own is written. Forwards to + /// ``TerminalFileStaging/directory``, which drops share. + @MainActor + public static var fileDirectory: URL { + get { TerminalFileStaging.directory } + set { TerminalFileStaging.directory = newValue } + } + + /// Whether a paste would deliver anything — what ``text(from:)`` or + /// ``files(from:completion:)`` would; the edit menu asks this on + /// every validation, so it stays on cheap pasteboard queries. + static func hasContent(_ pasteboard: UIPasteboard = .general) -> Bool { + if pasteboard.hasStrings || pasteboard.hasURLs || pasteboard.hasImages { return true } + if pasteboard.contains(pasteboardTypes: [UTType.fileURL.identifier]) { return true } + return TerminalFileStaging.fileType(among: pasteboard.types) != nil + } + + /// The pasteboard as text — see ``text(string:urls:)``. No side + /// effects. + static func text(from pasteboard: UIPasteboard = .general) -> String? { + let general = pasteboard.hasURLs ? (pasteboard.urls ?? []) : [] + // `hasURLs`/`urls` cover `public.url`; a file copied in Finder + // (Catalyst) or Files lands as `public.file-url`, which they do + // not report, so that representation is read on its own. + let files = general.contains(where: \.isFileURL) ? [] : fileURLs(in: pasteboard) + let urls = files + general + if !urls.isEmpty { + TerminalDebugLog.log(.input, "paste resolved \(urls.count) url(s), \(files.count) file url(s)") + } + return text(string: pasteboard.hasStrings ? pasteboard.string : nil, urls: urls) + } + + /// Every item's `public.file-url`, whatever form the pasteboard + /// stored it in. + static func fileURLs(in pasteboard: UIPasteboard) -> [URL] { + pasteboard.items.compactMap { item -> URL? in + guard let value = item[UTType.fileURL.identifier] else { return nil } + if let url = value as? URL { return url } + if let data = value as? Data { return URL(dataRepresentation: data, relativeTo: nil) } + if let string = value as? String { return URL(string: string) } + return nil + } + .filter(\.isFileURL) + } + + /// Image or document data on the pasteboard, staged under + /// ``fileDirectory`` and returned as space-joined shell-escaped + /// paths, or `nil` when there is none. Completes on the main queue. + @MainActor + static func files( + from pasteboard: UIPasteboard = .general, + completion: @escaping @MainActor (String?) -> Void + ) { + let items = pasteboard.itemProviders.compactMap { provider in + TerminalFileStaging.fileType(among: provider.registeredTypeIdentifiers) + .map { TerminalFileStaging.Item(provider: provider, type: $0) } + } + guard items.isEmpty else { + TerminalFileStaging.stage(items, completion: completion) + return + } + // A pasteboard that advertises an image without offering it + // through an item provider: take the encoded bytes when it has + // them, decode through `UIImage` only as a last resort. + guard pasteboard.hasImages else { + completion(nil) + return + } + let encoded = [UTType.png, .jpeg, .heic].lazy + .compactMap { type in + pasteboard.data(forPasteboardType: type.identifier).map { (data: $0, type: type) } + } + .first + if let encoded { + TerminalFileStaging.stage(data: encoded.data, name: "image", type: encoded.type, completion: completion) + } else if let data = pasteboard.image?.pngData() { + TerminalFileStaging.stage(data: data, name: "image", type: .png, completion: completion) + } else { + completion(nil) + } + } + + #elseif canImport(AppKit) + /// The pasteboard as text — see ``text(string:urls:)``. + static func text(from pasteboard: NSPasteboard = .general) -> String? { + text( + string: pasteboard.string(forType: .string), + urls: (pasteboard.readObjects(forClasses: [NSURL.self]) as? [URL]) ?? [] + ) + } + #endif +} diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalPointerPolicy.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalPointerPolicy.swift new file mode 100644 index 0000000..4086fab --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalPointerPolicy.swift @@ -0,0 +1,71 @@ +// +// TerminalPointerPolicy.swift +// libghostty-spm +// +// Host-side pointer decisions that must stay out of Ghostty: which +// physical button maps to which C enum, whether the copy menu may +// steal a secondary click, and press/release pairing so a cancel +// cannot leave a stuck button. +// + +import GhosttyKit + +enum TerminalPointerPolicy { + /// `ghostty.h` numbers extra buttons as FOUR=4 … ELEVEN=11, matching + /// UIKit's one-based `UIEvent.ButtonMask.button(n)` for n ≥ 4. + static let extraButtonRange = 4 ... 11 + + static func ghosttyButton( + secondary: Bool, + middle: Bool, + extraButtonNumber: Int? = nil + ) -> ghostty_input_mouse_button_e { + if secondary { + return GHOSTTY_MOUSE_RIGHT + } + if middle { + return GHOSTTY_MOUSE_MIDDLE + } + if let extra = extraButtonNumber, extraButtonRange.contains(extra) { + return ghostty_input_mouse_button_e(rawValue: UInt32(extra)) + } + return GHOSTTY_MOUSE_LEFT + } + + static func shouldPresentHostSecondaryMenu(mouseCaptured: Bool) -> Bool { + !mouseCaptured + } +} + +/// At most one Ghostty-visible mouse button. `press` is ignored while a +/// button is already reported. `release` is ignored unless it matches. +/// `cancel` / `finish` release whatever is reported, once. +struct TerminalPointerButtonSession: Equatable { + private(set) var reported: ghostty_input_mouse_button_e? + + mutating func press( + _ button: ghostty_input_mouse_button_e + ) -> ghostty_input_mouse_button_e? { + guard reported == nil else { return nil } + reported = button + return button + } + + mutating func release( + _ button: ghostty_input_mouse_button_e + ) -> ghostty_input_mouse_button_e? { + guard reported == button else { return nil } + reported = nil + return button + } + + mutating func finish() -> ghostty_input_mouse_button_e? { + guard let button = reported else { return nil } + reported = nil + return button + } + + mutating func cancel() -> ghostty_input_mouse_button_e? { + finish() + } +} diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalShellEscape.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalShellEscape.swift new file mode 100644 index 0000000..a39cca6 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalShellEscape.swift @@ -0,0 +1,35 @@ +// +// TerminalShellEscape.swift +// libghostty-spm +// +// Reference: +// - ghostty-org/ghostty +// - macos/Sources/Ghostty/Ghostty.Shell.swift +// Keep the character set aligned with Ghostty's `Shell.escape` so a path +// pasted here reads the same as one dropped on the macOS app. +// + +import Foundation + +enum TerminalShellEscape { + /// Characters a POSIX shell would otherwise interpret in a word. + private static let escapedCharacters: Set = [ + "\\", " ", "(", ")", "[", "]", "{", "}", "<", ">", "\"", "'", "`", + "!", "#", "$", "&", ";", "|", "*", "?", "\t", + ] + + /// Backslash-escapes every shell-sensitive character, the form a path + /// takes when typed into a live prompt (as opposed to a quoted form, which + /// would be right for building a command line to execute). + static func escape(_ string: String) -> String { + var result = "" + result.reserveCapacity(string.utf8.count) + for character in string { + if escapedCharacters.contains(character) { + result.append("\\") + } + result.append(character) + } + return result + } +} diff --git a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalView+Process.swift b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalView+Process.swift index 9ba254e..7e09e84 100644 --- a/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalView+Process.swift +++ b/ios/vendor/GhosttyTerminal/Platform/Shared/TerminalView+Process.swift @@ -15,12 +15,20 @@ public extension TerminalView { /// user runs a program in the pty this is that program's pid, so hosts can /// correlate the surface with an external process list. Nil until the /// surface has a process. + /// + /// On the pinned Ghostty (1.3.1) this is nil for every backend: + /// `ghostty_surface_foreground_pid` returns 0 there. It populates once + /// the pinned release carries the process-info API. var foregroundPid: pid_t? { surface?.foregroundPid } /// Name of the pty's controlling tty (e.g. `/dev/ttys004`), or nil until /// the surface has a process. + /// + /// On the pinned Ghostty (1.3.1) this is nil for every backend: + /// `ghostty_surface_tty_name` returns an empty name there. It populates + /// once the pinned release carries the process-info API. var ttyName: String? { surface?.ttyName } diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputAccessoryStyle.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputAccessoryStyle.swift index f1926fb..dfb49b2 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputAccessoryStyle.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputAccessoryStyle.swift @@ -3,7 +3,8 @@ // libghostty-spm // -#if canImport(UIKit) && !targetEnvironment(macCatalyst) +#if canImport(UIKit) + #if !targetEnvironment(macCatalyst) import UIKit public struct TerminalInputAccessoryStyle: Sendable { @@ -26,4 +27,5 @@ public static let `default` = TerminalInputAccessoryStyle() } + #endif #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputAccessoryView.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputAccessoryView.swift index 18da09b..2f48f4d 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputAccessoryView.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputAccessoryView.swift @@ -3,7 +3,8 @@ // libghostty-spm // -#if canImport(UIKit) && !targetEnvironment(macCatalyst) +#if canImport(UIKit) + #if !targetEnvironment(macCatalyst) import UIKit @MainActor @@ -43,7 +44,7 @@ setupViews() applyBarChrome() refreshContent() - terminalView.stickyModifiers.onChange = { [weak self] in + terminalView.stickyModifiers.onBarChange = { [weak self] in self?.refreshContent() } } @@ -160,42 +161,46 @@ } private func makeView(for item: TerminalInputAccessoryItem) -> UIView { + // Titles and glyphs come from the item itself + // (`TerminalInputAccessoryItem.title` / `.systemImage`) so hosts + // that render their own picker UI stay in sync with the bar. + let title = item.title ?? "" switch item { - case .esc: - makeTrackedKeyButton(title: "Escape", systemImage: "escape", key: .esc) - case .ctrl: - makeTrackedModifierButton(title: "Control", systemImage: "control", modifier: .ctrl) + return makeTrackedModifierButton(title: title, systemImage: item.systemImage ?? "", modifier: .ctrl) case .alt: - makeTrackedModifierButton(title: "Option", systemImage: "option", modifier: .alt) + return makeTrackedModifierButton(title: title, systemImage: item.systemImage ?? "", modifier: .alt) case .command: - makeTrackedModifierButton(title: "Command", systemImage: "command", modifier: .command) + return makeTrackedModifierButton(title: title, systemImage: item.systemImage ?? "", modifier: .command) + + case .esc: + return makeTrackedKeyButton(title: title, systemImage: item.systemImage, key: .esc) case .tab: - makeTrackedKeyButton(title: "Tab", systemImage: "arrow.right.to.line", key: .tab) + return makeTrackedKeyButton(title: title, systemImage: item.systemImage, key: .tab) case .arrowLeft: - makeTrackedKeyButton(title: "Left", systemImage: "arrowtriangle.left.fill", key: .arrowLeft) + return makeTrackedKeyButton(title: title, systemImage: item.systemImage, key: .arrowLeft) case .arrowUp: - makeTrackedKeyButton(title: "Up", systemImage: "arrowtriangle.up.fill", key: .arrowUp) + return makeTrackedKeyButton(title: title, systemImage: item.systemImage, key: .arrowUp) case .arrowDown: - makeTrackedKeyButton(title: "Down", systemImage: "arrowtriangle.down.fill", key: .arrowDown) + return makeTrackedKeyButton(title: title, systemImage: item.systemImage, key: .arrowDown) case .arrowRight: - makeTrackedKeyButton(title: "Right", systemImage: "arrowtriangle.right.fill", key: .arrowRight) + return makeTrackedKeyButton(title: title, systemImage: item.systemImage, key: .arrowRight) case let .symbol(symbol): - makeTrackedKeyButton(title: symbol, key: .symbol(symbol)) + return makeTrackedKeyButton(title: title, key: .symbol(symbol)) case .paste: - makeTrackedKeyButton(title: "Paste", systemImage: "doc.on.clipboard", key: .paste) + return makeTrackedKeyButton(title: title, systemImage: item.systemImage, key: .paste) case .divider: - makeDivider() + return makeDivider() } } @@ -278,13 +283,16 @@ } private func makeBarEffect() -> UIVisualEffect { + // visionOS windows are glass already and the SDK has no + // `UIGlassEffect`; the blur is the bar's chrome there. + #if !os(visionOS) if #available(iOS 26, *) { let effect = UIGlassEffect(style: .regular) effect.isInteractive = true return effect - } else { - return UIBlurEffect(style: .systemUltraThinMaterial) } + #endif + return UIBlurEffect(style: .systemUltraThinMaterial) } private func applyBarChrome() { @@ -397,4 +405,5 @@ configuration?.baseForegroundColor = tintColor } } + #endif #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputBarKey.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputBarKey.swift index 4a2b793..38ef991 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputBarKey.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalInputBarKey.swift @@ -3,7 +3,8 @@ // libghostty-spm // -#if canImport(UIKit) && !targetEnvironment(macCatalyst) +#if canImport(UIKit) + #if !targetEnvironment(macCatalyst) public enum TerminalInputAccessoryItem: Equatable, Sendable { case esc case ctrl @@ -18,6 +19,46 @@ case paste case divider + /// The English name the accessory bar uses as the button's + /// accessibility label; `symbol` returns its literal text and + /// `divider` has none. Public so a host's bar-configuration UI can + /// describe items without duplicating this table. + public var title: String? { + switch self { + case .esc: "Escape" + case .ctrl: "Control" + case .alt: "Option" + case .command: "Command" + case .tab: "Tab" + case .arrowLeft: "Left Arrow" + case .arrowUp: "Up Arrow" + case .arrowDown: "Down Arrow" + case .arrowRight: "Right Arrow" + case let .symbol(symbol): symbol + case .paste: "Paste" + case .divider: nil + } + } + + /// The SF Symbol the accessory bar renders for this item; nil for + /// items drawn as text (`symbol`) or non-buttons (`divider`). Public + /// so a host's bar-configuration UI shows the same glyphs as the bar. + public var systemImage: String? { + switch self { + case .esc: "escape" + case .ctrl: "control" + case .alt: "option" + case .command: "command" + case .tab: "arrow.right.to.line" + case .arrowLeft: "arrowtriangle.left.fill" + case .arrowUp: "arrowtriangle.up.fill" + case .arrowDown: "arrowtriangle.down.fill" + case .arrowRight: "arrowtriangle.right.fill" + case .paste: "doc.on.clipboard" + case .symbol, .divider: nil + } + } + public static let defaultItems: [TerminalInputAccessoryItem] = [ .esc, .tab, @@ -52,4 +93,5 @@ case symbol(String) case paste } + #endif #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalStickyModifierState.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalStickyModifierState.swift index 91a3c7b..ba38986 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalStickyModifierState.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalStickyModifierState.swift @@ -3,7 +3,8 @@ // libghostty-spm // -#if canImport(UIKit) && !targetEnvironment(macCatalyst) +#if canImport(UIKit) + #if !targetEnvironment(macCatalyst) import Foundation @MainActor @@ -15,7 +16,11 @@ private(set) var alt: Activation = .inactive private(set) var command: Activation = .inactive + /// The host's slot (`setStickyModifierChangeHandler`). The bundled + /// bar has its own so neither can overwrite the other — the lazy + /// bar is materialised by paths a custom-bar host cannot avoid. var onChange: (() -> Void)? + var onBarChange: (() -> Void)? private var lastCtrlTap: Date = .distantPast private var lastAltTap: Date = .distantPast @@ -34,7 +39,7 @@ command = nextActivation(command, lastTap: lastCommandTap) lastCommandTap = Date() } - onChange?() + notifyChange() } func consumeForNextKey() -> TerminalInputModifiers { @@ -45,7 +50,7 @@ if ctrl == .armed { ctrl = .inactive } if alt == .armed { alt = .inactive } if command == .armed { command = .inactive } - onChange?() + notifyChange() return mods } @@ -58,7 +63,12 @@ ctrl = .inactive alt = .inactive command = .inactive + notifyChange() + } + + private func notifyChange() { onChange?() + onBarChange?() } private func nextActivation( @@ -78,4 +88,5 @@ } } } + #endif #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalTextInputHandler@UIKit.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalTextInputHandler@UIKit.swift index 62a3ec9..5790a28 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalTextInputHandler@UIKit.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalTextInputHandler@UIKit.swift @@ -49,10 +49,10 @@ if applyingStickyModifiers { _ = view.handleStickyCommittedText(text) } else { - view.surface?.sendText(text) + sendTypedText(text) } #else - view.surface?.sendText(text) + sendTypedText(text) #endif view.refreshInputAccessoryContent() @@ -62,6 +62,57 @@ view.inputDelegate?.textDidChange(view) } + /// Deliver keyboard text the way a hardware keystroke does: on a key + /// event that carries the text, so ghostty's key encoder writes the + /// bytes. + /// + /// `ghostty_surface_text` is documented as "treated like a paste" — + /// it lands in `completeClipboardPaste`, so with bracketed paste + /// (mode 2004) active every software-keyboard character reaches the + /// shell wrapped in `ESC[200~ … ESC[201~`. zsh renders a pasted + /// region with `zle_highlight`'s `paste:standout`, which is why the + /// character just typed shows up reverse-video until the next edit + /// redraws the line. AppKit never hits this because typed text rides + /// its `keyDown` event, and the bundled sample app never hits it + /// because its simulated shell has no bracketed paste at all. + /// + /// The keycode is deliberately out of the AppKit virtual-keycode + /// table, so ghostty resolves the physical key to `.unidentified` + /// and encodes from the text alone. Both of ghostty's encoders + /// handle that: the legacy one writes unmodified printable text + /// directly, and the Kitty one treats an unmapped key carrying UTF-8 + /// as a pure text event — the same shape IME commits already had. + /// + /// Real pastes (the accessory's Paste button, the edit menu's + /// `paste(_:)`) keep using `sendText`, where the bracketed-paste + /// markers belong. Text with newlines is routed there too: whatever + /// produced it, a shell must not see those lines as Return presses. + private func sendTypedText(_ text: String) { + guard let view, !text.isEmpty else { return } + + guard !text.contains(where: \.isNewline) else { + TerminalDebugLog.log( + .input, + "typed text has newlines, sending as paste bytes=\(text.utf8.count)" + ) + view.surface?.sendText(text) + return + } + + var event = ghostty_input_key_s() + event.action = GHOSTTY_ACTION_PRESS + event.mods = ghostty_input_mods_e(rawValue: 0) + event.consumed_mods = ghostty_input_mods_e(rawValue: 0) + event.keycode = 0xFFFF + event.composing = false + event.unshifted_codepoint = text.unicodeScalars.first.map(\.value) ?? 0 + + text.withCString { ptr in + event.text = ptr + view.surface?.sendKeyEvent(event) + } + } + func setMarkedText(_ text: String?, selectedRange: NSRange) { guard let view else { return } let shouldNotifySelectionChange = shouldNotifySelectionChange @@ -133,10 +184,10 @@ if applyingStickyModifiers { _ = view.handleStickyCommittedText(committedText) } else { - view.surface?.sendText(committedText) + sendTypedText(committedText) } #else - view.surface?.sendText(committedText) + sendTypedText(committedText) #endif } view.refreshInputAccessoryContent() diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalTextPosition.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalTextPosition.swift index 92d73cf..2dee2eb 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalTextPosition.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/TerminalTextPosition.swift @@ -31,14 +31,6 @@ _start.index >= _end.index } - var startPosition: TerminalTextPosition { - _start - } - - var endPosition: TerminalTextPosition { - _end - } - var location: Int { _start.index } diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Drop.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Drop.swift new file mode 100644 index 0000000..737d4ac --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Drop.swift @@ -0,0 +1,72 @@ +// +// UITerminalView+Drop.swift +// libghostty-spm +// + +#if canImport(UIKit) + import UIKit + import UniformTypeIdentifiers + + /// Drag and drop onto the terminal, on iOS and Mac Catalyst alike. + /// + /// Files and images are copied into ``TerminalFileStaging/directory`` + /// and their shell-escaped paths are pasted — the drop's own + /// representation is gone the moment the drop completes, and a shell + /// that is not the app's user could not have read it anyway. A dropped + /// folder, link, or text pastes as text: a folder is a path the shell + /// can already reach. Everything a drop delivers travels the text path, + /// like a paste; a path or a string is never keystrokes. + extension UITerminalView: UIDropInteractionDelegate { + func setupDropInput() { + addInteraction(UIDropInteraction(delegate: self)) + } + + public func dropInteraction(_: UIDropInteraction, canHandle session: UIDropSession) -> Bool { + session.items.contains { item in + TerminalFileStaging.fileType(among: item.itemProvider.registeredTypeIdentifiers) != nil + } + || session.canLoadObjects(ofClass: NSURL.self) + || session.canLoadObjects(ofClass: NSString.self) + } + + public func dropInteraction(_: UIDropInteraction, sessionDidUpdate _: UIDropSession) -> UIDropProposal { + UIDropProposal(operation: .copy) + } + + public func dropInteraction(_: UIDropInteraction, performDrop session: UIDropSession) { + let files = session.items.compactMap { item in + TerminalFileStaging.fileType(among: item.itemProvider.registeredTypeIdentifiers) + .map { TerminalFileStaging.Item(provider: item.itemProvider, type: $0) } + } + if !files.isEmpty { + TerminalDebugLog.log(.input, "drop staging \(files.count) file(s)") + TerminalFileStaging.stage(files) { [weak self] paths in + guard let self, let paths else { + TerminalDebugLog.log(.input, "drop skipped: no file could be staged") + return + } + TerminalDebugLog.log(.input, "drop files bytes=\(paths.utf8.count)") + _ = surface?.sendText(paths) + } + return + } + if session.canLoadObjects(ofClass: NSURL.self) { + _ = session.loadObjects(ofClass: NSURL.self) { [weak self] objects in + let urls = objects.compactMap { ($0 as? NSURL).map { $0 as URL } } + guard let self, let text = TerminalPasteboardContent.text(string: nil, urls: urls) else { return } + TerminalDebugLog.log(.input, "drop urls count=\(urls.count)") + _ = surface?.sendText(text) + } + return + } + if session.canLoadObjects(ofClass: NSString.self) { + _ = session.loadObjects(ofClass: NSString.self) { [weak self] objects in + let text = objects.compactMap { ($0 as? NSString).map { $0 as String } }.joined() + guard let self, !text.isEmpty else { return } + TerminalDebugLog.log(.input, "drop text bytes=\(text.utf8.count)") + _ = surface?.sendText(text) + } + } + } + } +#endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+InputAccessory.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+InputAccessory.swift index 809e7dd..0aa4da5 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+InputAccessory.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+InputAccessory.swift @@ -3,14 +3,21 @@ // libghostty-spm // -#if canImport(UIKit) && !targetEnvironment(macCatalyst) +#if canImport(UIKit) + #if !targetEnvironment(macCatalyst) import GhosttyKit import UIKit extension UITerminalView { + // visionOS has no input accessory view: the software keyboard is its + // own window in the space, and UIKit does not offer the override. + // Hosts there draw a bar of their own and feed it through + // `+PublicSticky` / `sendKey`, as a Catalyst host does. + #if !os(visionOS) override open var inputAccessoryView: UIView? { inputAccessoryItems.isEmpty ? nil : terminalInputAccessory } + #endif func handleInputBarKey(_ key: TerminalInputBarKey) { commitMarkedTextIfStickyModifiersAreActive() @@ -20,10 +27,10 @@ _ = handleStickyTextInput(text) case .paste: + // Clipboard content, so the text path (bracketed paste) — + // see "Key Path vs Text Path" in AGENTS.md. _ = stickyModifiers.consumeForNextKey() - if let text = UIPasteboard.general.string, !text.isEmpty { - inputHandler.insertText(text) - } + pasteFromPasteboard() case .esc: let mods = stickyModifiers.consumeForNextKey() @@ -66,40 +73,25 @@ inputHandler.unmarkText() } - // Unmodified accessory arrows/Esc/Tab can still use the direct - // in-memory byte path, but sticky modifiers must round-trip - // through Ghostty so modifier-aware escape sequences are preserved. - let delivery = TerminalHardwareKeyRouter.routeUIKit( - usage: usage, - backend: configuration.backend, - modifiers: additionalMods + var event = ghostty_input_key_s() + event.action = GHOSTTY_ACTION_PRESS + event.keycode = TerminalHardwareKeyRouter.appKitKeyCode( + for: TerminalHardwareKeyRouter.ghosttyKey(forUIKitUsage: usage) ) + event.mods = additionalMods.ghosttyMods + _ = surface.sendKeyEvent(event) + sendSyntheticRelease(for: event) + } - if !additionalMods.isEmpty, let ghosttyKey = ghosttyKey(from: delivery) { - var event = ghostty_input_key_s() - event.action = GHOSTTY_ACTION_PRESS - event.keycode = TerminalHardwareKeyRouter.appKitKeyCode( - for: ghosttyKey - ) - event.mods = additionalMods.ghosttyMods - _ = surface.sendKeyEvent(event) - return - } - - switch delivery { - case let .data(data): - guard case let .inMemory(session) = configuration.backend else { return } - session.sendInput(data) - - case let .ghostty(ghosttyKey): - var event = ghostty_input_key_s() - event.action = GHOSTTY_ACTION_PRESS - event.keycode = TerminalHardwareKeyRouter.appKitKeyCode( - for: ghosttyKey - ) - event.mods = additionalMods.ghosttyMods - _ = surface.sendKeyEvent(event) - } + /// The matching release for a synthetic press, so a program on the + /// kitty protocol with event reporting never sees the key held + /// down — the same pairing `TerminalSurface.sendKey` guarantees. + func sendSyntheticRelease(for press: ghostty_input_key_s) { + guard let surface else { return } + var release = press + release.action = GHOSTTY_ACTION_RELEASE + release.text = nil + _ = surface.sendKeyEvent(release) } @discardableResult @@ -121,22 +113,12 @@ guard stickyModifiers.hasActiveModifiers else { return false } let keyText = String(text.prefix(1)) - guard !keyText.isEmpty else { - stickyModifiers.reset() - return false - } - let mods = stickyModifiers.consumeForNextKey() - let handled: Bool if mods == .ctrl, let controlByte = controlByte(for: keyText) { sendControlByte(controlByte, modifiers: mods) - handled = true - } else { - handled = sendModifiedTextKey(keyText, modifiers: mods) + return true } - - stickyModifiers.reset() - return handled + return sendModifiedTextKey(keyText, modifiers: mods) } @discardableResult @@ -173,19 +155,20 @@ inputHandler.unmarkText() } - if case let .inMemory(session) = configuration.backend { - session.sendInput(Data([byte])) - } else if let surface { - var event = ghostty_input_key_s() - event.action = GHOSTTY_ACTION_PRESS - event.mods = modifiers.ghosttyMods - let char = Character(UnicodeScalar(byte | 0x60)) - let ghosttyKey = ghosttyKeyForCharacter(char) - event.keycode = TerminalHardwareKeyRouter.appKitKeyCode( - for: ghosttyKey - ) - _ = surface.sendKeyEvent(event) - } + guard let surface else { return } + var event = ghostty_input_key_s() + event.action = GHOSTTY_ACTION_PRESS + event.mods = modifiers.ghosttyMods + let scalar = UnicodeScalar(byte | 0x60) + let ghosttyKey = ghosttyKeyForCharacter(Character(scalar)) + event.keycode = TerminalHardwareKeyRouter.appKitKeyCode( + for: ghosttyKey + ) + // The kitty encoder keys `CSI ;u` off this and drops + // the press without it (see "Key Path vs Text Path" in AGENTS.md). + event.unshifted_codepoint = scalar.value + _ = surface.sendKeyEvent(event) + sendSyntheticRelease(for: event) } private func controlByte(for text: String) -> UInt8? { @@ -195,7 +178,10 @@ return ascii & 0x1F } - private func sendModifiedTextKey( + // Not sticky-modifier logic — a plain "character + held modifiers → + // one key event" synthesizer; the hardware key-command fallback + // (UITerminalView+Keyboard) sends through it too. + func sendModifiedTextKey( _ text: String, modifiers: TerminalInputModifiers ) -> Bool { @@ -213,6 +199,9 @@ for: mapping.key ) event.mods = modifiers.union(mapping.extraModifiers).ghosttyMods + // See `sendControlByte`: the kitty encoder keys its CSI u + // sequence off this, not the keycode. + event.unshifted_codepoint = mapping.unshifted.value if !modifiers.contains(.super_) { text.withCString { ptr in @@ -222,57 +211,69 @@ } else { _ = surface.sendKeyEvent(event) } + sendSyntheticRelease(for: event) return true } - private func ghosttyKey(from delivery: TerminalHardwareKeyDelivery) -> ghostty_input_key_e? { - guard case let .ghostty(ghosttyKey) = delivery else { return nil } - return ghosttyKey - } - + /// The US-layout key that types `text`, the modifier it needs, and + /// what the same key types with no modifier at all — the codepoint + /// the kitty encoder reports. private func keyMapping( for text: String - ) -> (key: ghostty_input_key_e, extraModifiers: TerminalInputModifiers)? { - guard text.count == 1, let char = text.first else { return nil } + ) -> (key: ghostty_input_key_e, extraModifiers: TerminalInputModifiers, unshifted: UnicodeScalar)? { + guard text.count == 1, let char = text.first, let scalar = char.unicodeScalars.first else { + return nil + } switch char { case "a" ... "z": - return (ghosttyKeyForCharacter(char), []) + return (ghosttyKeyForCharacter(char), [], scalar) case "A" ... "Z": - return (ghosttyKeyForCharacter(Character(char.lowercased())), [.shift]) - case "0": return (GHOSTTY_KEY_DIGIT_0, []) - case "1": return (GHOSTTY_KEY_DIGIT_1, []) - case "2": return (GHOSTTY_KEY_DIGIT_2, []) - case "3": return (GHOSTTY_KEY_DIGIT_3, []) - case "4": return (GHOSTTY_KEY_DIGIT_4, []) - case "5": return (GHOSTTY_KEY_DIGIT_5, []) - case "6": return (GHOSTTY_KEY_DIGIT_6, []) - case "7": return (GHOSTTY_KEY_DIGIT_7, []) - case "8": return (GHOSTTY_KEY_DIGIT_8, []) - case "9": return (GHOSTTY_KEY_DIGIT_9, []) - case "`": return (GHOSTTY_KEY_BACKQUOTE, []) - case "~": return (GHOSTTY_KEY_BACKQUOTE, [.shift]) - case "-": return (GHOSTTY_KEY_MINUS, []) - case "_": return (GHOSTTY_KEY_MINUS, [.shift]) - case "=": return (GHOSTTY_KEY_EQUAL, []) - case "+": return (GHOSTTY_KEY_EQUAL, [.shift]) - case "[": return (GHOSTTY_KEY_BRACKET_LEFT, []) - case "{": return (GHOSTTY_KEY_BRACKET_LEFT, [.shift]) - case "]": return (GHOSTTY_KEY_BRACKET_RIGHT, []) - case "}": return (GHOSTTY_KEY_BRACKET_RIGHT, [.shift]) - case "\\": return (GHOSTTY_KEY_BACKSLASH, []) - case "|": return (GHOSTTY_KEY_BACKSLASH, [.shift]) - case ";": return (GHOSTTY_KEY_SEMICOLON, []) - case ":": return (GHOSTTY_KEY_SEMICOLON, [.shift]) - case "'": return (GHOSTTY_KEY_QUOTE, []) - case "\"": return (GHOSTTY_KEY_QUOTE, [.shift]) - case ",": return (GHOSTTY_KEY_COMMA, []) - case "<": return (GHOSTTY_KEY_COMMA, [.shift]) - case ".": return (GHOSTTY_KEY_PERIOD, []) - case ">": return (GHOSTTY_KEY_PERIOD, [.shift]) - case "/": return (GHOSTTY_KEY_SLASH, []) - case "?": return (GHOSTTY_KEY_SLASH, [.shift]) - case " ": return (GHOSTTY_KEY_SPACE, []) + let lowercase = Character(char.lowercased()) + return (ghosttyKeyForCharacter(lowercase), [.shift], lowercase.unicodeScalars.first ?? scalar) + case "0": return (GHOSTTY_KEY_DIGIT_0, [], scalar) + case "1": return (GHOSTTY_KEY_DIGIT_1, [], scalar) + case "2": return (GHOSTTY_KEY_DIGIT_2, [], scalar) + case "3": return (GHOSTTY_KEY_DIGIT_3, [], scalar) + case "4": return (GHOSTTY_KEY_DIGIT_4, [], scalar) + case "5": return (GHOSTTY_KEY_DIGIT_5, [], scalar) + case "6": return (GHOSTTY_KEY_DIGIT_6, [], scalar) + case "7": return (GHOSTTY_KEY_DIGIT_7, [], scalar) + case "8": return (GHOSTTY_KEY_DIGIT_8, [], scalar) + case "9": return (GHOSTTY_KEY_DIGIT_9, [], scalar) + case ")": return (GHOSTTY_KEY_DIGIT_0, [.shift], "0") + case "!": return (GHOSTTY_KEY_DIGIT_1, [.shift], "1") + case "@": return (GHOSTTY_KEY_DIGIT_2, [.shift], "2") + case "#": return (GHOSTTY_KEY_DIGIT_3, [.shift], "3") + case "$": return (GHOSTTY_KEY_DIGIT_4, [.shift], "4") + case "%": return (GHOSTTY_KEY_DIGIT_5, [.shift], "5") + case "^": return (GHOSTTY_KEY_DIGIT_6, [.shift], "6") + case "&": return (GHOSTTY_KEY_DIGIT_7, [.shift], "7") + case "*": return (GHOSTTY_KEY_DIGIT_8, [.shift], "8") + case "(": return (GHOSTTY_KEY_DIGIT_9, [.shift], "9") + case "`": return (GHOSTTY_KEY_BACKQUOTE, [], scalar) + case "~": return (GHOSTTY_KEY_BACKQUOTE, [.shift], "`") + case "-": return (GHOSTTY_KEY_MINUS, [], scalar) + case "_": return (GHOSTTY_KEY_MINUS, [.shift], "-") + case "=": return (GHOSTTY_KEY_EQUAL, [], scalar) + case "+": return (GHOSTTY_KEY_EQUAL, [.shift], "=") + case "[": return (GHOSTTY_KEY_BRACKET_LEFT, [], scalar) + case "{": return (GHOSTTY_KEY_BRACKET_LEFT, [.shift], "[") + case "]": return (GHOSTTY_KEY_BRACKET_RIGHT, [], scalar) + case "}": return (GHOSTTY_KEY_BRACKET_RIGHT, [.shift], "]") + case "\\": return (GHOSTTY_KEY_BACKSLASH, [], scalar) + case "|": return (GHOSTTY_KEY_BACKSLASH, [.shift], "\\") + case ";": return (GHOSTTY_KEY_SEMICOLON, [], scalar) + case ":": return (GHOSTTY_KEY_SEMICOLON, [.shift], ";") + case "'": return (GHOSTTY_KEY_QUOTE, [], scalar) + case "\"": return (GHOSTTY_KEY_QUOTE, [.shift], "'") + case ",": return (GHOSTTY_KEY_COMMA, [], scalar) + case "<": return (GHOSTTY_KEY_COMMA, [.shift], ",") + case ".": return (GHOSTTY_KEY_PERIOD, [], scalar) + case ">": return (GHOSTTY_KEY_PERIOD, [.shift], ".") + case "/": return (GHOSTTY_KEY_SLASH, [], scalar) + case "?": return (GHOSTTY_KEY_SLASH, [.shift], "/") + case " ": return (GHOSTTY_KEY_SPACE, [], scalar) default: return nil } @@ -310,4 +311,5 @@ } } } + #endif #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Interaction.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Interaction.swift index 3f3eeb2..208e8a0 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Interaction.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Interaction.swift @@ -8,8 +8,67 @@ #if canImport(UIKit) import GhosttyKit import UIKit + #if canImport(GameController) + import GameController + #endif + + /// Mouse/trackpad interaction state; behavior lives in +Interaction. + struct PointerInteractionState { + var session = TerminalPointerButtonSession() + var lastLocation: CGPoint? + var hoverRecognizer: UIHoverGestureRecognizer? + var pointerInteraction: UIPointerInteraction? + var selectionStartPoint: CGPoint? + var lastSelectionRect: CGRect? + var pendingSelectionMenuPoint: CGPoint? + /// Capture sampled at the matching press. Nil when no button is down. + var gestureCaptured: Bool? + var panOwnsTouchSequence = false + var suppressNextTouchEnd = false + var mouseShape: TerminalMouseShape = .default + + var activeButton: ghostty_input_mouse_button_e? { + session.reported + } + } + + /// A pan recognizer fed by wheel and trackpad scroll events alone. + /// + /// A scroll event is neither a touch nor a pointer drag: it reaches a + /// pan recognizer only through `allowedScrollTypesMask`, and the touch + /// recognizers never see it. Refusing every other event here keeps a + /// finger on the touch-scroll recognizer and a pointer drag on the + /// selection one without the view's delegate having to tell them apart. + final class TerminalScrollWheelGestureRecognizer: UIPanGestureRecognizer { + override init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + allowedScrollTypesMask = [.continuous, .discrete] + cancelsTouchesInView = false + delaysTouchesBegan = false + delaysTouchesEnded = false + } + + override func shouldReceive(_ event: UIEvent) -> Bool { + event.type == .scroll + } + } + + /// Touch-scroll momentum state; behavior lives in +Interaction. + struct MomentumScrollState { + var displayLink: CADisplayLink? + var velocity: CGPoint = .zero + } extension UITerminalView { + static let touchScrollMultiplier: CGFloat = 3.0 + /// How far a finger may wander and still count as a tap. + static let tapCandidateSlop: CGFloat = 10 + /// How long a press may last and still count as a tap. Below the + /// long-press recognizer's 0.5s so a stationary hold never + /// toggles the keyboard even when no selection delegate is + /// installed and the recognizer itself refuses to begin. + static let tapCandidateMaxDuration: TimeInterval = 0.35 + override open func touchesBegan( _ touches: Set, with event: UIEvent? @@ -21,12 +80,24 @@ #if targetEnvironment(macCatalyst) becomeFirstResponder() #else - pendingKeyboardDismissOnTouchEnd = false - touchDidScrollDuringCurrentTouch = false - if softwareKeyboardVisible { - pendingKeyboardDismissOnTouchEnd = true + if momentumScroll.displayLink != nil { + // A touch during momentum is a scroll-stop, not a tap. + stopMomentumScrolling() + softwareKeyboard.tapCandidateArmed = false + } else if let touch = touches.first, + // View-scoped on purpose: `allTouches` spans the + // whole app, and a finger resting on host chrome + // (sidebar, tab bar) must not swallow a tap here. + (event?.touches(for: self)?.count ?? touches.count) == 1 + { + softwareKeyboard.tapCandidateArmed = true + softwareKeyboard.tapCandidateStart = touch.location(in: self) + softwareKeyboard.tapCandidateTimestamp = touch.timestamp } else { - becomeFirstResponder() + // A second finger means pinch (or some other + // multi-touch gesture) — the sequence can no longer + // be a tap. + softwareKeyboard.tapCandidateArmed = false } #endif } @@ -38,6 +109,15 @@ if handleIndirectPointerTouches(touches, phase: .moved, event: event) { return } + #if !targetEnvironment(macCatalyst) + if softwareKeyboard.tapCandidateArmed, let touch = touches.first { + let point = touch.location(in: self) + let start = softwareKeyboard.tapCandidateStart + if hypot(point.x - start.x, point.y - start.y) > Self.tapCandidateSlop { + softwareKeyboard.tapCandidateArmed = false + } + } + #endif super.touchesMoved(touches, with: event) } @@ -49,11 +129,26 @@ return } #if !targetEnvironment(macCatalyst) - if pendingKeyboardDismissOnTouchEnd, !touchDidScrollDuringCurrentTouch { - resignFirstResponder() + if softwareKeyboard.tapCandidateArmed, let touch = touches.first { + softwareKeyboard.tapCandidateArmed = false + let duration = touch.timestamp - softwareKeyboard.tapCandidateTimestamp + if duration <= Self.tapCandidateMaxDuration { + TerminalDebugLog.log( + .input, + "tap toggles keyboard visible=\(softwareKeyboard.isVisible) duration=\(String(format: "%.3f", duration))" + ) + // The tap is a click first and a keyboard toggle + // second, in both directions: a TUI tracking the + // mouse gets its press before the resize the + // keyboard causes, and the shell sees the + // click-to-move at its prompt either way. + sendTapClick(at: touch.location(in: self)) + // Overridable: a host keyboard lock overrides + // `toggleSoftwareKeyboard()` to swallow the toggle; + // the click above still lands either way. + toggleSoftwareKeyboard() + } } - pendingKeyboardDismissOnTouchEnd = false - touchDidScrollDuringCurrentTouch = false #endif super.touchesEnded(touches, with: event) } @@ -66,21 +161,85 @@ return } #if !targetEnvironment(macCatalyst) - pendingKeyboardDismissOnTouchEnd = false - touchDidScrollDuringCurrentTouch = false + softwareKeyboard.tapCandidateArmed = false #endif super.touchesCancelled(touches, with: event) } func setupPlatformInput() { addInteraction(selectionContextMenuInteraction) - #if targetEnvironment(macCatalyst) - setupCatalystScrollWheelInput() - #else + setupDropInput() + addGestureRecognizer(TerminalScrollWheelGestureRecognizer( + target: self, + action: #selector(handleScrollWheelGesture(_:)) + )) + let pointerInteraction = UIPointerInteraction(delegate: self) + addInteraction(pointerInteraction) + pointer.pointerInteraction = pointerInteraction + let hover = UIHoverGestureRecognizer( + target: self, + action: #selector(handlePointerHover(_:)) + ) + hover.cancelsTouchesInView = false + hover.delegate = self + addGestureRecognizer(hover) + pointer.hoverRecognizer = hover + #if !targetEnvironment(macCatalyst) setupTouchScrollInput() #endif } + @objc func handlePointerHover(_ gesture: UIHoverGestureRecognizer) { + switch gesture.state { + case .began, .changed: + let point = gesture.location(in: self) + pointer.lastLocation = point + guard pointer.session.reported == nil else { return } + sendPointerPosition(at: point, remember: false) + default: + break + } + } + + @objc func handleScrollWheelGesture(_ gesture: UIPanGestureRecognizer) { + guard pointer.session.reported == nil else { return } + switch gesture.state { + case .began: + stopMomentumScrolling() + case .changed, .ended: + // `.ended` still carries whatever moved since the last + // `.changed`. + break + default: + return + } + + let translation = gesture.translation(in: self) + gesture.setTranslation(.zero, in: self) + TerminalDebugLog.log( + .input, + "scroll wheel translation=\(String(format: "%.2f", translation.x))x\(String(format: "%.2f", translation.y))" + ) + + // Ghostty's scroll C API has no key mods. The last mouse_pos + // carries them, so a wheel event can target the cell under the + // pointer (tmux, vim). View points: Ghostty applies + // content_scale internally. + let point = pointer.lastLocation ?? gesture.location(in: self) + sendPointerPosition(at: point, remember: pointer.lastLocation == nil) + + // Always precision: UIKit hands a discrete wheel notch to the pan + // recognizer as a point translation, not a line count, and + // ghostty's non-precision path reads the value as lines times + // the scroll multiplier. + let scrollMods = TerminalScrollModifiers(precision: true) + surface?.sendMouseScroll( + x: Double(translation.x), + y: Double(translation.y), + mods: scrollMods.rawValue + ) + } + enum IndirectPointerPhase { case began case moved @@ -96,17 +255,17 @@ let hasIndirectPointerTouch = touches.contains { $0.type == .indirectPointer } #if !targetEnvironment(macCatalyst) - if suppressNextIndirectPointerTouchEnd, hasIndirectPointerTouch { + if pointer.suppressNextTouchEnd, hasIndirectPointerTouch { if phase == .ended || phase == .cancelled { - suppressNextIndirectPointerTouchEnd = false + pointer.suppressNextTouchEnd = false return true } - suppressNextIndirectPointerTouchEnd = false + pointer.suppressNextTouchEnd = false } - if indirectPointerPanOwnsTouchSequence, hasIndirectPointerTouch { + if pointer.panOwnsTouchSequence, hasIndirectPointerTouch { if phase == .began { - indirectPointerPanOwnsTouchSequence = false + pointer.panOwnsTouchSequence = false } else { return true } @@ -120,105 +279,82 @@ } core.setFocus(true) - #if targetEnvironment(macCatalyst) - if phase == .began { - becomeFirstResponder() - } - #endif + // A pointer click claims keyboard focus the way a finger tap + // does — without this, clicking a terminal with a mouse or + // trackpad never made it first responder and hardware keys kept + // going to whatever held focus before. + if phase == .began, !isFirstResponder { + becomeFirstResponder() + } stopMomentumScrolling() let button = pointerButton(from: event) - let mods = ghostty_input_mods_e(rawValue: 0) let location = touch.location(in: self) - let suppressSurfacePositionForSelectionMenu = - button == GHOSTTY_MOUSE_RIGHT && - (pendingSelectionMenuPoint != nil || pointIsInsidePointerSelection(location)) TerminalDebugLog.log( .input, "pointer touch phase=\(phase) type=\(touch.type.rawValue) button=\(button.rawValue) location=\(NSCoder.string(for: location)) mask=\(event?.buttonMask.rawValue ?? 0)" ) - if !suppressSurfacePositionForSelectionMenu { - surface?.sendMousePos( - x: location.x, - y: location.y, - mods: mods - ) - } switch phase { case .began: - activePointerButton = button - switch button { - case GHOSTTY_MOUSE_LEFT: - pointerSelectionStartPoint = location - pendingSelectionMenuPoint = nil - surface?.sendMouseButton( - state: GHOSTTY_MOUSE_PRESS, - button: button, - mods: mods - ) - - case GHOSTTY_MOUSE_RIGHT: - if pointIsInsidePointerSelection(location) { - pendingSelectionMenuPoint = location - } else { - pendingSelectionMenuPoint = selectionMenuPoint(at: location) - } + if button == GHOSTTY_MOUSE_RIGHT, + TerminalPointerPolicy.shouldPresentHostSecondaryMenu( + mouseCaptured: surface?.isMouseCaptured == true + ), + let menuPoint = selectionMenuPoint(at: location) + { + pointer.pendingSelectionMenuPoint = menuPoint + pointer.gestureCaptured = false + return true + } - default: + pointer.pendingSelectionMenuPoint = nil + pointer.gestureCaptured = surface?.isMouseCaptured == true + if button == GHOSTTY_MOUSE_LEFT { + pointer.selectionStartPoint = location + } + sendPointerPosition(at: location) + if let sent = pointer.session.press(button) { surface?.sendMouseButton( state: GHOSTTY_MOUSE_PRESS, - button: button, - mods: mods + button: sent, + mods: pointerMods() ) } case .moved: - updatePointerSelectionRect(to: location) + sendPointerPosition(at: location) + if pointer.gestureCaptured != true { + updatePointerSelectionRect(to: location) + } case .ended: - let releasedButton = activePointerButton ?? button - activePointerButton = nil - - if releasedButton == GHOSTTY_MOUSE_RIGHT, - pendingSelectionMenuPoint != nil - { + if pointer.pendingSelectionMenuPoint != nil { if selectionMenuPoint(at: location) != nil { showSelectionCopyMenu(at: location) } - pendingSelectionMenuPoint = nil + pointer.pendingSelectionMenuPoint = nil + pointer.gestureCaptured = nil return true } - if releasedButton == GHOSTTY_MOUSE_RIGHT { + sendPointerPosition(at: location) + let released = pointer.session.reported + if let sent = pointer.session.finish() { surface?.sendMouseButton( - state: GHOSTTY_MOUSE_PRESS, - button: releasedButton, - mods: mods + state: GHOSTTY_MOUSE_RELEASE, + button: sent, + mods: pointerMods() ) } - - surface?.sendMouseButton( - state: GHOSTTY_MOUSE_RELEASE, - button: releasedButton, - mods: mods - ) - - if releasedButton == GHOSTTY_MOUSE_LEFT { + if released == GHOSTTY_MOUSE_LEFT { finishPointerSelection(at: location) } - pendingSelectionMenuPoint = nil + pointer.gestureCaptured = nil + pointer.pendingSelectionMenuPoint = nil case .cancelled: - let releasedButton = activePointerButton ?? button - activePointerButton = nil - pendingSelectionMenuPoint = nil - pointerSelectionStartPoint = nil - surface?.sendMouseButton( - state: GHOSTTY_MOUSE_RELEASE, - button: releasedButton, - mods: mods - ) + cancelReportedPointerButton(at: location) } return true @@ -226,21 +362,110 @@ func pointerButton(from event: UIEvent?) -> ghostty_input_mouse_button_e { guard let event else { return GHOSTTY_MOUSE_LEFT } - if event.buttonMask.contains(.secondary) { - return GHOSTTY_MOUSE_RIGHT + let mask = event.buttonMask + var extra: Int? + for number in TerminalPointerPolicy.extraButtonRange where mask.contains(.button(number)) { + extra = number + break } - if event.buttonMask.contains(.primary) { - return GHOSTTY_MOUSE_LEFT + return TerminalPointerPolicy.ghosttyButton( + secondary: mask.contains(.secondary), + middle: mask.contains(.button(3)), + extraButtonNumber: extra + ) + } + + func pointerMods() -> ghostty_input_mods_e { + if let hover = pointer.hoverRecognizer, + hover.state == .began || hover.state == .changed + { + return TerminalInputModifiers(from: hover.modifierFlags).ghosttyMods + } + #if !targetEnvironment(macCatalyst) && canImport(GameController) + if let live = gameControllerPointerMods() { + return live + } + #endif + if !hardwareKeyboard.heldModifierFlags.isEmpty { + return TerminalInputModifiers(from: hardwareKeyboard.heldModifierFlags) + .ghosttyMods } - return GHOSTTY_MOUSE_LEFT + #if targetEnvironment(macCatalyst) + if let flags = CGEvent(source: nil)?.flags { + var mods = TerminalInputModifiers() + if flags.contains(.maskCommand) { mods.insert(.super_) } + if flags.contains(.maskControl) { mods.insert(.ctrl) } + if flags.contains(.maskShift) { mods.insert(.shift) } + if flags.contains(.maskAlternate) { mods.insert(.alt) } + return mods.ghosttyMods + } + #endif + return TerminalInputModifiers(from: hardwareKeyboard.heldModifierFlags) + .ghosttyMods } - func updatePointerSelectionRect(to point: CGPoint) { - guard activePointerButton == GHOSTTY_MOUSE_LEFT, - let start = pointerSelectionStartPoint + #if !targetEnvironment(macCatalyst) && canImport(GameController) + func gameControllerPointerMods() -> ghostty_input_mods_e? { + guard let keyboard = GCKeyboard.coalesced?.keyboardInput else { return nil } + let pressed: (GCKeyCode) -> Bool = { key in + keyboard.button(forKeyCode: key)?.isPressed == true + } + var mods = TerminalInputModifiers() + if pressed(.leftShift) || pressed(.rightShift) { mods.insert(.shift) } + if pressed(.leftControl) || pressed(.rightControl) { mods.insert(.ctrl) } + if pressed(.leftAlt) || pressed(.rightAlt) { mods.insert(.alt) } + if pressed(.leftGUI) || pressed(.rightGUI) { mods.insert(.super_) } + return mods.ghosttyMods + } + #endif + + /// The pointer style is region-scoped, so it needs no reset when + /// the pointer leaves the view; `invalidate` re-asks the delegate + /// while the pointer is already inside. + func applyMouseShape(_ raw: ghostty_action_mouse_shape_e) { + pointer.mouseShape = TerminalMouseShape(raw) + pointer.pointerInteraction?.invalidate() + } + + /// View points. Ghostty applies `content_scale` internally. + func sendPointerPosition(at point: CGPoint, remember: Bool = true) { + if remember { + pointer.lastLocation = point + } + surface?.sendMousePos( + x: Double(point.x), + y: Double(point.y), + mods: pointerMods() + ) + } + + func refreshPointerPositionForModifierChange() { + guard pointer.session.reported == nil, + let point = pointer.lastLocation else { return } + sendPointerPosition(at: point, remember: false) + } + + func cancelReportedPointerButton(at point: CGPoint? = nil) { + if let point { + sendPointerPosition(at: point) + } + if let sent = pointer.session.cancel() { + surface?.sendMouseButton( + state: GHOSTTY_MOUSE_RELEASE, + button: sent, + mods: pointerMods() + ) + } + pointer.pendingSelectionMenuPoint = nil + pointer.gestureCaptured = nil + pointer.selectionStartPoint = nil + } + + func updatePointerSelectionRect(to point: CGPoint) { + guard let start = pointer.selectionStartPoint else { return } - lastPointerSelectionRect = CGRect( + pointer.lastSelectionRect = CGRect( x: min(start.x, point.x), y: min(start.y, point.y), width: abs(start.x - point.x), @@ -253,11 +478,11 @@ } func finishPointerSelection(at point: CGPoint) { - defer { pointerSelectionStartPoint = nil } - guard let start = pointerSelectionStartPoint else { return } + defer { pointer.selectionStartPoint = nil } + guard let start = pointer.selectionStartPoint else { return } let dragDistance = hypot(point.x - start.x, point.y - start.y) if dragDistance < 2 { - lastPointerSelectionRect = nil + pointer.lastSelectionRect = nil } else { updatePointerSelectionRect(to: point) } @@ -272,7 +497,7 @@ TerminalDebugLog.categories.contains(.input) else { return } - let rectDescription = lastPointerSelectionRect.map { + let rectDescription = pointer.lastSelectionRect.map { NSCoder.string(for: $0) } ?? "nil" let metricsDescription = surface?.size().map(\.debugSummary) ?? "nil" @@ -294,6 +519,50 @@ guard copySelectedTextToPasteboard() else { return } } + /// A paste has to reach the surface as a paste. + /// + /// `UIResponder`'s default implementation for a `UIKeyInput` conformer + /// pastes by calling `insertText(_:)`, and that path now encodes text + /// as key input — which strips the bracketed-paste markers a shell + /// relies on to tell pasted text from typing. A pasted command with + /// newlines would run line by line instead of landing in the edit + /// buffer. Taking the action ourselves routes it through ghostty's + /// own paste binding (`pasteFromPasteboard`), where the text path, + /// the mode 2004 wrapping, and paste protection all live. + @IBAction override open func paste(_: Any?) { + pasteFromPasteboard() + } + + /// Every host-driven paste — the edit menu, the accessory bar's + /// button — of text enters through ghostty's own paste binding, the + /// pipeline a hardware Cmd+V already used: the `read_clipboard` + /// callback reads the pasteboard, and paste protection gets to ask + /// before an unsafe paste lands. + /// + /// A pasteboard holding only image or document data is the one case + /// handled here: the data is written to a file and its escaped path + /// goes straight to the text path. A path carries nothing paste + /// protection weighs (no line breaks, no control characters), and a + /// program's own clipboard read must never write a file — so that + /// work belongs to the host's button, not the callback. + func pasteFromPasteboard() { + if inputHandler.hasMarkedText { + inputHandler.unmarkText() + } + if TerminalPasteboardContent.text(from: .general) != nil { + _ = surface?.performBindingAction("paste_from_clipboard") + return + } + TerminalPasteboardContent.files { [weak self] paths in + guard let self, let paths else { + TerminalDebugLog.log(.input, "paste skipped: pasteboard has nothing pasteable") + return + } + TerminalDebugLog.log(.input, "paste files bytes=\(paths.utf8.count)") + surface?.sendText(paths) + } + } + override open func canPerformAction( _ action: Selector, withSender sender: Any? @@ -301,48 +570,13 @@ if action == #selector(copy(_:)) { return surface?.hasSelection() == true } + if action == #selector(paste(_:)) { + return TerminalPasteboardContent.hasContent() + } return super.canPerformAction(action, withSender: sender) } - func pointIsInsidePointerSelection(_ point: CGPoint) -> Bool { - lastPointerSelectionRect.map { - $0.insetBy(dx: -4, dy: -4).contains(point) - } ?? false - } - - #if targetEnvironment(macCatalyst) - func setupCatalystScrollWheelInput() { - let gesture = UIPanGestureRecognizer( - target: self, - action: #selector(handleCatalystScrollWheelGesture(_:)) - ) - gesture.allowedScrollTypesMask = [.continuous, .discrete] - gesture.cancelsTouchesInView = false - gesture.delaysTouchesBegan = false - gesture.delaysTouchesEnded = false - addGestureRecognizer(gesture) - } - - @objc func handleCatalystScrollWheelGesture( - _ gesture: UIPanGestureRecognizer - ) { - guard activePointerButton == nil else { return } - - let translation = gesture.translation(in: self) - gesture.setTranslation(.zero, in: self) - TerminalDebugLog.log( - .input, - "catalyst scroll translation=\(String(format: "%.2f", translation.x))x\(String(format: "%.2f", translation.y))" - ) - - let scrollMods = TerminalScrollModifiers(precision: true) - surface?.sendMouseScroll( - x: Double(translation.x), - y: Double(translation.y), - mods: scrollMods.rawValue - ) - } - #else + #if !targetEnvironment(macCatalyst) func setupTouchScrollInput() { let gesture = UIPanGestureRecognizer( target: self, @@ -366,10 +600,31 @@ addGestureRecognizer(longPress) setupIndirectPointerSelectionGesture() - currentFontSize = configuration.fontSize ?? 14 setupPinchZoomGesture() } + /// One left click at `point`, the way a finger tap reaches the + /// terminal: a press and a release with no drag between them. + /// Any pointer-drag selection is over by definition — ghostty + /// clears its selection on the click. + func sendTapClick(at point: CGPoint) { + guard let surface else { return } + let mods = pointerMods() + sendPointerPosition(at: point) + surface.sendMouseButton( + state: GHOSTTY_MOUSE_PRESS, + button: GHOSTTY_MOUSE_LEFT, + mods: mods + ) + surface.sendMouseButton( + state: GHOSTTY_MOUSE_RELEASE, + button: GHOSTTY_MOUSE_LEFT, + mods: mods + ) + pointer.lastSelectionRect = nil + pointer.selectionStartPoint = nil + } + func setupIndirectPointerSelectionGesture() { let gesture = UIPanGestureRecognizer( target: self, @@ -388,7 +643,6 @@ _ gesture: UIPanGestureRecognizer ) { let location = gesture.location(in: self) - let mods = ghostty_input_mods_e(rawValue: 0) TerminalDebugLog.log( .input, "indirect pointer gesture state=\(gesture.state.rawValue) location=\(NSCoder.string(for: location)) translation=\(NSCoder.string(for: gesture.translation(in: self)))" @@ -396,63 +650,86 @@ switch gesture.state { case .began: + if let reported = pointer.session.reported, + reported != GHOSTTY_MOUSE_LEFT + { + return + } core.setFocus(true) stopMomentumScrolling() - indirectPointerPanOwnsTouchSequence = true - if activePointerButton != GHOSTTY_MOUSE_LEFT { - activePointerButton = GHOSTTY_MOUSE_LEFT + pointer.panOwnsTouchSequence = true + if pointer.gestureCaptured == nil { + pointer.gestureCaptured = surface?.isMouseCaptured == true + } + if pointer.session.reported != GHOSTTY_MOUSE_LEFT, + let sent = pointer.session.press(GHOSTTY_MOUSE_LEFT) + { surface?.sendMouseButton( state: GHOSTTY_MOUSE_PRESS, - button: GHOSTTY_MOUSE_LEFT, - mods: mods + button: sent, + mods: pointerMods() ) } - if pointerSelectionStartPoint == nil { - pointerSelectionStartPoint = location + if pointer.selectionStartPoint == nil { + pointer.selectionStartPoint = location } - pendingSelectionMenuPoint = nil - surface?.sendMousePos(x: location.x, y: location.y, mods: mods) + pointer.pendingSelectionMenuPoint = nil + sendPointerPosition(at: location) case .changed: - updatePointerSelectionRect(to: location) - surface?.sendMousePos(x: location.x, y: location.y, mods: mods) + if pointer.gestureCaptured != true { + updatePointerSelectionRect(to: location) + } + sendPointerPosition(at: location) case .ended: - activePointerButton = nil - updatePointerSelectionRect(to: location) - surface?.sendMousePos(x: location.x, y: location.y, mods: mods) - surface?.sendMouseButton( - state: GHOSTTY_MOUSE_RELEASE, - button: GHOSTTY_MOUSE_LEFT, - mods: mods - ) + if pointer.gestureCaptured != true { + updatePointerSelectionRect(to: location) + } + sendPointerPosition(at: location) + if let sent = pointer.session.finish() { + surface?.sendMouseButton( + state: GHOSTTY_MOUSE_RELEASE, + button: sent, + mods: pointerMods() + ) + } finishPointerSelection(at: location) - indirectPointerPanOwnsTouchSequence = false - suppressNextIndirectPointerTouchEnd = true + pointer.panOwnsTouchSequence = false + pointer.suppressNextTouchEnd = true + pointer.gestureCaptured = nil case .cancelled, .failed: - activePointerButton = nil - indirectPointerPanOwnsTouchSequence = false - suppressNextIndirectPointerTouchEnd = true - pointerSelectionStartPoint = nil - pendingSelectionMenuPoint = nil - lastPointerSelectionRect = nil - surface?.sendMouseButton( - state: GHOSTTY_MOUSE_RELEASE, - button: GHOSTTY_MOUSE_LEFT, - mods: mods - ) + pointer.panOwnsTouchSequence = false + pointer.suppressNextTouchEnd = true + pointer.lastSelectionRect = nil + cancelReportedPointerButton(at: location) default: break } } + /// The delegate to hand a long-press selection to, or nil when no + /// host opted in. A `TerminalViewState` delegate conforms + /// unconditionally, so for SwiftUI hosts the opt-in is its + /// `onTextSelectionRequest` closure being set. + var activeTextSelectionDelegate: (any TerminalSurfaceTextSelectionRequestDelegate)? { + guard let delegate = delegate as? any TerminalSurfaceTextSelectionRequestDelegate else { + return nil + } + if let state = delegate as? TerminalViewState, state.onTextSelectionRequest == nil { + return nil + } + return delegate + } + @objc func handleLongPressForSelection( _ gesture: UILongPressGestureRecognizer ) { guard gesture.state == .began else { return } - guard let delegate = delegate as? any TerminalSurfaceTextSelectionRequestDelegate else { return } + softwareKeyboard.tapCandidateArmed = false + guard let delegate = activeTextSelectionDelegate else { return } guard let surface else { return } guard case let .inMemory(session) = configuration.backend else { TerminalDebugLog.log(.input, "long-press selection ignored: backend not inMemory") @@ -462,11 +739,7 @@ stopMomentumScrolling() let viewPoint = gesture.location(in: self) - surface.sendMousePos( - x: Double(viewPoint.x), - y: Double(viewPoint.y), - mods: ghostty_input_mods_e(rawValue: 0) - ) + sendPointerPosition(at: viewPoint) let wordResult = surface.quicklookWord() @@ -480,19 +753,11 @@ var anchorRange: NSRange? if let w = wordResult, !text.isEmpty, let size = surface.size() { - let scale = Double(resolvedDisplayScale()) - // cellWidth/HeightPixels are surface pixels; ghostty's - // tl_px_x/y are host points. Convert to points before - // dividing so units match inside resolveRange. - let cellWidthPoints = scale > 0 ? Double(size.cellWidthPixels) / scale : 0 - let cellHeightPoints = scale > 0 ? Double(size.cellHeightPixels) / scale : 0 anchorRange = TerminalSelectionAnchor.resolveRange( in: text, word: w.word, - pointX: w.pointX, - pointY: w.pointY, - cellWidthPoints: cellWidthPoints, - cellHeightPoints: cellHeightPoints + offsetStart: w.offsetStart, + columns: UInt32(size.columns) ) } @@ -501,7 +766,9 @@ "long-press selection dispatch viewPoint=\(NSCoder.string(for: viewPoint)) word=\(TerminalDebugLog.describe(wordResult?.word ?? "nil")) anchor=\(anchorRange.map { NSStringFromRange($0) } ?? "nil")" ) + #if !os(visionOS) // no haptics on a headset UIImpactFeedbackGenerator(style: .medium).impactOccurred() + #endif delegate.terminalDidRequestTextSelection(.init( text: text, @@ -516,15 +783,15 @@ ) { switch gesture.state { case .began: - guard activePointerButton == nil else { return } + guard pointer.session.reported == nil else { return } #if !targetEnvironment(macCatalyst) - touchDidScrollDuringCurrentTouch = true + softwareKeyboard.tapCandidateArmed = false #endif TerminalDebugLog.log(.input, "touch scroll began") stopMomentumScrolling() case .changed: - guard activePointerButton == nil else { return } + guard pointer.session.reported == nil else { return } let translation = gesture.translation(in: self) gesture.setTranslation(.zero, in: self) TerminalDebugLog.log( @@ -534,13 +801,13 @@ let scrollMods = TerminalScrollModifiers(precision: true) surface?.sendMouseScroll( - x: Double(translation.x * touchScrollMultiplier), - y: Double(translation.y * touchScrollMultiplier), + x: Double(translation.x * Self.touchScrollMultiplier), + y: Double(translation.y * Self.touchScrollMultiplier), mods: scrollMods.rawValue ) case .ended: - guard activePointerButton == nil else { return } + guard pointer.session.reported == nil else { return } let velocity = gesture.velocity(in: self) TerminalDebugLog.log( .input, @@ -560,7 +827,7 @@ func startMomentumScrolling(velocity: CGPoint) { guard abs(velocity.x) > 50 || abs(velocity.y) > 50 else { return } - momentumVelocity = velocity + momentumScroll.velocity = velocity TerminalDebugLog.log( .input, "momentum start velocity=\(String(format: "%.2f", velocity.x))x\(String(format: "%.2f", velocity.y))" @@ -574,27 +841,29 @@ selector: #selector(momentumScrollFrame(_:)) ) link.add(to: .main, forMode: .common) - momentumDisplayLink = link + momentumScroll.displayLink = link } @objc func momentumScrollFrame(_ link: CADisplayLink) { let dt = link.targetTimestamp - link.timestamp - let deceleration: CGFloat = 0.92 + // 0.92 per 1/60 s, scaled to the frame so a flick travels the + // same distance at 120 Hz as at 60 Hz. + let decay = CGFloat(pow(0.92, dt * 60)) - momentumVelocity.x *= deceleration - momentumVelocity.y *= deceleration + momentumScroll.velocity.x *= decay + momentumScroll.velocity.y *= decay - let deltaX = momentumVelocity.x * dt * touchScrollMultiplier - let deltaY = momentumVelocity.y * dt * touchScrollMultiplier + let deltaX = momentumScroll.velocity.x * dt * Self.touchScrollMultiplier + let deltaY = momentumScroll.velocity.y * dt * Self.touchScrollMultiplier - if abs(momentumVelocity.x) < 50, abs(momentumVelocity.y) < 50 { + if abs(momentumScroll.velocity.x) < 50, abs(momentumScroll.velocity.y) < 50 { stopMomentumScrolling() return } TerminalDebugLog.log( .input, - "momentum frame velocity=\(String(format: "%.2f", momentumVelocity.x))x\(String(format: "%.2f", momentumVelocity.y)) delta=\(String(format: "%.2f", deltaX))x\(String(format: "%.2f", deltaY))" + "momentum frame velocity=\(String(format: "%.2f", momentumScroll.velocity.x))x\(String(format: "%.2f", momentumScroll.velocity.y)) delta=\(String(format: "%.2f", deltaX))x\(String(format: "%.2f", deltaY))" ) let mods = TerminalScrollModifiers(precision: true, momentum: .changed) @@ -606,7 +875,7 @@ } func stopMomentumScrolling(sendTerminalEndEvent: Bool = true) { - guard momentumDisplayLink != nil else { return } + guard momentumScroll.displayLink != nil else { return } TerminalDebugLog.log(.input, "momentum stop") if sendTerminalEndEvent { @@ -614,9 +883,9 @@ surface?.sendMouseScroll(x: 0, y: 0, mods: mods.rawValue) } - momentumDisplayLink?.invalidate() - momentumDisplayLink = nil - momentumVelocity = .zero + momentumScroll.displayLink?.invalidate() + momentumScroll.displayLink = nil + momentumScroll.velocity = .zero } } @@ -629,24 +898,49 @@ _ gestureRecognizer: UIGestureRecognizer ) -> Bool { if gestureRecognizer is UILongPressGestureRecognizer { - return (delegate as? any TerminalSurfaceTextSelectionRequestDelegate) != nil + #if targetEnvironment(macCatalyst) + return (delegate as? any TerminalSurfaceTextSelectionRequestDelegate) != nil + #else + return activeTextSelectionDelegate != nil + #endif } return true } + public func gestureRecognizer( + _ gestureRecognizer: UIGestureRecognizer, + shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer + ) -> Bool { + gestureRecognizer === pointer.hoverRecognizer + || otherGestureRecognizer === pointer.hoverRecognizer + } + open func contextMenuInteraction( _: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint ) -> UIContextMenuConfiguration? { - surface?.sendMousePos( - x: location.x, - y: location.y, - mods: ghostty_input_mods_e(rawValue: 0) - ) + sendPointerPosition(at: location) + guard TerminalPointerPolicy.shouldPresentHostSecondaryMenu( + mouseCaptured: surface?.isMouseCaptured == true + ) else { return nil } guard selectionMenuPoint(at: location) != nil else { return nil } return selectionContextMenuConfiguration(at: location) } } + + extension UITerminalView: UIPointerInteractionDelegate { + public func pointerInteraction( + _: UIPointerInteraction, + styleFor _: UIPointerRegion + ) -> UIPointerStyle? { + switch pointer.mouseShape { + case .text: + return UIPointerStyle(shape: .verticalBeam(length: 24)) + case .pointer, .notAllowed, .default, .other: + return nil + } + } + } #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Keyboard.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Keyboard.swift index 427aa9c..3636081 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Keyboard.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Keyboard.swift @@ -9,40 +9,334 @@ import GhosttyKit import UIKit + /// Hardware-keyboard routing state; behavior lives in +Keyboard. + struct HardwareKeyboardState { + /// A press the key path already delivered, telling the UITextInput + /// echo to stay silent. + var keyHandled = false + /// Signatures of keys already delivered this runloop turn by one of + /// the two paths that can carry a key we register as a `UIKeyCommand` + /// (the Ctrl combos, Escape). One physical press can reach us twice — + /// `pressesBegan` and the matching command — and which arrives (or + /// both) varies by iPadOS version; whichever runs first claims the + /// press here. + var recentKeyCommandDeliveries: Set = [] + /// Keys loaned to the input method this runloop turn. The text input + /// system claims them through any UITextInput mutation; whatever is + /// still here when the turn ends is replayed to the surface. + var pendingInputMethodKeys: [DeferredInputMethodKey] = [] + var inputMethodFlushScheduled = false + /// Learned once per process — not per view, or every new tab would + /// re-calibrate: the text input system ignored a loaned key + /// outright, so deferred presses must be forwarded to `super` — the + /// route that feeds them to the input method on the iPadOS versions + /// that do not process hardware keys on their own. + @MainActor static var inputMethodNeedsPressForwarding = false + /// Set on the first claim: the input method demonstrably hears our + /// keys. From then on an unclaimed forwarded key is never replayed + /// raw — the input method's responses arrive asynchronously (they + /// round-trip the keyboard daemon), and replaying a key it is still + /// composing types it twice. + @MainActor static var inputMethodProvenResponsive = false + /// Presses currently loaned to the input method; their release must + /// not reach the surface (a replay sends its own synthetic pair). + var pressesLoanedToInputMethod: Set = [] + /// Presses whose began was forwarded to `super`; their ended must + /// complete there too. + var pressesForwardedToInputMethod: Set = [] + /// Last hardware modifier flags seen on a `UIKey`. Pointer events + /// read this when the hover recognizer is not the live source. + var heldModifierFlags: UIKeyModifierFlags = [] + } + + /// Software-keyboard visibility and tap-to-toggle state; behavior in + /// +Keyboard (observers) and +Interaction (touch handling). + struct SoftwareKeyboardState { + var isVisible = false + /// The active direct-touch sequence can still resolve to a clean + /// tap. Armed on the first finger down; disarmed by a second + /// finger, by movement past the slop, by any recognized gesture + /// (scroll pan, pinch, long press), or by the press running long. + /// Only a sequence still armed at touch end toggles the keyboard — + /// a drag, zoom, or hold must never count as the tap. + var tapCandidateArmed = false + var tapCandidateStart: CGPoint = .zero + var tapCandidateTimestamp: TimeInterval = 0 + } + + /// A hardware key loaned to the input method, kept ready to replay: + /// everything the direct path would have put on the key event. + struct DeferredInputMethodKey { + let action: ghostty_input_action_e + let keycode: UInt32 + let mods: ghostty_input_mods_e + let consumedMods: ghostty_input_mods_e + let unshiftedCodepoint: UInt32 + let text: String? + /// The physical press, held for the turn so a calibration flush can + /// still hand it to `super` instead of leaking raw text. + weak var press: UIPress? + /// The press has been given to `super` (at press time or by a + /// calibration flush); the next unclaimed flush replays it raw + /// rather than retrying forever. + var forwardAttempted: Bool + } + extension UITerminalView { + /// Ctrl combos the text input system would otherwise interpret + /// itself: as a `UITextInput` first responder, the view hands + /// hardware keys to UIKit's text machinery, which consumes most + /// Ctrl+letter chords (its emacs-style bindings) before + /// `pressesBegan` ever fires. Registering them as key commands with + /// priority over system behavior is the only reliable claim — the + /// same route Blink and SwiftTerm take. + private static let controlKeyCommandInputs: [String] = { + var inputs = (UInt8(ascii: "a") ... UInt8(ascii: "z")).map { + String(UnicodeScalar($0)) + } + inputs += (UInt8(ascii: "0") ... UInt8(ascii: "9")).map { + String(UnicodeScalar($0)) + } + inputs += [" ", "-", "=", "[", "]", "\\", ";", "'", ",", ".", "/", "`"] + return inputs + }() + + private static let controlKeyCommands: [UIKeyCommand] = + controlKeyCommandInputs.map { input in + let command = UIKeyCommand( + input: input, + modifierFlags: .control, + action: #selector(handleControlKeyCommand(_:)) + ) + command.wantsPriorityOverSystemBehavior = true + return command + } + + /// Escape, which the text input system also handles itself: for a + /// `UITextInput` first responder UIKit's system behaviour for a + /// hardware Escape is to end editing — the view resigns, the keyboard + /// (and the accessory bar over it) drops — and that runs before + /// `pressesBegan` ever sees the key. A terminal cannot give Escape + /// away, so it is claimed the same way as the Ctrl combos, under + /// every modifier set a program might bind (Cmd-Escape stays with + /// the system). + /// + /// Catalyst needs this just the same. It has no software keyboard to + /// drop, but the end-editing behaviour is the text input system's, + /// not the keyboard's: an unclaimed Escape resigns the view there + /// too, the press never reaches `pressesBegan`, and every key after + /// it goes nowhere until the next click. + private static let escapeKeyCommands: [UIKeyCommand] = { + let modifierSets: [UIKeyModifierFlags] = [ + [], .shift, .control, .alternate, + [.shift, .control], [.shift, .alternate], [.control, .alternate], + [.shift, .control, .alternate], + ] + return modifierSets.map { flags in + let command = UIKeyCommand( + input: UIKeyCommand.inputEscape, + modifierFlags: flags, + action: #selector(handleEscapeKeyCommand(_:)) + ) + command.wantsPriorityOverSystemBehavior = true + return command + } + }() + + // Catalyst included: its text-input system also swallows Ctrl+letter + // before `pressesBegan` (the Control press itself arrives, the letter + // never does), and the key command is the only route left. The + // per-runloop claim below dedupes against a press on systems that + // deliver both. + // + // UIKit asks for this list on every key event, so it can change with + // the view's state: while a composition is on screen every key is the + // input method's (Escape cancels it — see `TerminalIMEComposition`), + // and the Escape commands step aside so the press takes the deferral + // path in `pressesBegan` as it always did. + override open var keyCommands: [UIKeyCommand]? { + var commands = super.keyCommands ?? [] + commands.append(contentsOf: Self.controlKeyCommands) + if !inputHandler.hasMarkedText { + commands.append(contentsOf: Self.escapeKeyCommands) + } + return commands + } + + @objc private func handleControlKeyCommand(_ command: UIKeyCommand) { + guard let input = command.input, input.count == 1, + let character = input.first, + let press = TerminalKeyPress( + typing: character, + modifiers: TerminalInputModifiers(from: command.modifierFlags) + ) + else { return } + guard claimKeyCommandDelivery( + input: input, + modifierFlags: command.modifierFlags + ) else { return } + TerminalDebugLog.log( + .input, + "uikit key command input=\(TerminalDebugLog.describe(input)) mods=0x\(String(command.modifierFlags.rawValue, radix: 16))" + ) + // A chord, not typing: it closes an open composition the way a + // hardware press would, and takes the shared key path. + if inputHandler.hasMarkedText { + inputHandler.unmarkText() + } + _ = surface?.sendKey(press) + } + + /// The Escape command's action: the key goes to the surface as a + /// press, exactly as `pressesBegan` would have sent it, and the view + /// stays first responder. The command is not offered while text is + /// marked (see `keyCommands`), so no composition is open here. + @objc private func handleEscapeKeyCommand(_ command: UIKeyCommand) { + guard claimKeyCommandDelivery( + input: UIKeyCommand.inputEscape, + modifierFlags: command.modifierFlags + ) else { return } + TerminalDebugLog.log( + .input, + "uikit key command input=escape mods=0x\(String(command.modifierFlags.rawValue, radix: 16))" + ) + _ = surface?.sendKey(TerminalKeyPress( + .escape, + modifiers: TerminalInputModifiers(from: command.modifierFlags) + )) + } + + /// Whether this path gets to deliver a key that is also registered + /// as a `UIKeyCommand` (a Ctrl combo, Escape). Whichever of + /// `pressesBegan` / the key command runs first wins the press; the + /// entry expires at the end of the runloop turn, before the key can + /// physically repeat. + func claimKeyCommandDelivery( + input: String, + modifierFlags: UIKeyModifierFlags + ) -> Bool { + let relevant = modifierFlags.intersection( + [.control, .shift, .alternate, .command] + ) + let signature = "\(input.lowercased())|\(relevant.rawValue)" + guard !hardwareKeyboard.recentKeyCommandDeliveries.contains(signature) else { + TerminalDebugLog.log( + .input, + "uikit key delivery deduped signature=\(signature)" + ) + return false + } + hardwareKeyboard.recentKeyCommandDeliveries.insert(signature) + DispatchQueue.main.async { [weak self] in + self?.hardwareKeyboard.recentKeyCommandDeliveries.remove(signature) + } + return true + } + override open func pressesBegan( _ presses: Set, - with _: UIPressesEvent? + with event: UIPressesEvent? ) { - for press in presses { - guard let key = press.key else { continue } - handleKeyPress(key, action: GHOSTTY_ACTION_PRESS) - } + #if targetEnvironment(macCatalyst) + for press in presses { + guard let key = press.key else { continue } + handleKeyPress(key, action: GHOSTTY_ACTION_PRESS) + } + #else + var forwardedToInputMethod: Set = [] + for press in presses { + guard let key = press.key else { continue } + if shouldDeferKeyToInputMethod(key) { + TerminalDebugLog.log( + .input, + "uikit key deferred to input method code=\(key.keyCode.rawValue) marked=\(inputHandler.hasMarkedText) lang=\(textInputMode?.primaryLanguage ?? "nil") forwarding=\(HardwareKeyboardState.inputMethodNeedsPressForwarding)" + ) + deferKeyToInputMethod(key, press: press, action: GHOSTTY_ACTION_PRESS) + hardwareKeyboard.pressesLoanedToInputMethod.insert(press) + if HardwareKeyboardState.inputMethodNeedsPressForwarding { + hardwareKeyboard.pressesForwardedToInputMethod.insert(press) + forwardedToInputMethod.insert(press) + } + continue + } + handleKeyPress(key, action: GHOSTTY_ACTION_PRESS) + } + // `super` is how UIKit feeds an unhandled press to the text + // input system on the iPadOS versions that do not process + // hardware keys before presses dispatch. + if !forwardedToInputMethod.isEmpty { + super.pressesBegan(forwardedToInputMethod, with: event) + } + #endif } override open func pressesEnded( _ presses: Set, - with _: UIPressesEvent? + with event: UIPressesEvent? ) { - for press in presses { - guard let key = press.key else { continue } - handleKeyPress(key, action: GHOSTTY_ACTION_RELEASE) - } - hardwareKeyHandled = false + #if targetEnvironment(macCatalyst) + for press in presses { + guard let key = press.key else { continue } + handleKeyPress(key, action: GHOSTTY_ACTION_RELEASE) + } + hardwareKeyboard.keyHandled = false + #else + var forwardedToInputMethod: Set = [] + for press in presses { + if hardwareKeyboard.pressesLoanedToInputMethod.remove(press) != nil { + // The surface never saw this press (a replayed key + // carries its own synthetic release), so it gets no + // release either — but a began that went to `super` + // must complete there. + if hardwareKeyboard.pressesForwardedToInputMethod.remove(press) != nil { + forwardedToInputMethod.insert(press) + } + continue + } + guard let key = press.key else { continue } + handleKeyPress(key, action: GHOSTTY_ACTION_RELEASE) + } + hardwareKeyboard.keyHandled = false + if !forwardedToInputMethod.isEmpty { + super.pressesEnded(forwardedToInputMethod, with: event) + } + #endif } override open func pressesCancelled( _ presses: Set, with event: UIPressesEvent? ) { - hardwareKeyHandled = false + hardwareKeyboard.keyHandled = false + #if !targetEnvironment(macCatalyst) + for press in presses { + hardwareKeyboard.pressesLoanedToInputMethod.remove(press) + hardwareKeyboard.pressesForwardedToInputMethod.remove(press) + } + #endif super.pressesCancelled(presses, with: event) } + #if !targetEnvironment(macCatalyst) + /// Whether this press belongs to the input method rather than the + /// terminal — see `TerminalIMEComposition` for the rules. + private func shouldDeferKeyToInputMethod(_ key: UIKey) -> Bool { + let flags = filteredModifierFlags(for: key) + guard flags.isDisjoint(with: [.control, .command]) else { return false } + return TerminalIMEComposition.shouldDeferKey( + characters: key.characters, + hasMarkedText: inputHandler.hasMarkedText, + inputModeUsesComposition: TerminalIMEComposition + .languageUsesComposition(textInputMode?.primaryLanguage) + ) + } + #endif + func handleKeyPress( _ key: UIKey, action: ghostty_input_action_e ) { + notePointerModifierFlags(key.modifierFlags) guard let surface else { TerminalDebugLog.log(.input, "uikit key ignored: missing surface") return @@ -60,36 +354,14 @@ if action == GHOSTTY_ACTION_PRESS, shouldSuppressUIKeyInput(for: key, isCommandModified: isCommandModified) { - hardwareKeyHandled = true + hardwareKeyboard.keyHandled = true } - let delivery = TerminalHardwareKeyRouter.routeUIKit( - usage: UInt16(key.keyCode.rawValue), - backend: configuration.backend, - modifiers: mods - ) - TerminalDebugLog.log( .input, - "uikit key action=\(TerminalDebugLog.describe(action)) code=\(key.keyCode.rawValue) chars=\(TerminalDebugLog.describe(key.characters)) ignoring=\(TerminalDebugLog.describe(key.charactersIgnoringModifiers)) mods=0x\(String(filteredModifierFlags.rawValue, radix: 16)) delivery=\(delivery.debugSummary) marked=\(inputHandler.hasMarkedText)" + "uikit key action=\(TerminalDebugLog.describe(action)) code=\(key.keyCode.rawValue) chars=\(TerminalDebugLog.describe(key.characters)) ignoring=\(TerminalDebugLog.describe(key.charactersIgnoringModifiers)) mods=0x\(String(filteredModifierFlags.rawValue, radix: 16)) marked=\(inputHandler.hasMarkedText)" ) - if action == GHOSTTY_ACTION_RELEASE, delivery.isDirectInput { - return - } - - if handleDirectInputIfNeeded( - delivery, - action: action, - isCommandModified: isCommandModified, - filteredModifierFlags: filteredModifierFlags - ) { - if let keyboardZoomDirection { - scheduleViewportRefreshAfterKeyboardZoom(keyboardZoomDirection) - } - return - } - var keyEvent = ghostty_input_key_s() keyEvent.action = action keyEvent.mods = mods.ghosttyMods @@ -121,6 +393,20 @@ keyEvent.unshifted_codepoint = codepoint.value } + // The key command fallback may have sent this very key already + // (see `controlKeyCommands` and `escapeKeyCommands`); on systems + // that deliver both, the first claim wins and this press stays + // silent. + if action == GHOSTTY_ACTION_PRESS, + let input = keyCommandInput(for: key, filteredModifierFlags: filteredModifierFlags), + !claimKeyCommandDelivery( + input: input, + modifierFlags: filteredModifierFlags + ) + { + return + } + guard !isCommandModified else { _ = surface.sendKeyEvent(keyEvent) if let keyboardZoomDirection { @@ -129,9 +415,23 @@ return } - guard let text = TerminalInputText.filteredFunctionKeyText(key.characters), - !text.isEmpty - else { + var derivedText = TerminalInputText.filteredFunctionKeyText(key.characters) + + // Ctrl+letter arrives with `characters` already collapsed to the + // raw control byte, which the core's key encoder does not accept + // as a key. AppKit re-derives the printable text without control + // (NSEvent.filteredCharacters); UIKey cannot re-apply modifier + // sets, so the unmodified character stands in. + if filteredModifierFlags.contains(.control), + let scalars = derivedText?.unicodeScalars, + scalars.count == 1, + let scalar = scalars.first, + scalar.value < 0x20 + { + derivedText = filteredIgnoringModifiers + } + + guard let text = derivedText, !text.isEmpty else { _ = surface.sendKeyEvent(keyEvent) return } @@ -147,36 +447,43 @@ isCommandModified: Bool ) -> Bool { guard !isCommandModified else { return false } - guard key.modifierFlags.intersection([.alternate, .control]).isEmpty else { - return false - } + // Ctrl and Alt combos travel the key path above, which already + // carries the composed character (option+a → "å") with alt + // consumed, exactly as AppKit's keyDown does. The text system's + // echo would type it a second time; for Ctrl it is a bare + // control byte with the modifier context stripped + // (`sendTypedText` zeroes mods), which loses the ctrl semantics + // as well. guard !key.characters.isEmpty else { return key.keyCode == .keyboardDeleteOrBackspace } return true } - private func handleDirectInputIfNeeded( - _ delivery: TerminalHardwareKeyDelivery, - action: ghostty_input_action_e, - isCommandModified: Bool, + /// The `UIKeyCommand.input` this press would arrive under, if it is + /// one of the keys `keyCommands` registers — the shared signature + /// both paths claim with. Nil for every other key. + private func keyCommandInput( + for key: UIKey, filteredModifierFlags: UIKeyModifierFlags - ) -> Bool { - // When IME composition is active, UIKit must own editing keys such as - // backspace and arrows so candidate text stays in sync. - guard !inputHandler.hasMarkedText else { return false } - guard !isCommandModified else { return false } - guard filteredModifierFlags.intersection([.alternate, .control]).isEmpty else { - return false - } - guard action == GHOSTTY_ACTION_PRESS || action == GHOSTTY_ACTION_REPEAT else { - return false + ) -> String? { + if key.keyCode == .keyboardEscape, + !filteredModifierFlags.contains(.command) + { + return UIKeyCommand.inputEscape } - guard case let .data(sequence) = delivery else { return false } - guard case let .inMemory(session) = configuration.backend else { return false } + guard filteredModifierFlags.contains(.control) else { return nil } + return TerminalInputText.filteredFunctionKeyText(key.charactersIgnoringModifiers) + } - session.sendInput(sequence) - return true + /// Pointer-only. Does not change key routing. + func notePointerModifierFlags(_ flags: UIKeyModifierFlags) { + let relevant = flags.intersection([ + .shift, .control, .alternate, .command, .alphaShift, + ]) + guard hardwareKeyboard.heldModifierFlags != relevant else { return } + hardwareKeyboard.heldModifierFlags = relevant + refreshPointerPositionForModifierChange() } private func filteredModifierFlags(for key: UIKey) -> UIKeyModifierFlags { @@ -223,9 +530,9 @@ #if !targetEnvironment(macCatalyst) switch direction { case .increase: - currentFontSize = min(currentFontSize + 1, Self.maxFontSize) + fontZoom.currentFontSize = min(fontZoom.currentFontSize + 1, Self.maxFontSize) case .decrease: - currentFontSize = max(currentFontSize - 1, Self.minFontSize) + fontZoom.currentFontSize = max(fontZoom.currentFontSize - 1, Self.minFontSize) } #endif @@ -243,4 +550,159 @@ case decrease } } + + #if !targetEnvironment(macCatalyst) + extension UITerminalView { + func deferKeyToInputMethod( + _ key: UIKey, + press: UIPress?, + action: ghostty_input_action_e + ) { + let mods = TerminalInputModifiers(from: filteredModifierFlags(for: key)) + var consumedFlags = key.modifierFlags + consumedFlags.remove(.control) + consumedFlags.remove(.command) + + let unshifted = TerminalInputText.filteredFunctionKeyText( + key.charactersIgnoringModifiers + )?.unicodeScalars.first?.value ?? 0 + + hardwareKeyboard.pendingInputMethodKeys.append(DeferredInputMethodKey( + action: action, + keycode: TerminalHardwareKeyRouter.appKitKeyCodeForUIKit( + usage: UInt16(key.keyCode.rawValue) + ), + mods: mods.ghosttyMods, + consumedMods: TerminalInputModifiers(from: consumedFlags).ghosttyMods, + unshiftedCodepoint: unshifted, + text: TerminalInputText.filteredFunctionKeyText(key.characters), + press: press, + // Forwarded at press time whenever calibration already + // happened; only then may an unclaimed flush replay raw. + forwardAttempted: HardwareKeyboardState.inputMethodNeedsPressForwarding + )) + scheduleInputMethodKeyFlush() + } + + /// The text input system spoke — every loaned key was heard. + /// Called from each UITextInput mutation entry point. + func claimPendingInputMethodKeys() { + guard !hardwareKeyboard.pendingInputMethodKeys.isEmpty else { return } + HardwareKeyboardState.inputMethodProvenResponsive = true + TerminalDebugLog.log( + .input, + "input method claimed \(hardwareKeyboard.pendingInputMethodKeys.count) deferred key(s)" + ) + hardwareKeyboard.pendingInputMethodKeys.removeAll() + } + + private func scheduleInputMethodKeyFlush(after delay: TimeInterval = 0) { + guard !hardwareKeyboard.inputMethodFlushScheduled else { return } + hardwareKeyboard.inputMethodFlushScheduled = true + let flush: @MainActor @Sendable () -> Void = { [weak self] in + guard let self else { return } + hardwareKeyboard.inputMethodFlushScheduled = false + replayUnclaimedInputMethodKeys() + } + if delay > 0 { + DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: flush) + } else { + DispatchQueue.main.async(execute: flush) + } + } + + private func replayUnclaimedInputMethodKeys() { + guard !hardwareKeyboard.pendingInputMethodKeys.isEmpty else { return } + let keys = hardwareKeyboard.pendingInputMethodKeys + hardwareKeyboard.pendingInputMethodKeys.removeAll() + + // The system never handed these presses to the input method + // on its own — calibrate to forwarding, and give these very + // presses to `super` right now: the input method can still + // compose them, so nothing leaks into the shell. Only keys + // whose forward has already been tried fall through to the + // raw replay below. + let retriable = keys.filter { !$0.forwardAttempted } + if !retriable.isEmpty { + if !HardwareKeyboardState.inputMethodNeedsPressForwarding { + HardwareKeyboardState.inputMethodNeedsPressForwarding = true + TerminalDebugLog.log( + .input, + "text input system ignored the loan; forwarding deferred presses to super from now on" + ) + } + hardwareKeyboard.pendingInputMethodKeys = keys.map { key in + var retried = key + retried.forwardAttempted = true + return retried + } + for key in retriable { + guard let press = key.press else { continue } + if hardwareKeyboard.pressesLoanedToInputMethod.contains(press) { + // Still held down: its ended will complete at + // `super` through the forwarded set. + hardwareKeyboard.pressesForwardedToInputMethod.insert(press) + super.pressesBegan([press], with: nil) + } else { + // Already released — hand `super` the whole + // pair so the input method sees a full press. + super.pressesBegan([press], with: nil) + super.pressesEnded([press], with: nil) + } + } + // The claim decides their fate — and it round-trips the + // keyboard daemon, so give it real time instead of one + // runloop turn. Costs a one-time delay on the process's + // first key when no input method is listening at all. + scheduleInputMethodKeyFlush(after: 0.25) + return + } + + // A responsive input method never gets keys replayed behind + // its back: its claims arrive asynchronously (a key we + // replay now may be mid-composition and would type twice), + // and a key it consumes without any mutation — candidate + // paging — is its to consume. The same goes for a visibly + // live composition even before the first claim. + guard !HardwareKeyboardState.inputMethodProvenResponsive, + !inputHandler.hasMarkedText + else { + TerminalDebugLog.log( + .input, + "dropping \(keys.count) unclaimed key(s): input method owns them (proven=\(HardwareKeyboardState.inputMethodProvenResponsive) marked=\(inputHandler.hasMarkedText))" + ) + return + } + + guard let surface else { return } + TerminalDebugLog.log( + .input, + "input method left \(keys.count) key(s) unclaimed, replaying" + ) + for key in keys { + var keyEvent = ghostty_input_key_s() + keyEvent.action = key.action + keyEvent.mods = key.mods + keyEvent.keycode = key.keycode + keyEvent.consumed_mods = key.consumedMods + keyEvent.unshifted_codepoint = key.unshiftedCodepoint + keyEvent.composing = false + if let text = key.text, !text.isEmpty { + text.withCString { ptr in + keyEvent.text = ptr + _ = surface.sendKeyEvent(keyEvent) + } + } else { + _ = surface.sendKeyEvent(keyEvent) + } + // The matching release: pressesEnded skips loaned + // presses, so the pair completes here. + var release = keyEvent + release.action = GHOSTTY_ACTION_RELEASE + release.text = nil + _ = surface.sendKeyEvent(release) + } + } + } + #endif #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Lifecycle.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Lifecycle.swift index e56733f..5ca8ea7 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Lifecycle.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Lifecycle.swift @@ -8,6 +8,17 @@ #if canImport(UIKit) import UIKit + /// SwiftUI focus-bridge hooks; behavior lives in +Lifecycle. + struct FocusBridgeState { + var onFocusChange: ((Bool) -> Void)? + /// Fires when the view lands in a window. The SwiftUI focus bridge + /// needs it: a focus request that arrives before the window exists + /// cannot become first responder and would otherwise be dropped — + /// the launch-time case, where the surface is created and focused in + /// the same transaction. + var onWindowAttach: (() -> Void)? + } + extension UITerminalView { func setupApplicationLifecycleObservers() { NotificationCenter.default.addObserver( @@ -22,6 +33,15 @@ name: UIApplication.didBecomeActiveNotification, object: nil ) + // Scene-based apps activate scene-first; on cold launch the + // app-level notification can precede this view's registration. + // Either signal re-syncs the same state. + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationDidBecomeActive), + name: UIScene.didActivateNotification, + object: nil + ) } func syncApplicationActiveState() { @@ -51,9 +71,28 @@ ) updateDisplayScale() if window != nil { - core.rebuildIfReady() + // Re-read the application state on every attach. The + // did-become-active notification can slip past a view whose + // registration races cold launch — the view then believes + // the app inactive forever, its surface is born occluded, + // the renderer skips every draw, and the first terminal of a + // cold launch sits blank. The attach is the moment we + // reliably know the answer matters. + syncApplicationActiveState() + // UIKit detaches the hierarchy temporarily all the time — a + // full-screen cover (tab switcher) pulls the presenter's view + // out of the window. Rebuilding on every reattach discards + // Ghostty's grid and scrollback, so a surface that already + // exists is kept and only re-measured, matching the AppKit + // twin's reattach guard. + if core.surface == nil { + core.rebuildIfReady() + } else { + core.synchronizeMetrics() + } updateColorScheme() core.startDisplayLink() + core.requestImmediateTick() // Defer sublayer frame and metrics sync to the next runloop // so that AutoLayout has resolved final bounds. DispatchQueue.main.async { [weak self] in @@ -61,9 +100,20 @@ updateSublayerFrames() core.fitToSize() } + focusBridge.onWindowAttach?() + // Same runloop hop as `requestFocus`: attaching can happen + // mid SwiftUI update, where the first-responder dance must + // not mutate focus state. + DispatchQueue.main.async { [weak self] in + guard let self else { return } + (delegate as? TerminalViewState)?.replayPendingFocusIfNeeded() + } } else { + // The surface survives on purpose: this detach may be a + // cover's temporary one, and the view's own teardown frees + // the surface when the terminal really goes away. + cancelReportedPointerButton() core.stopDisplayLink() - core.freeSurface() } } @@ -77,14 +127,30 @@ core.fitToSize() } + /// The scale used when neither the window nor the trait collection can + /// say. visionOS has no `UIScreen` — a window there is a rectangle in a + /// shared space, not on a display — and its content is rendered at 2× + /// for the compositor to resample; the trait collection reports 2.0 on + /// every device so far, and this is what `traitCollection.displayScale` + /// falls back to as well. + static var fallbackDisplayScale: CGFloat { + #if os(visionOS) + 2.0 + #else + UIScreen.main.nativeScale + #endif + } + func resolvedDisplayScale() -> CGFloat { + #if !os(visionOS) if let screen = window?.screen { return screen.nativeScale } + #endif if traitCollection.displayScale > 0 { return traitCollection.displayScale } - return UIScreen.main.nativeScale + return Self.fallbackDisplayScale } func updateDisplayScale() { @@ -98,13 +164,37 @@ updateSublayerFrames() } + /// Where the engine's layer sits: the view's bounds, except while a + /// resize throttle is holding the surface at an older size. Then + /// the layer stays that size, anchored top-left, so the pixels it + /// holds are shown 1:1 and the uncovered strip is background. A + /// layer stretched to the new bounds shows the old frame scaled, + /// and the engine — deriving `contentsScale` from its pixel size + /// over the layer's points — writes a wrong scale on each draw + /// that `enforceSublayerScale` then undoes: a whole-pane flicker + /// for as long as the window is open. `layoutSubviews` sizes the + /// surface right after placing the layer, so with the throttle off + /// the surface catches up inside the same pass and + /// `onMetricsUpdate` re-places the layer at the bounds. + var sublayerFrame: CGRect { + guard let synced = core.syncedViewSize, + synced.width != bounds.width || synced.height != bounds.height + else { return bounds } + // The full synced size, even past the bounds on a shrink: a + // frame clipped to the bounds would scale the pixels just the + // same. The view's layer masks the overflow instead. + return CGRect(x: 0, y: 0, width: synced.width, height: synced.height) + } + func updateSublayerFrames() { let scale = resolvedDisplayScale() contentScaleFactor = scale layer.contentsScale = scale + layer.masksToBounds = true guard let sublayers = layer.sublayers else { return } + let frame = sublayerFrame for sublayer in sublayers { - sublayer.frame = bounds + sublayer.frame = frame sublayer.contentsScale = scale } } @@ -112,12 +202,13 @@ func enforceSublayerScale() { let scale = resolvedDisplayScale() guard let sublayers = layer.sublayers else { return } + let frame = sublayerFrame for sublayer in sublayers { if sublayer.contentsScale != scale { sublayer.contentsScale = scale } - if sublayer.frame != bounds { - sublayer.frame = bounds + if sublayer.frame != frame { + sublayer.frame = frame } } } @@ -156,16 +247,26 @@ @discardableResult override open func becomeFirstResponder() -> Bool { let result = super.becomeFirstResponder() + // A failed acquire (view not in a window yet) must not report + // focus: the SwiftUI bridge would record this surface as focused + // while another view keeps eating the keyboard. + guard result else { return false } core.setFocus(true) - onFocusChange?(true) + focusBridge.onFocusChange?(true) return result } @discardableResult override open func resignFirstResponder() -> Bool { let result = super.resignFirstResponder() + #if !targetEnvironment(macCatalyst) + // A handoff to another responder keeps the keyboard up, so + // `keyboardDidHide` never fires for this view; the flag means + // "this view owns the visible keyboard" and must drop here. + softwareKeyboard.isVisible = false + #endif core.setFocus(false) - onFocusChange?(false) + focusBridge.onFocusChange?(false) return result } } diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PinchZoom.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PinchZoom.swift index 10834a3..e9b76f0 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PinchZoom.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PinchZoom.swift @@ -3,10 +3,19 @@ // libghostty-spm // -#if canImport(UIKit) && !targetEnvironment(macCatalyst) +#if canImport(UIKit) + #if !targetEnvironment(macCatalyst) import UIKit + /// Pinch-zoom font sizing state; behavior lives in +PinchZoom. + struct FontZoomState { + var currentFontSize: Float = 14 + var lastPinchScale: CGFloat = 1.0 + } + extension UITerminalView { + static let minFontSize: Float = 4 + static let maxFontSize: Float = 64 private static let scaleStepThreshold: CGFloat = 0.1 func setupPinchZoomGesture() { @@ -20,19 +29,20 @@ @objc func handlePinchGesture(_ gesture: UIPinchGestureRecognizer) { switch gesture.state { case .began: - lastPinchScale = gesture.scale + softwareKeyboard.tapCandidateArmed = false + fontZoom.lastPinchScale = gesture.scale TerminalDebugLog.log( .actions, - "pinch began scale=\(String(format: "%.3f", gesture.scale)) fontSize=\(currentFontSize)" + "pinch began scale=\(String(format: "%.3f", gesture.scale)) fontSize=\(fontZoom.currentFontSize)" ) case .changed: - let delta = gesture.scale - lastPinchScale + let delta = gesture.scale - fontZoom.lastPinchScale let steps = Int(delta / Self.scaleStepThreshold) guard steps != 0 else { return } - lastPinchScale += CGFloat(steps) * Self.scaleStepThreshold + fontZoom.lastPinchScale += CGFloat(steps) * Self.scaleStepThreshold TerminalDebugLog.log( .actions, "pinch changed scale=\(String(format: "%.3f", gesture.scale)) delta=\(String(format: "%.3f", delta)) steps=\(steps)" @@ -41,16 +51,16 @@ var changed = false if steps > 0 { for _ in 0 ..< steps { - guard currentFontSize < Self.maxFontSize else { break } + guard fontZoom.currentFontSize < Self.maxFontSize else { break } surface?.performBindingAction("increase_font_size:1") - currentFontSize += 1 + fontZoom.currentFontSize += 1 changed = true } } else { for _ in 0 ..< abs(steps) { - guard currentFontSize > Self.minFontSize else { break } + guard fontZoom.currentFontSize > Self.minFontSize else { break } surface?.performBindingAction("decrease_font_size:1") - currentFontSize -= 1 + fontZoom.currentFontSize -= 1 changed = true } } @@ -60,15 +70,15 @@ refreshTextInputGeometry(reason: "pinch-zoom") TerminalDebugLog.log( .actions, - "pinch applied fontSize=\(currentFontSize)" + "pinch applied fontSize=\(fontZoom.currentFontSize)" ) } case .ended, .cancelled, .failed: - lastPinchScale = 1.0 + fontZoom.lastPinchScale = 1.0 TerminalDebugLog.log( .actions, - "pinch ended state=\(gesture.state.rawValue) fontSize=\(currentFontSize)" + "pinch ended state=\(gesture.state.rawValue) fontSize=\(fontZoom.currentFontSize)" ) default: @@ -76,4 +86,5 @@ } } } + #endif #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PublicInput.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PublicInput.swift index 9384928..c41d1ab 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PublicInput.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PublicInput.swift @@ -6,9 +6,55 @@ // #if canImport(UIKit) + import GhosttyKit import UIKit extension UITerminalView { + /// Make this view the first responder, reporting whether keyboard + /// focus was actually acquired. Fails (returns false) while the view + /// is not in a window; ``TerminalViewState/requestFocus()`` retries + /// then on window attach. + @discardableResult + public func acquireProgrammaticFocus() -> Bool { + guard window != nil else { return false } + if isFirstResponder { return true } + return becomeFirstResponder() + } + + /// Paste text into the terminal. This is the text path: a program + /// that enabled bracketed paste receives it framed as a paste, so a + /// `\r` in it is a pasted character, not Enter. Keystrokes go + /// through ``sendKey(_:)``. False with no surface yet. + @discardableResult + public func paste(text: String) -> Bool { + surface?.sendText(text) ?? false + } + + /// Presses and releases a key, as if typed on a hardware keyboard — + /// see ``TerminalSurface/sendKey(_:)``. An open IME composition is + /// committed first, and armed sticky Ctrl/Alt/Cmd apply to the key + /// and are spent by it, exactly as for a tap on the bundled + /// accessory bar. False with no surface yet. + @discardableResult + public func sendKey(_ press: TerminalKeyPress) -> Bool { + guard let surface else { return false } + if inputHandler.hasMarkedText { + inputHandler.unmarkText() + } + var press = press + #if !targetEnvironment(macCatalyst) + press.modifiers.formUnion(stickyModifiers.consumeForNextKey()) + #endif + return surface.sendKey(press) + } + + /// ``sendKey(_:)`` for a key and its modifiers: `sendKey(.enter)`, + /// `sendKey(.c, modifiers: .ctrl)`. + @discardableResult + public func sendKey(_ key: TerminalKey, modifiers: TerminalInputModifiers = []) -> Bool { + sendKey(TerminalKeyPress(key, modifiers: modifiers)) + } + /// Invoke a named Ghostty binding action (e.g. "copy_to_clipboard", /// "clear_screen"). Returns true when the action dispatched. @discardableResult @@ -30,5 +76,40 @@ public func scrollToRow(_ row: UInt) -> Bool { surface?.scrollToRow(row) ?? false } + + /// Whether the application currently owns the mouse. + public var isMouseCaptured: Bool { + surface?.isMouseCaptured ?? false + } + + /// View points. Ghostty applies content scale internally. + public func sendMousePos( + x: Double, + y: Double, + modifiers: TerminalInputModifiers = [] + ) { + surface?.sendMousePos(x: x, y: y, modifiers: modifiers) + } + + @discardableResult + public func sendMouseButton( + state: ghostty_input_mouse_state_e, + button: ghostty_input_mouse_button_e, + modifiers: TerminalInputModifiers = [] + ) -> Bool { + surface?.sendMouseButton( + state: state, + button: button, + modifiers: modifiers + ) ?? false + } + + public func sendMouseScroll( + x: Double, + y: Double, + mods: TerminalScrollModifiers = TerminalScrollModifiers(precision: true) + ) { + surface?.sendMouseScroll(x: x, y: y, mods: mods) + } } #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PublicSticky.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PublicSticky.swift index 0ffbde5..90221fb 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PublicSticky.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+PublicSticky.swift @@ -22,7 +22,8 @@ // `consumeForNextKey()` codepath that `insertText` already respects. // -#if canImport(UIKit) && !targetEnvironment(macCatalyst) +#if canImport(UIKit) + #if !targetEnvironment(macCatalyst) import Foundation import UIKit @@ -104,4 +105,5 @@ } } } + #endif #endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Snapshot.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Snapshot.swift new file mode 100644 index 0000000..94d313c --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+Snapshot.swift @@ -0,0 +1,30 @@ +// +// UITerminalView+Snapshot.swift +// libghostty-spm +// + +#if canImport(UIKit) + import UIKit + + public extension UITerminalView { + /// Renders the surface's current on-screen contents into an image. + /// + /// Uses `drawHierarchy(in:afterScreenUpdates:)`, which snapshots + /// what the render server is presenting — including the Metal + /// layer, which `CALayer.render(in:)` cannot capture. The view must + /// be installed in a window and have a nonzero size; a surface + /// whose rendering is paused (`setSurfaceVisible(false)`) yields + /// its last presented frame. + func snapshotImage() -> UIImage? { + guard window != nil, bounds.width > 0, bounds.height > 0 else { + return nil + } + let format = UIGraphicsImageRendererFormat() + format.opaque = false + let renderer = UIGraphicsImageRenderer(bounds: bounds, format: format) + return renderer.image { _ in + drawHierarchy(in: bounds, afterScreenUpdates: false) + } + } + } +#endif diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+UITextInput.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+UITextInput.swift index f2fc42e..a22e243 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+UITextInput.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView+UITextInput.swift @@ -7,6 +7,11 @@ import GhosttyKit import UIKit + /// UITextInput delegate wiring; behavior lives in +UITextInput. + struct TextInputBridgeState { + weak var inputDelegate: (any UITextInputDelegate)? + } + extension UITerminalView: UITextInput, UITextInputTraits { // MARK: - UITextInputTraits @@ -40,6 +45,16 @@ set {} } + /// Off for the same reason as autocorrection, and doubly so: the + /// system delivers inline predictions as marked text, which a + /// terminal renders as highlighted preedit at the caret — plain + /// typing then looks perpetually selected. + @available(iOS 17.0, *) + open var inlinePredictionType: UITextInlinePredictionType { + get { .no } + set {} + } + open var keyboardType: UIKeyboardType { get { .default } set {} @@ -48,12 +63,41 @@ // MARK: - UIKeyInput open func insertText(_ text: String) { - guard !hardwareKeyHandled else { + #if !targetEnvironment(macCatalyst) + claimPendingInputMethodKeys() + #endif + // A lone, unmarked "\n"/"\r" is the software keyboard's Return — + // it must travel the key path (bracketed paste turns a text-path + // newline into a literal insertion instead of accepting the + // line). Longer strings containing newlines (dictation, actual + // pastes) stay on the text path, where paste semantics are + // correct. + let route = TerminalSoftwareKeyCommitRouter.route( + text: text, + hasMarkedText: inputHandler.hasMarkedText, + hardwareKeyHandled: hardwareKeyboard.keyHandled + ) + + if route == .suppressHardwareDuplicate { TerminalDebugLog.log( .input, "insertText suppressed text=\(TerminalDebugLog.describe(text))" ) - hardwareKeyHandled = false + hardwareKeyboard.keyHandled = false + return + } + + if route == .semanticEnter { + #if !targetEnvironment(macCatalyst) + let mods = stickyModifiers.consumeForNextKey() + TerminalDebugLog.log( + .input, + "insertText semantic enter mods=0x\(String(mods.ghosttyMods.rawValue, radix: 16))" + ) + sendSyntheticKey(usage: 0x28, additionalMods: mods) + #else + sendReturnKey() + #endif return } @@ -72,16 +116,49 @@ inputHandler.insertText(text) } + #if targetEnvironment(macCatalyst) + /// Deliver Return exactly as a hardware keyboard would: one Enter key + /// event through the core's key encoder, so terminal modes (kitty + /// keyboard protocol included) keep deciding the bytes. iOS routes + /// semantic Enter through sendSyntheticKey (sticky modifiers apply); + /// Catalyst has no accessory bar, so this direct path remains. + private func sendReturnKey() { + let usage = UInt16(UIKeyboardHIDUsage.keyboardReturnOrEnter.rawValue) + + var keyEvent = ghostty_input_key_s() + keyEvent.action = GHOSTTY_ACTION_PRESS + keyEvent.mods = ghostty_input_mods_e(rawValue: 0) + keyEvent.keycode = TerminalHardwareKeyRouter.appKitKeyCodeForUIKit( + usage: usage + ) + keyEvent.composing = false + + let carriageReturn = "\r" + carriageReturn.withCString { ptr in + keyEvent.text = ptr + surface?.sendKeyEvent(keyEvent) + } + // The matching release, so a kitty-protocol program with event + // reporting never sees Return held down. + keyEvent.action = GHOSTTY_ACTION_RELEASE + keyEvent.text = nil + surface?.sendKeyEvent(keyEvent) + } + #endif + open func deleteBackward() { + #if !targetEnvironment(macCatalyst) + claimPendingInputMethodKeys() + #endif if inputHandler.deleteBackwardInMarkedText() { TerminalDebugLog.log(.input, "deleteBackward handled by marked text") - hardwareKeyHandled = false + hardwareKeyboard.keyHandled = false return } - guard !hardwareKeyHandled else { + guard !hardwareKeyboard.keyHandled else { TerminalDebugLog.log(.input, "deleteBackward suppressed") - hardwareKeyHandled = false + hardwareKeyboard.keyHandled = false return } @@ -95,17 +172,6 @@ } #endif - let delivery = TerminalHardwareKeyRouter.routeUIKit( - usage: usage, - backend: configuration.backend - ) - if case let .data(sequence) = delivery, - case let .inMemory(session) = configuration.backend - { - session.sendInput(sequence) - return - } - var keyEvent = ghostty_input_key_s() keyEvent.action = GHOSTTY_ACTION_PRESS keyEvent.mods = ghostty_input_mods_e(rawValue: 0) @@ -119,6 +185,9 @@ keyEvent.text = ptr surface?.sendKeyEvent(keyEvent) } + keyEvent.action = GHOSTTY_ACTION_RELEASE + keyEvent.text = nil + surface?.sendKeyEvent(keyEvent) } // MARK: - UITextInput Marked Text @@ -127,10 +196,16 @@ _ markedText: String?, selectedRange: NSRange ) { + #if !targetEnvironment(macCatalyst) + claimPendingInputMethodKeys() + #endif inputHandler.setMarkedText(markedText, selectedRange: selectedRange) } open func unmarkText() { + #if !targetEnvironment(macCatalyst) + claimPendingInputMethodKeys() + #endif inputHandler.unmarkText(applyingStickyModifiers: false) } @@ -222,6 +297,9 @@ } open func replace(_: UITextRange, withText text: String) { + #if !targetEnvironment(macCatalyst) + claimPendingInputMethodKeys() + #endif #if !targetEnvironment(macCatalyst) if inputHandler.hasMarkedText { inputHandler.insertText(text) @@ -240,8 +318,8 @@ // MARK: - UITextInput Delegate open var inputDelegate: (any UITextInputDelegate)? { - get { _inputDelegate } - set { _inputDelegate = newValue } + get { textInputBridge.inputDelegate } + set { textInputBridge.inputDelegate = newValue } } // MARK: - UITextInput Tokenizer diff --git a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView.swift b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView.swift index 7bb1e98..d4f401f 100644 --- a/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView.swift +++ b/ios/vendor/GhosttyTerminal/Platform/UIKit/UITerminalView.swift @@ -12,37 +12,45 @@ @MainActor open class UITerminalView: UIView { let core = TerminalSurfaceCoordinator() - var momentumDisplayLink: CADisplayLink? - var momentumVelocity: CGPoint = .zero - #if !targetEnvironment(macCatalyst) - static let minFontSize: Float = 4 - static let maxFontSize: Float = 64 - #endif - var activePointerButton: ghostty_input_mouse_button_e? - var pointerSelectionStartPoint: CGPoint? - var lastPointerSelectionRect: CGRect? - var pendingSelectionMenuPoint: CGPoint? + + // Grouped view state, one struct per concern. Each state type is + // defined in the extension file that owns the behavior (+Keyboard, + // +Interaction, +PinchZoom, +Lifecycle, +UITextInput); the storage + // lives here because extensions cannot add stored properties. A new + // stored value joins (or starts) its concern's struct — the root + // class declares only these `var xxx: XxxState = .init()` lines, + // plus the lazy objects that need `self`. Constants live as statics + // in the extension that uses them. + var hardwareKeyboard: HardwareKeyboardState = .init() + var pointer: PointerInteractionState = .init() + var momentumScroll: MomentumScrollState = .init() + var focusBridge: FocusBridgeState = .init() + var textInputBridge: TextInputBridgeState = .init() #if !targetEnvironment(macCatalyst) - var indirectPointerPanOwnsTouchSequence = false - var suppressNextIndirectPointerTouchEnd = false + var softwareKeyboard: SoftwareKeyboardState = .init() + var fontZoom: FontZoomState = .init() #endif + lazy var selectionContextMenuInteraction = UIContextMenuInteraction(delegate: self) - var hardwareKeyHandled = false - let touchScrollMultiplier: CGFloat = 3.0 - #if !targetEnvironment(macCatalyst) - var currentFontSize: Float = 14 - var lastPinchScale: CGFloat = 1.0 - #endif lazy var inputHandler = TerminalTextInputHandler(view: self) - weak var _inputDelegate: (any UITextInputDelegate)? - var onFocusChange: ((Bool) -> Void)? + + /// Backing store for the iOS 16+ edit-menu interaction — untyped + /// because stored properties cannot carry availability. + private var _selectionEditMenuInteraction: Any? + @available(iOS 16.0, *) + var selectionEditMenuInteraction: UIEditMenuInteraction { + if let interaction = _selectionEditMenuInteraction as? UIEditMenuInteraction { + return interaction + } + let interaction = UIEditMenuInteraction(delegate: nil) + addInteraction(interaction) + _selectionEditMenuInteraction = interaction + return interaction + } #if !targetEnvironment(macCatalyst) lazy var terminalInputAccessory = TerminalInputAccessoryView(terminalView: self) - let stickyModifiers = TerminalStickyModifierState() - var softwareKeyboardVisible = false - var pendingKeyboardDismissOnTouchEnd = false - var touchDidScrollDuringCurrentTouch = false + let stickyModifiers: TerminalStickyModifierState = .init() #endif #if !targetEnvironment(macCatalyst) @@ -57,6 +65,19 @@ reloadInputViews() } } + + /// Toggles the software keyboard the way a clean tap does: the + /// touch path calls this after the tap's click has been sent. + /// Declared in the class body so a host's `makePlatformView` + /// subclass can override it — a keyboard lock overrides to do + /// nothing, and the click still lands. + open func toggleSoftwareKeyboard() { + if softwareKeyboard.isVisible { + resignFirstResponder() + } else { + becomeFirstResponder() + } + } #endif open weak var delegate: (any TerminalSurfaceViewDelegate)? { @@ -71,10 +92,30 @@ open var configuration: TerminalSurfaceOptions { get { core.configuration } - set { core.configuration = newValue } + set { + #if !targetEnvironment(macCatalyst) + // SwiftUI stamps the options on every update; only a + // changed fontSize rebuilds the surface at a new size, + // so only then does the pinch counter follow it. + if newValue.fontSize != core.configuration.fontSize { + fontZoom.currentFontSize = newValue.fontSize ?? 14 + } + #endif + core.configuration = newValue + } + } + + /// Whether this surface should keep drawing — the UIKit twin of the + /// AppKit view's method of the same name. A host that keeps several + /// surfaces mounted at once (tabs hidden behind `opacity(0)`) marks + /// the hidden ones invisible: the surface keeps its grid, scrollback, + /// and session — only rendering stops and the display link is + /// released. + open func setSurfaceVisible(_ visible: Bool) { + core.setDisplayVisible(visible) } - var surface: TerminalSurface? { + public var surface: TerminalSurface? { core.surface } @@ -104,7 +145,7 @@ core.isAttached = { [weak self] in self?.window != nil } core.scaleFactor = { [weak self] in - Double(self?.resolvedDisplayScale() ?? UIScreen.main.nativeScale) + Double(self?.resolvedDisplayScale() ?? UITerminalView.fallbackDisplayScale) } core.viewSize = { [weak self] in guard let self else { return (0, 0) } @@ -125,6 +166,9 @@ core.onCellSizeDidChange = { [weak self] in self?.refreshTextInputGeometry(reason: "cell-size-action") } + core.onMouseShape = { [weak self] shape in + self?.applyMouseShape(shape) + } core.onPostRender = { [weak self] in self?.enforceSublayerScale() } @@ -142,7 +186,7 @@ context: "selectionMenuPoint", point: point ) - if let rect = lastPointerSelectionRect { + if let rect = pointer.lastSelectionRect { let pointIsInsidePointerSelection = rect.insetBy(dx: -4, dy: -4).contains(point) guard pointIsInsidePointerSelection else { TerminalDebugLog.log( @@ -190,13 +234,24 @@ open func showSelectionCopyMenu(at point: CGPoint) { becomeFirstResponder() - let menu = UIMenuController.shared - menu.menuItems = nil - menu.showMenu( - from: self, - rect: CGRect(x: point.x, y: point.y, width: 1, height: 1) - ) - menu.update() + if #available(iOS 16.0, *) { + // UIMenuController stopped presenting anything on modern + // iOS — the menu silently never appears. The edit-menu + // interaction is its replacement; content still comes from + // the responder chain (canPerformAction), so Copy shows + // exactly when a selection exists. + selectionEditMenuInteraction.presentEditMenu( + with: UIEditMenuConfiguration(identifier: nil, sourcePoint: point) + ) + } else { + let menu = UIMenuController.shared + menu.menuItems = nil + menu.showMenu( + from: self, + rect: CGRect(x: point.x, y: point.y, width: 1, height: 1) + ) + menu.update() + } } @discardableResult @@ -262,11 +317,16 @@ @objc func keyboardDidShow(_: Notification) { guard isFirstResponder else { return } - softwareKeyboardVisible = true + // The accessory-only bar of a hardware keyboard counts too: + // a tap on the terminal is the only way to put the keyboard + // UI away, and resigning is no longer destructive — the next + // tap (or pointer click, or the host's focus handoff) + // re-acquires first responder and hardware input with it. + softwareKeyboard.isVisible = true } @objc func keyboardDidHide(_: Notification) { - softwareKeyboardVisible = false + softwareKeyboard.isVisible = false } #endif diff --git a/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/LICENSE-bash-preexec.md b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/LICENSE-bash-preexec.md new file mode 100644 index 0000000..8fb90b7 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/LICENSE-bash-preexec.md @@ -0,0 +1,24 @@ +bash-preexec.sh is vendored from https://github.com/rcaloras/bash-preexec +under the following license. + +The MIT License + +Copyright (c) 2017 Ryan Caloras and contributors (see https://github.com/rcaloras/bash-preexec) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/bash-preexec.sh b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/bash-preexec.sh new file mode 100644 index 0000000..e0d2fa0 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/bash-preexec.sh @@ -0,0 +1,376 @@ +# bash-preexec.sh -- Bash support for ZSH-like 'preexec' and 'precmd' functions. +# https://github.com/rcaloras/bash-preexec +# +# +# 'preexec' functions are executed before each interactive command is +# executed, with the interactive command as its argument. The 'precmd' +# function is executed before each prompt is displayed. +# +# Author: Ryan Caloras (ryan@bashhub.com) +# Forked from Original Author: Glyph Lefkowitz +# +# V0.6.0 +# + +# General Usage: +# +# 1. Source this file at the end of your bash profile so as not to interfere +# with anything else that's using PROMPT_COMMAND. +# +# 2. Add any precmd or preexec functions by appending them to their arrays: +# e.g. +# precmd_functions+=(my_precmd_function) +# precmd_functions+=(some_other_precmd_function) +# +# preexec_functions+=(my_preexec_function) +# +# 3. Consider changing anything using the DEBUG trap or PROMPT_COMMAND +# to use preexec and precmd instead. Preexisting usages will be +# preserved, but doing so manually may be less surprising. +# +# Note: This module requires two Bash features which you must not otherwise be +# using: the "DEBUG" trap, and the "PROMPT_COMMAND" variable. If you override +# either of these after bash-preexec has been installed it will most likely break. + +# Tell shellcheck what kind of file this is. +# shellcheck shell=bash + +# Make sure this is bash that's running and return otherwise. +# Use POSIX syntax for this line: +if [ -z "${BASH_VERSION-}" ]; then + return 1 +fi + +# We only support Bash 3.1+. +# Note: BASH_VERSINFO is first available in Bash-2.0. +if [[ -z "${BASH_VERSINFO-}" ]] || (( BASH_VERSINFO[0] < 3 || (BASH_VERSINFO[0] == 3 && BASH_VERSINFO[1] < 1) )); then + return 1 +fi + +# Avoid duplicate inclusion +if [[ -n "${bash_preexec_imported:-}" || -n "${__bp_imported:-}" ]]; then + return 0 +fi +bash_preexec_imported="defined" + +# WARNING: This variable is no longer used and should not be relied upon. +# Use ${bash_preexec_imported} instead. +# shellcheck disable=SC2034 +__bp_imported="${bash_preexec_imported}" + +# Should be available to each precmd and preexec +# functions, should they want it. $? and $_ are available as $? and $_, but +# $PIPESTATUS is available only in a copy, $BP_PIPESTATUS. +# TODO: Figure out how to restore PIPESTATUS before each precmd or preexec +# function. +__bp_last_ret_value="$?" +BP_PIPESTATUS=("${PIPESTATUS[@]}") +__bp_last_argument_prev_command="$_" + +__bp_inside_precmd=0 +__bp_inside_preexec=0 + +# Initial PROMPT_COMMAND string that is removed from PROMPT_COMMAND post __bp_install +__bp_install_string=$'__bp_trap_string="$(trap -p DEBUG)"\ntrap - DEBUG\n__bp_install' + +# Fails if any of the given variables are readonly +# Reference https://stackoverflow.com/a/4441178 +__bp_require_not_readonly() { + local var + for var; do + if ! ( unset "$var" 2> /dev/null ); then + echo "bash-preexec requires write access to ${var}" >&2 + return 1 + fi + done +} + +# Remove ignorespace and or replace ignoreboth from HISTCONTROL +# so we can accurately invoke preexec with a command from our +# history even if it starts with a space. +__bp_adjust_histcontrol() { + local histcontrol + histcontrol="${HISTCONTROL:-}" + histcontrol="${histcontrol//ignorespace}" + # Replace ignoreboth with ignoredups + if [[ "$histcontrol" == *"ignoreboth"* ]]; then + histcontrol="ignoredups:${histcontrol//ignoreboth}" + fi + export HISTCONTROL="$histcontrol" +} + +# This variable describes whether we are currently in "interactive mode"; +# i.e. whether this shell has just executed a prompt and is waiting for user +# input. It documents whether the current command invoked by the trace hook is +# run interactively by the user; it's set immediately after the prompt hook, +# and unset as soon as the trace hook is run. +__bp_preexec_interactive_mode="" + +# These arrays are used to add functions to be run before, or after, prompts. +declare -a precmd_functions +declare -a preexec_functions + +# Trims leading and trailing whitespace from $2 and writes it to the variable +# name passed as $1 +__bp_trim_whitespace() { + local var=${1:?} text=${2:-} + text="${text#"${text%%[![:space:]]*}"}" # remove leading whitespace characters + text="${text%"${text##*[![:space:]]}"}" # remove trailing whitespace characters + printf -v "$var" '%s' "$text" +} + + +# Trims whitespace and removes any leading or trailing semicolons from $2 and +# writes the resulting string to the variable name passed as $1. Used for +# manipulating substrings in PROMPT_COMMAND +__bp_sanitize_string() { + local var=${1:?} text=${2:-} sanitized + __bp_trim_whitespace sanitized "$text" + sanitized=${sanitized%;} + sanitized=${sanitized#;} + __bp_trim_whitespace sanitized "$sanitized" + printf -v "$var" '%s' "$sanitized" +} + +# This function is installed as part of the PROMPT_COMMAND; +# It sets a variable to indicate that the prompt was just displayed, +# to allow the DEBUG trap to know that the next command is likely interactive. +__bp_interactive_mode() { + __bp_preexec_interactive_mode="on" +} + + +# This function is installed as part of the PROMPT_COMMAND. +# It will invoke any functions defined in the precmd_functions array. +__bp_precmd_invoke_cmd() { + # Save the returned value from our last command, and from each process in + # its pipeline. Note: this MUST be the first thing done in this function. + # BP_PIPESTATUS may be unused, ignore + # shellcheck disable=SC2034 + + __bp_last_ret_value="$?" BP_PIPESTATUS=("${PIPESTATUS[@]}") + + # Don't invoke precmds if we are inside an execution of an "original + # prompt command" by another precmd execution loop. This avoids infinite + # recursion. + if (( __bp_inside_precmd > 0 )); then + return + fi + local __bp_inside_precmd=1 + + # Invoke every function defined in our function array. + local precmd_function + for precmd_function in "${precmd_functions[@]}"; do + + # Only execute this function if it actually exists. + # Test existence of functions with: declare -[Ff] + if type -t "$precmd_function" 1>/dev/null; then + __bp_set_ret_value "$__bp_last_ret_value" "$__bp_last_argument_prev_command" + # Quote our function invocation to prevent issues with IFS + "$precmd_function" + fi + done + + __bp_set_ret_value "$__bp_last_ret_value" +} + +# Sets a return value in $?. We may want to get access to the $? variable in our +# precmd functions. This is available for instance in zsh. We can simulate it in bash +# by setting the value here. +__bp_set_ret_value() { + return ${1:+"$1"} +} + +__bp_in_prompt_command() { + + local prompt_command_array IFS=$'\n;' + read -rd '' -a prompt_command_array <<< "${PROMPT_COMMAND[*]:-}" + + local trimmed_arg + __bp_trim_whitespace trimmed_arg "${1:-}" + + local command trimmed_command + for command in "${prompt_command_array[@]:-}"; do + __bp_trim_whitespace trimmed_command "$command" + if [[ "$trimmed_command" == "$trimmed_arg" ]]; then + return 0 + fi + done + + return 1 +} + +# This function is installed as the DEBUG trap. It is invoked before each +# interactive prompt display. Its purpose is to inspect the current +# environment to attempt to detect if the current command is being invoked +# interactively, and invoke 'preexec' if so. +__bp_preexec_invoke_exec() { + + # Save the contents of $_ so that it can be restored later on. + # https://stackoverflow.com/questions/40944532/bash-preserve-in-a-debug-trap#40944702 + __bp_last_argument_prev_command="${1:-}" + # Don't invoke preexecs if we are inside of another preexec. + if (( __bp_inside_preexec > 0 )); then + return + fi + local __bp_inside_preexec=1 + + # Checks if the file descriptor is not standard out (i.e. '1') + # __bp_delay_install checks if we're in test. Needed for bats to run. + # Prevents preexec from being invoked for functions in PS1 + if [[ ! -t 1 && -z "${__bp_delay_install:-}" ]]; then + return + fi + + if [[ -n "${COMP_POINT:-}" || -n "${READLINE_POINT:-}" ]]; then + # We're in the middle of a completer or a keybinding set up by "bind + # -x". This obviously can't be an interactively issued command. + return + fi + if [[ -z "${__bp_preexec_interactive_mode:-}" ]]; then + # We're doing something related to displaying the prompt. Let the + # prompt set the title instead of me. + return + else + # If we're in a subshell, then the prompt won't be re-displayed to put + # us back into interactive mode, so let's not set the variable back. + # In other words, if you have a subshell like + # (sleep 1; sleep 2) + # You want to see the 'sleep 2' as a set_command_title as well. + if [[ 0 -eq "${BASH_SUBSHELL:-}" ]]; then + __bp_preexec_interactive_mode="" + fi + fi + + if __bp_in_prompt_command "${BASH_COMMAND:-}"; then + # If we're executing something inside our prompt_command then we don't + # want to call preexec. Bash prior to 3.1 can't detect this at all :/ + __bp_preexec_interactive_mode="" + return + fi + + local this_command + this_command=$(LC_ALL=C HISTTIMEFORMAT='' builtin history 1) + this_command="${this_command#*[[:digit:]][* ] }" + + # Sanity check to make sure we have something to invoke our function with. + if [[ -z "$this_command" ]]; then + return + fi + + # Invoke every function defined in our function array. + local preexec_function + local preexec_function_ret_value + local preexec_ret_value=0 + for preexec_function in "${preexec_functions[@]:-}"; do + + # Only execute each function if it actually exists. + # Test existence of function with: declare -[fF] + if type -t "$preexec_function" 1>/dev/null; then + __bp_set_ret_value "${__bp_last_ret_value:-}" + # Quote our function invocation to prevent issues with IFS + "$preexec_function" "$this_command" + preexec_function_ret_value="$?" + if [[ "$preexec_function_ret_value" != 0 ]]; then + preexec_ret_value="$preexec_function_ret_value" + fi + fi + done + + # Restore the last argument of the last executed command, and set the return + # value of the DEBUG trap to be the return code of the last preexec function + # to return an error. + # If `extdebug` is enabled a non-zero return value from any preexec function + # will cause the user's command not to execute. + # Run `shopt -s extdebug` to enable + __bp_set_ret_value "$preexec_ret_value" "$__bp_last_argument_prev_command" +} + +__bp_install() { + # Exit if we already have this installed. + if [[ "${PROMPT_COMMAND[*]:-}" == *"__bp_precmd_invoke_cmd"* ]]; then + return 1 + fi + + trap '__bp_preexec_invoke_exec "$_"' DEBUG + + # Preserve any prior DEBUG trap as a preexec function + eval "local trap_argv=(${__bp_trap_string:-})" + local prior_trap=${trap_argv[2]:-} + unset __bp_trap_string + if [[ -n "$prior_trap" ]]; then + eval '__bp_original_debug_trap() { + '"$prior_trap"' + }' + preexec_functions+=(__bp_original_debug_trap) + fi + + # Adjust our HISTCONTROL Variable if needed. + __bp_adjust_histcontrol + + # Issue #25. Setting debug trap for subshells causes sessions to exit for + # backgrounded subshell commands (e.g. (pwd)& ). Believe this is a bug in Bash. + # + # Disabling this by default. It can be enabled by setting this variable. + if [[ -n "${__bp_enable_subshells:-}" ]]; then + + # Set so debug trap will work be invoked in subshells. + set -o functrace > /dev/null 2>&1 + shopt -s extdebug > /dev/null 2>&1 + fi + + local existing_prompt_command + # Remove setting our trap install string and sanitize the existing prompt command string + existing_prompt_command="${PROMPT_COMMAND:-}" + # Edge case of appending to PROMPT_COMMAND + existing_prompt_command="${existing_prompt_command//$__bp_install_string/:}" # no-op + existing_prompt_command="${existing_prompt_command//$'\n':$'\n'/$'\n'}" # remove known-token only + existing_prompt_command="${existing_prompt_command//$'\n':;/$'\n'}" # remove known-token only + __bp_sanitize_string existing_prompt_command "$existing_prompt_command" + if [[ "${existing_prompt_command:-:}" == ":" ]]; then + existing_prompt_command= + fi + + # Install our hooks in PROMPT_COMMAND to allow our trap to know when we've + # actually entered something. + PROMPT_COMMAND='__bp_precmd_invoke_cmd' + PROMPT_COMMAND+=${existing_prompt_command:+$'\n'$existing_prompt_command} + if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1) )); then + PROMPT_COMMAND+=('__bp_interactive_mode') + else + # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0 + PROMPT_COMMAND+=$'\n__bp_interactive_mode' + fi + + # Add two functions to our arrays for convenience + # of definition. + precmd_functions+=(precmd) + preexec_functions+=(preexec) + + # Invoke our two functions manually that were added to $PROMPT_COMMAND + __bp_precmd_invoke_cmd + __bp_interactive_mode +} + +# Sets an installation string as part of our PROMPT_COMMAND to install +# after our session has started. This allows bash-preexec to be included +# at any point in our bash profile. +__bp_install_after_session_init() { + # bash-preexec needs to modify these variables in order to work correctly + # if it can't, just stop the installation + __bp_require_not_readonly PROMPT_COMMAND HISTCONTROL HISTTIMEFORMAT || return + + local sanitized_prompt_command + __bp_sanitize_string sanitized_prompt_command "${PROMPT_COMMAND:-}" + if [[ -n "$sanitized_prompt_command" ]]; then + # shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0 + PROMPT_COMMAND=${sanitized_prompt_command}$'\n' + fi + # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0 + PROMPT_COMMAND+=${__bp_install_string} +} + +# Run our install so long as we're not delaying it. +if [[ -z "${__bp_delay_install:-}" ]]; then + __bp_install_after_session_init +fi diff --git a/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/ghostty.bash b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/ghostty.bash new file mode 100644 index 0000000..1a133b3 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/bash/ghostty.bash @@ -0,0 +1,191 @@ +# Ghostty bash shell integration. +# +# Copyright (c) 2026 @Lakr233 +# SPDX-License-Identifier: MIT +# +# Written from scratch for libghostty-spm. Not derived from Ghostty's or +# Kitty's bash integration (both GPLv3). Hooks come from bash-preexec.sh +# next to this file (MIT, https://github.com/rcaloras/bash-preexec — see +# LICENSE-bash-preexec.md). +# +# Two ways in: +# +# 1. Injected. The terminal starts `bash --posix` with ENV pointing here, so +# bash reads this file instead of its normal startup files. The contract, +# from libghostty's termio/shell_integration.zig (hosts that spawn bash +# themselves set the same variables): +# +# GHOSTTY_BASH_INJECT "1", plus any of " --norc" " --noprofile" +# the terminal swallowed from the command +# GHOSTTY_BASH_RCFILE the argument of --rcfile / --init-file +# GHOSTTY_BASH_ENV the user's ENV, if there was one +# GHOSTTY_BASH_UNEXPORT_HISTFILE the terminal exported HISTFILE only to +# undo POSIX mode's ~/.sh_history default +# +# The file leaves POSIX mode, undoes those variables, replays the startup +# files bash would have read on its own, then attaches the hooks. +# +# 2. Sourced from a .bashrc. Only the hooks attach. +# +# What the terminal gets from the hooks, per prompt: +# +# OSC 133 A / B / C / D prompt start, input start, output start, +# command end with its exit code +# OSC 7 the working directory, as a file:// URL +# OSC 2 the title — the directory at a prompt, the +# command while it runs (feature `title`) +# DECSCUSR a bar cursor while editing, the default shape +# while a command runs (feature `cursor`) +# +# Features come from GHOSTTY_SHELL_FEATURES, a comma-separated list the +# terminal exports (`cursor`, `cursor:blink`, `cursor:steady`, `title`, …). +# Anything else in the list is ignored. + +if [[ -n "${GHOSTTY_BASH_INJECT:-}" ]]; then + builtin set +o posix + + _ghostty_inject="$GHOSTTY_BASH_INJECT" + _ghostty_rcfile="${GHOSTTY_BASH_RCFILE:-}" + builtin unset GHOSTTY_BASH_INJECT GHOSTTY_BASH_RCFILE + + if [[ -n "${GHOSTTY_BASH_ENV:-}" ]]; then + builtin export ENV="$GHOSTTY_BASH_ENV" + else + builtin unset ENV + fi + builtin unset GHOSTTY_BASH_ENV + + if [[ -n "${GHOSTTY_BASH_UNEXPORT_HISTFILE:-}" ]]; then + builtin export -n HISTFILE + builtin unset GHOSTTY_BASH_UNEXPORT_HISTFILE + fi + + _ghostty_norc=0 + _ghostty_noprofile=0 + for _ghostty_word in $_ghostty_inject; do + case "$_ghostty_word" in + --norc) _ghostty_norc=1 ;; + --noprofile) _ghostty_noprofile=1 ;; + esac + done + + # The system files live in bash's compiled-in sysconfdir. Derive it from + # the binary's location: /bin/bash and /usr/bin/bash → /etc, + # /var/jb/usr/bin/bash → /var/jb/etc, /opt/homebrew/bin/bash → + # /opt/homebrew/etc. + _ghostty_sysconfdir="${BASH%/bin/bash}" + _ghostty_sysconfdir="${_ghostty_sysconfdir%/usr}/etc" + + if shopt -q login_shell; then + if (( ! _ghostty_noprofile )); then + [[ -r "$_ghostty_sysconfdir/profile" ]] && builtin source "$_ghostty_sysconfdir/profile" + for _ghostty_file in ~/.bash_profile ~/.bash_login ~/.profile; do + if [[ -r "$_ghostty_file" ]]; then + builtin source "$_ghostty_file" + break + fi + done + fi + elif (( ! _ghostty_norc )); then + for _ghostty_file in "$_ghostty_sysconfdir/bash.bashrc" "$_ghostty_sysconfdir/bashrc"; do + if [[ -r "$_ghostty_file" ]]; then + builtin source "$_ghostty_file" + break + fi + done + if [[ -n "$_ghostty_rcfile" ]]; then + [[ -r "$_ghostty_rcfile" ]] && builtin source "$_ghostty_rcfile" + elif [[ -r ~/.bashrc ]]; then + builtin source ~/.bashrc + fi + fi + + builtin unset _ghostty_inject _ghostty_rcfile _ghostty_norc _ghostty_noprofile \ + _ghostty_word _ghostty_file _ghostty_sysconfdir +fi + +[[ $- == *i* ]] || return 0 +[[ -n "${_ghostty_integration_loaded:-}" ]] && return 0 +_ghostty_integration_loaded=1 + +# Not a precmd: bash-preexec runs those before the pre-existing PROMPT_COMMAND +# text, so only a tail entry survives a PROMPT_COMMAND that rebuilds PS1 every +# cycle. Appended before bash-preexec installs itself so that on the first +# prompt it also precedes __bp_interactive_mode, whose flag the DEBUG trap +# spends on the next command it sees. +PROMPT_COMMAND+=$'\n_ghostty_mark_input' +builtin source "${BASH_SOURCE[0]%/*}/bash-preexec.sh" + +_ghostty_prompt_end='\[\e]133;B\a\]' +_ghostty_command_ran=0 + +_ghostty_feature() { + [[ ",${GHOSTTY_SHELL_FEATURES:-}," == *",$1,"* ]] +} + +# Percent-encodes $PWD byte by byte for the OSC 7 URL. +_ghostty_encoded_pwd() { + local LC_ALL=C + local out= byte i + for (( i = 0; i < ${#PWD}; i++ )); do + byte="${PWD:i:1}" + case "$byte" in + [A-Za-z0-9/_.~-]) out+="$byte" ;; + *) builtin printf -v byte '%%%02X' "$(( $(builtin printf '%d' "'$byte") & 0xFF ))" + out+="$byte" ;; + esac + done + builtin printf '%s' "$out" +} + +_ghostty_precmd() { + local exit_code=$? + + if (( _ghostty_command_ran )); then + builtin printf '\033]133;D;%s\007' "$exit_code" + _ghostty_command_ran=0 + fi + + # Prompt start goes out directly, so it lands however PS1 is built. + builtin printf '\033]133;A\007' + builtin printf '\033]7;file://%s%s\007' "${HOSTNAME:-}" "$(_ghostty_encoded_pwd)" + + if _ghostty_feature title; then + local directory="$PWD" + [[ -n "$HOME" && ( "$directory" == "$HOME" || "$directory" == "$HOME"/* ) ]] && directory="~${directory#"$HOME"}" + builtin printf '\033]2;%s\007' "$directory" + fi + + if _ghostty_feature cursor:steady; then + builtin printf '\033[6 q' + elif _ghostty_feature cursor || _ghostty_feature cursor:blink; then + builtin printf '\033[5 q' + fi +} + +_ghostty_preexec() { + _ghostty_command_ran=1 + + if _ghostty_feature cursor || _ghostty_feature cursor:blink || _ghostty_feature cursor:steady; then + builtin printf '\033[0 q' + fi + + if _ghostty_feature title; then + # The first line of the command, control characters stripped. + local command="${1%%$'\n'*}" + builtin printf '\033]2;%s\007' "${command//[[:cntrl:]]/}" + fi + + builtin printf '\033]133;C\007' +} + +# Input start rides on the end of PS1; re-append whenever something +# rebuilt PS1 without it. +_ghostty_mark_input() { + if [[ "$PS1" != *"$_ghostty_prompt_end" ]]; then + PS1="$PS1$_ghostty_prompt_end" + fi +} + +precmd_functions+=(_ghostty_precmd) +preexec_functions+=(_ghostty_preexec) diff --git a/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/zsh/.zshenv b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/zsh/.zshenv new file mode 100644 index 0000000..32e7acf --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/zsh/.zshenv @@ -0,0 +1,54 @@ +# Ghostty zsh shell integration — bootstrap. +# +# Copyright (c) 2026 @Lakr233 +# SPDX-License-Identifier: MIT +# +# Written from scratch for libghostty-spm. Not derived from Ghostty's or +# Kitty's zsh integration (both GPLv3); only the environment contract is +# shared, so libghostty's exec backend and any host that mimics it can load +# this file the same way: +# +# ZDOTDIR=/shell-integration/zsh zsh reads this file first +# GHOSTTY_ZSH_ZDOTDIR= set only if the user had one +# +# This file puts ZDOTDIR back, runs the user's own .zshenv, and — for an +# interactive shell — arranges for ghostty-integration to load after .zshrc, +# so the user's prompt and hooks are already in place when ours attach. + +# Where this file lives; ghostty-integration sits next to it. +typeset -g GHOSTTY_ZSH_INTEGRATION_DIR="${${(%):-%x}:A:h}" + +# Restore ZDOTDIR before anything else reads it. zsh looks the variable up +# again for every later startup file, so .zprofile/.zshrc/.zlogin come from +# the user's directory, not from ours. +if [[ -n "${GHOSTTY_ZSH_ZDOTDIR+set}" ]]; then + ZDOTDIR="$GHOSTTY_ZSH_ZDOTDIR" + unset GHOSTTY_ZSH_ZDOTDIR +else + unset ZDOTDIR +fi + +# The user's .zshenv, which may itself relocate ZDOTDIR. +if [[ -r "${ZDOTDIR:-$HOME}/.zshenv" ]]; then + builtin source -- "${ZDOTDIR:-$HOME}/.zshenv" +fi + +if [[ -o interactive ]]; then + # Load the integration from the first precmd: by then .zshrc has run, + # so hooks we add land after the user's and our prompt marks survive + # prompt frameworks that rebuild PS1 in their own precmd. + _ghostty_deferred_init() { + builtin unfunction _ghostty_deferred_init + precmd_functions=(${precmd_functions:#_ghostty_deferred_init}) + # Already sourced by .zshrc: its hooks are in this cycle's snapshot. + (( ${+_ghostty_integration_loaded} )) && return 0 + if [[ -r "$GHOSTTY_ZSH_INTEGRATION_DIR/ghostty-integration" ]]; then + builtin source -- "$GHOSTTY_ZSH_INTEGRATION_DIR/ghostty-integration" + # This precmd cycle iterates a snapshot of the hook list, so + # run ours once by hand for the very first prompt. + _ghostty_precmd + fi + } + typeset -ga precmd_functions + precmd_functions+=(_ghostty_deferred_init) +fi diff --git a/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/zsh/ghostty-integration b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/zsh/ghostty-integration new file mode 100644 index 0000000..6df942a --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Resources/Ghostty/shell-integration/zsh/ghostty-integration @@ -0,0 +1,100 @@ +# Ghostty zsh shell integration. +# +# Copyright (c) 2026 @Lakr233 +# SPDX-License-Identifier: MIT +# +# Written from scratch for libghostty-spm. Not derived from Ghostty's or +# Kitty's zsh integration (both GPLv3). +# +# What the terminal gets from this file, per prompt: +# +# OSC 133 A / B / C / D prompt start, input start, output start, +# command end with its exit code (semantic prompts: +# jump-to-prompt, prompt-aware selection) +# OSC 7 the working directory, as a file:// URL +# OSC 2 the title — the directory at a prompt, the +# command while it runs (feature `title`) +# DECSCUSR a bar cursor while editing, the default shape +# while a command runs (feature `cursor`) +# +# Features come from GHOSTTY_SHELL_FEATURES, a comma-separated list the +# terminal exports (`cursor`, `cursor:blink`, `cursor:steady`, `title`, …). +# Anything else in the list is ignored; sudo, ssh-* and path need a ghostty +# binary or change the user's environment, and this integration does neither. +# +# Loaded by the .zshenv next to it after the user's .zshrc, or sourced by +# hand from a .zshrc — both work; it only attaches hooks. + +[[ -o interactive ]] || return 0 +(( ${+_ghostty_integration_loaded} )) && return 0 +typeset -g _ghostty_integration_loaded=1 + +builtin autoload -Uz add-zsh-hook + +typeset -g _ghostty_prompt_end=$'%{\e]133;B\a%}' +typeset -g _ghostty_command_ran=0 + +_ghostty_feature() { + [[ ",${GHOSTTY_SHELL_FEATURES-}," == *",$1,"* ]] +} + +# Percent-encodes $PWD byte by byte for the OSC 7 URL. +_ghostty_encoded_pwd() { + builtin setopt localoptions nomultibyte + local out= byte + for byte in ${(s::)PWD}; do + case "$byte" in + [A-Za-z0-9/_.~-]) out+="$byte" ;; + *) out+="$(builtin printf '%%%02X' "'$byte")" ;; + esac + done + builtin print -rn -- "$out" +} + +_ghostty_precmd() { + local exit_code=$? + + if (( _ghostty_command_ran )); then + builtin print -n -- $'\e]133;D;'"$exit_code"$'\a' + _ghostty_command_ran=0 + fi + + # Prompt start goes out directly, so it lands however PS1 is built. + builtin print -n -- $'\e]133;A\a' + builtin print -n -- $'\e]7;file://'"${HOST-}$(_ghostty_encoded_pwd)"$'\a' + + if _ghostty_feature title; then + builtin print -rn -- $'\e]2;'"${(%):-%~}"$'\a' + fi + + if _ghostty_feature cursor:steady; then + builtin print -n -- $'\e[6 q' + elif _ghostty_feature cursor || _ghostty_feature cursor:blink; then + builtin print -n -- $'\e[5 q' + fi + + # Input start rides on the end of PS1; re-append whenever a prompt + # framework rebuilt PS1 without it. + if [[ "$PS1" != *"$_ghostty_prompt_end" ]]; then + PS1="$PS1$_ghostty_prompt_end" + fi +} + +_ghostty_preexec() { + _ghostty_command_ran=1 + + if _ghostty_feature cursor || _ghostty_feature cursor:blink || _ghostty_feature cursor:steady; then + builtin print -n -- $'\e[0 q' + fi + + if _ghostty_feature title; then + # The first line of the command, control characters stripped. + local command="${1%%$'\n'*}" + builtin print -rn -- $'\e]2;'"${command//[[:cntrl:]]/}"$'\a' + fi + + builtin print -n -- $'\e]133;C\a' +} + +add-zsh-hook precmd _ghostty_precmd +add-zsh-hook preexec _ghostty_preexec diff --git a/ios/vendor/GhosttyTerminal/Resources/terminfo/67/ghostty b/ios/vendor/GhosttyTerminal/Resources/terminfo/67/ghostty new file mode 100644 index 0000000..f0609ee Binary files /dev/null and b/ios/vendor/GhosttyTerminal/Resources/terminfo/67/ghostty differ diff --git a/ios/vendor/GhosttyTerminal/Resources/terminfo/78/xterm-ghostty b/ios/vendor/GhosttyTerminal/Resources/terminfo/78/xterm-ghostty new file mode 100644 index 0000000..f0609ee Binary files /dev/null and b/ios/vendor/GhosttyTerminal/Resources/terminfo/78/xterm-ghostty differ diff --git a/ios/vendor/GhosttyTerminal/State/TerminalViewState+Delegate.swift b/ios/vendor/GhosttyTerminal/State/TerminalViewState+Delegate.swift index bd8712c..8e2f849 100644 --- a/ios/vendor/GhosttyTerminal/State/TerminalViewState+Delegate.swift +++ b/ios/vendor/GhosttyTerminal/State/TerminalViewState+Delegate.swift @@ -16,19 +16,69 @@ extension TerminalViewState: TerminalSurfaceBellDelegate, TerminalSurfaceDesktopNotificationDelegate, TerminalSurfacePwdDelegate, + TerminalSurfaceScrollbarDelegate, TerminalSurfaceCommandFinishedDelegate, - TerminalSurfaceLifecycleDelegate + TerminalSurfaceLifecycleDelegate, + TerminalSurfaceTextSelectionRequestDelegate, + TerminalSurfaceClipboardConfirmationDelegate { + /// Applies a change to this state on the main queue's next turn. + /// + /// Every `@Published` property below goes through here, and the reason is + /// the same for all of them: SwiftUI runs a representable's + /// `layoutSubviews` — and the responder changes that layout provokes — + /// inside its own update pass. Publishing from there is what SwiftUI + /// reports as "Publishing changes from within view updates is not allowed, + /// this will cause undefined behavior", and the callbacks that can land + /// inside an update are not a fixed list: `terminalDidResize` and + /// `terminalDidChangeFocus` are the ones seen so far, but any of these can + /// be reached from a rebuild that a layout started. One rule for the whole + /// file is easier to keep true than a per-callback judgement that has to be + /// re-made every time one is added. + /// + /// The cost is one runloop turn on state a host only renders. Ordering + /// survives — the main queue is FIFO — and `weak self` keeps a detached + /// state from being resurrected by a change nobody will see. The + /// no-change checks run inside the closure, against the value at apply + /// time: two callbacks in one turn both see the same published value, + /// so a check made at call time drops the second of X→Y→X and the + /// state ends at Y. + /// + /// The closures further down are deliberately *not* routed through this. + /// They are requests with an answer expected, not state: a clipboard + /// confirmation must reach its host while the request is still live, and a + /// close must act before the surface goes. + private func publishSoon(_ apply: @escaping @MainActor (TerminalViewState) -> Void) { + terminalRunOnMainNextTurn { [weak self] in + guard let self else { return } + apply(self) + } + } + public func terminalDidChangeTitle(_ title: String) { - self.title = title + publishSoon { + guard $0.title != title else { return } + $0.title = title + } } + /// The metrics come from `synchronizeMetrics()`, which runs off the view's + /// layout — the callback that first showed the update-pass problem. The + /// turn of delay is invisible here in particular: the size had already + /// reached the engine before this was called (`synchronizeMetrics` says so + /// at length), so this notification only ever fed the host's own UI. public func terminalDidResize(_ size: TerminalGridMetrics) { - surfaceSize = size + publishSoon { + guard $0.surfaceSize != size else { return } + $0.surfaceSize = size + } } public func terminalDidChangeFocus(_ focused: Bool) { - isFocused = focused + publishSoon { + guard $0.isFocused != focused else { return } + $0.isFocused = focused + } } public func terminalDidClose(processAlive: Bool) { @@ -36,23 +86,59 @@ extension TerminalViewState: } public func terminalDidRingBell() { - bellCount += 1 - lastBellAt = Date() + // The instant the bell rang, not the instant it was published. + let at = Date() + publishSoon { + $0.bellCount += 1 + $0.lastBellAt = at + } } public func terminalDidRequestDesktopNotification(title: String, body: String) { - lastDesktopNotificationTitle = title - lastDesktopNotificationBody = body - lastDesktopNotificationAt = Date() + let at = Date() + publishSoon { + $0.lastDesktopNotificationTitle = title + $0.lastDesktopNotificationBody = body + $0.lastDesktopNotificationAt = at + } } public func terminalDidChangeWorkingDirectory(_ path: String) { - workingDirectory = path + publishSoon { + guard $0.workingDirectory != path else { return } + $0.workingDirectory = path + } + } + + public func terminalDidUpdateScrollbar(_ scrollbar: TerminalScrollbar) { + publishSoon { + guard $0.scrollbar != scrollbar else { return } + $0.scrollbar = scrollbar + } } public func terminalDidFinishCommand(exitCode: Int?, durationNanos: UInt64) { - lastCommandExitCode = exitCode - lastCommandDurationNanos = durationNanos + publishSoon { + $0.lastCommandExitCode = exitCode + $0.lastCommandDurationNanos = durationNanos + } + } + + public func terminalDidRequestTextSelection(_ request: TerminalTextSelectionRequest) { + onTextSelectionRequest?(request) + } + + public func terminalDidRequestClipboardConfirmation(_ request: TerminalClipboardConfirmationRequest) { + guard let onClipboardConfirmationRequest else { + // No host UI to ask. A paste the user started is theirs to + // make — the host's Paste button always pasted before it ran + // through the binding, and dropping it silently is worse than + // what paste protection guards against. A program's own read or + // write of the clipboard stays denied. + request.respond(allow: request.kind == .paste) + return + } + onClipboardConfirmationRequest(request) } public func terminalDidAttachSurface(_ surface: TerminalSurface) { @@ -60,6 +146,10 @@ extension TerminalViewState: } public func terminalDidDetachSurface() { + // Two views can report to one state while a SwiftUI swap keeps the + // outgoing one mounted; its teardown must not drop the replacement + // surface the incoming view attached. A freed surface reads nil. + guard surface?.rawValue == nil else { return } surface = nil } } diff --git a/ios/vendor/GhosttyTerminal/State/TerminalViewState.swift b/ios/vendor/GhosttyTerminal/State/TerminalViewState.swift index 7d24349..b9095fb 100644 --- a/ios/vendor/GhosttyTerminal/State/TerminalViewState.swift +++ b/ios/vendor/GhosttyTerminal/State/TerminalViewState.swift @@ -6,6 +6,7 @@ // import Foundation +import GhosttyKit import SwiftUI @MainActor @@ -26,22 +27,135 @@ public final class TerminalViewState: ObservableObject { @Published public internal(set) var lastCommandExitCode: Int? @Published public internal(set) var lastCommandDurationNanos: UInt64? + /// Latest scrollbar geometry reported by the terminal (nil until the first + /// update). Drives a host-drawn scrollbar. + @Published public internal(set) var scrollbar: TerminalScrollbar? + public internal(set) weak var surface: TerminalSurface? + /// The platform view currently presenting this state, set by the SwiftUI + /// representable. Weak: the state outlives detached views. + weak var attachedView: TerminalView? + + /// The platform view currently presenting this state, for host work that + /// needs the real view — a rendered ``TerminalView/snapshotImage()``, + /// coordinate math. `nil` while no view presents this state; weak like + /// `attachedView`, because the state outlives detached views. + public var attachedPlatformView: TerminalView? { attachedView } + + /// Factory for the platform view the SwiftUI representable creates. + /// Hosts that need their own view behavior — an interaction lock, + /// custom hit testing — return a `TerminalView` subclass here; `nil` + /// (the default) instantiates the base class. Read once, when the + /// surface view is made: set it before the surface first appears. + public var makePlatformView: (@MainActor () -> TerminalView)? + private var pendingFocusRequest = false + + /// Whether the attached surface should keep drawing. Hosts that keep + /// several surfaces mounted at once (tabs hidden behind `opacity(0)`) + /// set this false on the hidden ones: the surface keeps its grid, + /// scrollback, and session — only rendering stops and the display link + /// is released, instead of every mounted tab drawing frames nobody + /// sees. Defaults to true. + @Published public var isSurfaceVisible: Bool = true + @Published public var configuration: TerminalSurfaceOptions = .init() public var onClose: ((Bool) -> Void)? @Published public internal(set) var controller: TerminalController - /// Sends text to the attached surface. + #if canImport(UIKit) + #if !targetEnvironment(macCatalyst) + /// Items of the software keyboard's input accessory bar, in order. + /// `nil` shows `TerminalInputAccessoryItem.defaultItems`; an empty + /// array hides the bar. Applied to the platform view by the SwiftUI + /// representable. + @Published public var inputAccessoryItems: [TerminalInputAccessoryItem]? + #endif + #endif + + /// Host hook for the iOS long-press text-selection flow. Setting this is + /// the opt-in: while it is `nil` the long-press recognizer stays inactive, + /// exactly as if the delegate never adopted + /// ``TerminalSurfaceTextSelectionRequestDelegate``. + public var onTextSelectionRequest: ((TerminalTextSelectionRequest) -> Void)? + + /// Host hook for clipboard decisions ghostty will not make alone: a + /// program reading the clipboard through OSC 52 (`clipboard-read = ask`, + /// the default), writing it when `clipboard-write = ask`, or a paste + /// that paste protection flagged as unsafe. The host presents the + /// request and answers it with ``TerminalClipboardConfirmationRequest/respond(allow:)``. + /// While this is `nil`, a program's read or write is denied silently and + /// a paste the user started is allowed; a host that wants programs to + /// read the clipboard, or wants a say on unsafe pastes, sets it. + public var onClipboardConfirmationRequest: ((TerminalClipboardConfirmationRequest) -> Void)? + + /// Hands keyboard focus to the attached terminal view, imperatively. + /// + /// The SwiftUI `terminalFocused` bridge is best-effort: with no native + /// focusable view anchoring the `FocusState`, SwiftUI's focus system can + /// reset the state to nil before the bridge acts on it, leaving the + /// previously focused surface holding first responder — and eating every + /// hardware key. Hosts that must move focus deterministically (switching + /// tabs, dismissing a cover) call this; a request that lands before the + /// view is in a window replays once it attaches. + public func requestFocus() { + pendingFocusRequest = true + // Hop the runloop: hosts call this from SwiftUI `onChange`, and the + // first-responder dance writes focus state that must not mutate + // SwiftUI state mid-update. + DispatchQueue.main.async { [weak self] in + self?.replayPendingFocusIfNeeded() + } + } + + func replayPendingFocusIfNeeded() { + guard pendingFocusRequest else { return } + guard let view = attachedView, view.acquireProgrammaticFocus() else { + return + } + pendingFocusRequest = false + } + + /// Pastes text into the attached surface. This is the text path: a + /// program that enabled bracketed paste receives it framed as a paste, + /// so a `\r` in it lands in the shell's edit line instead of running + /// it. Keystrokes — Enter, Tab, Ctrl+C — go through ``sendKey(_:)``. @discardableResult - public func send(_ text: String) -> Bool { + public func paste(text: String) -> Bool { guard let surface else { - TerminalDebugLog.log(.input, "view state send ignored: missing surface") + TerminalDebugLog.log(.input, "view state paste ignored: missing surface") return false } return surface.sendText(text) } + /// The old name of ``paste(text:)``. It never sent keystrokes — the + /// text path is a paste — and the name led hosts to `send("ls\r")`, + /// which a shell with bracketed paste on does not run. + @available(*, deprecated, renamed: "paste(text:)", message: "The text path is a paste; press keys with sendKey(_:).") + @discardableResult + public func send(_ text: String) -> Bool { + paste(text: text) + } + + /// Presses and releases a key on the attached surface, as if typed on a + /// hardware keyboard — see ``TerminalSurface/sendKey(_:)``. + @discardableResult + public func sendKey(_ press: TerminalKeyPress) -> Bool { + guard let surface else { + TerminalDebugLog.log(.input, "view state key ignored: missing surface") + return false + } + return surface.sendKey(press) + } + + /// ``sendKey(_:)`` for a key and its modifiers: `sendKey(.enter)`, + /// `sendKey(.c, modifiers: .ctrl)`. + @discardableResult + public func sendKey(_ key: TerminalKey, modifiers: TerminalInputModifiers = []) -> Bool { + sendKey(TerminalKeyPress(key, modifiers: modifiers)) + } + /// Invoke a named Ghostty binding action on the attached surface. @discardableResult public func performBindingAction(_ action: String) -> Bool { @@ -63,6 +177,40 @@ public final class TerminalViewState: ObservableObject { surface?.scrollToRow(row) ?? false } + /// Whether the application currently owns the mouse (DEC 1000/1002/1003). + public var isMouseCaptured: Bool { + surface?.isMouseCaptured ?? false + } + + public func sendMousePos( + x: Double, + y: Double, + modifiers: TerminalInputModifiers = [] + ) { + surface?.sendMousePos(x: x, y: y, modifiers: modifiers) + } + + @discardableResult + public func sendMouseButton( + state: ghostty_input_mouse_state_e, + button: ghostty_input_mouse_button_e, + modifiers: TerminalInputModifiers = [] + ) -> Bool { + surface?.sendMouseButton( + state: state, + button: button, + modifiers: modifiers + ) ?? false + } + + public func sendMouseScroll( + x: Double, + y: Double, + mods: TerminalScrollModifiers = TerminalScrollModifiers(precision: true) + ) { + surface?.sendMouseScroll(x: x, y: y, mods: mods) + } + public convenience init() { self.init(configSource: .none) } diff --git a/ios/vendor/GhosttyTerminal/Surface/TerminalKey.swift b/ios/vendor/GhosttyTerminal/Surface/TerminalKey.swift new file mode 100644 index 0000000..07bfc1c --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Surface/TerminalKey.swift @@ -0,0 +1,519 @@ +// +// TerminalKey.swift +// libghostty-spm +// + +import GhosttyKit + +/// A physical key, named as the W3C UI Events `KeyboardEvent.code` value +/// names it — one case per `ghostty_input_key_e`, in the header's order +/// and sections. The set is complete on purpose: a host that wants to press +/// a key picks it from this list rather than from a hand-picked subset. +/// +/// libghostty resolves a key from the platform's native keycode, and its +/// Apple builds use macOS virtual keycodes on every platform. A key with no +/// Mac keycode (``hasPlatformKeycode`` is false — the numpad extras, media +/// keys, IME mode keys) cannot be pressed programmatically; ``TerminalSurface/sendKey(_:)`` +/// returns false for it. +public enum TerminalKey: Sendable, Hashable, CaseIterable { + // "Writing System Keys" § 3.1.1 + case backquote + case backslash + case bracketLeft + case bracketRight + case comma + case digit0 + case digit1 + case digit2 + case digit3 + case digit4 + case digit5 + case digit6 + case digit7 + case digit8 + case digit9 + case equal + case intlBackslash + case intlRo + case intlYen + case a + case b + case c + case d + case e + case f + case g + case h + case i + case j + case k + case l + case m + case n + case o + case p + case q + case r + case s + case t + case u + case v + case w + case x + case y + case z + case minus + case period + case quote + case semicolon + case slash + + // "Functional Keys" § 3.1.2 + case altLeft + case altRight + case backspace + case capsLock + case contextMenu + case controlLeft + case controlRight + case enter + case metaLeft + case metaRight + case shiftLeft + case shiftRight + case space + case tab + case convert + case kanaMode + case nonConvert + + // "Control Pad Section" § 3.2 + case delete + case end + case help + case home + case insert + case pageDown + case pageUp + + // "Arrow Pad Section" § 3.3 + case arrowDown + case arrowLeft + case arrowRight + case arrowUp + + // "Numpad Section" § 3.4 + case numLock + case numpad0 + case numpad1 + case numpad2 + case numpad3 + case numpad4 + case numpad5 + case numpad6 + case numpad7 + case numpad8 + case numpad9 + case numpadAdd + case numpadBackspace + case numpadClear + case numpadClearEntry + case numpadComma + case numpadDecimal + case numpadDivide + case numpadEnter + case numpadEqual + case numpadMemoryAdd + case numpadMemoryClear + case numpadMemoryRecall + case numpadMemoryStore + case numpadMemorySubtract + case numpadMultiply + case numpadParenLeft + case numpadParenRight + case numpadSubtract + case numpadSeparator + case numpadUp + case numpadDown + case numpadRight + case numpadLeft + case numpadBegin + case numpadHome + case numpadEnd + case numpadInsert + case numpadDelete + case numpadPageUp + case numpadPageDown + + // "Function Section" § 3.5 + case escape + case f1 + case f2 + case f3 + case f4 + case f5 + case f6 + case f7 + case f8 + case f9 + case f10 + case f11 + case f12 + case f13 + case f14 + case f15 + case f16 + case f17 + case f18 + case f19 + case f20 + case f21 + case f22 + case f23 + case f24 + case f25 + case fn + case fnLock + case printScreen + case scrollLock + case pause + + // "Media Keys" § 3.6 + case browserBack + case browserFavorites + case browserForward + case browserHome + case browserRefresh + case browserSearch + case browserStop + case eject + case launchApp1 + case launchApp2 + case launchMail + case mediaPlayPause + case mediaSelect + case mediaStop + case mediaTrackNext + case mediaTrackPrevious + case power + case sleep + case audioVolumeDown + case audioVolumeMute + case audioVolumeUp + case wakeUp + + // "Legacy, Non-standard, and Special Keys" § 3.7 + case copy + case cut + case paste + + /// The libghostty key this case names. + public var ghosttyKey: ghostty_input_key_e { + switch self { + case .backquote: GHOSTTY_KEY_BACKQUOTE + case .backslash: GHOSTTY_KEY_BACKSLASH + case .bracketLeft: GHOSTTY_KEY_BRACKET_LEFT + case .bracketRight: GHOSTTY_KEY_BRACKET_RIGHT + case .comma: GHOSTTY_KEY_COMMA + case .digit0: GHOSTTY_KEY_DIGIT_0 + case .digit1: GHOSTTY_KEY_DIGIT_1 + case .digit2: GHOSTTY_KEY_DIGIT_2 + case .digit3: GHOSTTY_KEY_DIGIT_3 + case .digit4: GHOSTTY_KEY_DIGIT_4 + case .digit5: GHOSTTY_KEY_DIGIT_5 + case .digit6: GHOSTTY_KEY_DIGIT_6 + case .digit7: GHOSTTY_KEY_DIGIT_7 + case .digit8: GHOSTTY_KEY_DIGIT_8 + case .digit9: GHOSTTY_KEY_DIGIT_9 + case .equal: GHOSTTY_KEY_EQUAL + case .intlBackslash: GHOSTTY_KEY_INTL_BACKSLASH + case .intlRo: GHOSTTY_KEY_INTL_RO + case .intlYen: GHOSTTY_KEY_INTL_YEN + case .a: GHOSTTY_KEY_A + case .b: GHOSTTY_KEY_B + case .c: GHOSTTY_KEY_C + case .d: GHOSTTY_KEY_D + case .e: GHOSTTY_KEY_E + case .f: GHOSTTY_KEY_F + case .g: GHOSTTY_KEY_G + case .h: GHOSTTY_KEY_H + case .i: GHOSTTY_KEY_I + case .j: GHOSTTY_KEY_J + case .k: GHOSTTY_KEY_K + case .l: GHOSTTY_KEY_L + case .m: GHOSTTY_KEY_M + case .n: GHOSTTY_KEY_N + case .o: GHOSTTY_KEY_O + case .p: GHOSTTY_KEY_P + case .q: GHOSTTY_KEY_Q + case .r: GHOSTTY_KEY_R + case .s: GHOSTTY_KEY_S + case .t: GHOSTTY_KEY_T + case .u: GHOSTTY_KEY_U + case .v: GHOSTTY_KEY_V + case .w: GHOSTTY_KEY_W + case .x: GHOSTTY_KEY_X + case .y: GHOSTTY_KEY_Y + case .z: GHOSTTY_KEY_Z + case .minus: GHOSTTY_KEY_MINUS + case .period: GHOSTTY_KEY_PERIOD + case .quote: GHOSTTY_KEY_QUOTE + case .semicolon: GHOSTTY_KEY_SEMICOLON + case .slash: GHOSTTY_KEY_SLASH + case .altLeft: GHOSTTY_KEY_ALT_LEFT + case .altRight: GHOSTTY_KEY_ALT_RIGHT + case .backspace: GHOSTTY_KEY_BACKSPACE + case .capsLock: GHOSTTY_KEY_CAPS_LOCK + case .contextMenu: GHOSTTY_KEY_CONTEXT_MENU + case .controlLeft: GHOSTTY_KEY_CONTROL_LEFT + case .controlRight: GHOSTTY_KEY_CONTROL_RIGHT + case .enter: GHOSTTY_KEY_ENTER + case .metaLeft: GHOSTTY_KEY_META_LEFT + case .metaRight: GHOSTTY_KEY_META_RIGHT + case .shiftLeft: GHOSTTY_KEY_SHIFT_LEFT + case .shiftRight: GHOSTTY_KEY_SHIFT_RIGHT + case .space: GHOSTTY_KEY_SPACE + case .tab: GHOSTTY_KEY_TAB + case .convert: GHOSTTY_KEY_CONVERT + case .kanaMode: GHOSTTY_KEY_KANA_MODE + case .nonConvert: GHOSTTY_KEY_NON_CONVERT + case .delete: GHOSTTY_KEY_DELETE + case .end: GHOSTTY_KEY_END + case .help: GHOSTTY_KEY_HELP + case .home: GHOSTTY_KEY_HOME + case .insert: GHOSTTY_KEY_INSERT + case .pageDown: GHOSTTY_KEY_PAGE_DOWN + case .pageUp: GHOSTTY_KEY_PAGE_UP + case .arrowDown: GHOSTTY_KEY_ARROW_DOWN + case .arrowLeft: GHOSTTY_KEY_ARROW_LEFT + case .arrowRight: GHOSTTY_KEY_ARROW_RIGHT + case .arrowUp: GHOSTTY_KEY_ARROW_UP + case .numLock: GHOSTTY_KEY_NUM_LOCK + case .numpad0: GHOSTTY_KEY_NUMPAD_0 + case .numpad1: GHOSTTY_KEY_NUMPAD_1 + case .numpad2: GHOSTTY_KEY_NUMPAD_2 + case .numpad3: GHOSTTY_KEY_NUMPAD_3 + case .numpad4: GHOSTTY_KEY_NUMPAD_4 + case .numpad5: GHOSTTY_KEY_NUMPAD_5 + case .numpad6: GHOSTTY_KEY_NUMPAD_6 + case .numpad7: GHOSTTY_KEY_NUMPAD_7 + case .numpad8: GHOSTTY_KEY_NUMPAD_8 + case .numpad9: GHOSTTY_KEY_NUMPAD_9 + case .numpadAdd: GHOSTTY_KEY_NUMPAD_ADD + case .numpadBackspace: GHOSTTY_KEY_NUMPAD_BACKSPACE + case .numpadClear: GHOSTTY_KEY_NUMPAD_CLEAR + case .numpadClearEntry: GHOSTTY_KEY_NUMPAD_CLEAR_ENTRY + case .numpadComma: GHOSTTY_KEY_NUMPAD_COMMA + case .numpadDecimal: GHOSTTY_KEY_NUMPAD_DECIMAL + case .numpadDivide: GHOSTTY_KEY_NUMPAD_DIVIDE + case .numpadEnter: GHOSTTY_KEY_NUMPAD_ENTER + case .numpadEqual: GHOSTTY_KEY_NUMPAD_EQUAL + case .numpadMemoryAdd: GHOSTTY_KEY_NUMPAD_MEMORY_ADD + case .numpadMemoryClear: GHOSTTY_KEY_NUMPAD_MEMORY_CLEAR + case .numpadMemoryRecall: GHOSTTY_KEY_NUMPAD_MEMORY_RECALL + case .numpadMemoryStore: GHOSTTY_KEY_NUMPAD_MEMORY_STORE + case .numpadMemorySubtract: GHOSTTY_KEY_NUMPAD_MEMORY_SUBTRACT + case .numpadMultiply: GHOSTTY_KEY_NUMPAD_MULTIPLY + case .numpadParenLeft: GHOSTTY_KEY_NUMPAD_PAREN_LEFT + case .numpadParenRight: GHOSTTY_KEY_NUMPAD_PAREN_RIGHT + case .numpadSubtract: GHOSTTY_KEY_NUMPAD_SUBTRACT + case .numpadSeparator: GHOSTTY_KEY_NUMPAD_SEPARATOR + case .numpadUp: GHOSTTY_KEY_NUMPAD_UP + case .numpadDown: GHOSTTY_KEY_NUMPAD_DOWN + case .numpadRight: GHOSTTY_KEY_NUMPAD_RIGHT + case .numpadLeft: GHOSTTY_KEY_NUMPAD_LEFT + case .numpadBegin: GHOSTTY_KEY_NUMPAD_BEGIN + case .numpadHome: GHOSTTY_KEY_NUMPAD_HOME + case .numpadEnd: GHOSTTY_KEY_NUMPAD_END + case .numpadInsert: GHOSTTY_KEY_NUMPAD_INSERT + case .numpadDelete: GHOSTTY_KEY_NUMPAD_DELETE + case .numpadPageUp: GHOSTTY_KEY_NUMPAD_PAGE_UP + case .numpadPageDown: GHOSTTY_KEY_NUMPAD_PAGE_DOWN + case .escape: GHOSTTY_KEY_ESCAPE + case .f1: GHOSTTY_KEY_F1 + case .f2: GHOSTTY_KEY_F2 + case .f3: GHOSTTY_KEY_F3 + case .f4: GHOSTTY_KEY_F4 + case .f5: GHOSTTY_KEY_F5 + case .f6: GHOSTTY_KEY_F6 + case .f7: GHOSTTY_KEY_F7 + case .f8: GHOSTTY_KEY_F8 + case .f9: GHOSTTY_KEY_F9 + case .f10: GHOSTTY_KEY_F10 + case .f11: GHOSTTY_KEY_F11 + case .f12: GHOSTTY_KEY_F12 + case .f13: GHOSTTY_KEY_F13 + case .f14: GHOSTTY_KEY_F14 + case .f15: GHOSTTY_KEY_F15 + case .f16: GHOSTTY_KEY_F16 + case .f17: GHOSTTY_KEY_F17 + case .f18: GHOSTTY_KEY_F18 + case .f19: GHOSTTY_KEY_F19 + case .f20: GHOSTTY_KEY_F20 + case .f21: GHOSTTY_KEY_F21 + case .f22: GHOSTTY_KEY_F22 + case .f23: GHOSTTY_KEY_F23 + case .f24: GHOSTTY_KEY_F24 + case .f25: GHOSTTY_KEY_F25 + case .fn: GHOSTTY_KEY_FN + case .fnLock: GHOSTTY_KEY_FN_LOCK + case .printScreen: GHOSTTY_KEY_PRINT_SCREEN + case .scrollLock: GHOSTTY_KEY_SCROLL_LOCK + case .pause: GHOSTTY_KEY_PAUSE + case .browserBack: GHOSTTY_KEY_BROWSER_BACK + case .browserFavorites: GHOSTTY_KEY_BROWSER_FAVORITES + case .browserForward: GHOSTTY_KEY_BROWSER_FORWARD + case .browserHome: GHOSTTY_KEY_BROWSER_HOME + case .browserRefresh: GHOSTTY_KEY_BROWSER_REFRESH + case .browserSearch: GHOSTTY_KEY_BROWSER_SEARCH + case .browserStop: GHOSTTY_KEY_BROWSER_STOP + case .eject: GHOSTTY_KEY_EJECT + case .launchApp1: GHOSTTY_KEY_LAUNCH_APP_1 + case .launchApp2: GHOSTTY_KEY_LAUNCH_APP_2 + case .launchMail: GHOSTTY_KEY_LAUNCH_MAIL + case .mediaPlayPause: GHOSTTY_KEY_MEDIA_PLAY_PAUSE + case .mediaSelect: GHOSTTY_KEY_MEDIA_SELECT + case .mediaStop: GHOSTTY_KEY_MEDIA_STOP + case .mediaTrackNext: GHOSTTY_KEY_MEDIA_TRACK_NEXT + case .mediaTrackPrevious: GHOSTTY_KEY_MEDIA_TRACK_PREVIOUS + case .power: GHOSTTY_KEY_POWER + case .sleep: GHOSTTY_KEY_SLEEP + case .audioVolumeDown: GHOSTTY_KEY_AUDIO_VOLUME_DOWN + case .audioVolumeMute: GHOSTTY_KEY_AUDIO_VOLUME_MUTE + case .audioVolumeUp: GHOSTTY_KEY_AUDIO_VOLUME_UP + case .wakeUp: GHOSTTY_KEY_WAKE_UP + case .copy: GHOSTTY_KEY_COPY + case .cut: GHOSTTY_KEY_CUT + case .paste: GHOSTTY_KEY_PASTE + } + } + + /// The case naming `ghosttyKey`; nil for `GHOSTTY_KEY_UNIDENTIFIED` and + /// any value this build of the header does not know. + public init?(ghosttyKey: ghostty_input_key_e) { + guard let key = Self.byGhosttyKey[ghosttyKey.rawValue] else { return nil } + self = key + } + + private static let byGhosttyKey: [UInt32: TerminalKey] = Dictionary( + uniqueKeysWithValues: allCases.map { ($0.ghosttyKey.rawValue, $0) } + ) + + /// Whether libghostty can resolve this key on Apple platforms. Its key + /// lookup takes a macOS virtual keycode, and some keys of the standard + /// have none (numpad extras, media keys, IME mode keys). + public var hasPlatformKeycode: Bool { + TerminalHardwareKeyRouter.appKitKeyCode(for: ghosttyKey) + != TerminalHardwareKeyRouter.unidentifiedAppKitKeyCode + } + + // MARK: - US layout + + /// The characters this key types on a US (ANSI) layout: the unshifted + /// one and, for keys with a shifted variant, the shifted one. Nil for + /// keys that type nothing (Enter, arrows, modifiers) and for the + /// layout-specific international keys. + /// + /// A programmatic press has no keyboard layout to ask, so this table + /// stands in: it gives the press the `text` the key encoder needs to + /// type a character on the legacy path and the unshifted codepoint the + /// kitty protocol reports. + public var usLayoutCharacters: (unshifted: Character, shifted: Character?)? { + switch self { + case .backquote: ("`", "~") + case .backslash: ("\\", "|") + case .bracketLeft: ("[", "{") + case .bracketRight: ("]", "}") + case .comma: (",", "<") + case .digit0: ("0", ")") + case .digit1: ("1", "!") + case .digit2: ("2", "@") + case .digit3: ("3", "#") + case .digit4: ("4", "$") + case .digit5: ("5", "%") + case .digit6: ("6", "^") + case .digit7: ("7", "&") + case .digit8: ("8", "*") + case .digit9: ("9", "(") + case .equal: ("=", "+") + case .a: ("a", "A") + case .b: ("b", "B") + case .c: ("c", "C") + case .d: ("d", "D") + case .e: ("e", "E") + case .f: ("f", "F") + case .g: ("g", "G") + case .h: ("h", "H") + case .i: ("i", "I") + case .j: ("j", "J") + case .k: ("k", "K") + case .l: ("l", "L") + case .m: ("m", "M") + case .n: ("n", "N") + case .o: ("o", "O") + case .p: ("p", "P") + case .q: ("q", "Q") + case .r: ("r", "R") + case .s: ("s", "S") + case .t: ("t", "T") + case .u: ("u", "U") + case .v: ("v", "V") + case .w: ("w", "W") + case .x: ("x", "X") + case .y: ("y", "Y") + case .z: ("z", "Z") + case .minus: ("-", "_") + case .period: (".", ">") + case .quote: ("'", "\"") + case .semicolon: (";", ":") + case .slash: ("/", "?") + case .space: (" ", nil) + case .numpad0: ("0", nil) + case .numpad1: ("1", nil) + case .numpad2: ("2", nil) + case .numpad3: ("3", nil) + case .numpad4: ("4", nil) + case .numpad5: ("5", nil) + case .numpad6: ("6", nil) + case .numpad7: ("7", nil) + case .numpad8: ("8", nil) + case .numpad9: ("9", nil) + case .numpadAdd: ("+", nil) + case .numpadComma: (",", nil) + case .numpadDecimal: (".", nil) + case .numpadDivide: ("/", nil) + case .numpadEqual: ("=", nil) + case .numpadMultiply: ("*", nil) + case .numpadParenLeft: ("(", nil) + case .numpadParenRight: (")", nil) + case .numpadSubtract: ("-", nil) + default: nil + } + } + + /// The US-layout key that types `character`, and whether it needs + /// Shift. Prefers the main block over the numpad, so "5" is ``digit5``. + /// Nil for characters no US key types (control characters, letters + /// outside ASCII). + public static func usLayoutKey( + typing character: Character + ) -> (key: TerminalKey, shifted: Bool)? { + usLayoutKeysByCharacter[character] + } + + private static let usLayoutKeysByCharacter: [Character: (key: TerminalKey, shifted: Bool)] = { + var result: [Character: (key: TerminalKey, shifted: Bool)] = [:] + // Declaration order: the writing-system block precedes the numpad, + // so its keys win the shared digits and operators. + for key in allCases { + guard let characters = key.usLayoutCharacters else { continue } + if result[characters.unshifted] == nil { + result[characters.unshifted] = (key, false) + } + if let shifted = characters.shifted, result[shifted] == nil { + result[shifted] = (key, true) + } + } + return result + }() +} diff --git a/ios/vendor/GhosttyTerminal/Surface/TerminalKeyPress.swift b/ios/vendor/GhosttyTerminal/Surface/TerminalKeyPress.swift new file mode 100644 index 0000000..35a7ff0 --- /dev/null +++ b/ios/vendor/GhosttyTerminal/Surface/TerminalKeyPress.swift @@ -0,0 +1,127 @@ +// +// TerminalKeyPress.swift +// libghostty-spm +// + +import GhosttyKit + +/// One key pressed with a set of modifiers, the way a host presses a key +/// programmatically. +/// +/// libghostty has two ways in: the key path (`ghostty_surface_key`), which +/// the core's key encoder turns into whatever the terminal's mode asks for +/// — legacy bytes, modifyOtherKeys, kitty — and the text path +/// (`ghostty_surface_text`), which is a paste. A pasted `\r` under +/// bracketed paste is text in the shell's edit line, not Enter. Keystrokes +/// therefore go here; only clipboard content belongs in +/// ``TerminalSurface/sendText(_:)``. +public struct TerminalKeyPress: Sendable, Hashable { + public var key: TerminalKey + public var modifiers: TerminalInputModifiers + + public init(_ key: TerminalKey, modifiers: TerminalInputModifiers = []) { + self.key = key + self.modifiers = modifiers + } + + /// The press that types `character` on a US layout, Shift included when + /// the character needs it: `"C"` is `c` with Shift, `"~"` is the + /// backquote key with Shift. Nil for a character no US key types. + public init?(typing character: Character, modifiers: TerminalInputModifiers = []) { + guard let match = TerminalKey.usLayoutKey(typing: character) else { return nil } + key = match.key + self.modifiers = match.shifted ? modifiers.union(.shift) : modifiers + } + + /// The text this press types, from the US layout: the shifted + /// character under Shift, else the unshifted one; nil for a key that + /// types nothing. Omitted under Command, as the hardware paths do — + /// libghostty's macOS build ignores text on a Command chord, its iOS + /// build would type it. + public var text: String? { + guard !modifiers.contains(.super_), !modifiers.contains(.superRight), + let characters = key.usLayoutCharacters + else { return nil } + if modifiers.contains(.shift) || modifiers.contains(.shiftRight), + let shifted = characters.shifted + { + return String(shifted) + } + return String(characters.unshifted) + } + + /// The codepoint the key types with no modifier at all — what the kitty + /// encoder reports, and what the legacy encoder derives Ctrl bytes + /// from. Zero for a key that types nothing. + public var unshiftedCodepoint: UInt32 { + key.usLayoutCharacters?.unshifted.unicodeScalars.first?.value ?? 0 + } + + /// Builds the libghostty event and hands it to `body` while the text + /// buffer is alive. + func withKeyEvent( + action: ghostty_input_action_e, + _ body: (ghostty_input_key_s) -> Result + ) -> Result { + var event = ghostty_input_key_s() + event.action = action + event.keycode = TerminalHardwareKeyRouter.appKitKeyCode(for: key.ghosttyKey) + event.mods = modifiers.ghosttyMods + // Only Shift produced the text (the US table knows no Option + // layer), so only Shift is spent; Control, Alt and Command stay + // visible to the encoder — libghostty strips consumed modifiers + // before deciding on Ctrl bytes and the Alt ESC prefix. + event.consumed_mods = modifiers + .intersection([.shift, .shiftRight]) + .ghosttyMods + event.unshifted_codepoint = unshiftedCodepoint + event.composing = false + + // Only the press carries text; a release types nothing. + guard action == GHOSTTY_ACTION_PRESS || action == GHOSTTY_ACTION_REPEAT, + let text + else { + return body(event) + } + return text.withCString { pointer in + event.text = pointer + return body(event) + } + } +} + +public extension TerminalSurface { + /// Presses and releases a key, as if typed on a hardware keyboard: the + /// event takes the key path, so the terminal's key encoding applies and + /// it is never paste-framed. Returns whether the press was accepted — + /// false with no surface, and for a ``TerminalKey`` that has no macOS + /// keycode for libghostty to resolve (``TerminalKey/hasPlatformKeycode``). + /// + /// The release follows the press so a program on the kitty keyboard + /// protocol with event reporting never sees a key held down; in the + /// legacy encoding a release encodes nothing. + @discardableResult + func sendKey(_ press: TerminalKeyPress) -> Bool { + guard press.key.hasPlatformKeycode else { + TerminalDebugLog.log( + .input, + "surface key ignored: \(press.key) has no platform keycode" + ) + return false + } + let pressed = press.withKeyEvent(action: GHOSTTY_ACTION_PRESS) { event in + sendKeyEvent(event) + } + _ = press.withKeyEvent(action: GHOSTTY_ACTION_RELEASE) { event in + sendKeyEvent(event) + } + return pressed + } + + /// ``sendKey(_:)`` for a key and its modifiers: `sendKey(.enter)`, + /// `sendKey(.c, modifiers: .ctrl)`, `sendKey(.tab, modifiers: .shift)`. + @discardableResult + func sendKey(_ key: TerminalKey, modifiers: TerminalInputModifiers = []) -> Bool { + sendKey(TerminalKeyPress(key, modifiers: modifiers)) + } +} diff --git a/ios/vendor/GhosttyTerminal/Surface/TerminalSelectionAnchor.swift b/ios/vendor/GhosttyTerminal/Surface/TerminalSelectionAnchor.swift index d37430f..8cc4fa8 100644 --- a/ios/vendor/GhosttyTerminal/Surface/TerminalSelectionAnchor.swift +++ b/ios/vendor/GhosttyTerminal/Surface/TerminalSelectionAnchor.swift @@ -6,24 +6,23 @@ import Foundation enum TerminalSelectionAnchor { - /// Map a quicklook word + its top-left host-point coordinate back into - /// an `NSRange` inside the viewport text snapshot, suitable for direct - /// assignment to `UITextView.selectedRange`. + /// Map a quicklook word back into an `NSRange` inside the viewport text + /// snapshot, suitable for direct assignment to + /// `UITextView.selectedRange`. /// - /// Strategy: derive `row` from `pointY / cellHeightPoints`; collect every - /// literal occurrence of `word` in that row; then use - /// `pointX / cellWidthPoints` as the expected UTF-16 column and pick the - /// match whose `location` is closest. This resolves substring ambiguity - /// (e.g. `catalog cat` long-pressed at the end picks the standalone - /// `cat`, not the prefix of `catalog`) without depending on word - /// boundaries — which would fail for tokens like `/foo` whose first - /// character is a non-word character. + /// `offsetStart` is ghostty's `ghostty_text_s.offset_start`: the word's + /// first cell as a linear index into the viewport grid + /// (`row * columns + column`, Surface.zig `dumpTextLocked`), and `columns` + /// is the grid width from `TerminalSurface.size()`. Both are cell counts, + /// so window padding, the text baseline and the display scale never enter. /// - /// Units: `pointX/Y` and `cellWidth/HeightPoints` must all be host - /// points (not surface pixels). Callers are responsible for converting - /// `cellPixels / displayScale → points` before invoking. Ghostty's - /// embedded API returns `tl_px_x/y` in host points, so passing them - /// through unchanged is correct. + /// Strategy: derive `row` and the expected UTF-16 column from the offset; + /// collect every literal occurrence of `word` in that row and pick the + /// match whose `location` is closest to the column. This resolves + /// substring ambiguity (e.g. `catalog cat` long-pressed at the end picks + /// the standalone `cat`, not the prefix of `catalog`) without depending + /// on word boundaries — which would fail for tokens like `/foo` whose + /// first character is a non-word character. /// /// Known limitation: when the target row contains CJK full-width /// characters before the match, cell columns and UTF-16 offsets diverge @@ -32,26 +31,25 @@ enum TerminalSelectionAnchor { static func resolveRange( in text: String, word: String, - pointX: Double, - pointY: Double, - cellWidthPoints: Double, - cellHeightPoints: Double + offsetStart: UInt32, + columns: UInt32 ) -> NSRange? { - guard !word.isEmpty else { return nil } - guard pointX.isFinite, pointY.isFinite, - cellWidthPoints.isFinite, cellHeightPoints.isFinite - else { return nil } - guard cellWidthPoints > 0, cellHeightPoints > 0 else { return nil } - guard pointX >= 0, pointY >= 0 else { return nil } - - let rowDouble = pointY / cellHeightPoints - let columnDouble = pointX / cellWidthPoints - guard rowDouble.isFinite, columnDouble.isFinite, - rowDouble < Double(Int.max), columnDouble < Double(Int.max) - else { return nil } + guard columns > 0 else { return nil } + return resolveRange( + in: text, + word: word, + row: Int(offsetStart / columns), + expectedColumnUTF16: Int(offsetStart % columns) + ) + } - let row = Int(rowDouble) - let expectedColumnUTF16 = Int(columnDouble) + private static func resolveRange( + in text: String, + word: String, + row: Int, + expectedColumnUTF16: Int + ) -> NSRange? { + guard !word.isEmpty else { return nil } let nsText = text as NSString let lines = nsText.components(separatedBy: "\n") diff --git a/ios/vendor/GhosttyTerminal/Surface/TerminalSurface.swift b/ios/vendor/GhosttyTerminal/Surface/TerminalSurface.swift index 076d21f..c1f5759 100644 --- a/ios/vendor/GhosttyTerminal/Surface/TerminalSurface.swift +++ b/ios/vendor/GhosttyTerminal/Surface/TerminalSurface.swift @@ -59,7 +59,16 @@ public final class TerminalSurface { } @discardableResult - func sendMouseButton( + public func sendMouseButton( + state: ghostty_input_mouse_state_e, + button: ghostty_input_mouse_button_e, + modifiers: TerminalInputModifiers = [] + ) -> Bool { + sendMouseButton(state: state, button: button, mods: modifiers.ghosttyMods) + } + + @discardableResult + public func sendMouseButton( state: ghostty_input_mouse_state_e, button: ghostty_input_mouse_button_e, mods: ghostty_input_mods_e @@ -76,7 +85,15 @@ public final class TerminalSurface { return result } - func sendMousePos(x: Double, y: Double, mods: ghostty_input_mods_e) { + public func sendMousePos( + x: Double, + y: Double, + modifiers: TerminalInputModifiers = [] + ) { + sendMousePos(x: x, y: y, mods: modifiers.ghosttyMods) + } + + public func sendMousePos(x: Double, y: Double, mods: ghostty_input_mods_e) { guard let s = surface else { TerminalDebugLog.log(.input, "surface mouse position ignored: missing surface") return @@ -88,6 +105,14 @@ public final class TerminalSurface { ghostty_surface_mouse_pos(s, x, y, mods) } + public func sendMouseScroll( + x: Double, + y: Double, + mods: TerminalScrollModifiers = TerminalScrollModifiers(precision: true) + ) { + sendMouseScroll(x: x, y: y, mods: mods.rawValue) + } + func sendMouseScroll(x: Double, y: Double, mods: ghostty_input_scroll_mods_t) { guard let s = surface else { TerminalDebugLog.log(.input, "surface scroll ignored: missing surface") @@ -100,6 +125,15 @@ public final class TerminalSurface { ghostty_surface_mouse_scroll(s, x, y, mods) } + /// Whether the application currently owns the mouse (DEC 1000/1002/1003). + /// Host UI (copy menu, context menu) must not steal a click while this + /// is true. Ghostty still owns reporting vs local selection for events + /// that reach the surface. + public var isMouseCaptured: Bool { + guard let s = surface else { return false } + return ghostty_surface_mouse_captured(s) + } + func preedit(_ text: String) { guard let s = surface else { TerminalDebugLog.log(.ime, "surface preedit ignored: missing surface") @@ -224,7 +258,7 @@ public final class TerminalSurface { let offsetLength: UInt32 } - func hasSelection() -> Bool { + public func hasSelection() -> Bool { guard let s = surface else { TerminalDebugLog.log(.input, "surface selection query ignored: missing surface") return false @@ -234,7 +268,7 @@ public final class TerminalSurface { return result } - func readSelection() -> String? { + public func readSelection() -> String? { readSelectionResult()?.text } @@ -290,26 +324,24 @@ public final class TerminalSurface { return (x, y, w, h) } - // MARK: - Mouse Capture - - var isMouseCaptured: Bool { - guard let s = surface else { return false } - return ghostty_surface_mouse_captured(s) - } - // MARK: - Quicklook Word (Apple-only) #if canImport(UIKit) || canImport(AppKit) struct QuicklookWordResult { let word: String + /// Linear cell index of the word's first cell in the viewport grid + /// (`row * columns + column`); the grid position to use, see + /// `TerminalSelectionAnchor`. let offsetStart: UInt32 let offsetLength: UInt32 // tl_px_x / tl_px_y are reported in host points (view coordinates), // not surface pixels. Ghostty's embedded API receives mouse_pos in // points and stores the cursor position * contentScale internally, // then divides by contentScale when reporting selection coordinates - // back. Callers must convert cell pixel dimensions to points before - // dividing. + // back. Despite the name, tl_px_y is the row's text baseline plus + // the top window padding, not the cell top, and tl_px_x includes + // the left padding — dividing them by the cell size does not give + // the grid position. let pointX: Double let pointY: Double } @@ -373,6 +405,11 @@ public final class TerminalSurface { /// user runs a program in the pty this is that program's pid, so hosts can /// correlate a surface with an external process list. Ghostty returns 0 /// when the surface has no process yet — surfaced here as nil. + /// + /// Always nil on the pinned Ghostty 1.3.1, for every backend: that release + /// predates upstream's process-info API, and the shipped + /// `ghostty_surface_foreground_pid` is a stub from + /// `Patches/ghostty/0002-host-managed-io.patch` that reports 0. var foregroundPid: pid_t? { guard let s = surface else { return nil } let pid = ghostty_surface_foreground_pid(s) @@ -381,6 +418,10 @@ public final class TerminalSurface { /// Name of the pty's controlling tty (e.g. `/dev/ttys004`), or nil when the /// surface has no process yet. Useful as a cross-check for ``foregroundPid``. + /// + /// Always nil on the pinned Ghostty 1.3.1, for every backend: the shipped + /// `ghostty_surface_tty_name` is a stub that returns the empty string, for + /// the same reason ``foregroundPid`` is nil there. var ttyName: String? { guard let s = surface else { return nil } let str = ghostty_surface_tty_name(s) diff --git a/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceCoordinator.swift b/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceCoordinator.swift index 1fb2cce..b94e806 100644 --- a/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceCoordinator.swift +++ b/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceCoordinator.swift @@ -35,6 +35,12 @@ final class TerminalSurfaceCoordinator { } var surface: TerminalSurface? + /// The in-memory session `surface` was handed to at build time. Teardown + /// must clear that session, not whichever one `configuration` names by + /// then: a rebuild runs from `configuration`'s didSet, after the property + /// already holds the new backend, and a swap to another session or to + /// `.exec` would otherwise free a surface the old session still uses. + private var surfaceSession: InMemoryTerminalSession? let bridge = TerminalCallbackBridge() // MARK: - Platform Hooks @@ -45,6 +51,7 @@ final class TerminalSurfaceCoordinator { var platformSetup: ((inout ghostty_surface_config_s) -> Void)? var onMetricsUpdate: (() -> Void)? var onCellSizeDidChange: (() -> Void)? + var onMouseShape: ((ghostty_action_mouse_shape_e) -> Void)? /// Called after every display-link render (`tick`). /// @@ -63,12 +70,45 @@ final class TerminalSurfaceCoordinator { var onPostRender: (() -> Void)? private var lastMetrics: TerminalViewportMetrics? + + /// The view size, in points, the surface was last sized to. While a + /// resize throttle window is open this trails the live bounds: the + /// surface's IOSurface is still the old size, so a platform layer that + /// stretches to the new bounds shows the old pixels scaled — and the + /// engine, deriving `contentsScale` from old pixels over new points, + /// fights the host's correction on every tick. A UIKit host keeps its + /// sublayer at *this* size until the trailing sync lands, so the gap + /// shows background instead of a stretched frame. `nil` until the first + /// sync and after teardown. + private(set) var syncedViewSize: (width: Double, height: Double)? + private var isDisplayVisible = true + /// The last visibility the SwiftUI host declared through + /// `TerminalViewState.isSurfaceVisible`. The representable forwards only + /// changes to this value, so an imperative `setSurfaceVisible` call made + /// between SwiftUI updates is not silently reverted by every refresh + /// re-stamping the declarative default. + var hostDeclaredDisplayVisible: Bool? private var isApplicationActive = true - private var isSurfaceFocused = false private var pendingImmediateTick = true private var lastTickTimestamp: TimeInterval = 0 - private var tickScheduled = false + + /// Held only while frames are owed. The engine's wakeups arrive at PTY + /// speed, not display speed; rendering straight from them draws far more + /// often than the screen can show and starves input handling under heavy + /// output. The link paces draws to vsync instead, and is released after + /// a stretch of idle frames so a quiet terminal costs no per-frame + /// wakeups at all. All instances share one platform link. + /// + /// The range floors at 60: letting the system drop to 30 while output + /// streams read as flicker on a scrolling screen. ProMotion displays may + /// go to 120. + private var displayLink: DisplayLink? + private var idleFrameCount = 0 + private static let displayLinkFrameRateRange = DisplayLinkFrameRateRange( + minimum: 60, maximum: 120, preferred: 120 + ) + private static let idleFramesBeforeRelease = 30 init() { bridge.onCellSizeChange = { [weak self] width, height in @@ -77,24 +117,55 @@ final class TerminalSurfaceCoordinator { bridge.onRenderRequest = { [weak self] in self?.requestImmediateTick() } + bridge.onMouseShape = { [weak self] shape in + self?.onMouseShape?(shape) + } } func requestImmediateTick() { pendingImmediateTick = true - scheduleTickIfNeeded() + ensureDisplayLink() } func startDisplayLink() { - scheduleTickIfNeeded() + ensureDisplayLink() } func stopDisplayLink() { - tickScheduled = false + releaseDisplayLink() } // MARK: - Surface Lifecycle func rebuildIfReady(removingBridgeFrom previousController: TerminalController? = nil) { + // A pane that is merely hidden reports a zero size. Tearing the surface + // down here and then bailing out at the size guard below would destroy + // ghostty's grid and scrollback for a condition that is temporary: when + // the pane comes back there is nothing left to show, and only the app + // redrawing can refill it. Keep the surface — the caller re-runs this + // once the view has a usable size again. + // + // This is the same intent as the reattach guard in viewDidMoveToWindow + // ("rebuilding on every reattach discards Ghostty's scrollback/state"), + // which cannot help while the teardown happens before the checks. + if surface != nil, previousController == nil, !hasValidViewSize { + // The rebuild is owed, not cancelled. Configuration options are + // consumed only by createSurface, and no caller re-runs this once + // the view regains a size (fitToSize, viewDidMoveToWindow and the + // UIKit twin all take the surface != nil branch and merely sync + // metrics). Dropping the request outright would leave a hidden + // pane running its old configuration indefinitely. + pendingRebuild = true + let size = viewSize() + TerminalDebugLog.log( + .lifecycle, + "surface kept: view size temporarily invalid \(String(format: "%.2f", size.width))x\(String(format: "%.2f", size.height))" + ) + + return + } + pendingRebuild = false + tearDownSurface(removingBridgeFrom: previousController ?? controller) guard let controller else { TerminalDebugLog.log(.lifecycle, "surface rebuild skipped: missing controller") @@ -134,13 +205,24 @@ final class TerminalSurfaceCoordinator { bridge.rawSurface = rawSurface let newSurface = TerminalSurface(rawSurface) surface = newSurface + surfaceSession = configuration.inMemorySession newSurface.setOcclusion(effectiveSurfaceVisible) - controller.shouldProcessWakeup = { [weak self] in - self?.canRenderFrame == true - } - controller.onWakeup = { [weak self] in - self?.requestImmediateTick() - } + // Wakeups must keep draining while the surface is merely occluded: + // the app mailbox (titles, pwd, bell, child-exit) only empties in + // ghostty_app_tick, and a full mailbox blocks the session's write + // thread on its next push. Only a detached surface or a + // backgrounded app suspends ticks — visibility gates rendering + // alone (canRenderFrame). + controller.addWakeupObserver( + ObjectIdentifier(self), + shouldProcess: { [weak self] in + guard let self else { return false } + return isApplicationActive && isAttached() + }, + onWakeup: { [weak self] in + self?.requestImmediateTick() + } + ) TerminalDebugLog.log(.lifecycle, "surface rebuild succeeded") (delegate as? any TerminalSurfaceLifecycleDelegate)? .terminalDidAttachSurface(newSurface) @@ -150,10 +232,109 @@ final class TerminalSurfaceCoordinator { // MARK: - Metrics + // Ghostty's IO thread coalesces resize messages with a hardcoded 25ms + // trailing-only window (Thread.zig). For an alt-screen TUI that fully + // repaints on every winsize (Claude Code), a live divider drag posts new + // sizes faster than that window resolves — the grid reflow runs + // permanently behind the layer bounds and the renderer composites the + // stale grid into the new frame: the mid-drag collapse. Bounding the + // stream (leading edge for responsiveness, trailing edge so the final + // size always lands) hands the engine a signal it can settle on. + // + // The right window is CONTENT-dependent, so it is a per-surface value + // the host sets (`TerminalSurfaceOptions.resizeThrottleMilliseconds`, or + // the platform setter for a live change): a primary-screen transcript + // that never re-emits its scrollback (codex-style) renders best fully + // unthrottled — large throttled jumps read as blinking — while the + // alt-screen full-repaint agents need ~96ms. 0 disables. The env var + // GHOSTTY_SURFACE_RESIZE_THROTTLE_MS, when set, overrides every surface + // for whole-process A/B runs. + private static let resizeThrottleOverride: TimeInterval? = { + guard + let raw = ProcessInfo.processInfo + .environment["GHOSTTY_SURFACE_RESIZE_THROTTLE_MS"], + let ms = Double(raw), ms >= 0 + else { return nil } + return ms / 1000 + }() + + /// Set directly by a platform view; otherwise sourced from + /// `configuration.resizeThrottleMilliseconds`. Kept as an override so the + /// AppKit setter can adjust a live surface without rebuilding it. + var resizeThrottleInterval: TimeInterval? + + private var effectiveResizeThrottle: TimeInterval { + if let override = Self.resizeThrottleOverride { return override } + if let interval = resizeThrottleInterval { return interval } + return max(0, configuration.resizeThrottleMilliseconds) / 1000 + } + + private var resizeThrottleArmed = false + private var resizeThrottleTrailing = false + /// Invalidates in-flight throttle timers across a teardown. A timer + /// armed for the old surface must not size — or re-arm against — the + /// surface that replaced it. + private var resizeThrottleGeneration = 0 + /// A rebuild deferred by the zero-size guard above, replayed by + /// `synchronizeMetrics` as soon as the view has a usable size again. + private var pendingRebuild = false + func synchronizeMetrics() { + // Redeem a rebuild the zero-size guard deferred. Every caller that + // could restore a usable size lands here, so this is the one place + // that reliably observes the transition. + if pendingRebuild, hasValidViewSize { + pendingRebuild = false + rebuildIfReady() + return + } + + guard effectiveResizeThrottle > 0 else { + performMetricsSync() + return + } + + guard !resizeThrottleArmed else { + // Newest wins: the trailing fire re-reads the live view size, + // so nothing needs to be captured here. + resizeThrottleTrailing = true + return + } + + // Arm only behind a size the surface actually received. The + // creation-time cell_size callback lands here while `surface` is still + // nil; arming then would make the new surface's own first sync wait + // out a full window as a trailing edge. + guard performMetricsSync() else { return } + armResizeThrottle() + } + + private func armResizeThrottle() { + resizeThrottleArmed = true + let generation = resizeThrottleGeneration + DispatchQueue.main.asyncAfter( + deadline: .now() + effectiveResizeThrottle + ) { [weak self] in + guard let self else { return } + // A teardown bumped the generation: this timer belongs to a + // surface that no longer exists. Returning without touching + // `resizeThrottleArmed` leaves the current surface's own state + // alone. + guard generation == resizeThrottleGeneration else { return } + resizeThrottleArmed = false + guard resizeThrottleTrailing else { return } + resizeThrottleTrailing = false + guard performMetricsSync() else { return } + armResizeThrottle() + } + } + + /// Returns whether a size reached the surface. + @discardableResult + private func performMetricsSync() -> Bool { guard let surface else { TerminalDebugLog.log(.metrics, "synchronizeMetrics skipped: missing surface") - return + return false } let scale = scaleFactor() @@ -163,7 +344,7 @@ final class TerminalSurfaceCoordinator { .metrics, "synchronizeMetrics skipped: invalid view size=\(String(format: "%.2f", size.width))x\(String(format: "%.2f", size.height))" ) - return + return false } let pixelWidth = UInt32((size.width * scale).rounded(.down)) @@ -173,7 +354,7 @@ final class TerminalSurfaceCoordinator { .metrics, "synchronizeMetrics skipped: invalid pixel size=\(pixelWidth)x\(pixelHeight)" ) - return + return false } TerminalDebugLog.log( @@ -183,13 +364,14 @@ final class TerminalSurfaceCoordinator { surface.setContentScale(x: scale, y: scale) surface.setSize(width: pixelWidth, height: pixelHeight) + syncedViewSize = size guard let surfaceSize = surface.size(), surfaceSize.columns > 0, surfaceSize.rows > 0 else { TerminalDebugLog.log(.metrics, "sync missing grid metrics after resize") onMetricsUpdate?() - return + return true } let metrics = TerminalViewportMetrics(surfaceSize: surfaceSize, scale: scale) @@ -199,12 +381,28 @@ final class TerminalSurfaceCoordinator { "sync unchanged \(metrics.debugSummary)" ) onMetricsUpdate?() - return + return true } lastMetrics = metrics TerminalDebugLog.log(.metrics, "sync updated \(metrics.debugSummary)") - configuration.inMemorySession?.updateViewport(surfaceSize) + // Deliberately no host resize dispatch here. This runs on the AppKit + // thread right after setSize(), i.e. before the engine's IO thread has + // run the resize operation at all — the host would learn the new size + // from a thread that has not yet reflowed anything. + // + // Worse, it poisons the correctly-phased notification: the IO thread's + // `receiveResizeCallback` (invoked from HostManaged.resize, immediately + // before `terminal.resize`, both serial within one IO-thread resize + // operation) then arrives carrying a size the session already recorded, + // and is discarded as unchanged. A relative-cursor TUI therefore + // repainted against a winsize that led the grid, and its clamped CUD + // merged the footer. + // + // Leaving `receiveResizeCallback` as the sole PTY-resize source matches + // stock Ghostty, where pty.setSize runs only inside Termio.resize. + // Local UI metrics still flow via the delegate below and + // onMetricsUpdate. if let delegate = delegate as? any TerminalSurfaceGridResizeDelegate { delegate.terminalDidResize(surfaceSize) } else if let delegate = delegate as? any TerminalSurfaceResizeDelegate { @@ -214,6 +412,7 @@ final class TerminalSurfaceCoordinator { ) } onMetricsUpdate?() + return true } func fitToSize() { @@ -245,6 +444,11 @@ final class TerminalSurfaceCoordinator { func setApplicationActive(_ active: Bool) { guard isApplicationActive != active else { + // Same state, but not necessarily the same surface: one built + // between two syncs still wears the occlusion stamped at its + // birth. Re-stamp — a surface born occluded that never hears + // otherwise skips every draw and shows as a blank pane. + surface?.setOcclusion(effectiveSurfaceVisible) if active { renderImmediately() } else { @@ -267,9 +471,18 @@ final class TerminalSurfaceCoordinator { // MARK: - Frame Rendering func tick(context: DisplayLinkCallbackContext) { + guard canRenderFrame else { + releaseDisplayLink() + return + } guard shouldRenderFrame(at: context.timestamp) else { + idleFrameCount += 1 + if idleFrameCount >= Self.idleFramesBeforeRelease { + releaseDisplayLink() + } return } + idleFrameCount = 0 pendingImmediateTick = false lastTickTimestamp = context.timestamp TerminalDebugLog.log(.render, "tick") @@ -282,7 +495,6 @@ final class TerminalSurfaceCoordinator { // MARK: - Focus func setFocus(_ focused: Bool) { - isSurfaceFocused = focused requestImmediateTick() TerminalDebugLog.log(.lifecycle, "focus=\(focused)") surface?.setFocus(focused) @@ -290,6 +502,20 @@ final class TerminalSurfaceCoordinator { .terminalDidChangeFocus(focused) } + #if DEBUG + // Test access to the host-managed resize state. Read-only apart from + // `pendingRebuild`, which tests set to drive the redemption path + // without needing a real ghostty surface. + var testHooks_pendingRebuild: Bool { + get { pendingRebuild } + set { pendingRebuild = newValue } + } + + var testHooks_throttleArmed: Bool { resizeThrottleArmed } + var testHooks_throttleTrailing: Bool { resizeThrottleTrailing } + var testHooks_throttleGeneration: Int { resizeThrottleGeneration } + #endif + // MARK: - Cleanup func freeSurface() { @@ -310,18 +536,31 @@ final class TerminalSurfaceCoordinator { private func tearDownSurface(removingBridgeFrom controller: TerminalController?) { TerminalDebugLog.log(.lifecycle, "tear down surface") - tickScheduled = false - if let session = configuration.inMemorySession { - session.clearSurface(ifMatches: surface?.rawValue) - } - controller?.onWakeup = nil - controller?.shouldProcessWakeup = nil + releaseDisplayLink() + surfaceSession?.clearSurface(ifMatches: surface?.rawValue) + surfaceSession = nil + controller?.removeWakeupObserver(ObjectIdentifier(self)) + // Must run before rawSurface is cleared: a clipboard-read + // confirmation still awaiting the host's answer must resolve to a + // deny before the surface it names goes away, or the requesting + // program hangs forever. This is the wrapper-level backstop behind + // "exactly one of complete/deny fires for every request" — it + // fires regardless of whether the host's own confirmation UI ever + // dismisses. + bridge.denyAllPendingClipboardRequests() bridge.rawSurface = nil let hadSurface = surface != nil surface?.setFocus(false) surface?.free() surface = nil lastMetrics = nil + syncedViewSize = nil + // Retire any armed timer with the surface it was armed for, and + // clear the gate so the replacement surface sizes immediately + // instead of being suppressed by the old surface's armed flag. + resizeThrottleGeneration &+= 1 + resizeThrottleArmed = false + resizeThrottleTrailing = false pendingImmediateTick = true lastTickTimestamp = 0 controller?.remove(bridge) @@ -348,28 +587,24 @@ final class TerminalSurfaceCoordinator { return pendingImmediateTick || lastTickTimestamp == 0 } - private func scheduleTickIfNeeded() { + private func ensureDisplayLink() { guard canRenderFrame else { - tickScheduled = false + releaseDisplayLink() return } - guard !tickScheduled else { - return - } - tickScheduled = true - TerminalDebugLog.log(.lifecycle, "tick scheduled") - DispatchQueue.main.async { [weak self] in - guard let self else { return } - tickScheduled = false - let timestamp = Self.monotonicTimestamp() - tick( - context: .init( - duration: 0, - timestamp: timestamp, - targetTimestamp: timestamp - ) - ) - } + idleFrameCount = 0 + guard displayLink == nil else { return } + let link = DisplayLink(preferredFrameRateRange: Self.displayLinkFrameRateRange) + link.delegatingObject(self) + displayLink = link + TerminalDebugLog.log(.lifecycle, "display link acquired") + } + + private func releaseDisplayLink() { + guard displayLink != nil else { return } + displayLink = nil + idleFrameCount = 0 + TerminalDebugLog.log(.lifecycle, "display link released") } private static func monotonicTimestamp() -> TimeInterval { @@ -391,12 +626,11 @@ final class TerminalSurfaceCoordinator { private func renderImmediately() { guard canRenderFrame else { - tickScheduled = false + releaseDisplayLink() return } pendingImmediateTick = true - tickScheduled = false let timestamp = Self.monotonicTimestamp() tick( context: .init( @@ -405,5 +639,16 @@ final class TerminalSurfaceCoordinator { targetTimestamp: timestamp ) ) + ensureDisplayLink() + } +} + +extension TerminalSurfaceCoordinator: DisplayLinkDelegate { + // The shared CADisplayLink dispatches synchronously on the main run + // loop; the protocol just cannot say so. + nonisolated func synchronization(context: DisplayLinkCallbackContext) { + MainActor.assumeIsolated { + tick(context: context) + } } } diff --git a/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceOptions.swift b/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceOptions.swift index a451c2f..593b278 100644 --- a/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceOptions.swift +++ b/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceOptions.swift @@ -17,26 +17,62 @@ public struct TerminalSurfaceOptions: Sendable { /// embedding hosts tag a surface (e.g. `MYAPP_PANE=`) and correlate /// externally observed processes back to it. public var envVars: [String: String] + /// Overrides the command executed in the child process spawned for this + /// surface (exec backend), taking the place of the user's default shell. + /// Passed through `ghostty_surface_config_s.command`. When `nil`, the + /// value already established by `ghostty_surface_config_new()` (e.g. the + /// app-level config's command, if any) is left untouched. + public var command: String? + /// Controls whether the surface stays open after `command` exits instead + /// of closing immediately. Passed through + /// `ghostty_surface_config_s.wait_after_command`. When `nil`, the value + /// already established by `ghostty_surface_config_new()` is left + /// untouched. + public var waitAfterCommand: Bool? public var context: TerminalSurfaceContext + /// Coalescing window for host-driven resizes, in milliseconds. `0` + /// (the default) sizes the surface synchronously on every metrics change, + /// which is the behaviour with no coalescing at all. + /// + /// Set this when the content re-renders on every resize: a live drag posts + /// sizes faster than a full-repaint TUI can settle, so the grid reflow + /// runs permanently behind the layer bounds. Bounding the stream — leading + /// edge for responsiveness, trailing edge so the final size always lands — + /// gives such a client something to settle on. Content that redraws + /// incrementally is better off at `0`; coalesced jumps read as blinking. + public var resizeThrottleMilliseconds: Double + public init( backend: TerminalSessionBackend = .exec, fontSize: Float? = nil, workingDirectory: String? = nil, envVars: [String: String] = [:], - context: TerminalSurfaceContext = .window + command: String? = nil, + waitAfterCommand: Bool? = nil, + context: TerminalSurfaceContext = .window, + resizeThrottleMilliseconds: Double = 0 ) { self.backend = backend self.fontSize = fontSize self.workingDirectory = workingDirectory self.envVars = envVars + self.command = command + self.waitAfterCommand = waitAfterCommand self.context = context + self.resizeThrottleMilliseconds = max(0, resizeThrottleMilliseconds) } + // `resizeThrottleMilliseconds` is deliberately absent: it is a delivery + // policy, not part of the surface's identity. Including it would tear down + // and rebuild a live surface — discarding its grid and scrollback — for a + // change that only affects how often the existing surface is resized. func isEquivalent(to other: TerminalSurfaceOptions) -> Bool { fontSize == other.fontSize && workingDirectory == other.workingDirectory && envVars == other.envVars + && command == other.command + && waitAfterCommand == other.waitAfterCommand && context == other.context && backend.isEquivalent(to: other.backend) } diff --git a/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceView.swift b/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceView.swift index 3fd341f..61e8068 100644 --- a/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceView.swift +++ b/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceView.swift @@ -31,6 +31,7 @@ public struct TerminalSurfaceView: View { context: context, controller: context.controller, configuration: context.configuration, + isSurfaceVisible: context.isSurfaceVisible, focusBinding: focusBinding ) .background(.clear) diff --git a/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceViewDelegate.swift b/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceViewDelegate.swift index 7a7b11d..70e652e 100644 --- a/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceViewDelegate.swift +++ b/ios/vendor/GhosttyTerminal/Surface/TerminalSurfaceViewDelegate.swift @@ -42,6 +42,69 @@ public protocol TerminalSurfaceCloseDelegate: TerminalSurfaceViewDelegate { func terminalDidClose(processAlive: Bool) } +/// The operation that caused Ghostty to request a clipboard decision. +public enum TerminalClipboardRequestKind: Sendable { + case paste + case osc52Read + case osc52Write + + init?(_ rawValue: ghostty_clipboard_request_e) { + switch rawValue { + case GHOSTTY_CLIPBOARD_REQUEST_PASTE: + self = .paste + case GHOSTTY_CLIPBOARD_REQUEST_OSC_52_READ: + self = .osc52Read + case GHOSTTY_CLIPBOARD_REQUEST_OSC_52_WRITE: + self = .osc52Write + default: + return nil + } + } +} + +/// A one-shot host decision for an unsafe or otherwise protected clipboard +/// request. Hosts must call ``respond(allow:)`` exactly once. Dropping an +/// unanswered request cancels it. +@MainActor +public final class TerminalClipboardConfirmationRequest { + public let contents: String + public let kind: TerminalClipboardRequestKind + + private var completion: ((Bool) -> Void)? + + init( + contents: String, + kind: TerminalClipboardRequestKind, + completion: @escaping (Bool) -> Void + ) { + self.contents = contents + self.kind = kind + self.completion = completion + } + + public func respond(allow: Bool) { + guard let completion else { return } + self.completion = nil + completion(allow) + } + + deinit { + MainActor.assumeIsolated { + completion?(false) + } + } +} + +/// Lets an embedding host own confirmation UI for unsafe paste and protected +/// OSC 52 operations. If the delegate does not implement this protocol, the +/// request is denied. +@MainActor +public protocol TerminalSurfaceClipboardConfirmationDelegate: TerminalSurfaceViewDelegate { + func terminalDidRequestClipboardConfirmation( + _ request: TerminalClipboardConfirmationRequest + ) +} + // MARK: - Extended action delegates /// State of an OSC 9;4 / DECSET progress report. @@ -111,12 +174,62 @@ public protocol TerminalSurfaceHoverLinkDelegate: TerminalSurfaceViewDelegate { func terminalDidUpdateHoverLink(_ url: String?) } +/// Ghostty mouse cursor shape (OSC 22 / application request). +public enum TerminalMouseShape: Sendable, Equatable { + case `default` + case pointer + case text + case notAllowed + case other + + init(_ raw: ghostty_action_mouse_shape_e) { + switch raw { + case GHOSTTY_MOUSE_SHAPE_DEFAULT: + self = .default + case GHOSTTY_MOUSE_SHAPE_POINTER: + self = .pointer + case GHOSTTY_MOUSE_SHAPE_TEXT, GHOSTTY_MOUSE_SHAPE_VERTICAL_TEXT, GHOSTTY_MOUSE_SHAPE_CELL: + self = .text + case GHOSTTY_MOUSE_SHAPE_NOT_ALLOWED, GHOSTTY_MOUSE_SHAPE_NO_DROP: + self = .notAllowed + default: + self = .other + } + } +} + +@MainActor +public protocol TerminalSurfaceMouseShapeDelegate: TerminalSurfaceViewDelegate { + func terminalDidChangeMouseShape(_ shape: TerminalMouseShape) +} + /// OSC 7 working-directory update. @MainActor public protocol TerminalSurfacePwdDelegate: TerminalSurfaceViewDelegate { func terminalDidChangeWorkingDirectory(_ path: String) } +/// Scrollbar geometry reported by the terminal, in rows: `offset` rows are +/// scrolled off above the viewport, `len` rows are visible, out of `total` +/// rows of content (scrollback + screen). +public struct TerminalScrollbar: Equatable, Sendable { + public let total: UInt64 + public let offset: UInt64 + public let len: UInt64 + + public init(total: UInt64, offset: UInt64, len: UInt64) { + self.total = total + self.offset = offset + self.len = len + } +} + +/// The scrollbar geometry changed (the viewport scrolled or the content grew). +@MainActor +public protocol TerminalSurfaceScrollbarDelegate: TerminalSurfaceViewDelegate { + func terminalDidUpdateScrollbar(_ scrollbar: TerminalScrollbar) +} + /// User long-pressed to request a selection-page presentation. public struct TerminalTextSelectionRequest: Sendable { /// Viewport text snapshot. Lines separated by `\n`. @@ -145,3 +258,18 @@ public protocol TerminalSurfaceLifecycleDelegate: TerminalSurfaceViewDelegate { func terminalDidAttachSurface(_ surface: TerminalSurface) func terminalDidDetachSurface() } + +// MARK: - Clipboard content + +/// One MIME-typed clipboard representation. Binary-safe: `data` may contain +/// non-UTF8 bytes and is never implicitly text, even for a `mime` that looks +/// text-like. +public struct TerminalClipboardContent: Sendable { + public let mime: String + public let data: Data + + public init(mime: String, data: Data) { + self.mime = mime + self.data = data + } +} diff --git a/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable.swift b/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable.swift index a33ac89..fa42b7b 100644 --- a/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable.swift +++ b/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable.swift @@ -17,6 +17,12 @@ struct TerminalViewRepresentable { let context: TerminalViewState let controller: TerminalController let configuration: TerminalSurfaceOptions + /// A stored input, not read off `context` in the update pass: SwiftUI + /// runs `updateNSView`/`updateUIView` only when the representable's own + /// properties differ from the previous value. A flag read through the + /// class reference is invisible to that comparison, and the update pass + /// was skipped even for the visible surface. + let isSurfaceVisible: Bool let focusBinding: TerminalFocusBinding? func configureView(_ view: TerminalView, initial: Bool) { @@ -24,15 +30,37 @@ struct TerminalViewRepresentable { view.delegate = context } + if context.attachedView !== view { + context.attachedView = view + } + if let currentController = view.controller, currentController === controller { // Keep the current surface. } else { view.controller = controller } - if !view.configuration.isEquivalent(to: configuration) { - view.configuration = configuration + // Unconditional: the coordinator's didSet gates rebuilds on + // isEquivalent, which ignores resizeThrottleMilliseconds on purpose. + view.configuration = configuration + + // Forward only changes: stamping unconditionally would revert an + // imperative `setSurfaceVisible` call on every SwiftUI update and + // pay a per-update C call for nothing. + if view.core.hostDeclaredDisplayVisible != isSurfaceVisible { + view.core.hostDeclaredDisplayVisible = isSurfaceVisible + view.setSurfaceVisible(isSurfaceVisible) } + + #if canImport(UIKit) + #if !targetEnvironment(macCatalyst) + let accessoryItems = context.inputAccessoryItems + ?? TerminalInputAccessoryItem.defaultItems + if view.inputAccessoryItems != accessoryItems { + view.inputAccessoryItems = accessoryItems + } + #endif + #endif } static func synchronizeFocus(_ view: TerminalView, with binding: TerminalFocusBinding?) { @@ -41,10 +69,14 @@ struct TerminalViewRepresentable { DispatchQueue.main.async { [weak view] in #if canImport(UIKit) guard let view, view.window != nil else { return } - if binding.isFocused { - if !view.isFirstResponder { view.becomeFirstResponder() } - } else if view.isFirstResponder { - _ = view.resignFirstResponder() + // Acquire-only: `FocusState` resets itself to nil whenever + // SwiftUI's own focus system re-evaluates (no native focusable + // view anchors it), so treating false as "resign" tears the + // keyboard down right after it opens. Moving focus between + // surfaces doesn't need the resign either — UIKit retires the + // old first responder when the next surface acquires. + if binding.isFocused, !view.isFirstResponder { + view.becomeFirstResponder() } #elseif canImport(AppKit) guard let view, let window = view.window else { return } diff --git a/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable@AppKit.swift b/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable@AppKit.swift index 782ffc5..5bcec7f 100644 --- a/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable@AppKit.swift +++ b/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable@AppKit.swift @@ -5,15 +5,15 @@ // Created by Lakr233 on 2026/3/16. // -#if canImport(AppKit) && !canImport(UIKit) +#if !canImport(UIKit) && canImport(AppKit) import AppKit import SwiftUI extension TerminalViewRepresentable: NSViewRepresentable { func makeNSView(context _: Context) -> TerminalView { - let view = TerminalView(frame: .zero) + let view = context.makePlatformView?() ?? TerminalView(frame: .zero) configureView(view, initial: true) - view.onFocusChange = { focused in + view.focusBridge.onFocusChange = { focused in focusBinding.setFocused(focused) } Self.synchronizeFocus(view, with: focusBinding) @@ -22,14 +22,14 @@ func updateNSView(_ view: TerminalView, context _: Context) { configureView(view, initial: false) - view.onFocusChange = { focused in + view.focusBridge.onFocusChange = { focused in focusBinding.setFocused(focused) } Self.synchronizeFocus(view, with: focusBinding) } static func dismantleNSView(_ view: TerminalView, coordinator _: ()) { - view.onFocusChange = nil + view.focusBridge.onFocusChange = nil } } #endif diff --git a/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable@UIKit.swift b/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable@UIKit.swift index 27c3b6f..6083280 100644 --- a/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable@UIKit.swift +++ b/ios/vendor/GhosttyTerminal/View/TerminalViewRepresentable@UIKit.swift @@ -15,7 +15,7 @@ } func makeUIView(context viewContext: Context) -> TerminalView { - let view = TerminalView(frame: .zero) + let view = context.makePlatformView?() ?? TerminalView(frame: .zero) configureView(view, initial: true) viewContext.coordinator.attach(to: view, focusBinding: focusBinding) Self.synchronizeFocus(view, with: focusBinding) @@ -43,13 +43,24 @@ ) { self.view = view self.focusBinding = focusBinding - view.onFocusChange = { [weak self] focused in + view.focusBridge.onFocusChange = { [weak self] focused in self?.focusBinding.setFocused(focused) } + // synchronizeFocus can only act on a view that is in a + // window; at launch the focus request precedes the window, + // so replay it the moment the view attaches. + view.focusBridge.onWindowAttach = { [weak self] in + guard let self, let view = self.view else { return } + TerminalViewRepresentable.synchronizeFocus( + view, + with: focusBinding + ) + } } func detach() { - view?.onFocusChange = nil + view?.focusBridge.onFocusChange = nil + view?.focusBridge.onWindowAttach = nil focusBinding = nil view = nil } diff --git a/ios/vendor/MSDisplayLink/DisplayLink+SwiftUI.swift b/ios/vendor/MSDisplayLink/DisplayLink+SwiftUI.swift index 42c5791..30a1b18 100644 --- a/ios/vendor/MSDisplayLink/DisplayLink+SwiftUI.swift +++ b/ios/vendor/MSDisplayLink/DisplayLink+SwiftUI.swift @@ -12,14 +12,25 @@ public struct DisplayLinkModifier: ViewModifier { let link: DisplayLink let context: DisplayLinkModifierContext - public init(scheduleToMainThread: Bool = true, _ callback: @escaping @Sendable (DisplayLinkCallbackContext) -> Void) { - link = .init() + public init( + scheduleToMainThread: Bool = true, + preferredFrameRateRange: DisplayLinkFrameRateRange = .default, + _ callback: @escaping @Sendable (DisplayLinkCallbackContext) -> Void + ) { + link = .init(preferredFrameRateRange: preferredFrameRateRange) context = .init(scheduleToMainThread: scheduleToMainThread, callback: callback) link.delegatingObject(context) } - public init(scheduleToMainThread: Bool = true, _ callback: @escaping @Sendable () -> Void) { - self.init(scheduleToMainThread: scheduleToMainThread) { _ in callback() } + public init( + scheduleToMainThread: Bool = true, + preferredFrameRateRange: DisplayLinkFrameRateRange = .default, + _ callback: @escaping @Sendable () -> Void + ) { + self.init( + scheduleToMainThread: scheduleToMainThread, + preferredFrameRateRange: preferredFrameRateRange + ) { _ in callback() } } public func body(content: Content) -> some View { diff --git a/ios/vendor/MSDisplayLink/DisplayLink.swift b/ios/vendor/MSDisplayLink/DisplayLink.swift index 780d969..5427fb4 100644 --- a/ios/vendor/MSDisplayLink/DisplayLink.swift +++ b/ios/vendor/MSDisplayLink/DisplayLink.swift @@ -12,14 +12,25 @@ public protocol DisplayLinkDelegate: AnyObject { func synchronization(context: DisplayLinkCallbackContext) } -public class DisplayLink: @unchecked Sendable { +open class DisplayLink: @unchecked Sendable { private weak var delegatingObject: DisplayLinkDelegate? private var driver: DisplayLinkDriver? private var driverSubscription: Set = .init() - public init() { + /// The frame rate this instance asks the display for. All live + /// `DisplayLink` instances share one platform link, so the applied + /// range is the union of every instance's request — one caller asking + /// for 120 lifts the shared link to 120 without slowing anyone else. + /// Set from the main thread. + open var preferredFrameRateRange: DisplayLinkFrameRateRange { + get { driver?.preferredFrameRateRange ?? .default } + set { driver?.preferredFrameRateRange = newValue } + } + + public init(preferredFrameRateRange: DisplayLinkFrameRateRange = .default) { let driver = DisplayLinkDriver() + driver.preferredFrameRateRange = preferredFrameRateRange driver.synchronizationPublisher .sink { [weak self] output in self?.delegatingObject?.synchronization(context: output) } .store(in: &driverSubscription) @@ -34,7 +45,7 @@ public class DisplayLink: @unchecked Sendable { driver = nil } - public func delegatingObject(_ object: DisplayLinkDelegate?) { + open func delegatingObject(_ object: DisplayLinkDelegate?) { delegatingObject = object } } diff --git a/ios/vendor/MSDisplayLink/DisplayLinkDriver+CA.swift b/ios/vendor/MSDisplayLink/DisplayLinkDriver+CA.swift index eaaf9b3..966168d 100644 --- a/ios/vendor/MSDisplayLink/DisplayLinkDriver+CA.swift +++ b/ios/vendor/MSDisplayLink/DisplayLinkDriver+CA.swift @@ -35,6 +35,7 @@ import Foundation assert(Thread.isMainThread) guard displayLink == nil else { return } displayLink = CADisplayLink(target: self, selector: #selector(displayLinkCallback(_:))) + applyFrameRateRange() displayLink?.add(to: .main, forMode: .common) } @@ -44,6 +45,28 @@ import Foundation displayLink = nil } + override func frameRatePreferencesDidChange() { + applyFrameRateRange() + } + + /// Without this a CADisplayLink runs at the system's 60 fps default + /// on ProMotion displays, whatever the display can do — mixing a + /// 60 Hz animation into 120 Hz native scrolling. (On iPhone the + /// host app must also set `CADisableMinimumFrameDurationOnPhone`.) + private func applyFrameRateRange() { + guard let displayLink else { return } + let range = resolvedFrameRateRange() + if #available(iOS 15.0, tvOS 15.0, macCatalyst 15.0, *) { + displayLink.preferredFrameRateRange = CAFrameRateRange( + minimum: range.minimum, + maximum: range.maximum, + preferred: range.preferred + ) + } else { + displayLink.preferredFramesPerSecond = Int(range.preferred.rounded()) + } + } + @objc private func displayLinkCallback(_ displayLink: CADisplayLink) { let context = DisplayLinkCallbackContext( duration: displayLink.duration, diff --git a/ios/vendor/MSDisplayLink/DisplayLinkDriver+Helper.swift b/ios/vendor/MSDisplayLink/DisplayLinkDriver+Helper.swift index f1fe96c..667820d 100644 --- a/ios/vendor/MSDisplayLink/DisplayLinkDriver+Helper.swift +++ b/ios/vendor/MSDisplayLink/DisplayLinkDriver+Helper.swift @@ -17,7 +17,10 @@ class DisplayLinkDriverHelperBase: Identifiable { final func delegate(_ object: DisplayLinkDriver) { assert(Thread.isMainThread) var shouldStartDisplayLink = false - defer { if shouldStartDisplayLink { startDisplayLink() } } + defer { + if shouldStartDisplayLink { startDisplayLink() } + frameRatePreferencesDidChange() + } referenceHolder = referenceHolder .filter { $0.object != nil } @@ -31,6 +34,7 @@ class DisplayLinkDriverHelperBase: Identifiable { assert(Thread.isMainThread) referenceHolder = referenceHolder.filter { $0.object?.id != object.id } + frameRatePreferencesDidChange() } final func reclaimComputeResourceIfPossible() { @@ -49,6 +53,22 @@ class DisplayLinkDriverHelperBase: Identifiable { } } + /// The union of every live driver's request — what the platform link + /// should actually run at. Falls back to the library default when no + /// driver is alive (the link is about to stop anyway). + final func resolvedFrameRateRange() -> DisplayLinkFrameRateRange { + var drivers = referenceHolder.compactMap(\.object) + guard let first = drivers.popLast() else { return .default } + return drivers.reduce(first.preferredFrameRateRange) { + $0.union($1.preferredFrameRateRange) + } + } + + /// Called whenever a driver joins, leaves, or changes its request. + /// Platform helpers that can steer their link's rate re-apply it here; + /// the base does nothing (CVDisplayLink runs at the display's rate). + func frameRatePreferencesDidChange() {} + func startDisplayLink() { fatalError("Subclasses need to implement the `startDisplayLink()` method.") } diff --git a/ios/vendor/MSDisplayLink/DisplayLinkDriver.swift b/ios/vendor/MSDisplayLink/DisplayLinkDriver.swift index 5d0db4d..6022bdb 100644 --- a/ios/vendor/MSDisplayLink/DisplayLinkDriver.swift +++ b/ios/vendor/MSDisplayLink/DisplayLinkDriver.swift @@ -17,6 +17,15 @@ class DisplayLinkDriver: Identifiable { > let synchronizationPublisher: SynchornizationPublisher + /// This driver's vote on the shared link's rate — see + /// ``DisplayLink/preferredFrameRateRange``. + var preferredFrameRateRange: DisplayLinkFrameRateRange = .default { + didSet { + guard preferredFrameRateRange != oldValue else { return } + DisplayLinkDriverHelper.shared.frameRatePreferencesDidChange() + } + } + init() { synchronizationPublisher = .init() DisplayLinkDriverHelper.shared.delegate(self) diff --git a/ios/vendor/MSDisplayLink/DisplayLinkFrameRateRange.swift b/ios/vendor/MSDisplayLink/DisplayLinkFrameRateRange.swift new file mode 100644 index 0000000..bfb0c05 --- /dev/null +++ b/ios/vendor/MSDisplayLink/DisplayLinkFrameRateRange.swift @@ -0,0 +1,51 @@ +// +// DisplayLinkFrameRateRange.swift +// MSDisplayLink +// +// Created by 秋星桥 on 2026/8/11. +// + +import Foundation + +/// The frame rate a `DisplayLink` asks the display for, expressed in frames +/// per second. Platform-neutral on purpose: UIKit platforms hand it to +/// `CADisplayLink.preferredFrameRateRange` (iOS 15+), the CVDisplayLink +/// path on macOS always runs at the display's own rate and ignores it. +/// +/// The library defaults to the full ProMotion range rather than the +/// system's 60 fps fallback — a display link exists to animate, and an +/// animation library that silently ticks at half the display's rate is how +/// 120 Hz scroll and 60 Hz animation end up mixed on one screen. +/// +/// Note that a range is a request, not a guarantee: the system still clamps +/// it to what the hardware supports, and on iPhone the app must also declare +/// `CADisableMinimumFrameDurationOnPhone` in its Info.plist before any rate +/// above 60 is honored. +public struct DisplayLinkFrameRateRange: Sendable, Equatable { + /// The slowest rate the caller can tolerate. + public var minimum: Float + /// The fastest rate worth ticking at. + public var maximum: Float + /// The rate the caller actually wants. + public var preferred: Float + + public init(minimum: Float = 60, maximum: Float = 120, preferred: Float = 120) { + self.minimum = minimum + self.maximum = maximum + self.preferred = preferred + } + + /// Full ProMotion: 60 minimum, 120 preferred. + public static let `default` = DisplayLinkFrameRateRange() + + /// The union of two requests — never slower than either caller asked + /// for. This is what the shared display link applies when multiple + /// `DisplayLink` instances disagree. + public func union(_ other: DisplayLinkFrameRateRange) -> DisplayLinkFrameRateRange { + DisplayLinkFrameRateRange( + minimum: Swift.min(minimum, other.minimum), + maximum: Swift.max(maximum, other.maximum), + preferred: Swift.max(preferred, other.preferred) + ) + } +} diff --git a/ios/vendor/README.md b/ios/vendor/README.md index 4ddca34..6b58b05 100644 --- a/ios/vendor/README.md +++ b/ios/vendor/README.md @@ -5,8 +5,8 @@ Synced by `scripts/sync-vendor.sh` from the tags pinned in | Directory | Upstream | Tag | | --- | --- | --- | -| `GhosttyKit/`, `GhosttyTerminal/` | https://github.com/Lakr233/libghostty-spm.git | 1.3.1 | -| `MSDisplayLink/` | https://github.com/Lakr233/MSDisplayLink.git | 2.1.0 | +| `GhosttyKit/`, `GhosttyTerminal/` | https://github.com/Lakr233/libghostty-spm.git | 1.5.20260903 | +| `MSDisplayLink/` | https://github.com/Lakr233/MSDisplayLink.git | 2.2.0 | Licenses: `LICENSE-libghostty-spm.txt`, `LICENSE-MSDisplayLink.txt` (both MIT). The `Frameworks/` directory is populated at install time by diff --git a/package.json b/package.json index 49f85cc..761ab94 100644 --- a/package.json +++ b/package.json @@ -31,17 +31,20 @@ "license": "MIT", "homepage": "https://github.com/arcboxlabs/expo-libghostty#readme", "devDependencies": { - "@types/react": "~19.2.2", - "eslint": "~9.39.4", - "eslint-config-universe": "^15.0.3", - "expo": "^57.0.5", - "jsdom": "^29.1.1", - "prettier": "^3.0.0", - "react": "^19.2.3", - "react-dom": "19.2.3", - "react-native": "0.86.0", - "typescript": "^5.9.2", - "vitest": "^4.1.10" + "@types/react": "~19.2.18", + "@typescript/native": "npm:typescript@7.0.2", + "@typescript-eslint/eslint-plugin": "^8.69.0", + "@typescript-eslint/parser": "^8.69.0", + "eslint": "~10.9.1", + "eslint-config-universe": "^16.0.0", + "expo": "^57.0.19", + "jsdom": "^30.0.1", + "prettier": "^3.9.6", + "react": "^19.2.8", + "react-dom": "19.2.8", + "react-native": "0.86.3", + "typescript": "npm:@typescript/typescript6@^6.0.2", + "vitest": "^4.1.11" }, "peerDependencies": { "expo": "*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb0a3a1..66834ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,77 +9,79 @@ importers: .: devDependencies: '@types/react': - specifier: ~19.2.2 - version: 19.2.17 + specifier: ~19.2.18 + version: 19.2.18 + '@typescript-eslint/eslint-plugin': + specifier: ^8.69.0 + version: 8.69.0(@typescript-eslint/parser@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/parser': + specifier: ^8.69.0 + version: 8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript/native': + specifier: npm:typescript@7.0.2 + version: typescript@7.0.2 eslint: - specifier: ~9.39.4 - version: 9.39.5 + specifier: ~10.9.1 + version: 10.9.1(supports-color@8.1.1) eslint-config-universe: - specifier: ^15.0.3 - version: 15.2.0(eslint@9.39.5)(prettier@3.9.5)(typescript@5.9.3) + specifier: ^16.0.0 + version: 16.0.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(prettier@3.9.6)(supports-color@8.1.1) expo: - specifier: ^57.0.5 - version: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + specifier: ^57.0.19 + version: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) jsdom: - specifier: ^29.1.1 - version: 29.1.1 + specifier: ^30.0.1 + version: 30.0.1 prettier: - specifier: ^3.0.0 - version: 3.9.5 + specifier: ^3.9.6 + version: 3.9.6 react: - specifier: ^19.2.3 - version: 19.2.3 + specifier: ^19.2.8 + version: 19.2.8 react-dom: - specifier: 19.2.3 - version: 19.2.3(react@19.2.3) + specifier: 19.2.8 + version: 19.2.8(react@19.2.8) react-native: - specifier: 0.86.0 - version: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + specifier: 0.86.3 + version: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) typescript: - specifier: ^5.9.2 - version: 5.9.3 + specifier: npm:@typescript/typescript6@^6.0.2 + version: '@typescript/typescript6@6.0.2' vitest: - specifier: ^4.1.10 - version: 4.1.10(@types/node@26.1.1)(jsdom@29.1.1)(vite@8.1.5(@types/node@26.1.1)(terser@5.49.0)(yaml@2.9.0)) + specifier: ^4.1.11 + version: 4.1.11(@types/node@26.4.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.4.0)(terser@5.51.2)(yaml@2.9.0)) example: dependencies: expo: - specifier: ~57.0.6 - version: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + specifier: ~57.0.19 + version: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2) react: - specifier: 19.2.3 - version: 19.2.3 + specifier: 19.2.8 + version: 19.2.8 react-native: - specifier: 0.86.0 - version: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + specifier: 0.86.3 + version: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) devDependencies: '@types/react': - specifier: ~19.2.2 - version: 19.2.17 + specifier: ~19.2.18 + version: 19.2.18 babel-preset-expo: - specifier: ^57.0.3 - version: 57.0.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.6)(react-refresh@0.14.2) + specifier: ^57.0.10 + version: 57.0.10(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo@57.0.19)(react-refresh@0.14.2)(supports-color@8.1.1) typescript: - specifier: ~6.0.3 - version: 6.0.3 + specifier: ~7.0.2 + version: 7.0.2 packages: - '@asamuzakjp/css-color@5.1.11': - resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - '@asamuzakjp/dom-selector@7.1.1': - resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - '@asamuzakjp/generational-cache@1.0.1': - resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@asamuzakjp/css-color@6.0.7': + resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==} + engines: {node: ^22.13.0 || >=24.0.0} - '@asamuzakjp/nwsapi@2.3.9': - resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@asamuzakjp/dom-selector@8.3.2': + resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} + engines: {node: ^22.13.0 || >=24.0.0} '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} @@ -93,8 +95,8 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.29.7': @@ -184,8 +186,8 @@ packages: resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} engines: {node: '>=6.0.0'} hasBin: true @@ -201,27 +203,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-decorators@7.29.7': resolution: {integrity: sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==} engines: {node: '>=6.9.0'} @@ -245,70 +226,22 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.29.7': - resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.29.7': resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-optional-chaining@7.8.3': resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.29.7': resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} engines: {node: '>=6.9.0'} @@ -491,31 +424,31 @@ packages: resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true - '@csstools/color-helpers@6.1.0': - resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + '@csstools/color-helpers@6.1.1': + resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==} engines: {node: '>=20.19.0'} - '@csstools/css-calc@3.2.1': - resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.1.9': - resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==} + '@csstools/css-color-parser@4.2.2': + resolution: {integrity: sha512-3QKjR/vxyjcSXBLgb6lP0S3MGdvwbmqSsvLPbYdVORqPDc8FX1HAJ0Spk38bxaRXgvENTA47tlhhbb5Z2e8hEg==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -527,8 +460,8 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.6': - resolution: {integrity: sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==} + '@csstools/css-syntax-patches-for-csstree@1.1.12': + resolution: {integrity: sha512-3vLQK+dXxhBMR2Wx99PTCifE+vHtW2ndZWyla8yK813ev6oGhyn8Lja8jCyGAWTJ+LEYZK7EVtJxrDj8ztevJw==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: @@ -539,17 +472,8 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} - '@emnapi/core@1.11.1': - resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - - '@emnapi/runtime@1.11.1': - resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -558,33 +482,25 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.6': - resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@9.39.5': - resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@exodus/bytes@1.15.1': resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} @@ -595,8 +511,8 @@ packages: '@noble/hashes': optional: true - '@expo/cli@57.0.8': - resolution: {integrity: sha512-RzE6o39O6Ual4nw1LbUyrIMY5VzQlW4CbOyoww/RhKclMRVItnfkLi89cVKQoPFw+5EKW2KKC6LLtA4or7Trwg==} + '@expo/cli@57.0.21': + resolution: {integrity: sha512-CkcgAOsp1KkNRENXWfyr/esIwgFtiHosKECWZ0QQl2nFOUEQlyjWXAZ6uTCMnbHLonVnPMZpXTkHZNw3gtanFw==} hasBin: true peerDependencies: expo: '*' @@ -611,14 +527,14 @@ packages: '@expo/code-signing-certificates@0.0.6': resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==} - '@expo/config-plugins@57.0.5': - resolution: {integrity: sha512-xhUGgzpFWRghDUH98+Wl4RDakYhTsbyMg6aOYiBjRzPO/THH8tKMw3vlksgFYlU2PkiAdABJN3tNPf5qmvOQhA==} + '@expo/config-plugins@57.0.9': + resolution: {integrity: sha512-hHgfL1avkCdEvDSw7IwlKwRYYNgcxzbNNMIk6W6lTkJpY0MajinAfeJUS0J+wPCsjUfGbVqOJM+XhPaO5ulUxg==} '@expo/config-types@57.0.2': resolution: {integrity: sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g==} - '@expo/config@57.0.5': - resolution: {integrity: sha512-XqveHQzr6PTqHGnv6NVVZ1CFgB/TgR2mKtHsJA/gYS/76pe2cP1yK/O820xGW2RTnDGTmyhOdagmK6khcN46vg==} + '@expo/config@57.0.9': + resolution: {integrity: sha512-dmzlKraIFxa7wLwV6K7WzI8jp6QZpW6Mc5mGjLimJUFjzh4uQdYaT3m3plEutM5yxBoBEwqzks7l+I/ljCbxAQ==} '@expo/devcert@1.2.1': resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} @@ -641,50 +557,50 @@ packages: react: '*' react-native: '*' - '@expo/env@2.4.2': - resolution: {integrity: sha512-28pqaEqwnmLduZ00Pq9HkSzE5wbj1MTwp5/n8nm8rD8MCjR9eUnVOwmNksPI3Be2ReAPO/DbPn1puy0mvoocsQ==} + '@expo/env@2.4.3': + resolution: {integrity: sha512-M1NXeZCA1mkMkYOyIe7PlyRX0/jqFtMoJgyblnlq/vpCRfmueFT7RnGSQG8uEFDF5WHOFGijAQ3fogPh3/n5Ng==} engines: {node: '>=20.12.0'} '@expo/expo-modules-macros-plugin@0.6.1': resolution: {integrity: sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA==} - '@expo/fingerprint@0.20.5': - resolution: {integrity: sha512-XCDfmbkTpTsYVq1xvvUJvXjfFQs2Hj+icQACBrc6BZmA91YPr2H3uw8sUX13d+1ij6E8lgVCtsK9jh3J2cN/SQ==} + '@expo/fingerprint@0.20.12': + resolution: {integrity: sha512-FIR5fkZYeFaLSowmjgyB6RPKl8AXeE8HuCBHHvyxK8UhOjpPfCAmzT4E7v2yub4qqDafKnfcOFCYxvpUEu+01w==} hasBin: true - '@expo/image-utils@0.11.3': - resolution: {integrity: sha512-yMVjkndhXm9mct0uMq+ndxqT6FgAnhucdUfmXuQ6V6uE021GOiYCACO+KZ0MB4vearPSvbWTGfi32QQr2qocfQ==} + '@expo/image-utils@0.11.5': + resolution: {integrity: sha512-KPQBTpmpAfy/Vu9y4wPW808/qtZxjYmyJg8cm2QCPAupp+qEWA3b5zmk0ulOwQ9OgeHxuCPgUqWgkwHFo7UsrQ==} - '@expo/inline-modules@0.1.3': - resolution: {integrity: sha512-eHSxWYfgq65mP3Qz8PclVjUkSrDIlGl3va9U7PMcTpGItOvee/i0ZzGinH5A25oARR5ouD64eESBKwtT/CwdHg==} + '@expo/inline-modules@0.1.7': + resolution: {integrity: sha512-Bz/khd1gIJqDkje7t5ejD5e9jFbm4xEJzWSwRKadRo6gruepbw1xJ3Eb+e58OS8fY1ZNQYjzZbspnuph67sN0g==} '@expo/json-file@11.0.1': resolution: {integrity: sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==} - '@expo/local-build-cache-provider@57.0.4': - resolution: {integrity: sha512-B/cI73shkLSYBYuFyh+zCbS+WhqJgawWPW4MPdMiNLJKv9RmV4dv1FGjsidiIiG2k4kYKerBDLK4bLbC7qERQQ==} + '@expo/local-build-cache-provider@57.0.8': + resolution: {integrity: sha512-SEdE0pAQrr90bRh3MNR0ZuwoIBw390cYdFgbn7Vk0Mtm9EHaBfP7kYj+2hXnXZJgOEm3/JMtfoD3rV7rWA9FGg==} - '@expo/log-box@57.0.1': - resolution: {integrity: sha512-fuVNHhOerdRWtpq27gD6JTSVYESsfRu+SMdrNCWxW+gFnusS6dGKfx3lKGBZ4ZkMNiLWn8maBHo39YKzJNXFYQ==} + '@expo/log-box@57.0.4': + resolution: {integrity: sha512-IxwS9s1L2muj8mj8AQSuiy7u8OFJdc02NRFo2me/Tj6DiaeG5SREqmpBE4rQpR2cadqSg5jl8Qab8Cjie616dg==} peerDependencies: '@expo/dom-webview': ^57.0.1 expo: '*' react: '*' react-native: '*' - '@expo/metro-config@57.0.5': - resolution: {integrity: sha512-KyiYvQ9rwT3X3CFTRP1grSs/z2gizJuKbo7akPCgqACC4XtG/MWYsvbOB2fH6cfeRn8Zv3JNHgRGF4AmCOIzFw==} + '@expo/metro-config@57.0.12': + resolution: {integrity: sha512-S62Lrq35HZqBFD55423pmWb8PjaiR/W02zQC1uECBmw1vTN8WZaFz4TJ0i21EeJzwfebMb9MLxL8JZ102Z6VbA==} peerDependencies: expo: '*' peerDependenciesMeta: expo: optional: true - '@expo/metro-file-map@57.0.1': - resolution: {integrity: sha512-8JXfVstZN7QnP4NianZZnlTVboOWR0sG8trUDNajOjnbGlPln29vponXM84tY+3tAHapz5/TxE53L0ixUwqPtA==} + '@expo/metro-file-map@57.0.2': + resolution: {integrity: sha512-tb50nSIWwKpRufSkGuivOK0FbUxv1Uwqptb0SzFTk0bkmYmcy3gIwnCbOHTfmrQDVndRhPEiu2SYgh9Z0PCvGg==} - '@expo/metro@56.0.0': - resolution: {integrity: sha512-5gIgQHtEpjjvsjKfVtIv23a98LLRV0/y07PDShEwYSytAMlE3FSF8RHXqtHc1sUJL6dn7hnuIBpIbrLXXuVi0A==} + '@expo/metro@56.0.2': + resolution: {integrity: sha512-Ld5AeYMCCDa8bLeWhfuLbZFFjlV3f6ORqyPz2glGh6RltIngMuLf9BTC2yvHFjkKuGxL5SynijmA8xmNNWn5iA==} '@expo/osascript@2.7.1': resolution: {integrity: sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==} @@ -696,26 +612,26 @@ packages: '@expo/plist@0.8.1': resolution: {integrity: sha512-3gTReGIUm0oRaMClsAJYxBnVPCl6fVpsl8HS+DTVxDhW4GyVyxg9E/Znm3BvcHtUJ51RJJI14pC1wvrNilCRHw==} - '@expo/prebuild-config@57.0.7': - resolution: {integrity: sha512-VrsRKc+je3bAZp8ocM8fVjRbXAQqYnBLiy3gm10VY0WoNspQwxcE9gllTu2jtMcqf6pNfAxGaSSp9I74SNOEzg==} + '@expo/prebuild-config@57.0.15': + resolution: {integrity: sha512-xTbWHroj0PDmlbqvmU+zF9ZZxveJkiuyiPoeRJYRGruFHebRAWnoTdw5S7d/UCzDBI8ropGGu9g2eb2nMxtvAw==} - '@expo/require-utils@57.0.3': - resolution: {integrity: sha512-ns05X1K8tM+Qtzp6dNloUFOopSdh3J+HC61BtOR8WHhgtPFyX8TKuO2diqZUqVg9K8yfkWug7g8tBS0qRniSTA==} + '@expo/require-utils@57.0.5': + resolution: {integrity: sha512-kTAXj9lDFEIPMsbAOGCGbjBbMF0oi7CqkYM79KOX0DDD9wSwXmlKL1z2h8OwsrBf7mbOo2DjlRvZu4BEjrIxGw==} peerDependencies: - typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0 + typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0 peerDependenciesMeta: typescript: optional: true - '@expo/router-server@57.0.3': - resolution: {integrity: sha512-gkboMZUv+eAK4XSBGSIQ6at3dSa/QYARm+8PKj8pMEhGnt08vgcXpWAW5rYJMoLukaDD/bUDX/JU2Iz1Azwziw==} + '@expo/router-server@57.0.9': + resolution: {integrity: sha512-/PxRQozFesIyCJZOAtrQE8XcmcojNiL5ctPMQnbE4ojC2EJPu0zc7c0Y4PuXsoRxrZxm8Usw76/lCVrXcfTZ2w==} peerDependencies: - '@expo/metro-runtime': ^57.0.5 + '@expo/metro-runtime': ^57.0.15 expo: '*' - expo-constants: ^57.0.5 - expo-font: ^57.0.1 + expo-constants: ^57.0.17 + expo-font: ^57.0.3 expo-router: '*' - expo-server: ^57.0.1 + expo-server: ^57.0.3 react: '*' react-dom: '*' react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 @@ -775,34 +691,10 @@ packages: resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} engines: {node: '>=12'} - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - - '@istanbuljs/schema@0.1.6': - resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} - engines: {node: '>=8'} - - '@jest/create-cache-key-function@29.7.0': - resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/environment@29.7.0': - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/fake-timers@29.7.0': - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jest/types@29.6.3': resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -820,182 +712,171 @@ packages: '@jridgewell/source-map@0.3.11': resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@napi-rs/wasm-runtime@1.1.6': - resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - - '@oxc-project/types@0.139.0': - resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + '@oxc-project/types@0.147.0': + resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} '@pkgr/core@0.3.6': resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} - '@react-native/assets-registry@0.86.0': - resolution: {integrity: sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==} + '@react-native/assets-registry@0.86.3': + resolution: {integrity: sha512-TDhgCZA4wjJg84d5A9swiOQYPIWSKEEVdg9IwMFZDupQzW/F3QoLUrfAJOcalgqTDA9/buTB8awhE3Whwg6u9Q==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/babel-plugin-codegen@0.86.0': - resolution: {integrity: sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA==} + '@react-native/babel-plugin-codegen@0.86.3': + resolution: {integrity: sha512-O6Xza4JBGPIU8J7YbKTyBoYL4thpy8jMW/oaLDWdAyOwYHKIjK47pAL5HUEbOe2bWz2PEKjbYRF2ApkJv1ottQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/codegen@0.86.0': - resolution: {integrity: sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==} + '@react-native/codegen@0.86.3': + resolution: {integrity: sha512-Ux4jHi0fh+bdtVEcL0gaPLbY56V+SvFUDl/8sRAE1jdb4k+o7fT/4Nc29yz4X+qfjstkSqObQTMBGhdzxH9JvA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} peerDependencies: '@babel/core': '*' - '@react-native/community-cli-plugin@0.86.0': - resolution: {integrity: sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==} + '@react-native/community-cli-plugin@0.86.3': + resolution: {integrity: sha512-qSDL9LQc5mZSZPNczT95WU9YQuPzxBklgON9vLhhqfI0yWIwKInqFx88dQ/uiEXBtf0yossthaQIqA4Ml6bF6g==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} peerDependencies: '@react-native-community/cli': '*' - '@react-native/metro-config': 0.86.0 + '@react-native/metro-config': 0.86.3 peerDependenciesMeta: '@react-native-community/cli': optional: true '@react-native/metro-config': optional: true - '@react-native/debugger-frontend@0.86.0': - resolution: {integrity: sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==} + '@react-native/debugger-frontend@0.86.3': + resolution: {integrity: sha512-TQmeofQ0PcuylhhlleOeuzHYZfbrgm3gayXzowqUEzgRisTm1D40/J3ggqs7XkQi5HP5ZA3n8dHmKL9vIzPcsw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/debugger-shell@0.86.0': - resolution: {integrity: sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==} + '@react-native/debugger-shell@0.86.3': + resolution: {integrity: sha512-O4ds+J7xZfxkbih9T+cAGegBdvKSPKYJm/lDgC9CpEjFMkmzWTpVLU3Qsv9sqZuo58z+sGhIcJfPsCFFWHpqbQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/dev-middleware@0.86.0': - resolution: {integrity: sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==} + '@react-native/dev-middleware@0.86.3': + resolution: {integrity: sha512-LiEPTqTg/63bYUnrPyHLfjTDCNhA/+CUqI1+DsA9tYyewtSbULd5awsva6SgE10I+2iMhgKXS3ymkhU/kSCrGA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/gradle-plugin@0.86.0': - resolution: {integrity: sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==} + '@react-native/gradle-plugin@0.86.3': + resolution: {integrity: sha512-lxmx0GqLEWRIpZfpYFXlYVIs3ENQwaW6Vmp6oi29l2GoQJ1wZfFZRdMimDWlGEk8LKfHar3QH3iaPMkTcK9lEQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/jest-preset@0.86.0': - resolution: {integrity: sha512-KA+xpIP3DvJy7PQJ9c6ZdEKkOPChl+Rk/rV2MhQACEAzfhWU84407KZQv4ccyO3B4caD0gPrFjE96a4P993nsQ==} - engines: {node: '>= 20.19.4'} - peerDependencies: - react: ^19.2.3 - - '@react-native/js-polyfills@0.86.0': - resolution: {integrity: sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==} + '@react-native/js-polyfills@0.86.3': + resolution: {integrity: sha512-eYIJ0es967+tePBFQDnl/gidVFxLns3fnbiK6rxscQrGodvuUO6hwxpQnfNynJ8MjbbndImXihXjcnJdc7SzJg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/normalize-colors@0.86.0': - resolution: {integrity: sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==} + '@react-native/normalize-colors@0.86.3': + resolution: {integrity: sha512-Cv3CDkprb67GrzuaS9BGbBJC/6G4lIw3nyKOHRKTqTTum4bn37y5+R0Z04L8mcbQN85eEohNrRwb7IOM4j6uvg==} - '@react-native/virtualized-lists@0.86.0': - resolution: {integrity: sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==} + '@react-native/virtualized-lists@0.86.3': + resolution: {integrity: sha512-1j44NEyNn05Ut40vHAmoSWbsIcybFkMAOBTwQt1PrESyfSS+qBoyU1LGIogNva0VIa0rQyEC5PzbA7R4/7Nhyw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} peerDependencies: '@types/react': ^19.2.0 react: '*' - react-native: 0.86.0 + react-native: 0.86.3 peerDependenciesMeta: '@types/react': optional: true - '@rolldown/binding-android-arm64@1.1.5': - resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + '@rolldown/binding-android-arm-eabi@1.2.6': + resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.6': + resolution: {integrity: sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.1.5': - resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + '@rolldown/binding-darwin-arm64@1.2.6': + resolution: {integrity: sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.1.5': - resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + '@rolldown/binding-darwin-x64@1.2.6': + resolution: {integrity: sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.1.5': - resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + '@rolldown/binding-freebsd-x64@1.2.6': + resolution: {integrity: sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.6': + resolution: {integrity: sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.1.5': - resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + '@rolldown/binding-linux-arm64-gnu@1.2.6': + resolution: {integrity: sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.1.5': - resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + '@rolldown/binding-linux-arm64-musl@1.2.6': + resolution: {integrity: sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.1.5': - resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + '@rolldown/binding-linux-ppc64-gnu@1.2.6': + resolution: {integrity: sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.1.5': - resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + '@rolldown/binding-linux-s390x-gnu@1.2.6': + resolution: {integrity: sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.1.5': - resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + '@rolldown/binding-linux-x64-gnu@1.2.6': + resolution: {integrity: sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.1.5': - resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + '@rolldown/binding-linux-x64-musl@1.2.6': + resolution: {integrity: sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.1.5': - resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + '@rolldown/binding-openharmony-arm64@1.2.6': + resolution: {integrity: sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.1.5': - resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.1.5': - resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + '@rolldown/binding-win32-arm64-msvc@1.2.6': + resolution: {integrity: sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.1.5': - resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + '@rolldown/binding-win32-x64-msvc@1.2.6': + resolution: {integrity: sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1006,45 +887,24 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@sinclair/typebox@0.27.10': - resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} - - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@sinclair/typebox@0.27.12': + resolution: {integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==} '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1060,14 +920,11 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@26.1.1': - resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} + '@types/node@26.4.0': + resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} - '@types/react@19.2.17': - resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} - - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -1075,73 +932,197 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.64.0': - resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} + '@typescript-eslint/eslint-plugin@8.69.0': + resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.64.0 + '@typescript-eslint/parser': ^8.69.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.64.0': - resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} + '@typescript-eslint/parser@8.69.0': + resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.64.0': - resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} + '@typescript-eslint/project-service@8.69.0': + resolution: {integrity: sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.64.0': - resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} + '@typescript-eslint/scope-manager@8.69.0': + resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.64.0': - resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} + '@typescript-eslint/tsconfig-utils@8.69.0': + resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.64.0': - resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} + '@typescript-eslint/type-utils@8.69.0': + resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.64.0': - resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} + '@typescript-eslint/types@8.69.0': + resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.64.0': - resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} + '@typescript-eslint/typescript-estree@8.69.0': + resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.64.0': - resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} + '@typescript-eslint/utils@8.69.0': + resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.64.0': - resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} + '@typescript-eslint/visitor-keys@8.69.0': + resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.3': - resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@typescript/typescript6@6.0.2': + resolution: {integrity: sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==} + hasBin: true + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} - '@vitest/expect@4.1.10': - resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} - '@vitest/mocker@4.1.10': - resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1151,27 +1132,27 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.10': - resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - '@vitest/runner@4.1.10': - resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - '@vitest/snapshot@4.1.10': - resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - '@vitest/spy@4.1.10': - resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} - '@vitest/utils@4.1.10': - resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} - '@xmldom/xmldom@0.8.13': - resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} + '@xmldom/xmldom@0.8.15': + resolution: {integrity: sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==} engines: {node: '>=10.0.0'} - '@xmldom/xmldom@0.9.10': - resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} + '@xmldom/xmldom@0.9.12': + resolution: {integrity: sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==} engines: {node: '>=14.6'} abort-controller@3.0.0: @@ -1191,8 +1172,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} engines: {node: '>=0.4.0'} hasBin: true @@ -1200,8 +1181,8 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - agent-cli-detector@0.1.2: - resolution: {integrity: sha512-qdZ/9JFORtTKJNhT/IczMeEfEUbUU0K5umYeiIQHX+AjHs+Y9SXVzSgaYlpZeyNMrvuh2HpZiOTpvS57iPfBkQ==} + agent-cli-detector@0.1.7: + resolution: {integrity: sha512-d8OWDVdZMgjhLUT9ZPgSv/BdFFF9pVuscC0JdUSz3bjwE15gcp6u/o0/JooM2yyAWC49KThFhXlgTXRa9B7yng==} engines: {node: '>=18.18'} hasBin: true @@ -1235,16 +1216,9 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1295,20 +1269,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - babel-plugin-polyfill-corejs2@0.4.17: resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} peerDependencies: @@ -1339,17 +1299,12 @@ packages: babel-plugin-transform-flow-enums@0.0.2: resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} - babel-preset-current-node-syntax@1.2.0: - resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} - peerDependencies: - '@babel/core': ^7.0.0 || ^8.0.0-0 - - babel-preset-expo@57.0.3: - resolution: {integrity: sha512-JuTLwC4dt30GF3L8sY7EBwh5iD7L3dSWumfg+i99bFK2SIXWyfn01UHxu+azfKXFU/ufim9oUt9KUoJ9AvyOPA==} + babel-preset-expo@57.0.10: + resolution: {integrity: sha512-08bt6bqMZFoQuWO9gkt0EXotbqKHimELFGa1LlKJvY89iKsi4S0FJSFBFJ4pn9NZvofet48HIjYDQ0SZJ7bNrg==} peerDependencies: '@babel/runtime': ^7.20.0 expo: '*' - expo-widgets: ^57.0.5 + expo-widgets: ^57.0.16 react-refresh: '>=0.14.0 <1.0.0' peerDependenciesMeta: '@babel/runtime': @@ -1359,12 +1314,6 @@ packages: expo-widgets: optional: true - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1375,8 +1324,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.43: - resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} + baseline-browser-mapping@2.11.20: + resolution: {integrity: sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==} engines: {node: '>=6.0.0'} hasBin: true @@ -1398,19 +1347,19 @@ packages: resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} engines: {node: '>= 5.10.0'} - brace-expansion@1.1.16: - resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} + brace-expansion@1.1.18: + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.6: - resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1436,20 +1385,12 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001805: - resolution: {integrity: sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==} + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -1533,11 +1474,16 @@ packages: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js-compat@3.50.0: + resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} + engines: {node: '>=6.4.0'} cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} @@ -1638,8 +1584,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.389: - resolution: {integrity: sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==} + electron-to-chromium@1.5.418: + resolution: {integrity: sha512-UzS26r3AEbG5wSoGVpJKqwHIU9zwQN7LHdVIThDrJpS0I5KdlXFMEb8543fhc9dVnIIAST6ar8rhwa00AL5MlA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1652,8 +1598,8 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - enhanced-resolve@5.24.2: - resolution: {integrity: sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==} + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} engines: {node: '>=10.13.0'} entities@8.0.0: @@ -1683,8 +1629,8 @@ packages: resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==} engines: {node: '>= 0.4'} - es-module-lexer@2.3.1: - resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} @@ -1713,10 +1659,6 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1733,8 +1675,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-config-universe@15.2.0: - resolution: {integrity: sha512-n2662q/mM+2pTFVz7ELosqhN+/nbR75Ut/4vLme40kKSHHe0oPbPMxgPqyYrASlANuSDP4aAJ71rRviDMCZTxg==} + eslint-config-universe@16.0.0: + resolution: {integrity: sha512-T5DMGRJsE92dU/BeI4eAP0MRJnso5Sldm27Fx6Gsdcubwsn0gQopihzfLINegSfq/DrW/90Tdv+EqjraQyeE+Q==} peerDependencies: eslint: '>=8.10' prettier: '>=3' @@ -1826,9 +1768,9 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-utils@2.1.0: resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} @@ -1842,17 +1784,13 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-visitor-keys@5.0.1: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.39.5: - resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@10.9.1: + resolution: {integrity: sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -1860,14 +1798,9 @@ packages: jiti: optional: true - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} @@ -1900,27 +1833,27 @@ packages: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - expo-asset@57.0.5: - resolution: {integrity: sha512-vRwG+QvoW1u1vd6yY1oQ4f5doNGGpuCQHvzSY25eCNlQjNJtHJyjWcllzn1KdQ8aQc9TMMYedxeLoYZshSI7MA==} + expo-asset@57.0.16: + resolution: {integrity: sha512-IBRfQdW3iFT+GOBERMZLZM1MUNyrjMgMskuD0elVZ1ae44858UFlTJkHO3f8vi+u5zuv7O7KofsiN8NMG/uWzw==} peerDependencies: expo: '*' react: '*' react-native: '*' - expo-constants@57.0.5: - resolution: {integrity: sha512-HVxPZc1uBdqrlcmNvdyO3L107vt/gsCRNGvXrYXWjZqmz1XOvGeUCR7S3MG4wUj4cQw4/WMCp+fcoDIhynJ80A==} + expo-constants@57.0.17: + resolution: {integrity: sha512-cPWYBKN1SEbg2lXg2f8VkePJqGZrJPLvVdQDCTfbFu9sQHO1M31Y1zILReUuGnmt/VKeUz40ze579vR00xGu/A==} peerDependencies: expo: '*' react-native: '*' - expo-file-system@57.0.1: - resolution: {integrity: sha512-w7/ERvQFrGP2apTO9lDtZ+O6JQIhfakL7+Xqzh+rfMO9B4LB4qwrz+YvLgir8KFRVX64JHBnRuYBVLY1oQZcqw==} + expo-file-system@57.0.6: + resolution: {integrity: sha512-pm8PMYEW6BnVOCBJ7df9FcDmQtE1tqImuYphlfYe1ipQRLYtdCayRczJcbKIsnB3mqEyp4gC2gWmMbsAsAOjVg==} peerDependencies: expo: '*' react-native: '*' - expo-font@57.0.1: - resolution: {integrity: sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==} + expo-font@57.0.3: + resolution: {integrity: sha512-kiVUnc2A8vAvO2FfDJTsQa5BwmY+PAkof/1wRb5MOkcX1jtiaSTwz9gCUAyscMBFLundZDmZrLy1P7LZVC+NvA==} peerDependencies: expo: '*' react: '*' @@ -1932,12 +1865,12 @@ packages: expo: '*' react: '*' - expo-modules-autolinking@57.0.7: - resolution: {integrity: sha512-arYvWy3odY0JxFwjosVeu8OrS/CvYy+Voroe126X/fYlkdXryJZSYbOsmM1ruol3Eo4ji8HKcfO+mvEVKGxUbQ==} + expo-modules-autolinking@57.0.12: + resolution: {integrity: sha512-Q8KAlq37nLKsQ+HsS9NpQVpd5jCgqtu694TDUNHBUBpV9ViD82mRBh8Uug/h68RG9xnLS+kuL4nYaCuFRghHjg==} hasBin: true - expo-modules-core@57.0.5: - resolution: {integrity: sha512-jyx2yAKUO5wJRlRTj74GC8P6NFhXWw/wB42LaPgAco7tjvWujl4O5D8fM7jCE+VFRa3OV/4SZUyp7H0aeuqYdg==} + expo-modules-core@57.0.15: + resolution: {integrity: sha512-HBxPXsx3eVLLRjAY57/8ghKDWIxjaEbluKKYFIq1OHPJC85ZfHsawwc0SOU4Z/qu+mdLz7Pwt5IkkWimeNSH5A==} peerDependencies: react: '*' react-native: '*' @@ -1946,17 +1879,17 @@ packages: react-native-worklets: optional: true - expo-modules-jsi@57.0.3: - resolution: {integrity: sha512-B+iXJCC5OIXjFKkLLSY2DZ8BGS+hC3PBTrALpV43pHqhXqqZrRH3uTEylZgsoKQO8N8tjmjAua2ucTFHtr1o0w==} + expo-modules-jsi@57.0.7: + resolution: {integrity: sha512-/GOrTFPnCfg7+m6M1yOSEKNeNdHBCm0ju6aJ4XHlSmtE02nb7mWh1Cs6QsK2eB5Rz7hjbb356Nd7UimsFvN7Vg==} peerDependencies: react-native: '*' - expo-server@57.0.1: - resolution: {integrity: sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w==} + expo-server@57.0.3: + resolution: {integrity: sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==} engines: {node: '>=20.16.0'} - expo@57.0.6: - resolution: {integrity: sha512-4NKM1ArfRAmmY82Xcw/guGHlTSItD5mzNxbK4Qd3aOPuRAPAkCgJVLC/eqccZCEGS5aJn0tyjHNKE65k2GrcFw==} + expo@57.0.19: + resolution: {integrity: sha512-oIoAIisim1DwS2Zjp0rcEY9cfcq1wU6KpkBfwwFhJWI5YHV6i/aIzTUMX+uf0Q0CD6AvMls7IZVh34o27NjaJA==} hasBin: true peerDependencies: '@expo/dom-webview': '*' @@ -2025,10 +1958,6 @@ packages: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -2037,8 +1966,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} @@ -2054,9 +1983,6 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2088,10 +2014,6 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -2100,8 +2022,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.14.0: - resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} getenv@2.0.0: resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} @@ -2115,14 +2037,6 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - globals@15.15.0: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} @@ -2176,8 +2090,8 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} - hermes-compiler@250829098.0.14: - resolution: {integrity: sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==} + hermes-compiler@250829098.0.17: + resolution: {integrity: sha512-qG1PXzTEtriF6oQLZF3vyHhSMxOdW5h2TqqLri0rdpstPustd2fSvRZQMVAPdlhgFwBfYnj3OUZtiO6LjYsEFw==} hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -2223,27 +2137,14 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.6: - resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + ignore@7.0.8: + resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==} engines: {node: '>= 4'} - image-size@1.2.1: - resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} - engines: {node: '>=16.x'} - hasBin: true - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2380,42 +2281,14 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-get-type@29.6.3: resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-util@29.7.0: resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2434,22 +2307,18 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.15.0: - resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} - hasBin: true - - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true jsc-safe-url@0.2.4: resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} - jsdom@29.1.1: - resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + jsdom@30.0.1: + resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} peerDependencies: - canvas: ^3.0.0 + canvas: ^3.2.3 peerDependenciesMeta: canvas: optional: true @@ -2503,84 +2372,80 @@ packages: lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2588,9 +2453,6 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -2634,61 +2496,61 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - metro-babel-transformer@0.84.4: - resolution: {integrity: sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==} + metro-babel-transformer@0.84.5: + resolution: {integrity: sha512-2WbHILKMiJUzfdjmGOQOqU1bWi9//gqiclc/tkk/AIsrrVw3efhZ1uhkOwMTxUEPOzqoo091H0olLmVZH5FHGQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-cache-key@0.84.4: - resolution: {integrity: sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==} + metro-cache-key@0.84.5: + resolution: {integrity: sha512-3dPB2TnvGjjf0/9O7AXVQURKXuQNauTZE7WpTGTlR017Gh/B5y0m/2wcqxfveUguHSpu89KhVxCAlr2k/H7uhQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-cache@0.84.4: - resolution: {integrity: sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==} + metro-cache@0.84.5: + resolution: {integrity: sha512-WHS0n2OxQqtwEjSeQFPePNrMvEFhmQcUQM9cRJMHByWoi/GMWFBEWOf7hVkAM/0KRutAXNbDlSu/cZB6CyxgQQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-config@0.84.4: - resolution: {integrity: sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==} + metro-config@0.84.5: + resolution: {integrity: sha512-zie+uN6oohscowi2S7ByU+wUw6CrT4ZxW9uAbONOObSxx86RGmnIAmjXHLkfmcdYoY7jzOPEbqcI6oeVmqyBQA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-core@0.84.4: - resolution: {integrity: sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==} + metro-core@0.84.5: + resolution: {integrity: sha512-xwm605hCi5Y6eJTTb8ZWo6pkUcoBEIyiQOfkZh5GwtDwUrP9SNhTQZhzJHrBCwwxlf3Ptl/pxWJgQ1rsNYMnrA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-file-map@0.84.4: - resolution: {integrity: sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==} + metro-file-map@0.84.5: + resolution: {integrity: sha512-mlm/JL8toSbSc2akpKIGmzvrVRSCgZ5vkbycI34oMLoOnLGuLyC8WTyVJ6P0hZG/usDaGwZSl/s9BCRriqjGJA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-minify-terser@0.84.4: - resolution: {integrity: sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==} + metro-minify-terser@0.84.5: + resolution: {integrity: sha512-BJoFwCEDsYnagPqarayInv2+diCDNDdLlaof/p6s9w4gh+gc9HXYM+pDvsKGKKUumpZswNF3Z/ftTMqKl/5IBg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-resolver@0.84.4: - resolution: {integrity: sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==} + metro-resolver@0.84.5: + resolution: {integrity: sha512-VSSnepg1k6LyCwtb6eirWdAWlpKwBG8Rdtsr1mU38rMelFyWgh3/QuMSiZIZAIjwg/fsa8GhW5/FO54CAUPCEA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-runtime@0.84.4: - resolution: {integrity: sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==} + metro-runtime@0.84.5: + resolution: {integrity: sha512-U1m2+d1Pr+JO2/iVXBB2OfXXityz7tqwIorxfrT15IEgaHvpJBq/OHiqnOWPKJbUl3JcxjcdviZZOKk85oK4Qg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-source-map@0.84.4: - resolution: {integrity: sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==} + metro-source-map@0.84.5: + resolution: {integrity: sha512-2BtV5L9uPc49F13Gn5wiP6bX/EncqzqTIk2VL/0F/96Vo0YEOjluT/qktQjFODfqGFsucwnh5mPEAl/2jVEfeg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-symbolicate@0.84.4: - resolution: {integrity: sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==} + metro-symbolicate@0.84.5: + resolution: {integrity: sha512-rQ40zYDAkaWBN9yvjUuAD0ZpzBMZSoKyGYXnb5JrfbKjun7fTvfoLHL3KXFYenBTYZkQtlp4cKSCv/1utxFyOw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} hasBin: true - metro-transform-plugins@0.84.4: - resolution: {integrity: sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==} + metro-transform-plugins@0.84.5: + resolution: {integrity: sha512-+InaSVGaOyt0DyRo4Y/zIdPI6CZwnbNho5LAL23tgmuGwv7fyfkF7kKfPjZcfxXBcoYdTLLFnCfCH/dHSiCqNg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-transform-worker@0.84.4: - resolution: {integrity: sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==} + metro-transform-worker@0.84.5: + resolution: {integrity: sha512-ui1Z8x4s5RL36gMmKLaMMO7O9NNDHNdthEZSCDQHAau3JcAsTaFOK6I+2q4I/kW5u8hSEjJk9L45TXSVJw6g1A==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro@0.84.4: - resolution: {integrity: sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==} + metro@0.84.5: + resolution: {integrity: sha512-r1liLkyFZMVSEMNjU1CJU5pRzs3NdkxHqXS60O25c0rCIqAR+cGk7rPydw/g0WAIKVXojIBIF45yYBPagJGcgw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} hasBin: true @@ -2721,8 +2583,8 @@ packages: resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} engines: {node: '>=4'} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} minimatch@3.1.5: @@ -2746,11 +2608,11 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - multitars@1.0.0: - resolution: {integrity: sha512-H/J4fMLedtudftaYMOg7ajzLYgT3/rwbWVJbqr/iUgB8DQztn38ys5HOqI1CzSxx8QhXXwOOnnBvd4v3jG5+Mg==} + multitars@1.0.2: + resolution: {integrity: sha512-6GwVw5eLi9sThdtlS4PKwC7yRLaf45pYhIEzKBHdKxi+YOXGKFX8acIniH+Uh/+k9mS2lQOupTccjoe5r0/1IQ==} - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2765,9 +2627,9 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} node-exports-info@1.6.2: resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} @@ -2780,14 +2642,10 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.51: - resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + node-releases@2.0.54: + resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==} engines: {node: '>=18'} - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - npm-package-arg@11.0.3: resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} engines: {node: ^16.14.0 || >=18.0.0} @@ -2795,8 +2653,8 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} - ob1@0.84.4: - resolution: {integrity: sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==} + ob1@0.84.5: + resolution: {integrity: sha512-aH9RkoZc7w/90HBamFxTw8ZLFr05wXS+iOnvmrgo53Ep8Pyrm5FieQSaPIVROkfFVQISeD/zo92fes26TOwe+A==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} object-assign@4.1.1: @@ -2831,8 +2689,8 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - obug@2.1.3: - resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} on-finished@2.3.0: @@ -2847,9 +2705,6 @@ packages: resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} engines: {node: '>= 0.8'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} engines: {node: '>=4'} @@ -2866,34 +2721,18 @@ packages: resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} engines: {node: '>=6'} - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + own-keys@1.0.2: + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} engines: {node: '>= 0.4'} - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - parse-png@2.1.0: resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} engines: {node: '>=10'} @@ -2909,10 +2748,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2934,14 +2769,10 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - plist@3.1.1: resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} engines: {node: '>=10.4.0'} @@ -2954,8 +2785,8 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.5.19: - resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2966,8 +2797,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.9.5: - resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} engines: {node: '>=14'} hasBin: true @@ -2997,9 +2828,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - queue@6.0.2: - resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -3007,10 +2835,10 @@ packages: react-devtools-core@6.1.5: resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==} - react-dom@19.2.3: - resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} peerDependencies: - react: ^19.2.3 + react: ^19.2.8 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -3018,12 +2846,12 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-native@0.86.0: - resolution: {integrity: sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w==} + react-native@0.86.3: + resolution: {integrity: sha512-JR5s3bM9ezud+Mw24GlNXNfthqPIKwrQgPPJcam+L97t2sKjjEavhCzBn+fyqZZRcM5+XlhYxpTxVkK7e1n38Q==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} hasBin: true peerDependencies: - '@react-native/jest-preset': 0.86.0 + '@react-native/jest-preset': 0.86.3 '@types/react': ^19.1.1 react: ^19.2.3 peerDependenciesMeta: @@ -3036,8 +2864,8 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react@19.2.3: - resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} engines: {node: '>=0.10.0'} reflect.getprototypeof@1.0.10: @@ -3081,10 +2909,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -3109,8 +2933,8 @@ packages: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} - rolldown@1.1.5: - resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + rolldown@1.2.6: + resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -3129,8 +2953,13 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} - sax@1.6.0: - resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + sandbox-cli-detector@0.2.0: + resolution: {integrity: sha512-4lyHX0ZU0AZKwjgZ1InxZAa3PNpyEb8rOQ+Zss1ReYmhNzW0Q+h1zE5nvniXN0HaAWZaZE1zgVNEirb0R7LmNg==} + engines: {node: '>=18.18'} + hasBin: true + + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} engines: {node: '>=11.0.0'} saxes@6.0.0: @@ -3216,10 +3045,6 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - slugify@1.6.9: resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} engines: {node: '>=8.0.0'} @@ -3239,13 +3064,6 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -3279,8 +3097,8 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + string.prototype.matchall@4.1.0: + resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==} engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: @@ -3310,10 +3128,6 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - structured-headers@0.4.1: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} @@ -3352,38 +3166,34 @@ packages: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} - terser@5.49.0: - resolution: {integrity: sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==} + terser@5.51.2: + resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} engines: {node: '>=10'} hasBin: true - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} - tldts-core@7.4.8: - resolution: {integrity: sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==} + tldts-core@7.4.11: + resolution: {integrity: sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==} - tldts@7.4.8: - resolution: {integrity: sha512-htwgN/8KRB3z3vnC0BOETVh2m499g5GmyTK9Wq5JBLX3FNz6tSBveAd+fQhzy9hkjif8vy2jwDMR1sGhLtZl2A==} + tldts@7.4.11: + resolution: {integrity: sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==} hasBin: true tmpl@1.0.5: @@ -3422,17 +3232,10 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -3457,16 +3260,16 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -3474,9 +3277,9 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@7.28.0: - resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} - engines: {node: '>=20.18.1'} + undici@8.10.1: + resolution: {integrity: sha512-YQ3WlbqjYMmNpdvDH64jAgLjxuAR9+649calDWhbshYaeQGO2bR4nI94ORJmwI3J9YhoKQnpyGOK+0zlWS5N5Q==} + engines: {node: '>=22.19.0'} unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} @@ -3498,8 +3301,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.2: + resolution: {integrity: sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -3524,13 +3327,13 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@8.1.5: - resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + vite@8.2.2: + resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 + '@vitejs/devtools': ^0.4.0 || ^0.5.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -3567,20 +3370,20 @@ packages: yaml: optional: true - vitest@4.1.10: - resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.10 - '@vitest/browser-preview': 4.1.10 - '@vitest/browser-webdriverio': 4.1.10 - '@vitest/coverage-istanbul': 4.1.10 - '@vitest/coverage-v8': 4.1.10 - '@vitest/ui': 4.1.10 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3639,6 +3442,10 @@ packages: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3673,15 +3480,8 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - ws@7.5.11: - resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==} + ws@7.5.13: + resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3692,8 +3492,8 @@ packages: utf-8-validate: optional: true - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3760,30 +3560,25 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zod@4.5.4: + resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} snapshots: - '@asamuzakjp/css-color@5.1.11': + '@asamuzakjp/css-color@6.0.7': dependencies: - '@asamuzakjp/generational-cache': 1.0.1 - '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 - '@asamuzakjp/dom-selector@7.1.1': + '@asamuzakjp/dom-selector@8.3.2': dependencies: - '@asamuzakjp/generational-cache': 1.0.1 - '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - - '@asamuzakjp/generational-cache@1.0.1': {} - - '@asamuzakjp/nwsapi@2.3.9': {} + lru-cache: 11.5.2 '@babel/code-frame@7.29.7': dependencies: @@ -3793,72 +3588,72 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7': + '@babel/core@7.29.7(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.29.7': + '@babel/generator@7.29.8': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-compilation-targets@7.29.7': dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.6 + browserslist: 4.28.8 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@8.1.1) '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7)': + '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.12 transitivePeerDependencies: @@ -3866,57 +3661,57 @@ snapshots: '@babel/helper-globals@7.29.7': {} - '@babel/helper-member-expression-to-functions@7.29.7': + '@babel/helper-member-expression-to-functions@7.29.7(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.29.7': + '@babel/helper-module-imports@7.29.7(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7)': + '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-wrap-function': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/helper-wrap-function': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@8.1.1) '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + '@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -3926,369 +3721,291 @@ snapshots: '@babel/helper-validator-option@7.29.7': {} - '@babel/helper-wrap-function@7.29.7': + '@babel/helper-wrap-function@7.29.7(supports-color@8.1.1)': dependencies: '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helpers@7.29.7': dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 - '@babel/parser@7.29.7': + '@babel/parser@7.29.8': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 - '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-export-default-from@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - - '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-export-default-from@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-proposal-export-default-from@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-export-default-from@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - optional: true - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - optional: true - - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-globals': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/traverse': 7.29.8(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-react-jsx-development@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-react-jsx-development@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-module-imports': 7.29.7 + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/types': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-react-pure-annotations@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.29.7 - '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -4297,22 +4014,22 @@ snapshots: '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.8(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - debug: 4.4.3 + '@babel/types': 7.29.8 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/types@7.29.7': + '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 @@ -4321,17 +4038,17 @@ snapshots: dependencies: css-tree: 3.2.1 - '@csstools/color-helpers@6.1.0': {} + '@csstools/color-helpers@6.1.1': {} - '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 6.1.0 - '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/color-helpers': 6.1.1 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -4339,139 +4056,108 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.6(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.12(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 '@csstools/css-tokenizer@4.0.0': {} - '@emnapi/core@1.11.1': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.11.1': + '@eslint-community/eslint-utils@4.10.1(eslint@10.9.1(supports-color@8.1.1))': dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5)': - dependencies: - eslint: 9.39.5 + eslint: 10.9.1(supports-color@8.1.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2': + '@eslint/config-array@0.23.5(supports-color@8.1.1)': dependencies: - '@eslint/object-schema': 2.1.7 - debug: 4.4.3 - minimatch: 3.1.5 + '@eslint/object-schema': 3.0.5 + debug: 4.4.3(supports-color@8.1.1) + minimatch: 10.2.6 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.2': + '@eslint/config-helpers@0.7.0': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 - '@eslint/core@0.17.0': + '@eslint/core@1.2.1': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.6': - dependencies: - ajv: 6.15.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.3.0 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.5': {} - - '@eslint/object-schema@2.1.7': {} + '@eslint/object-schema@3.0.5': {} - '@eslint/plugin-kit@0.4.1': + '@eslint/plugin-kit@0.7.2': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 levn: 0.4.1 '@exodus/bytes@1.15.1': {} - '@expo/cli@57.0.8(@expo/dom-webview@57.0.1)(expo-constants@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)))(expo-font@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(expo@57.0.6)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@expo/cli@57.0.21(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(expo-constants@57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1))(expo-font@57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8))(expo@57.0.19)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)': dependencies: '@expo/code-signing-certificates': 0.0.6 - '@expo/config': 57.0.5(typescript@5.9.3) - '@expo/config-plugins': 57.0.5(typescript@5.9.3) - '@expo/devcert': 1.2.1 - '@expo/env': 2.4.2 - '@expo/image-utils': 0.11.3(typescript@5.9.3) - '@expo/inline-modules': 0.1.3(typescript@5.9.3) + '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/devcert': 1.2.1(supports-color@8.1.1) + '@expo/env': 2.4.3(supports-color@8.1.1) + '@expo/image-utils': 0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/inline-modules': 0.1.7(@typescript/typescript6@6.0.2)(supports-color@8.1.1) '@expo/json-file': 11.0.1 - '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - '@expo/metro': 56.0.0 - '@expo/metro-config': 57.0.5(expo@57.0.6)(typescript@5.9.3) - '@expo/metro-file-map': 57.0.1 + '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + '@expo/metro': 56.0.2(supports-color@8.1.1) + '@expo/metro-config': 57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.19)(supports-color@8.1.1) + '@expo/metro-file-map': 57.0.2(supports-color@8.1.1) '@expo/osascript': 2.7.1 '@expo/package-manager': 1.13.1 '@expo/plist': 0.8.1 - '@expo/prebuild-config': 57.0.7(typescript@5.9.3) - '@expo/require-utils': 57.0.3(typescript@5.9.3) - '@expo/router-server': 57.0.3(expo-constants@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)))(expo-font@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(expo-server@57.0.1)(expo@57.0.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@expo/prebuild-config': 57.0.15(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/router-server': 57.0.9(expo-constants@57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1))(expo-font@57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8))(expo-server@57.0.3)(expo@57.0.19)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@8.1.1) '@expo/schema-utils': 57.0.2 '@expo/spawn-async': 1.8.0 - '@expo/ws-tunnel': 2.0.0(ws@8.21.0) + '@expo/ws-tunnel': 2.0.0(ws@8.21.3) '@expo/xcpretty': 4.4.4 - '@react-native/dev-middleware': 0.86.0 + '@react-native/dev-middleware': 0.86.3(supports-color@8.1.1) accepts: 1.3.8 - agent-cli-detector: 0.1.2 + agent-cli-detector: 0.1.7 arg: 5.0.2 bplist-creator: 0.1.0 bplist-parser: 0.3.2 chalk: 4.1.2 ci-info: 3.9.0 - compression: 1.8.1 - connect: 3.7.0 - debug: 4.4.3 + compression: 1.8.1(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) dnssd-advertise: 1.1.6 - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - expo-server: 57.0.1 + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + expo-server: 57.0.3 fetch-nodeshim: 0.4.10 getenv: 2.0.0 glob: 13.0.6 lan-network: 0.2.1 - multitars: 1.0.0 + multitars: 1.0.2 node-forge: 1.4.0 npm-package-arg: 11.0.3 ora: 3.4.0 - picomatch: 4.0.5 + picomatch: 4.0.7 pretty-format: 29.7.0 progress: 2.0.3 prompts: 2.4.2 resolve-from: 5.0.0 + sandbox-cli-detector: 0.2.0 semver: 7.8.5 - send: 0.19.2 + send: 0.19.2(supports-color@8.1.1) slugify: 1.6.9 stacktrace-parser: 0.1.11 structured-headers: 0.4.1 terminal-link: 2.1.1 toqr: 0.1.1 wrap-ansi: 7.0.0 - ws: 8.21.0 + ws: 8.21.3 zod: 3.25.76 optionalDependencies: - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) transitivePeerDependencies: - '@expo/dom-webview' - '@expo/metro-runtime' @@ -4485,69 +4171,70 @@ snapshots: - typescript - utf-8-validate - '@expo/cli@57.0.8(@expo/dom-webview@57.0.1)(expo-constants@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)))(expo-font@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(expo@57.0.6)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + '@expo/cli@57.0.21(@expo/dom-webview@57.0.1)(expo-constants@57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1))(expo-font@57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8))(expo@57.0.19)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2)': dependencies: '@expo/code-signing-certificates': 0.0.6 - '@expo/config': 57.0.5(typescript@6.0.3) - '@expo/config-plugins': 57.0.5(typescript@6.0.3) - '@expo/devcert': 1.2.1 - '@expo/env': 2.4.2 - '@expo/image-utils': 0.11.3(typescript@6.0.3) - '@expo/inline-modules': 0.1.3(typescript@6.0.3) + '@expo/config': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config-plugins': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/devcert': 1.2.1(supports-color@8.1.1) + '@expo/env': 2.4.3(supports-color@8.1.1) + '@expo/image-utils': 0.11.5(supports-color@8.1.1)(typescript@7.0.2) + '@expo/inline-modules': 0.1.7(supports-color@8.1.1)(typescript@7.0.2) '@expo/json-file': 11.0.1 - '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - '@expo/metro': 56.0.0 - '@expo/metro-config': 57.0.5(expo@57.0.6)(typescript@6.0.3) - '@expo/metro-file-map': 57.0.1 + '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + '@expo/metro': 56.0.2(supports-color@8.1.1) + '@expo/metro-config': 57.0.12(expo@57.0.19)(supports-color@8.1.1)(typescript@7.0.2) + '@expo/metro-file-map': 57.0.2(supports-color@8.1.1) '@expo/osascript': 2.7.1 '@expo/package-manager': 1.13.1 '@expo/plist': 0.8.1 - '@expo/prebuild-config': 57.0.7(typescript@6.0.3) - '@expo/require-utils': 57.0.3(typescript@6.0.3) - '@expo/router-server': 57.0.3(expo-constants@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)))(expo-font@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(expo-server@57.0.1)(expo@57.0.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@expo/prebuild-config': 57.0.15(supports-color@8.1.1)(typescript@7.0.2) + '@expo/require-utils': 57.0.5(supports-color@8.1.1)(typescript@7.0.2) + '@expo/router-server': 57.0.9(expo-constants@57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1))(expo-font@57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8))(expo-server@57.0.3)(expo@57.0.19)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@8.1.1) '@expo/schema-utils': 57.0.2 '@expo/spawn-async': 1.8.0 - '@expo/ws-tunnel': 2.0.0(ws@8.21.0) + '@expo/ws-tunnel': 2.0.0(ws@8.21.3) '@expo/xcpretty': 4.4.4 - '@react-native/dev-middleware': 0.86.0 + '@react-native/dev-middleware': 0.86.3(supports-color@8.1.1) accepts: 1.3.8 - agent-cli-detector: 0.1.2 + agent-cli-detector: 0.1.7 arg: 5.0.2 bplist-creator: 0.1.0 bplist-parser: 0.3.2 chalk: 4.1.2 ci-info: 3.9.0 - compression: 1.8.1 - connect: 3.7.0 - debug: 4.4.3 + compression: 1.8.1(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) dnssd-advertise: 1.1.6 - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - expo-server: 57.0.1 + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2) + expo-server: 57.0.3 fetch-nodeshim: 0.4.10 getenv: 2.0.0 glob: 13.0.6 lan-network: 0.2.1 - multitars: 1.0.0 + multitars: 1.0.2 node-forge: 1.4.0 npm-package-arg: 11.0.3 ora: 3.4.0 - picomatch: 4.0.5 + picomatch: 4.0.7 pretty-format: 29.7.0 progress: 2.0.3 prompts: 2.4.2 resolve-from: 5.0.0 + sandbox-cli-detector: 0.2.0 semver: 7.8.5 - send: 0.19.2 + send: 0.19.2(supports-color@8.1.1) slugify: 1.6.9 stacktrace-parser: 0.1.11 structured-headers: 0.4.1 terminal-link: 2.1.1 toqr: 0.1.1 wrap-ansi: 7.0.0 - ws: 8.21.0 + ws: 8.21.3 zod: 3.25.76 optionalDependencies: - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) transitivePeerDependencies: - '@expo/dom-webview' - '@expo/metro-runtime' @@ -4565,15 +4252,15 @@ snapshots: dependencies: node-forge: 1.4.0 - '@expo/config-plugins@57.0.5(typescript@5.9.3)': + '@expo/config-plugins@57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: '@expo/config-types': 57.0.2 '@expo/json-file': 11.0.1 '@expo/plist': 0.8.1 - '@expo/require-utils': 57.0.3(typescript@5.9.3) + '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) '@expo/sdk-runtime-versions': 1.0.0 chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) getenv: 2.0.0 glob: 13.0.6 semver: 7.8.5 @@ -4584,15 +4271,15 @@ snapshots: - supports-color - typescript - '@expo/config-plugins@57.0.5(typescript@6.0.3)': + '@expo/config-plugins@57.0.9(supports-color@8.1.1)(typescript@7.0.2)': dependencies: '@expo/config-types': 57.0.2 '@expo/json-file': 11.0.1 '@expo/plist': 0.8.1 - '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/require-utils': 57.0.5(supports-color@8.1.1)(typescript@7.0.2) '@expo/sdk-runtime-versions': 1.0.0 chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) getenv: 2.0.0 glob: 13.0.6 semver: 7.8.5 @@ -4605,12 +4292,12 @@ snapshots: '@expo/config-types@57.0.2': {} - '@expo/config@57.0.5(typescript@5.9.3)': + '@expo/config@57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@expo/config-plugins': 57.0.5(typescript@5.9.3) + '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) '@expo/config-types': 57.0.2 '@expo/json-file': 11.0.1 - '@expo/require-utils': 57.0.3(typescript@5.9.3) + '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) deepmerge: 4.3.1 getenv: 2.0.0 glob: 13.0.6 @@ -4621,12 +4308,12 @@ snapshots: - supports-color - typescript - '@expo/config@57.0.5(typescript@6.0.3)': + '@expo/config@57.0.9(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@expo/config-plugins': 57.0.5(typescript@6.0.3) + '@expo/config-plugins': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) '@expo/config-types': 57.0.2 '@expo/json-file': 11.0.1 - '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/require-utils': 57.0.5(supports-color@8.1.1)(typescript@7.0.2) deepmerge: 4.3.1 getenv: 2.0.0 glob: 13.0.6 @@ -4637,55 +4324,55 @@ snapshots: - supports-color - typescript - '@expo/devcert@1.2.1': + '@expo/devcert@1.2.1(supports-color@8.1.1)': dependencies: '@expo/sudo-prompt': 9.3.2 - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@expo/devtools@57.0.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + '@expo/devtools@57.0.1(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)': dependencies: chalk: 4.1.2 optionalDependencies: - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) - '@expo/dom-webview@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + '@expo/dom-webview@57.0.1(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)': dependencies: - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) - '@expo/env@2.4.2': + '@expo/env@2.4.3(supports-color@8.1.1)': dependencies: chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) getenv: 2.0.0 transitivePeerDependencies: - supports-color '@expo/expo-modules-macros-plugin@0.6.1': {} - '@expo/fingerprint@0.20.5': + '@expo/fingerprint@0.20.12(supports-color@8.1.1)': dependencies: - '@expo/env': 2.4.2 + '@expo/env': 2.4.3(supports-color@8.1.1) '@expo/spawn-async': 1.8.0 arg: 5.0.2 chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) getenv: 2.0.0 glob: 13.0.6 ignore: 5.3.2 - minimatch: 10.2.5 + minimatch: 10.2.6 resolve-from: 5.0.0 semver: 7.8.5 transitivePeerDependencies: - supports-color - '@expo/image-utils@0.11.3(typescript@5.9.3)': + '@expo/image-utils@0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@expo/require-utils': 57.0.3(typescript@5.9.3) + '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) '@expo/spawn-async': 1.8.0 chalk: 4.1.2 getenv: 2.0.0 @@ -4696,9 +4383,9 @@ snapshots: - supports-color - typescript - '@expo/image-utils@0.11.3(typescript@6.0.3)': + '@expo/image-utils@0.11.5(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/require-utils': 57.0.5(supports-color@8.1.1)(typescript@7.0.2) '@expo/spawn-async': 1.8.0 chalk: 4.1.2 getenv: 2.0.0 @@ -4709,16 +4396,16 @@ snapshots: - supports-color - typescript - '@expo/inline-modules@0.1.3(typescript@5.9.3)': + '@expo/inline-modules@0.1.7(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@expo/config-plugins': 57.0.5(typescript@5.9.3) + '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript - '@expo/inline-modules@0.1.3(typescript@6.0.3)': + '@expo/inline-modules@0.1.7(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@expo/config-plugins': 57.0.5(typescript@6.0.3) + '@expo/config-plugins': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) transitivePeerDependencies: - supports-color - typescript @@ -4728,100 +4415,100 @@ snapshots: '@babel/code-frame': 7.29.7 json5: 2.2.3 - '@expo/local-build-cache-provider@57.0.4(typescript@5.9.3)': + '@expo/local-build-cache-provider@57.0.8(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@expo/config': 57.0.5(typescript@5.9.3) + '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) chalk: 4.1.2 transitivePeerDependencies: - supports-color - typescript - '@expo/local-build-cache-provider@57.0.4(typescript@6.0.3)': + '@expo/local-build-cache-provider@57.0.8(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@expo/config': 57.0.5(typescript@6.0.3) + '@expo/config': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) chalk: 4.1.2 transitivePeerDependencies: - supports-color - typescript - '@expo/log-box@57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + '@expo/log-box@57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)': dependencies: - '@expo/dom-webview': 57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/dom-webview': 57.0.1(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) anser: 1.4.10 - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) stacktrace-parser: 0.1.11 - '@expo/metro-config@57.0.5(expo@57.0.6)(typescript@5.9.3)': + '@expo/metro-config@57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.19)(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@expo/config': 57.0.5(typescript@5.9.3) - '@expo/env': 2.4.2 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 + '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/env': 2.4.3(supports-color@8.1.1) '@expo/json-file': 11.0.1 - '@expo/metro': 56.0.0 - '@expo/require-utils': 57.0.3(typescript@5.9.3) + '@expo/metro': 56.0.2(supports-color@8.1.1) + '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) '@expo/spawn-async': 1.8.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/remapping': 2.3.5 - '@jridgewell/sourcemap-codec': 1.5.5 - browserslist: 4.28.6 + '@jridgewell/sourcemap-codec': 1.6.0 + browserslist: 4.28.8 chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) getenv: 2.0.0 glob: 13.0.6 hermes-parser: 0.36.1 jsc-safe-url: 0.2.4 - lightningcss: 1.32.0 - picomatch: 4.0.5 - postcss: 8.5.19 + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.26 resolve-from: 5.0.0 optionalDependencies: - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) transitivePeerDependencies: - bufferutil - supports-color - typescript - utf-8-validate - '@expo/metro-config@57.0.5(expo@57.0.6)(typescript@6.0.3)': + '@expo/metro-config@57.0.12(expo@57.0.19)(supports-color@8.1.1)(typescript@7.0.2)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@expo/config': 57.0.5(typescript@6.0.3) - '@expo/env': 2.4.2 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 + '@expo/config': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/env': 2.4.3(supports-color@8.1.1) '@expo/json-file': 11.0.1 - '@expo/metro': 56.0.0 - '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/metro': 56.0.2(supports-color@8.1.1) + '@expo/require-utils': 57.0.5(supports-color@8.1.1)(typescript@7.0.2) '@expo/spawn-async': 1.8.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/remapping': 2.3.5 - '@jridgewell/sourcemap-codec': 1.5.5 - browserslist: 4.28.6 + '@jridgewell/sourcemap-codec': 1.6.0 + browserslist: 4.28.8 chalk: 4.1.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) getenv: 2.0.0 glob: 13.0.6 hermes-parser: 0.36.1 jsc-safe-url: 0.2.4 - lightningcss: 1.32.0 - picomatch: 4.0.5 - postcss: 8.5.19 + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.26 resolve-from: 5.0.0 optionalDependencies: - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2) transitivePeerDependencies: - bufferutil - supports-color - typescript - utf-8-validate - '@expo/metro-file-map@57.0.1': + '@expo/metro-file-map@57.0.2(supports-color@8.1.1)': dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) fb-watchman: 2.0.2 invariant: 2.2.4 jest-worker: 29.7.0 @@ -4830,22 +4517,22 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/metro@56.0.0': - dependencies: - metro: 0.84.4 - metro-babel-transformer: 0.84.4 - metro-cache: 0.84.4 - metro-cache-key: 0.84.4 - metro-config: 0.84.4 - metro-core: 0.84.4 - metro-file-map: 0.84.4 - metro-minify-terser: 0.84.4 - metro-resolver: 0.84.4 - metro-runtime: 0.84.4 - metro-source-map: 0.84.4 - metro-symbolicate: 0.84.4 - metro-transform-plugins: 0.84.4 - metro-transform-worker: 0.84.4 + '@expo/metro@56.0.2(supports-color@8.1.1)': + dependencies: + metro: 0.84.5(supports-color@8.1.1) + metro-babel-transformer: 0.84.5(supports-color@8.1.1) + metro-cache: 0.84.5(supports-color@8.1.1) + metro-cache-key: 0.84.5 + metro-config: 0.84.5(supports-color@8.1.1) + metro-core: 0.84.5 + metro-file-map: 0.84.5(supports-color@8.1.1) + metro-minify-terser: 0.84.5 + metro-resolver: 0.84.5 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5(supports-color@8.1.1) + metro-symbolicate: 0.84.5(supports-color@8.1.1) + metro-transform-plugins: 0.84.5(supports-color@8.1.1) + metro-transform-worker: 0.84.5(supports-color@8.1.1) transitivePeerDependencies: - bufferutil - supports-color @@ -4866,72 +4553,72 @@ snapshots: '@expo/plist@0.8.1': dependencies: - '@xmldom/xmldom': 0.8.13 + '@xmldom/xmldom': 0.8.15 base64-js: 1.5.1 xmlbuilder: 15.1.1 - '@expo/prebuild-config@57.0.7(typescript@5.9.3)': + '@expo/prebuild-config@57.0.15(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@expo/config': 57.0.5(typescript@5.9.3) - '@expo/config-plugins': 57.0.5(typescript@5.9.3) + '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) '@expo/config-types': 57.0.2 - '@expo/image-utils': 0.11.3(typescript@5.9.3) + '@expo/image-utils': 0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) '@expo/json-file': 11.0.1 - '@react-native/normalize-colors': 0.86.0 - debug: 4.4.3 - expo-modules-autolinking: 57.0.7(typescript@5.9.3) + '@react-native/normalize-colors': 0.86.3 + debug: 4.4.3(supports-color@8.1.1) + expo-modules-autolinking: 57.0.12(@typescript/typescript6@6.0.2)(supports-color@8.1.1) resolve-from: 5.0.0 semver: 7.8.5 transitivePeerDependencies: - supports-color - typescript - '@expo/prebuild-config@57.0.7(typescript@6.0.3)': + '@expo/prebuild-config@57.0.15(supports-color@8.1.1)(typescript@7.0.2)': dependencies: - '@expo/config': 57.0.5(typescript@6.0.3) - '@expo/config-plugins': 57.0.5(typescript@6.0.3) + '@expo/config': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config-plugins': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) '@expo/config-types': 57.0.2 - '@expo/image-utils': 0.11.3(typescript@6.0.3) + '@expo/image-utils': 0.11.5(supports-color@8.1.1)(typescript@7.0.2) '@expo/json-file': 11.0.1 - '@react-native/normalize-colors': 0.86.0 - debug: 4.4.3 - expo-modules-autolinking: 57.0.7(typescript@6.0.3) + '@react-native/normalize-colors': 0.86.3 + debug: 4.4.3(supports-color@8.1.1) + expo-modules-autolinking: 57.0.12(supports-color@8.1.1)(typescript@7.0.2) resolve-from: 5.0.0 semver: 7.8.5 transitivePeerDependencies: - supports-color - typescript - '@expo/require-utils@57.0.3(typescript@5.9.3)': + '@expo/require-utils@57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.7 - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) optionalDependencies: - typescript: 5.9.3 + typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@expo/require-utils@57.0.3(typescript@6.0.3)': + '@expo/require-utils@57.0.5(supports-color@8.1.1)(typescript@7.0.2)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.7 - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - supports-color - '@expo/router-server@57.0.3(expo-constants@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)))(expo-font@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(expo-server@57.0.1)(expo@57.0.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@expo/router-server@57.0.9(expo-constants@57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1))(expo-font@57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8))(expo-server@57.0.3)(expo@57.0.19)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@8.1.1)': dependencies: - debug: 4.4.3 - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - expo-constants: 57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)) - expo-font: 57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - expo-server: 57.0.1 - react: 19.2.3 + debug: 4.4.3(supports-color@8.1.1) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + expo-constants: 57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1) + expo-font: 57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + expo-server: 57.0.3 + react: 19.2.8 optionalDependencies: - react-dom: 19.2.3(react@19.2.3) + react-dom: 19.2.8(react@19.2.8) transitivePeerDependencies: - supports-color @@ -4945,15 +4632,15 @@ snapshots: '@expo/sudo-prompt@9.3.2': {} - '@expo/ws-tunnel@2.0.0(ws@8.21.0)': + '@expo/ws-tunnel@2.0.0(ws@8.21.3)': dependencies: - ws: 8.21.0 + ws: 8.21.3 '@expo/xcpretty@4.4.4': dependencies: '@babel/code-frame': 7.29.7 chalk: 4.1.2 - js-yaml: 4.3.0 + js-yaml: 4.3.2 '@humanfs/core@0.19.2': dependencies: @@ -4973,78 +4660,22 @@ snapshots: '@isaacs/ttlcache@1.4.1': {} - '@istanbuljs/load-nyc-config@1.1.0': - dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.15.0 - resolve-from: 5.0.0 - optional: true - - '@istanbuljs/schema@0.1.6': - optional: true - - '@jest/create-cache-key-function@29.7.0': - dependencies: - '@jest/types': 29.6.3 - optional: true - - '@jest/environment@29.7.0': - dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 26.1.1 - jest-mock: 29.7.0 - optional: true - - '@jest/fake-timers@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 26.1.1 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 - optional: true - '@jest/schemas@29.6.3': dependencies: - '@sinclair/typebox': 0.27.10 - - '@jest/transform@29.7.0': - dependencies: - '@babel/core': 7.29.7 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.31 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.8 - pirates: 4.0.7 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - optional: true + '@sinclair/typebox': 0.27.12 '@jest/types@29.6.3': dependencies: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 26.1.1 + '@types/node': 26.4.0 '@types/yargs': 17.0.35 chalk: 4.1.2 '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': @@ -5059,212 +4690,148 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@tybys/wasm-util': 0.10.3 - optional: true - - '@oxc-project/types@0.139.0': {} + '@oxc-project/types@0.147.0': {} '@pkgr/core@0.3.6': {} - '@react-native/assets-registry@0.86.0': {} + '@react-native/assets-registry@0.86.3': {} - '@react-native/babel-plugin-codegen@0.86.0(@babel/core@7.29.7)': + '@react-native/babel-plugin-codegen@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.7 - '@react-native/codegen': 0.86.0(@babel/core@7.29.7) + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@react-native/codegen': 0.86.3(@babel/core@7.29.7(supports-color@8.1.1)) transitivePeerDependencies: - '@babel/core' - supports-color - '@react-native/codegen@0.86.0(@babel/core@7.29.7)': + '@react-native/codegen@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/parser': 7.29.8 hermes-parser: 0.36.0 invariant: 2.2.4 nullthrows: 1.1.1 tinyglobby: 0.2.17 yargs: 17.7.3 - '@react-native/community-cli-plugin@0.86.0': + '@react-native/community-cli-plugin@0.86.3(supports-color@8.1.1)': dependencies: - '@react-native/dev-middleware': 0.86.0 - debug: 4.4.3 + '@react-native/dev-middleware': 0.86.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) invariant: 2.2.4 - metro: 0.84.4 - metro-config: 0.84.4 - metro-core: 0.84.4 + metro: 0.84.5(supports-color@8.1.1) + metro-config: 0.84.5(supports-color@8.1.1) + metro-core: 0.84.5 semver: 7.8.5 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@react-native/debugger-frontend@0.86.0': {} + '@react-native/debugger-frontend@0.86.3': {} - '@react-native/debugger-shell@0.86.0': + '@react-native/debugger-shell@0.86.3(supports-color@8.1.1)': dependencies: cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) fb-dotslash: 0.5.8 transitivePeerDependencies: - supports-color - '@react-native/dev-middleware@0.86.0': + '@react-native/dev-middleware@0.86.3(supports-color@8.1.1)': dependencies: '@isaacs/ttlcache': 1.4.1 - '@react-native/debugger-frontend': 0.86.0 - '@react-native/debugger-shell': 0.86.0 - chrome-launcher: 0.15.2 - chromium-edge-launcher: 0.3.0 - connect: 3.7.0 - debug: 4.4.3 + '@react-native/debugger-frontend': 0.86.3 + '@react-native/debugger-shell': 0.86.3(supports-color@8.1.1) + chrome-launcher: 0.15.2(supports-color@8.1.1) + chromium-edge-launcher: 0.3.0(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) invariant: 2.2.4 nullthrows: 1.1.1 open: 7.4.2 - serve-static: 1.16.3 - ws: 7.5.11 + serve-static: 1.16.3(supports-color@8.1.1) + ws: 7.5.13 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@react-native/gradle-plugin@0.86.0': {} - - '@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3)': - dependencies: - '@jest/create-cache-key-function': 29.7.0 - '@react-native/js-polyfills': 0.86.0 - babel-jest: 29.7.0(@babel/core@7.29.7) - jest-environment-node: 29.7.0 - react: 19.2.3 - regenerator-runtime: 0.13.11 - transitivePeerDependencies: - - '@babel/core' - - supports-color - optional: true + '@react-native/gradle-plugin@0.86.3': {} - '@react-native/js-polyfills@0.86.0': {} + '@react-native/js-polyfills@0.86.3': {} - '@react-native/normalize-colors@0.86.0': {} + '@react-native/normalize-colors@0.86.3': {} - '@react-native/virtualized-lists@0.86.0(@types/react@19.2.17)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + '@react-native/virtualized-lists@0.86.3(@types/react@19.2.18)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@rolldown/binding-android-arm64@1.1.5': + '@rolldown/binding-android-arm-eabi@1.2.6': optional: true - '@rolldown/binding-darwin-arm64@1.1.5': + '@rolldown/binding-android-arm64@1.2.6': optional: true - '@rolldown/binding-darwin-x64@1.1.5': + '@rolldown/binding-darwin-arm64@1.2.6': optional: true - '@rolldown/binding-freebsd-x64@1.1.5': + '@rolldown/binding-darwin-x64@1.2.6': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + '@rolldown/binding-freebsd-x64@1.2.6': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.5': + '@rolldown/binding-linux-arm-gnueabihf@1.2.6': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.5': + '@rolldown/binding-linux-arm64-gnu@1.2.6': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.5': + '@rolldown/binding-linux-arm64-musl@1.2.6': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.5': + '@rolldown/binding-linux-ppc64-gnu@1.2.6': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.5': + '@rolldown/binding-linux-s390x-gnu@1.2.6': optional: true - '@rolldown/binding-linux-x64-musl@1.1.5': + '@rolldown/binding-linux-x64-gnu@1.2.6': optional: true - '@rolldown/binding-openharmony-arm64@1.1.5': + '@rolldown/binding-linux-x64-musl@1.2.6': optional: true - '@rolldown/binding-wasm32-wasi@1.1.5': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@rolldown/binding-openharmony-arm64@1.2.6': optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.5': + '@rolldown/binding-win32-arm64-msvc@1.2.6': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.5': + '@rolldown/binding-win32-x64-msvc@1.2.6': optional: true '@rolldown/pluginutils@1.0.1': {} '@rtsao/scc@1.1.0': {} - '@sinclair/typebox@0.27.10': {} - - '@sinonjs/commons@3.0.1': - dependencies: - type-detect: 4.0.8 - optional: true - - '@sinonjs/fake-timers@10.3.0': - dependencies: - '@sinonjs/commons': 3.0.1 - optional: true + '@sinclair/typebox@0.27.12': {} '@standard-schema/spec@1.1.0': {} - '@tybys/wasm-util@0.10.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - optional: true - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.7 - optional: true - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - optional: true - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.7 - optional: true - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -5272,12 +4839,9 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/estree@1.0.9': {} + '@types/esrecurse@4.3.1': {} - '@types/graceful-fs@4.1.9': - dependencies: - '@types/node': 26.1.1 - optional: true + '@types/estree@1.0.9': {} '@types/istanbul-lib-coverage@2.0.6': {} @@ -5293,160 +4857,221 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@26.1.1': + '@types/node@26.4.0': dependencies: undici-types: 8.3.0 - '@types/react@19.2.17': + '@types/react@19.2.18': dependencies: csstype: 3.2.3 - '@types/stack-utils@2.0.3': - optional: true - '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.64.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/type-utils': 8.64.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/utils': 8.64.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.64.0 - eslint: 9.39.5 - ignore: 7.0.6 + '@typescript-eslint/parser': 8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/type-utils': 8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/utils': 8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/visitor-keys': 8.69.0 + eslint: 10.9.1(supports-color@8.1.1) + ignore: 7.0.8 natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) + typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/parser@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.64.0 - debug: 4.4.3 - eslint: 9.39.5 - typescript: 5.9.3 + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@typescript-eslint/visitor-keys': 8.69.0 + debug: 4.4.3(supports-color@8.1.1) + eslint: 10.9.1(supports-color@8.1.1) + typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.64.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.69.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@5.9.3) - '@typescript-eslint/types': 8.64.0 - debug: 4.4.3 - typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils': 8.69.0(@typescript/typescript6@6.0.2) + '@typescript-eslint/types': 8.69.0 + debug: 4.4.3(supports-color@8.1.1) + typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.64.0': + '@typescript-eslint/scope-manager@8.69.0': dependencies: - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/visitor-keys': 8.64.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 - '@typescript-eslint/tsconfig-utils@8.64.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.69.0(@typescript/typescript6@6.0.2)': dependencies: - typescript: 5.9.3 + typescript: '@typescript/typescript6@6.0.2' - '@typescript-eslint/type-utils@8.64.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.64.0(eslint@9.39.5)(typescript@5.9.3) - debug: 4.4.3 - eslint: 9.39.5 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@typescript-eslint/utils': 8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) + eslint: 10.9.1(supports-color@8.1.1) + ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) + typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.64.0': {} + '@typescript-eslint/types@8.69.0': {} - '@typescript-eslint/typescript-estree@8.64.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.69.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1)': dependencies: - '@typescript-eslint/project-service': 8.64.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@5.9.3) - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/visitor-keys': 8.64.0 - debug: 4.4.3 - minimatch: 10.2.5 + '@typescript-eslint/project-service': 8.69.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@typescript-eslint/tsconfig-utils': 8.69.0(@typescript/typescript6@6.0.2) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 + debug: 4.4.3(supports-color@8.1.1) + minimatch: 10.2.6 semver: 7.8.5 tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) + typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.64.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/utils@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5) - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) - eslint: 9.39.5 - typescript: 5.9.3 + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1)) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + eslint: 10.9.1(supports-color@8.1.1) + typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.64.0': + '@typescript-eslint/visitor-keys@8.69.0': dependencies: - '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/types': 8.69.0 eslint-visitor-keys: 5.0.1 - '@ungap/structured-clone@1.3.3': {} + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true - '@vitest/expect@4.1.10': + '@typescript/typescript6@6.0.2': + dependencies: + '@typescript/old': typescript@6.0.3 + + '@ungap/structured-clone@1.4.0': {} + + '@vitest/expect@4.1.11': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 chai: 6.2.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.1.1)(terser@5.49.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@26.4.0)(terser@5.51.2)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.10 + '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.5(@types/node@26.1.1)(terser@5.49.0)(yaml@2.9.0) + vite: 8.2.2(@types/node@26.4.0)(terser@5.51.2)(yaml@2.9.0) - '@vitest/pretty-format@4.1.10': + '@vitest/pretty-format@4.1.11': dependencies: - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/runner@4.1.10': + '@vitest/runner@4.1.11': dependencies: - '@vitest/utils': 4.1.10 + '@vitest/utils': 4.1.11 pathe: 2.0.3 - '@vitest/snapshot@4.1.10': + '@vitest/snapshot@4.1.11': dependencies: - '@vitest/pretty-format': 4.1.10 - '@vitest/utils': 4.1.10 + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.10': {} + '@vitest/spy@4.1.11': {} - '@vitest/utils@4.1.10': + '@vitest/utils@4.1.11': dependencies: - '@vitest/pretty-format': 4.1.10 + '@vitest/pretty-format': 4.1.11 convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@xmldom/xmldom@0.8.13': {} + '@xmldom/xmldom@0.8.15': {} - '@xmldom/xmldom@0.9.10': {} + '@xmldom/xmldom@0.9.12': {} abort-controller@3.0.0: dependencies: @@ -5460,17 +5085,17 @@ snapshots: accepts@2.0.0: dependencies: mime-types: 3.0.2 - negotiator: 1.0.0 + negotiator: 1.1.0 - acorn-jsx@5.3.2(acorn@8.17.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 - acorn@8.17.0: {} + acorn@8.18.0: {} agent-base@7.1.4: {} - agent-cli-detector@0.1.2: {} + agent-cli-detector@0.1.7: {} ajv@6.15.0: dependencies: @@ -5499,19 +5124,8 @@ snapshots: ansi-styles@5.2.0: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.2 - optional: true - arg@5.0.2: {} - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - optional: true - argparse@2.0.1: {} array-buffer-byte-length@1.0.2: @@ -5591,66 +5205,33 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - babel-jest@29.7.0(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.29.7) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - optional: true - - babel-plugin-istanbul@6.1.1: - dependencies: - '@babel/helper-plugin-utils': 7.29.7 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.6 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - optional: true - - babel-plugin-jest-hoist@29.6.3: - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.28.0 - optional: true - - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@babel/compat-data': 7.29.7 - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) - core-js-compat: 3.49.0 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + core-js-compat: 3.50.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color babel-plugin-react-compiler@1.0.0: dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 babel-plugin-react-native-web@0.21.2: {} @@ -5662,98 +5243,71 @@ snapshots: dependencies: hermes-parser: 0.36.1 - babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7): + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7(supports-color@8.1.1)): dependencies: - '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) transitivePeerDependencies: - '@babel/core' - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) - optional: true - - babel-preset-expo@57.0.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.6)(react-refresh@0.14.2): - dependencies: - '@babel/generator': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-pure-annotations': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) - '@react-native/babel-plugin-codegen': 0.86.0(@babel/core@7.29.7) + babel-preset-expo@57.0.10(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo@57.0.19)(react-refresh@0.14.2)(supports-color@8.1.1): + dependencies: + '@babel/generator': 7.29.8 + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) + '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-pure-annotations': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@react-native/babel-plugin-codegen': 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) babel-plugin-react-compiler: 1.0.0 babel-plugin-react-native-web: 0.21.2 babel-plugin-syntax-hermes-parser: 0.36.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) - debug: 4.4.3 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7(supports-color@8.1.1)) + debug: 4.4.3(supports-color@8.1.1) react-refresh: 0.14.2 optionalDependencies: '@babel/runtime': 7.29.7 - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) transitivePeerDependencies: - '@babel/core' - supports-color - babel-preset-jest@29.6.3(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) - optional: true - balanced-match@1.0.2: {} balanced-match@4.0.4: {} base64-js@1.5.1: {} - baseline-browser-mapping@2.10.43: {} + baseline-browser-mapping@2.11.20: {} bidi-js@1.0.3: dependencies: @@ -5773,12 +5327,12 @@ snapshots: dependencies: big-integer: 1.6.52 - brace-expansion@1.1.16: + brace-expansion@1.1.18: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.7: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -5786,13 +5340,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.6: + browserslist@4.28.8: dependencies: - baseline-browser-mapping: 2.10.43 - caniuse-lite: 1.0.30001805 - electron-to-chromium: 1.5.389 - node-releases: 2.0.51 - update-browserslist-db: 1.2.3(browserslist@4.28.6) + baseline-browser-mapping: 2.11.20 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.418 + node-releases: 2.0.54 + update-browserslist-db: 1.3.2(browserslist@4.28.8) bser@2.1.1: dependencies: @@ -5819,14 +5373,9 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - callsites@3.1.0: {} - - camelcase@5.3.1: - optional: true - camelcase@6.3.0: {} - caniuse-lite@1.0.30001805: {} + caniuse-lite@1.0.30001810: {} chai@6.2.2: {} @@ -5841,21 +5390,21 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chrome-launcher@0.15.2: + chrome-launcher@0.15.2(supports-color@8.1.1): dependencies: - '@types/node': 26.1.1 + '@types/node': 26.4.0 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 - lighthouse-logger: 1.4.2 + lighthouse-logger: 1.4.2(supports-color@8.1.1) transitivePeerDependencies: - supports-color - chromium-edge-launcher@0.3.0: + chromium-edge-launcher@0.3.0(supports-color@8.1.1): dependencies: - '@types/node': 26.1.1 + '@types/node': 26.4.0 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 - lighthouse-logger: 1.4.2 + lighthouse-logger: 1.4.2(supports-color@8.1.1) mkdirp: 1.0.4 transitivePeerDependencies: - supports-color @@ -5900,11 +5449,11 @@ snapshots: dependencies: mime-db: 1.54.0 - compression@1.8.1: + compression@1.8.1(supports-color@8.1.1): dependencies: bytes: 3.1.2 compressible: 2.0.18 - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) negotiator: 0.6.4 on-headers: 1.1.0 safe-buffer: 5.2.1 @@ -5914,20 +5463,22 @@ snapshots: concat-map@0.0.1: {} - connect@3.7.0: + connect@3.7.0(supports-color@8.1.1): dependencies: - debug: 2.6.9 - finalhandler: 1.1.2 + debug: 2.6.9(supports-color@8.1.1) + finalhandler: 1.1.2(supports-color@8.1.1) parseurl: 1.3.3 utils-merge: 1.0.1 transitivePeerDependencies: - supports-color + content-type@2.1.0: {} + convert-source-map@2.0.0: {} - core-js-compat@3.49.0: + core-js-compat@3.50.0: dependencies: - browserslist: 4.28.6 + browserslist: 4.28.8 cross-spawn@7.0.6: dependencies: @@ -5967,17 +5518,23 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - debug@2.6.9: + debug@2.6.9(supports-color@8.1.1): dependencies: ms: 2.0.0 + optionalDependencies: + supports-color: 8.1.1 - debug@3.2.7: + debug@3.2.7(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 - debug@4.4.3: + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 decimal.js@10.6.0: {} @@ -6021,7 +5578,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.389: {} + electron-to-chromium@1.5.418: {} emoji-regex@8.0.0: {} @@ -6029,7 +5586,7 @@ snapshots: encodeurl@2.0.0: {} - enhanced-resolve@5.24.2: + enhanced-resolve@5.24.5: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -6087,7 +5644,7 @@ snapshots: object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 - own-keys: 1.0.1 + own-keys: 1.0.2 regexp.prototype.flags: 1.5.4 safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 @@ -6127,7 +5684,7 @@ snapshots: iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 - es-module-lexer@2.3.1: {} + es-module-lexer@2.3.2: {} es-object-atoms@1.1.2: dependencies: @@ -6159,35 +5716,32 @@ snapshots: escape-string-regexp@1.0.5: {} - escape-string-regexp@2.0.0: - optional: true - escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.39.5): + eslint-compat-utils@0.5.1(eslint@10.9.1(supports-color@8.1.1)): dependencies: - eslint: 9.39.5 + eslint: 10.9.1(supports-color@8.1.1) semver: 7.8.5 - eslint-config-prettier@9.1.2(eslint@9.39.5): + eslint-config-prettier@9.1.2(eslint@10.9.1(supports-color@8.1.1)): dependencies: - eslint: 9.39.5 + eslint: 10.9.1(supports-color@8.1.1) - eslint-config-universe@15.2.0(eslint@9.39.5)(prettier@3.9.5)(typescript@5.9.3): + eslint-config-universe@16.0.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(prettier@3.9.6)(supports-color@8.1.1): dependencies: - '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/parser': 8.64.0(eslint@9.39.5)(typescript@5.9.3) - eslint: 9.39.5 - eslint-config-prettier: 9.1.2(eslint@9.39.5) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5) - eslint-plugin-n: 17.24.0(eslint@9.39.5)(typescript@5.9.3) - eslint-plugin-node: 11.1.0(eslint@9.39.5) - eslint-plugin-prettier: 5.5.6(eslint-config-prettier@9.1.2(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.5) - eslint-plugin-react: 7.37.5(eslint@9.39.5) - eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5) + '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1))(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + '@typescript-eslint/parser': 8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + eslint: 10.9.1(supports-color@8.1.1) + eslint-config-prettier: 9.1.2(eslint@10.9.1(supports-color@8.1.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + eslint-plugin-n: 17.24.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1)) + eslint-plugin-node: 11.1.0(eslint@10.9.1(supports-color@8.1.1)) + eslint-plugin-prettier: 5.5.6(eslint-config-prettier@9.1.2(eslint@10.9.1(supports-color@8.1.1)))(eslint@10.9.1(supports-color@8.1.1))(prettier@3.9.6) + eslint-plugin-react: 7.37.5(eslint@10.9.1(supports-color@8.1.1)) + eslint-plugin-react-hooks: 7.1.1(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) globals: 16.5.0 optionalDependencies: - prettier: 3.9.5 + prettier: 3.9.6 transitivePeerDependencies: - '@types/eslint' - eslint-import-resolver-typescript @@ -6195,49 +5749,49 @@ snapshots: - supports-color - typescript - eslint-import-resolver-node@0.3.10: + eslint-import-resolver-node@0.3.10(supports-color@8.1.1): dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) is-core-module: 2.16.2 resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.5): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 8.64.0(eslint@9.39.5)(typescript@5.9.3) - eslint: 9.39.5 - eslint-import-resolver-node: 0.3.10 + '@typescript-eslint/parser': 8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) + eslint: 10.9.1(supports-color@8.1.1) + eslint-import-resolver-node: 0.3.10(supports-color@8.1.1) transitivePeerDependencies: - supports-color - eslint-plugin-es-x@7.8.0(eslint@9.39.5): + eslint-plugin-es-x@7.8.0(eslint@10.9.1(supports-color@8.1.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1)) '@eslint-community/regexpp': 4.12.2 - eslint: 9.39.5 - eslint-compat-utils: 0.5.1(eslint@9.39.5) + eslint: 10.9.1(supports-color@8.1.1) + eslint-compat-utils: 0.5.1(eslint@10.9.1(supports-color@8.1.1)) - eslint-plugin-es@3.0.1(eslint@9.39.5): + eslint-plugin-es@3.0.1(eslint@10.9.1(supports-color@8.1.1)): dependencies: - eslint: 9.39.5 + eslint: 10.9.1(supports-color@8.1.1) eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1))(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 9.39.5 - eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.5) + eslint: 10.9.1(supports-color@8.1.1) + eslint-import-resolver-node: 0.3.10(supports-color@8.1.1) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1))(eslint-import-resolver-node@0.3.10(supports-color@8.1.1))(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -6249,58 +5803,58 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.64.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/parser': 8.69.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-n@17.24.0(eslint@9.39.5)(typescript@5.9.3): + eslint-plugin-n@17.24.0(@typescript/typescript6@6.0.2)(eslint@10.9.1(supports-color@8.1.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5) - enhanced-resolve: 5.24.2 - eslint: 9.39.5 - eslint-plugin-es-x: 7.8.0(eslint@9.39.5) - get-tsconfig: 4.14.0 + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1)) + enhanced-resolve: 5.24.5 + eslint: 10.9.1(supports-color@8.1.1) + eslint-plugin-es-x: 7.8.0(eslint@10.9.1(supports-color@8.1.1)) + get-tsconfig: 4.14.3 globals: 15.15.0 globrex: 0.1.2 ignore: 5.3.2 semver: 7.8.5 - ts-declaration-location: 1.0.7(typescript@5.9.3) + ts-declaration-location: 1.0.7(@typescript/typescript6@6.0.2) transitivePeerDependencies: - typescript - eslint-plugin-node@11.1.0(eslint@9.39.5): + eslint-plugin-node@11.1.0(eslint@10.9.1(supports-color@8.1.1)): dependencies: - eslint: 9.39.5 - eslint-plugin-es: 3.0.1(eslint@9.39.5) + eslint: 10.9.1(supports-color@8.1.1) + eslint-plugin-es: 3.0.1(eslint@10.9.1(supports-color@8.1.1)) eslint-utils: 2.1.0 ignore: 5.3.2 minimatch: 3.1.5 resolve: 1.22.12 semver: 6.3.1 - eslint-plugin-prettier@5.5.6(eslint-config-prettier@9.1.2(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.5): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@9.1.2(eslint@10.9.1(supports-color@8.1.1)))(eslint@10.9.1(supports-color@8.1.1))(prettier@3.9.6): dependencies: - eslint: 9.39.5 - prettier: 3.9.5 + eslint: 10.9.1(supports-color@8.1.1) + prettier: 3.9.6 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 optionalDependencies: - eslint-config-prettier: 9.1.2(eslint@9.39.5) + eslint-config-prettier: 9.1.2(eslint@10.9.1(supports-color@8.1.1)) - eslint-plugin-react-hooks@7.1.1(eslint@9.39.5): + eslint-plugin-react-hooks@7.1.1(eslint@10.9.1(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 - eslint: 9.39.5 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/parser': 7.29.8 + eslint: 10.9.1(supports-color@8.1.1) hermes-parser: 0.25.1 - zod: 4.4.3 - zod-validation-error: 4.0.2(zod@4.4.3) + zod: 4.5.4 + zod-validation-error: 4.0.2(zod@4.5.4) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.5): + eslint-plugin-react@7.37.5(eslint@10.9.1(supports-color@8.1.1)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -6308,7 +5862,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.4.0 - eslint: 9.39.5 + eslint: 10.9.1(supports-color@8.1.1) estraverse: 5.3.0 hasown: 2.0.4 jsx-ast-utils: 3.3.5 @@ -6319,11 +5873,13 @@ snapshots: prop-types: 15.8.1 resolve: 2.0.0-next.7 semver: 6.3.1 - string.prototype.matchall: 4.0.12 + string.prototype.matchall: 4.1.0 string.prototype.repeat: 1.0.0 - eslint-scope@8.4.0: + eslint-scope@9.1.2: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -6335,32 +5891,27 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.1: {} - eslint-visitor-keys@5.0.1: {} - eslint@9.39.5: + eslint@10.9.1(supports-color@8.1.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@8.1.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.6 - '@eslint/js': 9.39.5 - '@eslint/plugin-kit': 0.4.1 + '@eslint/config-array': 0.23.5(supports-color@8.1.1) + '@eslint/config-helpers': 0.7.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.9 ajv: 6.15.0 - chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -6371,21 +5922,17 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 + minimatch: 10.2.6 natural-compare: 1.4.0 optionator: 0.9.4 transitivePeerDependencies: - supports-color - espree@10.4.0: + espree@11.2.0: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) - eslint-visitor-keys: 4.2.1 - - esprima@4.0.1: - optional: true + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 5.0.1 esquery@1.7.0: dependencies: @@ -6409,56 +5956,56 @@ snapshots: expect-type@1.4.0: {} - expo-asset@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3): + expo-asset@57.0.16(@typescript/typescript6@6.0.2)(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1): dependencies: - '@expo/image-utils': 0.11.3(typescript@5.9.3) - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - expo-constants: 57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)) - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + '@expo/image-utils': 0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + expo-constants: 57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript - expo-asset@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3): + expo-asset@57.0.16(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2): dependencies: - '@expo/image-utils': 0.11.3(typescript@6.0.3) - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - expo-constants: 57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)) - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + '@expo/image-utils': 0.11.5(supports-color@8.1.1)(typescript@7.0.2) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2) + expo-constants: 57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript - expo-constants@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)): + expo-constants@57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@expo/env': 2.4.2 - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + '@expo/env': 2.4.3(supports-color@8.1.1) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - expo-file-system@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)): + expo-file-system@57.0.6(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1)): dependencies: - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) - expo-font@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + expo-font@57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8): dependencies: - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) fontfaceobserver: 2.3.0 - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) - expo-keep-awake@57.0.1(expo@57.0.6)(react@19.2.3): + expo-keep-awake@57.0.1(expo@57.0.19)(react@19.2.8): dependencies: - expo: 57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - react: 19.2.3 + expo: 57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + react: 19.2.8 - expo-modules-autolinking@57.0.7(typescript@5.9.3): + expo-modules-autolinking@57.0.12(@typescript/typescript6@6.0.2)(supports-color@8.1.1): dependencies: - '@expo/require-utils': 57.0.3(typescript@5.9.3) + '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1) '@expo/spawn-async': 1.8.0 chalk: 4.1.2 commander: 7.2.0 @@ -6466,9 +6013,9 @@ snapshots: - supports-color - typescript - expo-modules-autolinking@57.0.7(typescript@6.0.3): + expo-modules-autolinking@57.0.12(supports-color@8.1.1)(typescript@7.0.2): dependencies: - '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/require-utils': 57.0.5(supports-color@8.1.1)(typescript@7.0.2) '@expo/spawn-async': 1.8.0 chalk: 4.1.2 commander: 7.2.0 @@ -6476,49 +6023,49 @@ snapshots: - supports-color - typescript - expo-modules-core@57.0.5(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + expo-modules-core@57.0.15(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8): dependencies: '@expo/expo-modules-macros-plugin': 0.6.1 - expo-modules-jsi: 57.0.3(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)) + expo-modules-jsi: 57.0.7(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1)) invariant: 2.2.4 - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) - expo-modules-jsi@57.0.3(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)): + expo-modules-jsi@57.0.7(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1)): dependencies: - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) - expo-server@57.0.1: {} + expo-server@57.0.3: {} - expo@57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3): + expo@57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1): dependencies: '@babel/runtime': 7.29.7 - '@expo/cli': 57.0.8(@expo/dom-webview@57.0.1)(expo-constants@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)))(expo-font@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(expo@57.0.6)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@expo/config': 57.0.5(typescript@5.9.3) - '@expo/config-plugins': 57.0.5(typescript@5.9.3) - '@expo/devtools': 57.0.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - '@expo/fingerprint': 0.20.5 - '@expo/local-build-cache-provider': 57.0.4(typescript@5.9.3) - '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - '@expo/metro': 56.0.0 - '@expo/metro-config': 57.0.5(expo@57.0.6)(typescript@5.9.3) - '@ungap/structured-clone': 1.3.3 - babel-preset-expo: 57.0.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.6)(react-refresh@0.14.2) - expo-asset: 57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - expo-constants: 57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)) - expo-file-system: 57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)) - expo-font: 57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - expo-keep-awake: 57.0.1(expo@57.0.6)(react@19.2.3) - expo-modules-autolinking: 57.0.7(typescript@5.9.3) - expo-modules-core: 57.0.5(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/cli': 57.0.21(@expo/dom-webview@57.0.1)(@typescript/typescript6@6.0.2)(expo-constants@57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1))(expo-font@57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8))(expo@57.0.19)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/devtools': 57.0.1(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + '@expo/fingerprint': 0.20.12(supports-color@8.1.1) + '@expo/local-build-cache-provider': 57.0.8(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + '@expo/metro': 56.0.2(supports-color@8.1.1) + '@expo/metro-config': 57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.19)(supports-color@8.1.1) + '@ungap/structured-clone': 1.4.0 + babel-preset-expo: 57.0.10(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo@57.0.19)(react-refresh@0.14.2)(supports-color@8.1.1) + expo-asset: 57.0.16(@typescript/typescript6@6.0.2)(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1) + expo-constants: 57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1) + expo-file-system: 57.0.6(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1)) + expo-font: 57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + expo-keep-awake: 57.0.1(expo@57.0.19)(react@19.2.8) + expo-modules-autolinking: 57.0.12(@typescript/typescript6@6.0.2)(supports-color@8.1.1) + expo-modules-core: 57.0.15(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) pretty-format: 29.7.0 - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) react-refresh: 0.14.2 whatwg-url-minimum: 0.1.2 optionalDependencies: - '@expo/dom-webview': 57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - react-dom: 19.2.3(react@19.2.3) + '@expo/dom-webview': 57.0.1(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + react-dom: 19.2.8(react@19.2.8) transitivePeerDependencies: - '@babel/core' - bufferutil @@ -6530,35 +6077,35 @@ snapshots: - typescript - utf-8-validate - expo@57.0.6(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3): + expo@57.0.19(@babel/core@7.29.7(supports-color@8.1.1))(@expo/dom-webview@57.0.1)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2): dependencies: '@babel/runtime': 7.29.7 - '@expo/cli': 57.0.8(@expo/dom-webview@57.0.1)(expo-constants@57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)))(expo-font@57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(expo@57.0.6)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - '@expo/config': 57.0.5(typescript@6.0.3) - '@expo/config-plugins': 57.0.5(typescript@6.0.3) - '@expo/devtools': 57.0.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - '@expo/fingerprint': 0.20.5 - '@expo/local-build-cache-provider': 57.0.4(typescript@6.0.3) - '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - '@expo/metro': 56.0.0 - '@expo/metro-config': 57.0.5(expo@57.0.6)(typescript@6.0.3) - '@ungap/structured-clone': 1.3.3 - babel-preset-expo: 57.0.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.6)(react-refresh@0.14.2) - expo-asset: 57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) - expo-constants: 57.0.5(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)) - expo-file-system: 57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3)) - expo-font: 57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - expo-keep-awake: 57.0.1(expo@57.0.6)(react@19.2.3) - expo-modules-autolinking: 57.0.7(typescript@6.0.3) - expo-modules-core: 57.0.5(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/cli': 57.0.21(@expo/dom-webview@57.0.1)(expo-constants@57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1))(expo-font@57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8))(expo@57.0.19)(react-dom@19.2.8(react@19.2.8))(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/config-plugins': 57.0.9(supports-color@8.1.1)(typescript@7.0.2) + '@expo/devtools': 57.0.1(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + '@expo/fingerprint': 0.20.12(supports-color@8.1.1) + '@expo/local-build-cache-provider': 57.0.8(supports-color@8.1.1)(typescript@7.0.2) + '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + '@expo/metro': 56.0.2(supports-color@8.1.1) + '@expo/metro-config': 57.0.12(expo@57.0.19)(supports-color@8.1.1)(typescript@7.0.2) + '@ungap/structured-clone': 1.4.0 + babel-preset-expo: 57.0.10(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo@57.0.19)(react-refresh@0.14.2)(supports-color@8.1.1) + expo-asset: 57.0.16(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8)(supports-color@8.1.1)(typescript@7.0.2) + expo-constants: 57.0.17(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(supports-color@8.1.1) + expo-file-system: 57.0.6(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1)) + expo-font: 57.0.3(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + expo-keep-awake: 57.0.1(expo@57.0.19)(react@19.2.8) + expo-modules-autolinking: 57.0.12(supports-color@8.1.1)(typescript@7.0.2) + expo-modules-core: 57.0.15(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) pretty-format: 29.7.0 - react: 19.2.3 - react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3) + react: 19.2.8 + react-native: 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1) react-refresh: 0.14.2 whatwg-url-minimum: 0.1.2 optionalDependencies: - '@expo/dom-webview': 57.0.1(expo@57.0.6)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - react-dom: 19.2.3(react@19.2.3) + '@expo/dom-webview': 57.0.1(expo@57.0.19)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) + react-dom: 19.2.8(react@19.2.8) transitivePeerDependencies: - '@babel/core' - bufferutil @@ -6586,9 +6133,9 @@ snapshots: dependencies: bser: 2.1.1 - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 fetch-nodeshim@0.4.10: {} @@ -6600,9 +6147,9 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.1.2: + finalhandler@1.1.2(supports-color@8.1.1): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) encodeurl: 1.0.2 escape-html: 1.0.3 on-finished: 2.3.0 @@ -6612,12 +6159,6 @@ snapshots: transitivePeerDependencies: - supports-color - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - optional: true - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -6625,10 +6166,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.2 + flatted: 3.4.4 keyv: 4.5.4 - flatted@3.4.2: {} + flatted@3.4.4: {} flow-enums-runtime@0.0.6: {} @@ -6640,9 +6181,6 @@ snapshots: fresh@0.5.2: {} - fs.realpath@1.0.0: - optional: true - fsevents@2.3.3: optional: true @@ -6681,9 +6219,6 @@ snapshots: hasown: 2.0.4 math-intrinsics: 1.1.0 - get-package-type@0.1.0: - optional: true - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -6695,7 +6230,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.14.0: + get-tsconfig@4.14.3: dependencies: resolve-pkg-maps: 1.0.0 @@ -6707,22 +6242,10 @@ snapshots: glob@13.0.6: dependencies: - minimatch: 10.2.5 + minimatch: 10.2.6 minipass: 7.1.3 path-scurry: 2.0.2 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - optional: true - - globals@14.0.0: {} - globals@15.15.0: {} globals@16.5.0: {} @@ -6762,7 +6285,7 @@ snapshots: dependencies: function-bind: 1.1.2 - hermes-compiler@250829098.0.14: {} + hermes-compiler@250829098.0.17: {} hermes-estree@0.25.1: {} @@ -6806,34 +6329,19 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@8.1.1): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color ignore@5.3.2: {} - ignore@7.0.6: {} - - image-size@1.2.1: - dependencies: - queue: 6.0.2 - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 + ignore@7.0.8: {} imurmurhash@0.1.4: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - optional: true - inherits@2.0.4: {} internal-slot@1.1.0: @@ -6972,20 +6480,6 @@ snapshots: isexe@2.0.0: {} - istanbul-lib-coverage@3.2.2: - optional: true - - istanbul-lib-instrument@5.2.1: - dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 - '@istanbuljs/schema': 0.1.6 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - optional: true - iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -6995,62 +6489,12 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 - jest-environment-node@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 26.1.1 - jest-mock: 29.7.0 - jest-util: 29.7.0 - optional: true - jest-get-type@29.6.3: {} - jest-haste-map@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.9 - '@types/node': 26.1.1 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - jest-worker: 29.7.0 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 - optional: true - - jest-message-util@29.7.0: - dependencies: - '@babel/code-frame': 7.29.7 - '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - stack-utils: 2.0.6 - optional: true - - jest-mock@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 26.1.1 - jest-util: 29.7.0 - optional: true - - jest-regex-util@29.6.3: - optional: true - jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 26.1.1 + '@types/node': 26.4.0 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -7067,7 +6511,7 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 26.1.1 + '@types/node': 26.4.0 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -7076,24 +6520,18 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.15.0: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - optional: true - - js-yaml@4.3.0: + js-yaml@4.3.2: dependencies: argparse: 2.0.1 jsc-safe-url@0.2.4: {} - jsdom@29.1.1: + jsdom@30.0.1: dependencies: - '@asamuzakjp/css-color': 5.1.11 - '@asamuzakjp/dom-selector': 7.1.1 + '@asamuzakjp/css-color': 6.0.7 + '@asamuzakjp/dom-selector': 8.3.2 '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.6(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.12(css-tree@3.2.1) '@exodus/bytes': 1.15.1 css-tree: 3.2.1 data-urls: 7.0.0 @@ -7105,11 +6543,11 @@ snapshots: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.2 - undici: 7.28.0 + undici: 8.10.1 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 + whatwg-url: 17.1.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' @@ -7150,66 +6588,61 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lighthouse-logger@1.4.2: + lighthouse-logger@1.4.2(supports-color@8.1.1): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) marky: 1.3.0 transitivePeerDependencies: - supports-color - lightningcss-android-arm64@1.32.0: + lightningcss-android-arm64@1.33.0: optional: true - lightningcss-darwin-arm64@1.32.0: + lightningcss-darwin-arm64@1.33.0: optional: true - lightningcss-darwin-x64@1.32.0: + lightningcss-darwin-x64@1.33.0: optional: true - lightningcss-freebsd-x64@1.32.0: + lightningcss-freebsd-x64@1.33.0: optional: true - lightningcss-linux-arm-gnueabihf@1.32.0: + lightningcss-linux-arm-gnueabihf@1.33.0: optional: true - lightningcss-linux-arm64-gnu@1.32.0: + lightningcss-linux-arm64-gnu@1.33.0: optional: true - lightningcss-linux-arm64-musl@1.32.0: + lightningcss-linux-arm64-musl@1.33.0: optional: true - lightningcss-linux-x64-gnu@1.32.0: + lightningcss-linux-x64-gnu@1.33.0: optional: true - lightningcss-linux-x64-musl@1.32.0: + lightningcss-linux-x64-musl@1.33.0: optional: true - lightningcss-win32-arm64-msvc@1.32.0: + lightningcss-win32-arm64-msvc@1.33.0: optional: true - lightningcss-win32-x64-msvc@1.32.0: + lightningcss-win32-x64-msvc@1.33.0: optional: true - lightningcss@1.32.0: + lightningcss@1.33.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - optional: true + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 locate-path@6.0.0: dependencies: @@ -7217,8 +6650,6 @@ snapshots: lodash.debounce@4.0.8: {} - lodash.merge@4.6.2: {} - lodash.throttle@4.1.1: {} log-symbols@2.2.0: @@ -7239,7 +6670,7 @@ snapshots: magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 makeerror@1.0.12: dependencies: @@ -7255,53 +6686,53 @@ snapshots: merge-stream@2.0.0: {} - metro-babel-transformer@0.84.4: + metro-babel-transformer@0.84.5(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) flow-enums-runtime: 0.0.6 hermes-parser: 0.35.0 - metro-cache-key: 0.84.4 + metro-cache-key: 0.84.5 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - metro-cache-key@0.84.4: + metro-cache-key@0.84.5: dependencies: flow-enums-runtime: 0.0.6 - metro-cache@0.84.4: + metro-cache@0.84.5(supports-color@8.1.1): dependencies: exponential-backoff: 3.1.3 flow-enums-runtime: 0.0.6 - https-proxy-agent: 7.0.6 - metro-core: 0.84.4 + https-proxy-agent: 7.0.6(supports-color@8.1.1) + metro-core: 0.84.5 transitivePeerDependencies: - supports-color - metro-config@0.84.4: + metro-config@0.84.5(supports-color@8.1.1): dependencies: - connect: 3.7.0 + connect: 3.7.0(supports-color@8.1.1) flow-enums-runtime: 0.0.6 jest-validate: 29.7.0 - metro: 0.84.4 - metro-cache: 0.84.4 - metro-core: 0.84.4 - metro-runtime: 0.84.4 + metro: 0.84.5(supports-color@8.1.1) + metro-cache: 0.84.5(supports-color@8.1.1) + metro-core: 0.84.5 + metro-runtime: 0.84.5 yaml: 2.9.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro-core@0.84.4: + metro-core@0.84.5: dependencies: flow-enums-runtime: 0.0.6 lodash.throttle: 4.1.1 - metro-resolver: 0.84.4 + metro-resolver: 0.84.5 - metro-file-map@0.84.4: + metro-file-map@0.84.5(supports-color@8.1.1): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) fb-watchman: 2.0.2 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 @@ -7313,116 +6744,115 @@ snapshots: transitivePeerDependencies: - supports-color - metro-minify-terser@0.84.4: + metro-minify-terser@0.84.5: dependencies: flow-enums-runtime: 0.0.6 - terser: 5.49.0 + terser: 5.51.2 - metro-resolver@0.84.4: + metro-resolver@0.84.5: dependencies: flow-enums-runtime: 0.0.6 - metro-runtime@0.84.4: + metro-runtime@0.84.5: dependencies: '@babel/runtime': 7.29.7 flow-enums-runtime: 0.0.6 - metro-source-map@0.84.4: + metro-source-map@0.84.5(supports-color@8.1.1): dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-symbolicate: 0.84.4 + metro-symbolicate: 0.84.5(supports-color@8.1.1) nullthrows: 1.1.1 - ob1: 0.84.4 + ob1: 0.84.5 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-symbolicate@0.84.4: + metro-symbolicate@0.84.5(supports-color@8.1.1): dependencies: flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-source-map: 0.84.4 + metro-source-map: 0.84.5(supports-color@8.1.1) nullthrows: 1.1.1 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-transform-plugins@0.84.4: + metro-transform-plugins@0.84.5(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - metro-transform-worker@0.84.4: + metro-transform-worker@0.84.5(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 flow-enums-runtime: 0.0.6 - metro: 0.84.4 - metro-babel-transformer: 0.84.4 - metro-cache: 0.84.4 - metro-cache-key: 0.84.4 - metro-minify-terser: 0.84.4 - metro-source-map: 0.84.4 - metro-transform-plugins: 0.84.4 + metro: 0.84.5(supports-color@8.1.1) + metro-babel-transformer: 0.84.5(supports-color@8.1.1) + metro-cache: 0.84.5(supports-color@8.1.1) + metro-cache-key: 0.84.5 + metro-minify-terser: 0.84.5 + metro-source-map: 0.84.5(supports-color@8.1.1) + metro-transform-plugins: 0.84.5(supports-color@8.1.1) nullthrows: 1.1.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro@0.84.4: + metro@0.84.5(supports-color@8.1.1): dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 accepts: 2.0.0 ci-info: 2.0.0 - connect: 3.7.0 - debug: 4.4.3 + connect: 3.7.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) error-stack-parser: 2.1.4 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 hermes-parser: 0.35.0 - image-size: 1.2.1 invariant: 2.2.4 jest-worker: 29.7.0 jsc-safe-url: 0.2.4 lodash.throttle: 4.1.1 - metro-babel-transformer: 0.84.4 - metro-cache: 0.84.4 - metro-cache-key: 0.84.4 - metro-config: 0.84.4 - metro-core: 0.84.4 - metro-file-map: 0.84.4 - metro-resolver: 0.84.4 - metro-runtime: 0.84.4 - metro-source-map: 0.84.4 - metro-symbolicate: 0.84.4 - metro-transform-plugins: 0.84.4 - metro-transform-worker: 0.84.4 + metro-babel-transformer: 0.84.5(supports-color@8.1.1) + metro-cache: 0.84.5(supports-color@8.1.1) + metro-cache-key: 0.84.5 + metro-config: 0.84.5(supports-color@8.1.1) + metro-core: 0.84.5 + metro-file-map: 0.84.5(supports-color@8.1.1) + metro-resolver: 0.84.5 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5(supports-color@8.1.1) + metro-symbolicate: 0.84.5(supports-color@8.1.1) + metro-transform-plugins: 0.84.5(supports-color@8.1.1) + metro-transform-worker: 0.84.5(supports-color@8.1.1) mime-types: 3.0.2 nullthrows: 1.1.1 serialize-error: 2.1.0 source-map: 0.5.7 throat: 5.0.0 - ws: 7.5.11 + ws: 7.5.13 yargs: 17.7.3 transitivePeerDependencies: - bufferutil @@ -7450,13 +6880,13 @@ snapshots: mimic-fn@1.2.0: {} - minimatch@10.2.5: + minimatch@10.2.6: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.9 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.16 + brace-expansion: 1.1.18 minimist@1.2.8: {} @@ -7468,9 +6898,9 @@ snapshots: ms@2.1.3: {} - multitars@1.0.0: {} + multitars@1.0.2: {} - nanoid@3.3.16: {} + nanoid@3.3.18: {} natural-compare@1.4.0: {} @@ -7478,7 +6908,9 @@ snapshots: negotiator@0.6.4: {} - negotiator@1.0.0: {} + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 node-exports-info@1.6.2: dependencies: @@ -7491,10 +6923,7 @@ snapshots: node-int64@0.4.0: {} - node-releases@2.0.51: {} - - normalize-path@3.0.0: - optional: true + node-releases@2.0.54: {} npm-package-arg@11.0.3: dependencies: @@ -7505,7 +6934,7 @@ snapshots: nullthrows@1.1.1: {} - ob1@0.84.4: + ob1@0.84.5: dependencies: flow-enums-runtime: 0.0.6 @@ -7551,7 +6980,7 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.2 - obug@2.1.3: {} + obug@2.1.4: {} on-finished@2.3.0: dependencies: @@ -7563,11 +6992,6 @@ snapshots: on-headers@1.1.0: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - optional: true - onetime@2.0.1: dependencies: mimic-fn: 1.2.0 @@ -7595,37 +7019,21 @@ snapshots: strip-ansi: 5.2.0 wcwidth: 1.0.1 - own-keys@1.0.1: + own-keys@1.0.2: dependencies: + call-bound: 1.0.4 get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - optional: true - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - optional: true - p-locate@5.0.0: dependencies: p-limit: 3.1.0 - p-try@2.2.0: - optional: true - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - parse-png@2.1.0: dependencies: pngjs: 3.4.0 @@ -7638,9 +7046,6 @@ snapshots: path-exists@4.0.0: {} - path-is-absolute@1.0.1: - optional: true - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -7656,14 +7061,11 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.5: {} - - pirates@4.0.7: - optional: true + picomatch@4.0.7: {} plist@3.1.1: dependencies: - '@xmldom/xmldom': 0.9.10 + '@xmldom/xmldom': 0.9.12 base64-js: 1.5.1 xmlbuilder: 15.1.1 @@ -7671,9 +7073,9 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.5.19: + postcss@8.5.26: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -7683,7 +7085,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.9.5: {} + prettier@3.9.6: {} pretty-format@29.7.0: dependencies: @@ -7712,38 +7114,34 @@ snapshots: punycode@2.3.1: {} - queue@6.0.2: - dependencies: - inherits: 2.0.4 - range-parser@1.2.1: {} react-devtools-core@6.1.5: dependencies: shell-quote: 1.10.0 - ws: 7.5.11 + ws: 7.5.13 transitivePeerDependencies: - bufferutil - utf-8-validate - react-dom@19.2.3(react@19.2.3): + react-dom@19.2.8(react@19.2.8): dependencies: - react: 19.2.3 + react: 19.2.8 scheduler: 0.27.0 react-is@16.13.1: {} react-is@18.3.1: {} - react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3): + react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1): dependencies: - '@react-native/assets-registry': 0.86.0 - '@react-native/codegen': 0.86.0(@babel/core@7.29.7) - '@react-native/community-cli-plugin': 0.86.0 - '@react-native/gradle-plugin': 0.86.0 - '@react-native/js-polyfills': 0.86.0 - '@react-native/normalize-colors': 0.86.0 - '@react-native/virtualized-lists': 0.86.0(@types/react@19.2.17)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@react-native/assets-registry': 0.86.3 + '@react-native/codegen': 0.86.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@react-native/community-cli-plugin': 0.86.3(supports-color@8.1.1) + '@react-native/gradle-plugin': 0.86.3 + '@react-native/js-polyfills': 0.86.3 + '@react-native/normalize-colors': 0.86.3 + '@react-native/virtualized-lists': 0.86.3(@types/react@19.2.18)(react-native@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.8)(supports-color@8.1.1))(react@19.2.8) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -7751,15 +7149,15 @@ snapshots: base64-js: 1.5.1 commander: 12.1.0 flow-enums-runtime: 0.0.6 - hermes-compiler: 250829098.0.14 + hermes-compiler: 250829098.0.17 invariant: 2.2.4 memoize-one: 5.2.1 - metro-runtime: 0.84.4 - metro-source-map: 0.84.4 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5(supports-color@8.1.1) nullthrows: 1.1.1 pretty-format: 29.7.0 promise: 8.3.0 - react: 19.2.3 + react: 19.2.8 react-devtools-core: 6.1.5 react-refresh: 0.14.2 regenerator-runtime: 0.13.11 @@ -7768,11 +7166,10 @@ snapshots: stacktrace-parser: 0.1.11 tinyglobby: 0.2.17 whatwg-fetch: 3.6.20 - ws: 7.5.11 + ws: 7.5.13 yargs: 17.7.3 optionalDependencies: - '@react-native/jest-preset': 0.86.0(@babel/core@7.29.7)(react@19.2.3) - '@types/react': 19.2.17 + '@types/react': 19.2.18 transitivePeerDependencies: - '@babel/core' - '@react-native-community/cli' @@ -7783,7 +7180,7 @@ snapshots: react-refresh@0.14.2: {} - react@19.2.3: {} + react@19.2.8: {} reflect.getprototypeof@1.0.10: dependencies: @@ -7834,8 +7231,6 @@ snapshots: require-from-string@2.0.2: {} - resolve-from@4.0.0: {} - resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -7863,26 +7258,26 @@ snapshots: onetime: 2.0.1 signal-exit: 3.0.7 - rolldown@1.1.5: + rolldown@1.2.6: dependencies: - '@oxc-project/types': 0.139.0 + '@oxc-project/types': 0.147.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.5 - '@rolldown/binding-darwin-arm64': 1.1.5 - '@rolldown/binding-darwin-x64': 1.1.5 - '@rolldown/binding-freebsd-x64': 1.1.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 - '@rolldown/binding-linux-arm64-gnu': 1.1.5 - '@rolldown/binding-linux-arm64-musl': 1.1.5 - '@rolldown/binding-linux-ppc64-gnu': 1.1.5 - '@rolldown/binding-linux-s390x-gnu': 1.1.5 - '@rolldown/binding-linux-x64-gnu': 1.1.5 - '@rolldown/binding-linux-x64-musl': 1.1.5 - '@rolldown/binding-openharmony-arm64': 1.1.5 - '@rolldown/binding-wasm32-wasi': 1.1.5 - '@rolldown/binding-win32-arm64-msvc': 1.1.5 - '@rolldown/binding-win32-x64-msvc': 1.1.5 + '@rolldown/binding-android-arm-eabi': 1.2.6 + '@rolldown/binding-android-arm64': 1.2.6 + '@rolldown/binding-darwin-arm64': 1.2.6 + '@rolldown/binding-darwin-x64': 1.2.6 + '@rolldown/binding-freebsd-x64': 1.2.6 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.6 + '@rolldown/binding-linux-arm64-gnu': 1.2.6 + '@rolldown/binding-linux-arm64-musl': 1.2.6 + '@rolldown/binding-linux-ppc64-gnu': 1.2.6 + '@rolldown/binding-linux-s390x-gnu': 1.2.6 + '@rolldown/binding-linux-x64-gnu': 1.2.6 + '@rolldown/binding-linux-x64-musl': 1.2.6 + '@rolldown/binding-openharmony-arm64': 1.2.6 + '@rolldown/binding-win32-arm64-msvc': 1.2.6 + '@rolldown/binding-win32-x64-msvc': 1.2.6 safe-array-concat@1.1.4: dependencies: @@ -7905,7 +7300,9 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - sax@1.6.0: {} + sandbox-cli-detector@0.2.0: {} + + sax@1.6.1: {} saxes@6.0.0: dependencies: @@ -7917,9 +7314,9 @@ snapshots: semver@7.8.5: {} - send@0.19.2: + send@0.19.2(supports-color@8.1.1): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@8.1.1) depd: 2.0.0 destroy: 1.2.0 encodeurl: 2.0.0 @@ -7937,12 +7334,12 @@ snapshots: serialize-error@2.1.0: {} - serve-static@1.16.3: + serve-static@1.16.3(supports-color@8.1.1): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.19.2 + send: 0.19.2(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8018,9 +7415,6 @@ snapshots: sisteransi@1.0.5: {} - slash@3.0.0: - optional: true - slugify@1.6.9: {} source-map-js@1.2.1: {} @@ -8034,14 +7428,6 @@ snapshots: source-map@0.6.1: {} - sprintf-js@1.0.3: - optional: true - - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - optional: true - stackback@0.0.2: {} stackframe@1.3.4: {} @@ -8069,7 +7455,7 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string.prototype.matchall@4.0.12: + string.prototype.matchall@4.1.0: dependencies: call-bind: 1.0.9 call-bound: 1.0.4 @@ -8124,8 +7510,6 @@ snapshots: strip-bom@3.0.0: {} - strip-json-comments@3.1.1: {} - structured-headers@0.4.1: {} supports-color@5.5.0: @@ -8160,38 +7544,31 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser@5.49.0: + terser@5.51.2: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.17.0 + acorn: 8.18.0 commander: 2.20.3 source-map-support: 0.5.21 - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.6 - glob: 7.2.3 - minimatch: 3.1.5 - optional: true - throat@5.0.0: {} tinybench@2.9.0: {} - tinyexec@1.2.4: {} + tinyexec@1.3.0: {} tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 - tinyrainbow@3.1.0: {} + tinyrainbow@3.1.1: {} - tldts-core@7.4.8: {} + tldts-core@7.4.11: {} - tldts@7.4.8: + tldts@7.4.11: dependencies: - tldts-core: 7.4.8 + tldts-core: 7.4.11 tmpl@1.0.5: {} @@ -8205,20 +7582,20 @@ snapshots: tough-cookie@6.0.2: dependencies: - tldts: 7.4.8 + tldts: 7.4.11 tr46@6.0.0: dependencies: punycode: 2.3.1 - ts-api-utils@2.5.0(typescript@5.9.3): + ts-api-utils@2.5.0(@typescript/typescript6@6.0.2): dependencies: - typescript: 5.9.3 + typescript: '@typescript/typescript6@6.0.2' - ts-declaration-location@1.0.7(typescript@5.9.3): + ts-declaration-location@1.0.7(@typescript/typescript6@6.0.2): dependencies: - picomatch: 4.0.5 - typescript: 5.9.3 + picomatch: 4.0.7 + typescript: '@typescript/typescript6@6.0.2' tsconfig-paths@3.15.0: dependencies: @@ -8227,16 +7604,10 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tslib@2.8.1: - optional: true - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - type-detect@4.0.8: - optional: true - type-fest@0.21.3: {} type-fest@0.7.1: {} @@ -8274,10 +7645,31 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript@5.9.3: {} - typescript@6.0.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -8287,7 +7679,7 @@ snapshots: undici-types@8.3.0: {} - undici@7.28.0: {} + undici@8.10.1: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -8302,9 +7694,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.6): + update-browserslist-db@1.3.2(browserslist@4.28.8): dependencies: - browserslist: 4.28.6 + browserslist: 4.28.8 escalade: 3.2.0 picocolors: 1.1.1 @@ -8320,44 +7712,44 @@ snapshots: vary@1.1.2: {} - vite@8.1.5(@types/node@26.1.1)(terser@5.49.0)(yaml@2.9.0): + vite@8.2.2(@types/node@26.4.0)(terser@5.51.2)(yaml@2.9.0): dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.5 - postcss: 8.5.19 - rolldown: 1.1.5 + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.26 + rolldown: 1.2.6 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 26.4.0 fsevents: 2.3.3 - terser: 5.49.0 + terser: 5.51.2 yaml: 2.9.0 - vitest@4.1.10(@types/node@26.1.1)(jsdom@29.1.1)(vite@8.1.5(@types/node@26.1.1)(terser@5.49.0)(yaml@2.9.0)): + vitest@4.1.11(@types/node@26.4.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.4.0)(terser@5.51.2)(yaml@2.9.0)): dependencies: - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.1)(terser@5.49.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - es-module-lexer: 2.3.1 + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@26.4.0)(terser@5.51.2)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.2 expect-type: 1.4.0 magic-string: 0.30.21 - obug: 2.1.3 + obug: 2.1.4 pathe: 2.0.3 - picomatch: 4.0.5 + picomatch: 4.0.7 std-env: 4.2.0 tinybench: 2.9.0 - tinyexec: 1.2.4 + tinyexec: 1.3.0 tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.1.5(@types/node@26.1.1)(terser@5.49.0)(yaml@2.9.0) + tinyrainbow: 3.1.1 + vite: 8.2.2(@types/node@26.4.0)(terser@5.51.2)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 26.1.1 - jsdom: 29.1.1 + '@types/node': 26.4.0 + jsdom: 30.0.1 transitivePeerDependencies: - msw @@ -8391,6 +7783,14 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + whatwg-url@17.1.0: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -8449,18 +7849,9 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrappy@1.0.2: - optional: true - - write-file-atomic@4.0.2: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - optional: true - - ws@7.5.11: {} + ws@7.5.13: {} - ws@8.21.0: {} + ws@8.21.3: {} xcode@3.0.1: dependencies: @@ -8471,7 +7862,7 @@ snapshots: xml2js@0.6.0: dependencies: - sax: 1.6.0 + sax: 1.6.1 xmlbuilder: 11.0.1 xmlbuilder@11.0.1: {} @@ -8500,10 +7891,10 @@ snapshots: yocto-queue@0.1.0: {} - zod-validation-error@4.0.2(zod@4.4.3): + zod-validation-error@4.0.2(zod@4.5.4): dependencies: - zod: 4.4.3 + zod: 4.5.4 zod@3.25.76: {} - zod@4.4.3: {} + zod@4.5.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0ab70c5..e578080 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,52 +7,37 @@ packages: verifyDepsBeforeRun: false minimumReleaseAgeExclude: - - '@expo/cli@57.0.7' - - '@expo/config-plugins@57.0.4' - - '@expo/config@57.0.4' - - '@expo/fingerprint@0.20.4' - - '@expo/image-utils@0.11.2' - - '@expo/local-build-cache-provider@57.0.3' - - '@expo/metro-config@57.0.4' - - '@expo/prebuild-config@57.0.6' - - '@expo/require-utils@57.0.2' - - expo-asset@57.0.4 - - expo-constants@57.0.4 - - expo-modules-autolinking@57.0.6 - - expo-modules-core@57.0.4 - - expo-modules-jsi@57.0.2 - - expo@57.0.5 - - '@expo/cli@57.0.8' - - '@expo/config-plugins@57.0.5' + - '@expo/cli@57.0.7 || 57.0.8 || 57.0.21' + - '@expo/config-plugins@57.0.4 || 57.0.5' + - '@expo/config@57.0.4 || 57.0.5' + - '@expo/fingerprint@0.20.4 || 0.20.5 || 0.20.12' + - '@expo/image-utils@0.11.2 || 0.11.3' + - '@expo/local-build-cache-provider@57.0.3 || 57.0.4' + - '@expo/metro-config@57.0.4 || 57.0.5' + - '@expo/prebuild-config@57.0.6 || 57.0.7' + - '@expo/require-utils@57.0.2 || 57.0.3' + - expo-asset@57.0.4 || 57.0.5 || 57.0.16 + - expo-constants@57.0.4 || 57.0.5 || 57.0.17 + - expo-modules-autolinking@57.0.6 || 57.0.7 + - expo-modules-core@57.0.4 || 57.0.5 || 57.0.15 + - expo-modules-jsi@57.0.2 || 57.0.3 || 57.0.7 + - expo@57.0.5 || 57.0.6 || 57.0.19 - '@expo/config-types@57.0.2' - - '@expo/config@57.0.5' - '@expo/devtools@57.0.1' - '@expo/env@2.4.2' - - '@expo/fingerprint@0.20.5' - - '@expo/image-utils@0.11.3' - '@expo/inline-modules@0.1.3' - '@expo/json-file@11.0.1' - - '@expo/local-build-cache-provider@57.0.4' - '@expo/log-box@57.0.1' - - '@expo/metro-config@57.0.5' - '@expo/metro-file-map@57.0.1' - '@expo/osascript@2.7.1' - '@expo/package-manager@1.13.1' - '@expo/plist@0.8.1' - - '@expo/prebuild-config@57.0.7' - - '@expo/require-utils@57.0.3' - - '@expo/router-server@57.0.3' + - '@expo/router-server@57.0.3 || 57.0.9' - '@expo/schema-utils@57.0.2' - - babel-preset-expo@57.0.3 - - expo-asset@57.0.5 - - expo-constants@57.0.5 + - babel-preset-expo@57.0.3 || 57.0.10 - expo-file-system@57.0.1 - - expo-font@57.0.1 + - expo-font@57.0.1 || 57.0.3 - expo-keep-awake@57.0.1 - - expo-modules-autolinking@57.0.7 - - expo-modules-core@57.0.5 - - expo-modules-jsi@57.0.3 - expo-server@57.0.1 - - expo@57.0.6 - '@expo/dom-webview@57.0.1' - jest-expo@57.0.2 diff --git a/scripts/vendor-watch.sh b/scripts/vendor-watch.sh index 11984ba..734cdfb 100755 --- a/scripts/vendor-watch.sh +++ b/scripts/vendor-watch.sh @@ -23,9 +23,12 @@ spm_pin=$(json '.["libghostty-spm"].tag') msdl_pin=$(json '.MSDisplayLink.tag') vt_pin=$(json '.["libghostty-vt"].commit') +# Package version tags are X.Y.Z. Binary zips live on storage.* (legacy) +# or upstream.* (from 1.5.1); the XCFramework url+checksum is in +# Package.swift's binaryTarget, not implied by the package tag. spm_latest=$(gh api repos/Lakr233/libghostty-spm/releases --paginate \ - --jq '[.[].tag_name | select(startswith("storage."))] | .[]' | - sed 's/^storage\.//' | sort -V | tail -1) + --jq '[.[].tag_name | select(test("^[0-9]+\\.[0-9]+\\.[0-9]+$"))] | .[]' | + sort -V | tail -1) msdl_latest=$(gh api repos/Lakr233/MSDisplayLink/tags --jq '.[].name' | sort -V | tail -1) vt_ahead=$(gh api "repos/ghostty-org/ghostty/compare/${vt_pin}...HEAD" --jq '.ahead_by') @@ -34,7 +37,7 @@ lines=() if [[ "$spm_pin" != "$spm_latest" ]]; then drift=true - lines+=("| libghostty-spm | \`$spm_pin\` | \`$spm_latest\` | \`pnpm sync-vendor\` after bumping the tag, then update the \`storage.$spm_latest\` XCFramework url + sha256 |") + lines+=("| libghostty-spm | \`$spm_pin\` | \`$spm_latest\` | bump the package tag, \`pnpm sync-vendor\`, then copy Package.swift's binaryTarget url + checksum into vendor-manifest.json |") fi if [[ "$msdl_pin" != "$msdl_latest" ]]; then drift=true diff --git a/vendor-manifest.json b/vendor-manifest.json index b652438..3aeed22 100644 --- a/vendor-manifest.json +++ b/vendor-manifest.json @@ -1,22 +1,22 @@ { "libghostty-spm": { "repo": "https://github.com/Lakr233/libghostty-spm.git", - "tag": "1.3.1", + "tag": "1.5.20260903", "xcframework": { - "url": "https://github.com/Lakr233/libghostty-spm/releases/download/storage.1.3.1/GhosttyKit.xcframework.zip", - "sha256": "cfb3fbbfe1365e4c90e01969e2576b4dfa33f04975bcafd84c6368514f791fe9" + "url": "https://github.com/Lakr233/libghostty-spm/releases/download/upstream.c4e16970a803/GhosttyKit.xcframework.zip", + "sha256": "bd9bba3b95652900e87a6a0f190f33d82a1d8e42d1c0119c330072be361385da" } }, "MSDisplayLink": { "repo": "https://github.com/Lakr233/MSDisplayLink.git", - "tag": "2.1.0" + "tag": "2.2.0" }, "libghostty-vt": { "repo": "https://github.com/ghostty-org/ghostty.git", - "commit": "b0947378349eff70f7030dda0e6d022fae1e6fbd", + "commit": "3c1ef5b32fc5ea6b93d28493fabf193f595139cf", "android": { - "url": "https://github.com/arcboxlabs/expo-libghostty/releases/download/storage.libghostty-vt.b0947378349e.r3/libghostty-vt-android-b0947378349e-r3.tar.gz", - "sha256": "b38f032f3f5e42cd3686ba6a10ecbb0669382de8df74ddd44be30e362c637d96" + "url": "https://github.com/rudironsoni/expo-libghostty/releases/download/storage.libghostty-vt.3c1ef5b32fc5.r1/libghostty-vt-android-3c1ef5b32fc5-r1.tar.gz", + "sha256": "39ae5ed0eb0b0330a583045c98ed5cc0dc5adff0b0d7d87a884bfc0ef995fcea" } } }