From 6d45a6a75b23e2e49a2bc1ce0f8c76b6036346cb Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Fri, 15 May 2026 11:57:36 +0200 Subject: [PATCH 01/15] [JSI] Fix off-thread execute tests calling noasync overload from async context (#45799) --- .../apple/Tests/JavaScriptRuntimeTests.swift | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/expo-modules-jsi/apple/Tests/JavaScriptRuntimeTests.swift b/packages/expo-modules-jsi/apple/Tests/JavaScriptRuntimeTests.swift index 073e176d18648d..8823b0e3be7387 100644 --- a/packages/expo-modules-jsi/apple/Tests/JavaScriptRuntimeTests.swift +++ b/packages/expo-modules-jsi/apple/Tests/JavaScriptRuntimeTests.swift @@ -1,5 +1,6 @@ import Testing import ExpoModulesJSI +import Foundation @Suite @JavaScriptActor @@ -122,28 +123,30 @@ struct JavaScriptRuntimeTests { // schedules the closure onto the JS thread and pumps the caller's run loop until it // completes. The tests above run on `@JavaScriptActor` (the JS thread), so they only // exercise the fast path. The next three hop off the JS thread first to cover the - // cross-thread scheduling + run-loop pump. + // cross-thread scheduling + run-loop pump. The sync overloads of `execute` are + // `@available(*, noasync)`, so the caller must be a real synchronous thread — wrapping + // in `Task.detached` would stay on the cooperative pool and trip the noasync diagnostic. @Test func `execute sync from off-thread caller`() async throws { let runtime = self.runtime - let result = try await Task.detached { @Sendable in + let result = try await onSyncOffThread { try runtime.execute { @JavaScriptActor in - runtime.global().hasProperty("Object") ? 1 : 0 + return runtime.global().hasProperty("Object") ? 1 : 0 } - }.value + } #expect(result == 1) } @Test func `execute blocking-async from off-thread caller`() async throws { let runtime = self.runtime - let result = try await Task.detached { @Sendable in + let result = try await onSyncOffThread { try runtime.execute { @JavaScriptActor () async in await Task.yield() return runtime.global().hasProperty("Object") ? 1 : 0 } - }.value + } #expect(result == 1) } @@ -151,11 +154,11 @@ struct JavaScriptRuntimeTests { func `execute sync rethrows from off-thread caller`() async throws { let runtime = self.runtime await #expect(throws: ScriptEvaluationError.self) { - try await Task.detached { @Sendable in + try await onSyncOffThread { try runtime.execute { @JavaScriptActor in try runtime.eval("invalid syntax +++") } - }.value + } } } @@ -739,3 +742,19 @@ struct JavaScriptRuntimeTests { #expect(result.getInt() == 3) } } + +/// Runs `body` on a freshly spawned synchronous thread and bridges the result back into the +/// async test. The thread has a real run loop, which the cross-thread `execute` path pumps. +private func onSyncOffThread( + _ body: @escaping @Sendable () throws -> R +) async throws -> R { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + Thread.detachNewThread { + do { + continuation.resume(returning: try body()) + } catch { + continuation.resume(throwing: error) + } + } + } +} From 0453d37ec72237072d5a6e68cb6d1cf745e917bd Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Fri, 15 May 2026 12:05:21 +0200 Subject: [PATCH 02/15] [widgets] Use shared JS runtime (#45781) # Why To prevent high memory usage as recreating `JSContext` and loading whole bundle on each rerender is pretty expensive. # How Refactored rendering to use a shared JSC runtime instead of creating a new `JSContext` for every widget/live activity render or widget button interaction. This adds a shared `WidgetsJSRuntime` that: - Contains shared `JSContext` - Loads the ExpoWidgets JS bundle once - Caches evaluated layout functions by layout string # Test Plan Widgets and live activities should work as before (but faster) # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [x] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [x] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/expo-widgets/CHANGELOG.md | 4 +- .../expo-widgets/ios/Widgets/AppIntent.swift | 16 +- packages/expo-widgets/ios/Widgets/Utils.swift | 38 ++--- .../ios/Widgets/WidgetContext.swift | 23 --- .../ios/Widgets/WidgetsJSRuntime.swift | 142 ++++++++++++++++++ 5 files changed, 168 insertions(+), 55 deletions(-) delete mode 100644 packages/expo-widgets/ios/Widgets/WidgetContext.swift create mode 100644 packages/expo-widgets/ios/Widgets/WidgetsJSRuntime.swift diff --git a/packages/expo-widgets/CHANGELOG.md b/packages/expo-widgets/CHANGELOG.md index 185a53aac5a19a..9bde156e7e6e75 100644 --- a/packages/expo-widgets/CHANGELOG.md +++ b/packages/expo-widgets/CHANGELOG.md @@ -6,10 +6,12 @@ ### 🎉 New features +- Use shared JS runtime. ([#45781](https://github.com/expo/expo/pull/45781) by [@jakex7](https://github.com/jakex7)) + ### 🐛 Bug fixes - Fix Live Activity multiple evaluations. ([#45675](https://github.com/expo/expo/pull/45675) by [@nkopylov](https://github.com/nkopylov)) -- Fix module precompile. +- Fix module precompile. ([#45715](https://github.com/expo/expo/pull/45715) by [@jakex7](https://github.com/jakex7)) ### 💡 Others diff --git a/packages/expo-widgets/ios/Widgets/AppIntent.swift b/packages/expo-widgets/ios/Widgets/AppIntent.swift index 09aa9d82e6d5f5..972fa4f190d5da 100644 --- a/packages/expo-widgets/ios/Widgets/AppIntent.swift +++ b/packages/expo-widgets/ios/Widgets/AppIntent.swift @@ -58,19 +58,25 @@ struct WidgetUserInteraction: AppIntent { guard let timeline, let entryIndex, + timeline.indices.contains(entryIndex), let entry = timeline[entryIndex] as? [String: Any], let props = entry["props"] as? [String: Any], - let context = createWidgetContext(layout: layout), let environmentData = environmentString?.data(using: .utf8), var environment = try? JSONSerialization.jsonObject(with: environmentData) as? [String: Any] else { return .result() } environment["target"] = target - let result = context.objectForKeyedSubscript("__expoWidgetHandlePress")?.call( - withArguments: [props, environment] - ) - if let newProps = result?.toObject() as? [String: Any] { + let newProps: [String: Any]? + switch evaluateWidgetButtonPress(layout: layout, props: props, environment: environment) { + case .success(let result): + newProps = result + case .failure(let error): + print("[ExpoWidgets] Button press evaluation failed: \(error.message)") + newProps = nil + } + + if let newProps { var newEntry = entry if let originalProps = entry["props"] as? [String: Any] { newEntry["props"] = originalProps.merging(newProps) { _, new in new } diff --git a/packages/expo-widgets/ios/Widgets/Utils.swift b/packages/expo-widgets/ios/Widgets/Utils.swift index 3238dfde98537c..6b70430feac66f 100644 --- a/packages/expo-widgets/ios/Widgets/Utils.swift +++ b/packages/expo-widgets/ios/Widgets/Utils.swift @@ -33,41 +33,27 @@ public func evaluateLayout( props: [String: Any], environment: [String: Any] ) -> [String: Any] { - guard let context = createWidgetContext(layout: layout) else { - return createRedBox(message: "Could not create context for layout evaluation.") + switch evaluateWidgetLayout(layout: layout, props: props, environment: environment) { + case .success(let result): + return result + case .failure(let error): + print("[ExpoWidgets] Layout evaluation failed: \(error.message)") + return createRedBox(message: error.message) } - - let result = context.objectForKeyedSubscript("__expoWidgetRender")?.call( - withArguments: [props, environment] - ) - if let exception = context.exception { - print("[ExpoWidgets] Layout evaluation failed: \(exception)") - return createRedBox(message: exception.toString()) - } - return result?.toObject() as? [String: Any] ?? createRedBox(message: "Expo widget render did not produce any results.") } func getLiveActivityNodes(forName name: String, props: String = "{}", environment: [String: Any]) -> [String: Any] { let layout = WidgetsStorage.getString(forKey: "__expo_widgets_live_activity_\(name)_layout") ?? "" let propsData = props.data(using: .utf8) let propsDict = propsData.flatMap { try? JSONSerialization.jsonObject(with: $0, options: []) as? [String: Any] } ?? [:] - guard let context = createWidgetContext(layout: layout) else { - return ["banner": createRedBox(message: "Could not create context for layout evaluation.")] - } - var widgetEnvironment = environment - widgetEnvironment["timestamp"] = Int(Date.now.timeIntervalSince1970 * 1000) - - let result = context.objectForKeyedSubscript("__expoWidgetRender")?.call( - withArguments: [propsDict, environment] - ) - - if let exception = context.exception { - print("[ExpoWidgets] Layout evaluation failed: \(exception)") - return ["banner": createRedBox(message: exception.toString())] + switch evaluateWidgetLayout(layout: layout, props: propsDict, environment: environment) { + case .success(let result): + return result + case .failure(let error): + print("[ExpoWidgets] Layout evaluation failed: \(error.message)") + return ["banner": createRedBox(message: error.message)] } - - return result?.toObject() as? [String: Any] ?? ["banner": createRedBox(message: "Expo widget render did not produce any results.")] } func getLiveActivityUrl(forName name: String) -> URL? { diff --git a/packages/expo-widgets/ios/Widgets/WidgetContext.swift b/packages/expo-widgets/ios/Widgets/WidgetContext.swift deleted file mode 100644 index eb207a390b929b..00000000000000 --- a/packages/expo-widgets/ios/Widgets/WidgetContext.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation -import JavaScriptCore - -func createWidgetContext(layout: String) -> JSContext? { - guard let context = JSContext() else { - return nil - } - - // Inject ExpoUI bundle - guard let bundleURL = Bundle.main.url(forResource: "ExpoWidgets", withExtension: "bundle"), - let bundle = Bundle(url: bundleURL), - let url = bundle.url(forResource: "ExpoWidgets", withExtension: "bundle"), - let bundleJS = try? String(contentsOf: url, encoding: .utf8) else { - print("[ExpoWidgets] Missing ExpoWidgets.bundle") - return nil - } - context.evaluateScript(bundleJS) - - // Inject layout - let layoutValue = context.evaluateScript("(\(layout))") - context.setObject(layoutValue, forKeyedSubscript: "__expoWidgetLayout" as NSString) - return context -} diff --git a/packages/expo-widgets/ios/Widgets/WidgetsJSRuntime.swift b/packages/expo-widgets/ios/Widgets/WidgetsJSRuntime.swift new file mode 100644 index 00000000000000..52083c32a009c7 --- /dev/null +++ b/packages/expo-widgets/ios/Widgets/WidgetsJSRuntime.swift @@ -0,0 +1,142 @@ +import Foundation +import JavaScriptCore + +struct WidgetJavaScriptError: Error { + let message: String +} + +typealias WidgetJavaScriptResult = Result + +private final class WidgetsJSRuntime { + static let shared = WidgetsJSRuntime() + + private let lock = NSRecursiveLock() + private var context: JSContext? + private var bundleScript: String? + private var layoutCache: [String: JSValue] = [:] + + private init() {} + + func render(layout: String, props: [String: Any], environment: [String: Any]) -> WidgetJavaScriptResult<[String: Any]> { + call(layout: layout, functionName: "__expoWidgetRender", arguments: [props, environment]).flatMap { result in + guard let renderedNode = result?.toObject() as? [String: Any] else { + return .failure(WidgetJavaScriptError(message: "Expo widget render did not produce any results.")) + } + return .success(renderedNode) + } + } + + func handlePress(layout: String, props: [String: Any], environment: [String: Any]) -> WidgetJavaScriptResult<[String: Any]?> { + call(layout: layout, functionName: "__expoWidgetHandlePress", arguments: [props, environment]) + .map { $0?.toObject() as? [String: Any] } + } + + private func call(layout: String, functionName: String, arguments: [Any]) -> WidgetJavaScriptResult { + lock.lock() + defer { lock.unlock() } + + guard let context = getContext() else { + return .failure(WidgetJavaScriptError(message: "Could not create context for layout evaluation.")) + } + guard let layoutValue = getLayoutValue(layout, in: context) else { + return .failure(WidgetJavaScriptError(message: contextExceptionMessage(context) ?? "Could not evaluate layout.")) + } + + context.exception = nil + context.setObject(layoutValue, forKeyedSubscript: "__expoWidgetLayout" as NSString) + + let function = context.objectForKeyedSubscript(functionName) + guard let function, function.isObject else { + return .failure(WidgetJavaScriptError(message: "Expo widget runtime function \(functionName) is unavailable.")) + } + + let result = function.call(withArguments: arguments) + if let exceptionMessage = contextExceptionMessage(context) { + return .failure(WidgetJavaScriptError(message: exceptionMessage)) + } + return .success(result) + } + + private func getContext() -> JSContext? { + if let context { + return context + } + + guard let context = JSContext() else { + return nil + } + + guard let script = getBundleScript() else { + print("[ExpoWidgets] Missing ExpoWidgets.bundle") + return nil + } + + context.evaluateScript(script) + if let exceptionMessage = contextExceptionMessage(context) { + print("[ExpoWidgets] Bundle evaluation failed: \(exceptionMessage)") + return nil + } + + self.context = context + return context + } + + private func getBundleScript() -> String? { + if let bundleScript { + return bundleScript + } + + guard let bundleURL = Bundle.main.url(forResource: "ExpoWidgets", withExtension: "bundle"), + let bundle = Bundle(url: bundleURL), + let url = bundle.url(forResource: "ExpoWidgets", withExtension: "bundle"), + let script = try? String(contentsOf: url, encoding: .utf8) else { + return nil + } + + bundleScript = script + return script + } + + private func getLayoutValue(_ layout: String, in context: JSContext) -> JSValue? { + if let layoutValue = layoutCache[layout] { + return layoutValue + } + + context.exception = nil + guard let layoutValue = context.evaluateScript("(\(layout))"), + !layoutValue.isUndefined else { + return nil + } + guard context.exception == nil else { + return nil + } + + layoutCache[layout] = layoutValue + return layoutValue + } + + private func contextExceptionMessage(_ context: JSContext) -> String? { + guard let exception = context.exception else { + return nil + } + + context.exception = nil + return exception.toString() ?? "Unknown JavaScript exception." + } +} + +func evaluateWidgetLayout( + layout: String, + props: [String: Any], + environment: [String: Any] +) -> WidgetJavaScriptResult<[String: Any]> { + WidgetsJSRuntime.shared.render(layout: layout, props: props, environment: environment) +} + +func evaluateWidgetButtonPress( + layout: String, + props: [String: Any], + environment: [String: Any] +) -> WidgetJavaScriptResult<[String: Any]?> { + WidgetsJSRuntime.shared.handlePress(layout: layout, props: props, environment: environment) +} From fcb2a5c305067e659853f86dfca7823601169b35 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Fri, 15 May 2026 12:36:00 +0200 Subject: [PATCH 03/15] Add fetch Response clone() (#45740) --- apps/test-suite/tests/Fetch.ts | 82 +++++ packages/expo/CHANGELOG.md | 2 + .../build/winter/fetch/FetchResponse.d.ts | 19 +- .../build/winter/fetch/FetchResponse.d.ts.map | 2 +- .../build/winter/fetch/NativeRequest.d.ts | 12 +- .../build/winter/fetch/NativeRequest.d.ts.map | 2 +- .../expo/src/winter/fetch/FetchResponse.ts | 338 +++++++++++++++--- .../expo/src/winter/fetch/NativeRequest.ts | 12 +- .../fetch/__tests__/FetchResponse-test.ts | 240 ++++++++++++- 9 files changed, 642 insertions(+), 67 deletions(-) diff --git a/apps/test-suite/tests/Fetch.ts b/apps/test-suite/tests/Fetch.ts index 1a53763668118f..f7d1096ca614b6 100644 --- a/apps/test-suite/tests/Fetch.ts +++ b/apps/test-suite/tests/Fetch.ts @@ -64,6 +64,88 @@ export function test({ describe, expect, it, ...t }) { }); }); + describe('Response clone', () => { + setupTestTimeout(t); + + it('should clone a response and read both bodies independently', async () => { + const resp = await fetch('https://httpbin.io/get'); + const cloned = resp.clone(); + const [original, clone] = await Promise.all([resp.json(), cloned.json()]); + expect(original.url).toBe(clone.url); + expect(resp.bodyUsed).toBe(true); + expect(cloned.bodyUsed).toBe(true); + }); + + it('should clone a streaming response', async () => { + const resp = await fetch('https://httpbin.io/drip?numbytes=64&duration=1'); + const cloned = resp.clone(); + const [originalBuffer, clonedBuffer] = await Promise.all([ + resp.arrayBuffer(), + cloned.arrayBuffer(), + ]); + expect(originalBuffer.byteLength).toBe(64); + expect(clonedBuffer.byteLength).toBe(64); + }); + + it('should clone a response and read both bodies as text independently', async () => { + const resp = await fetch('https://httpbin.io/get'); + const cloned = resp.clone(); + const [originalText, clonedText] = await Promise.all([resp.text(), cloned.text()]); + expect(originalText).toBe(clonedText); + expect(originalText).not.toBe(''); + expect(resp.bodyUsed).toBe(true); + expect(cloned.bodyUsed).toBe(true); + }); + + it('should preserve response metadata on the clone', async () => { + const resp = await fetch('https://httpbin.io/get'); + const cloned = resp.clone(); + expect(cloned.status).toBe(resp.status); + expect(cloned.statusText).toBe(resp.statusText); + expect(cloned.url).toBe(resp.url); + expect(cloned.ok).toBe(resp.ok); + expect(cloned.headers.get('content-type')).toBe(resp.headers.get('content-type')); + }); + + it('should support cloning a clone', async () => { + const resp = await fetch('https://httpbin.io/get'); + const cloned = resp.clone(); + const reCloned = cloned.clone(); + const json = await reCloned.json(); + expect(json.url).toMatch(/^https?:\/\/httpbin\.io\/get$/); + }); + + it('should throw a TypeError when cloning after the body has been read', async () => { + const resp = await fetch('https://httpbin.io/get'); + await resp.text(); + let error: TypeError | null = null; + try { + resp.clone(); + } catch (e: unknown) { + if (e instanceof TypeError) { + error = e; + } + } + expect(error).not.toBeNull(); + expect(error?.message).toContain('body is already used'); + }); + + it('should throw a TypeError when cloning a response with a locked body', async () => { + const resp = await fetch('https://httpbin.io/get'); + resp.body!.getReader(); + let error: TypeError | null = null; + try { + resp.clone(); + } catch (e: unknown) { + if (e instanceof TypeError) { + error = e; + } + } + expect(error).not.toBeNull(); + expect(error?.message).toContain('body is already used'); + }); + }); + describe('Redirect handling', () => { setupTestTimeout(t); diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index a78503d14bfec9..a32ef26bc3929c 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -6,6 +6,8 @@ ### 🎉 New features +- Implement `Response.clone()` on `expo/fetch`, and throw the spec's `TypeError` when a body is read twice. ([#45740](https://github.com/expo/expo/pull/45740) by [@zoontek](https://github.com/zoontek)) + ### 🐛 Bug fixes - Fix loader HMR when streaming SSR is enabled in dev mode ([#45702](https://github.com/expo/expo/pull/45702) by [@hassankhan](https://github.com/hassankhan)) diff --git a/packages/expo/build/winter/fetch/FetchResponse.d.ts b/packages/expo/build/winter/fetch/FetchResponse.d.ts index acf29091c25549..8b56ca3320d31e 100644 --- a/packages/expo/build/winter/fetch/FetchResponse.d.ts +++ b/packages/expo/build/winter/fetch/FetchResponse.d.ts @@ -1,20 +1,26 @@ -import type { NativeResponse } from './NativeRequest'; +import type { NativeHeadersType, NativeResponse } from './NativeRequest'; declare const ConcreteNativeResponse: typeof NativeResponse; export type AbortSubscriptionCleanupFunction = () => void; type RNFormData = Awaited>; type UniversalFormData = globalThis.FormData & RNFormData; +declare const stateKey: unique symbol; /** * A response implementation for the `fetch.Response` API. */ export declare class FetchResponse extends ConcreteNativeResponse implements Response { private readonly abortCleanupFunction; - private streamingState; - private bodyStream; + private [stateKey]; constructor(abortCleanupFunction: AbortSubscriptionCleanupFunction); + get _rawHeaders(): NativeHeadersType; + get status(): number; + get statusText(): string; + get url(): string; + get redirected(): boolean; + get type(): 'default'; get body(): ReadableStream> | null; + get bodyUsed(): boolean; get headers(): Headers; get ok(): boolean; - readonly type = "default"; /** * This method is not currently supported by react-native's Blob constructor. */ @@ -22,9 +28,12 @@ export declare class FetchResponse extends ConcreteNativeResponse implements Res formData(): Promise; json(): Promise; bytes(): Promise>; + arrayBuffer(): Promise; + text(): Promise; toString(): string; toJSON(): object; - clone(): Response; + clone(): FetchResponse; + private checkBodyUsedError; private finalize; } export {}; diff --git a/packages/expo/build/winter/fetch/FetchResponse.d.ts.map b/packages/expo/build/winter/fetch/FetchResponse.d.ts.map index ea1371377438ba..c07963b285e45d 100644 --- a/packages/expo/build/winter/fetch/FetchResponse.d.ts.map +++ b/packages/expo/build/winter/fetch/FetchResponse.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FetchResponse.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/FetchResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,QAAA,MAAM,sBAAsB,EAAqC,OAAO,cAAc,CAAC;AACvF,MAAM,MAAM,gCAAgC,GAAG,MAAM,IAAI,CAAC;AAI1D,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACvE,KAAK,iBAAiB,GAAG,UAAU,CAAC,QAAQ,GAAG,UAAU,CAAC;AAE1D;;GAEG;AACH,qBAAa,aAAc,SAAQ,sBAAuB,YAAW,QAAQ;IAI/D,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IAHjD,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,UAAU,CAAwD;gBAE7C,oBAAoB,EAAE,gCAAgC;IAKnF,IAAI,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,CAuDzD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,EAAE,IAAI,OAAO,CAEhB;IAED,SAAgB,IAAI,aAAa;IAEjC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAKrB,QAAQ,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAYtC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;IAKpB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAI/C,QAAQ,IAAI,MAAM;IAIlB,MAAM,IAAI,MAAM;IAShB,KAAK,IAAI,QAAQ;IAIjB,OAAO,CAAC,QAAQ,CAQd;CACH"} \ No newline at end of file +{"version":3,"file":"FetchResponse.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/FetchResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEzE,QAAA,MAAM,sBAAsB,EAAqC,OAAO,cAAc,CAAC;AACvF,MAAM,MAAM,gCAAgC,GAAG,MAAM,IAAI,CAAC;AAI1D,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACvE,KAAK,iBAAiB,GAAG,UAAU,CAAC,QAAQ,GAAG,UAAU,CAAC;AAW1D,QAAA,MAAM,QAAQ,eAAgC,CAAC;AAkH/C;;GAEG;AACH,qBAAa,aAAc,SAAQ,sBAAuB,YAAW,QAAQ;IAM/D,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IALjD,OAAO,CAAC,CAAC,QAAQ,CAAC,CAGhB;gBAE2B,oBAAoB,EAAE,gCAAgC;IAQnF,IAAa,WAAW,IAAI,iBAAiB,CAE5C;IAED,IAAa,MAAM,IAAI,MAAM,CAE5B;IAED,IAAa,UAAU,IAAI,MAAM,CAEhC;IAED,IAAa,GAAG,IAAI,MAAM,CAEzB;IAED,IAAa,UAAU,IAAI,OAAO,CAEjC;IAED,IAAI,IAAI,IAAI,SAAS,CAEpB;IAED,IAAI,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,CAoEzD;IAED,IAAa,QAAQ,IAAI,OAAO,CAE/B;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,EAAE,IAAI,OAAO,CAEhB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,QAAQ,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAgBtC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;IAMpB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAKhC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAanC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAatC,QAAQ,IAAI,MAAM;IAIlB,MAAM,IAAI,MAAM;IAShB,KAAK,IAAI,aAAa;IAsCtB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,QAAQ,CAQd;CACH"} \ No newline at end of file diff --git a/packages/expo/build/winter/fetch/NativeRequest.d.ts b/packages/expo/build/winter/fetch/NativeRequest.d.ts index 81db28893e11d6..6bd8e53587aa90 100644 --- a/packages/expo/build/winter/fetch/NativeRequest.d.ts +++ b/packages/expo/build/winter/fetch/NativeRequest.d.ts @@ -17,12 +17,12 @@ export type NativeResponseEvents = { readyForJSFinalization(): void; }; export declare class NativeResponse extends SharedObject { - readonly bodyUsed: boolean; - readonly _rawHeaders: NativeHeadersType; - readonly status: number; - readonly statusText: string; - readonly url: string; - readonly redirected: boolean; + get bodyUsed(): boolean; + get _rawHeaders(): NativeHeadersType; + get status(): number; + get statusText(): string; + get url(): string; + get redirected(): boolean; startStreaming(): Promise | null>; cancelStreaming(reason: string): void; arrayBuffer(): Promise; diff --git a/packages/expo/build/winter/fetch/NativeRequest.d.ts.map b/packages/expo/build/winter/fetch/NativeRequest.d.ts.map index 0e138983209b67..a6d34b67606ac2 100644 --- a/packages/expo/build/winter/fetch/NativeRequest.d.ts.map +++ b/packages/expo/build/winter/fetch/NativeRequest.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"NativeRequest.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/NativeRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AAEnD,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,YAAY;IAC9C,KAAK,CACV,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,iBAAiB,EAC9B,WAAW,EAAE,UAAU,GAAG,IAAI,GAC7B,OAAO,CAAC,cAAc,CAAC;IACnB,MAAM,IAAI,IAAI;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/C,WAAW,IAAI,IAAI,CAAC;IACpB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,sBAAsB,IAAI,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC5E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;IACzD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IACrC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IACnC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CACxB"} \ No newline at end of file +{"version":3,"file":"NativeRequest.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/NativeRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AAEnD,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,YAAY;IAC9C,KAAK,CACV,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,iBAAiB,EAC9B,WAAW,EAAE,UAAU,GAAG,IAAI,GAC7B,OAAO,CAAC,cAAc,CAAC;IACnB,MAAM,IAAI,IAAI;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/C,WAAW,IAAI,IAAI,CAAC;IACpB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,sBAAsB,IAAI,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC5E,IAAI,QAAQ,IAAI,OAAO,CAAC;IACxB,IAAI,WAAW,IAAI,iBAAiB,CAAC;IACrC,IAAI,MAAM,IAAI,MAAM,CAAC;IACrB,IAAI,UAAU,IAAI,MAAM,CAAC;IACzB,IAAI,GAAG,IAAI,MAAM,CAAC;IAClB,IAAI,UAAU,IAAI,OAAO,CAAC;IAC1B,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;IACzD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IACrC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IACnC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CACxB"} \ No newline at end of file diff --git a/packages/expo/src/winter/fetch/FetchResponse.ts b/packages/expo/src/winter/fetch/FetchResponse.ts index fc700c29220e64..2e29b604edcfab 100644 --- a/packages/expo/src/winter/fetch/FetchResponse.ts +++ b/packages/expo/src/winter/fetch/FetchResponse.ts @@ -1,5 +1,5 @@ import { ExpoFetchModule } from './ExpoFetchModule'; -import type { NativeResponse } from './NativeRequest'; +import type { NativeHeadersType, NativeResponse } from './NativeRequest'; const ConcreteNativeResponse = ExpoFetchModule.NativeResponse as typeof NativeResponse; export type AbortSubscriptionCleanupFunction = () => void; @@ -9,73 +9,242 @@ export type AbortSubscriptionCleanupFunction = () => void; type RNFormData = Awaited>; type UniversalFormData = globalThis.FormData & RNFormData; +// Snapshot used by clones so their metadata getters skip the native side. +interface ResponseMetadata { + readonly rawHeaders: NativeHeadersType; + readonly status: number; + readonly statusText: string; + readonly url: string; + readonly redirected: boolean; +} + +const stateKey = Symbol('FetchResponse.state'); + +function wrapWithConsumption( + source: ReadableStream>, + body: Body +): ReadableStream> { + const reader = source.getReader(); + let markedConsumed = false; + + return new ReadableStream( + { + async pull(controller) { + if (!markedConsumed) { + markedConsumed = true; + body.consumed = true; + } + + try { + const { done, value } = await reader.read(); + + if (done) { + controller.close(); + reader.releaseLock(); + } else { + controller.enqueue(value); + } + } catch (error) { + controller.error(error); + reader.releaseLock(); + } + }, + cancel(reason) { + if (!markedConsumed) { + markedConsumed = true; + body.consumed = true; + } + + // ReadableStreamTee uses a single shared cancelPromise that only + // resolves once both branches are canceled, so awaiting here would + // hang whenever the user cancels just one side. + reader.cancel(reason).catch(() => {}); + }, + }, + { + // Keep pull lazy. The default highWaterMark of 1 would fire pull at + // construction and flip consumed before anything had actually been read. + highWaterMark: 0, + } + ); +} + +// JS-side body state. Held behind the stateKey symbol slot. +class Body { + streamingState: 'none' | 'started' | 'completed' = 'none'; + stream: ReadableStream> | null = null; + cloned: boolean; + consumed = false; + + constructor({ cloned }: { cloned: boolean }) { + this.cloned = cloned; + } + + get used(): boolean { + // After clone(), each branch tracks its own reads via its wrapper, and + // streamingState would flip on either branch's read, so we ignore it here. + if (this.cloned) { + return this.consumed; + } + + return this.consumed || this.streamingState !== 'none'; + } + + async readAsBuffer(): Promise { + if (this.stream == null) { + return new ArrayBuffer(0); + } + + const reader = this.stream.getReader(); + const chunks: Uint8Array[] = []; + let length = 0; + + try { + while (true) { + const { done, value } = await reader.read(); + + if (!done) { + chunks.push(value); + length += value.byteLength; + } else { + break; + } + } + } finally { + reader.releaseLock(); + } + + const output = new Uint8Array(length); + let offset = 0; + + for (const chunk of chunks) { + output.set(chunk, offset); + offset += chunk.byteLength; + } + + return output.buffer; + } +} + +// metadata is null for originals (they read from native) and set for clones. +interface State { + body: Body; + metadata: ResponseMetadata | null; +} + /** * A response implementation for the `fetch.Response` API. */ export class FetchResponse extends ConcreteNativeResponse implements Response { - private streamingState: 'none' | 'started' | 'completed' = 'none'; - private bodyStream: ReadableStream> | null = null; + private [stateKey]: State = { + body: new Body({ cloned: false }), + metadata: null, + }; constructor(private readonly abortCleanupFunction: AbortSubscriptionCleanupFunction) { super(); this.addListener('readyForJSFinalization', this.finalize); } + // Originals have metadata=null and fall through to the native getter via super. + // Clones carry a snapshot and stand alone. + + override get _rawHeaders(): NativeHeadersType { + return this[stateKey].metadata?.rawHeaders ?? super._rawHeaders; + } + + override get status(): number { + return this[stateKey].metadata?.status ?? super.status; + } + + override get statusText(): string { + return this[stateKey].metadata?.statusText ?? super.statusText; + } + + override get url(): string { + return this[stateKey].metadata?.url ?? super.url; + } + + override get redirected(): boolean { + return this[stateKey].metadata?.redirected ?? super.redirected; + } + + get type(): 'default' { + return 'default'; + } + get body(): ReadableStream> | null { - if (this.bodyStream == null) { - const response = this; + const body = this[stateKey].body; + if (body.stream == null) { // This flag prevents enqueuing data after the stream is closed or canceled. // Because it might be too late for the multithreaded native code to stop enqueuing data, // we cannot simply rely on the native code to stop sending `didReceiveResponseData`. let isControllerClosed = false; - this.bodyStream = new ReadableStream({ - start(controller) { - if (response.streamingState === 'completed') { - return; - } - response.addListener('didReceiveResponseData', (data: Uint8Array) => { - if (!isControllerClosed) { - controller.enqueue(data); + body.stream = new ReadableStream( + { + start: (controller) => { + if (body.streamingState === 'completed') { + return; } - }); - - response.addListener('didComplete', () => { - controller.close(); - isControllerClosed = true; - }); - response.addListener('didFailWithError', (error: string) => { - controller.error(new Error(error)); - isControllerClosed = true; - }); - }, - async pull(controller) { - if (response.streamingState === 'none') { - const completedData = await response.startStreaming(); - if (completedData != null) { + this.addListener('didReceiveResponseData', (data: Uint8Array) => { if (!isControllerClosed) { - controller.enqueue(completedData); - controller.close(); - isControllerClosed = true; + controller.enqueue(data); + } + }); + + this.addListener('didComplete', () => { + controller.close(); + isControllerClosed = true; + }); + + this.addListener('didFailWithError', (error: string) => { + controller.error(new Error(error)); + isControllerClosed = true; + }); + }, + + pull: async (controller) => { + if (body.streamingState === 'none') { + const completedData = await this.startStreaming(); + + if (completedData != null) { + if (!isControllerClosed) { + controller.enqueue(completedData); + controller.close(); + isControllerClosed = true; + } + + body.streamingState = 'completed'; + } else { + body.streamingState = 'started'; } - response.streamingState = 'completed'; - } else { - response.streamingState = 'started'; + } else if (body.streamingState === 'completed') { + controller.close(); + isControllerClosed = true; } - } else if (response.streamingState === 'completed') { - controller.close(); + }, + + cancel: (reason) => { + this.cancelStreaming(String(reason)); isControllerClosed = true; - } + }, }, - cancel(reason) { - response.cancelStreaming(String(reason)); - isControllerClosed = true; - }, - }); + { + // Keep pull lazy. The default highWaterMark of 1 would fire pull at + // construction and flip streamingState before anything had actually + // been read, making bodyUsed return true after merely touching .body. + highWaterMark: 0, + } + ); } - return this.bodyStream; + return body.stream; + } + + override get bodyUsed(): boolean { + return this[stateKey].body.used; } get headers(): Headers { @@ -86,37 +255,68 @@ export class FetchResponse extends ConcreteNativeResponse implements Response { return this.status >= 200 && this.status < 300; } - public readonly type = 'default'; - /** * This method is not currently supported by react-native's Blob constructor. */ async blob(): Promise { + this.checkBodyUsedError('blob'); const buffer = await this.arrayBuffer(); return new Blob([buffer]); } async formData(): Promise { + this.checkBodyUsedError('formData'); + // Reference implementation: // https://chromium.googlesource.com/chromium/src/+/ed9f0b5933cf5ffb413be1ca844de5be140514bf/third_party/blink/renderer/core/fetch/body.cc#120 const text = await this.text(); const searchParams = new URLSearchParams(text); const formData = new FormData() as UniversalFormData; + searchParams.forEach((value, key) => { formData.append(key, value); }); + return formData; } async json(): Promise { + this.checkBodyUsedError('json'); const text = await this.text(); return JSON.parse(text); } async bytes(): Promise> { + this.checkBodyUsedError('bytes'); return new Uint8Array(await this.arrayBuffer()); } + override async arrayBuffer(): Promise { + this.checkBodyUsedError('arrayBuffer'); + + const body = this[stateKey].body; + body.consumed = true; + + if (body.cloned) { + return body.readAsBuffer(); + } + + return super.arrayBuffer(); + } + + override async text(): Promise { + this.checkBodyUsedError('text'); + + const body = this[stateKey].body; + body.consumed = true; + + if (body.cloned) { + return new TextDecoder().decode(await body.readAsBuffer()); + } + + return super.text(); + } + toString(): string { return `FetchResponse: { status: ${this.status}, statusText: ${this.statusText}, url: ${this.url} }`; } @@ -130,8 +330,52 @@ export class FetchResponse extends ConcreteNativeResponse implements Response { }; } - clone(): Response { - throw new Error('Not implemented'); + clone(): FetchResponse { + this.checkBodyUsedError('clone'); + + const state = this[stateKey]; + // Object.create skips the native constructor. The clone reads metadata + // from its own snapshot, so it doesn't touch native after this. + const clone = Object.create(FetchResponse.prototype) as FetchResponse; + + const cloneState: State = { + body: new Body({ cloned: true }), + metadata: { + rawHeaders: this._rawHeaders.slice(), + status: this.status, + statusText: this.statusText, + url: this.url, + redirected: this.redirected, + }, + }; + + Object.defineProperty(clone, stateKey, { + value: cloneState, + configurable: true, + writable: true, + }); + + // Tee so both responses can be read independently. Each branch is wrapped + // so the first read flips the right consumed flag (otherwise bodyUsed lies). + if (this.body != null) { + const [stream1, stream2] = this.body.tee(); + state.body.stream = wrapWithConsumption(stream1, state.body); + cloneState.body.stream = wrapWithConsumption(stream2, cloneState.body); + } + + state.body.cloned = true; + + return clone; + } + + private checkBodyUsedError(method: string): void { + const body = this[stateKey].body; + + if (body.used || body.stream?.locked === true) { + throw new TypeError( + `Failed to execute '${method}' on 'Response': Response body is already used.` + ); + } } private finalize = (): void => { diff --git a/packages/expo/src/winter/fetch/NativeRequest.ts b/packages/expo/src/winter/fetch/NativeRequest.ts index bb1444c9ba38a3..8f348d4b582c93 100644 --- a/packages/expo/src/winter/fetch/NativeRequest.ts +++ b/packages/expo/src/winter/fetch/NativeRequest.ts @@ -26,12 +26,12 @@ export type NativeResponseEvents = { }; export declare class NativeResponse extends SharedObject { - readonly bodyUsed: boolean; - readonly _rawHeaders: NativeHeadersType; - readonly status: number; - readonly statusText: string; - readonly url: string; - readonly redirected: boolean; + get bodyUsed(): boolean; + get _rawHeaders(): NativeHeadersType; + get status(): number; + get statusText(): string; + get url(): string; + get redirected(): boolean; startStreaming(): Promise | null>; cancelStreaming(reason: string): void; arrayBuffer(): Promise; diff --git a/packages/expo/src/winter/fetch/__tests__/FetchResponse-test.ts b/packages/expo/src/winter/fetch/__tests__/FetchResponse-test.ts index 6f4c10e47e3aa2..a1281833a24801 100644 --- a/packages/expo/src/winter/fetch/__tests__/FetchResponse-test.ts +++ b/packages/expo/src/winter/fetch/__tests__/FetchResponse-test.ts @@ -4,9 +4,64 @@ import { FetchResponse } from '../FetchResponse'; +globalThis.ReadableStream = require('node:stream/web').ReadableStream; +globalThis.TextDecoder = require('node:util').TextDecoder; +globalThis.TextEncoder = require('node:util').TextEncoder; + jest.mock('../ExpoFetchModule', () => { - class StubNativeResponse {} + const { TextEncoder, TextDecoder } = require('node:util'); + const helloWorld = new TextEncoder().encode('hello world'); + + class StubNativeResponse { + private _bodyUsed = false; + + // Getters on the prototype, like the real native binding, so super.x works. + get _rawHeaders(): [string, string][] { + return [['content-type', 'text/plain']]; + } + get status(): number { + return 200; + } + get statusText(): string { + return 'OK'; + } + get url(): string { + return 'https://example.test/'; + } + get redirected(): boolean { + return false; + } + + get bodyUsed(): boolean { + return this._bodyUsed; + } + + addListener() {} + removeListener() {} + removeAllListeners() {} + + async arrayBuffer(): Promise { + this._bodyUsed = true; + return helloWorld.buffer.slice( + helloWorld.byteOffset, + helloWorld.byteOffset + helloWorld.byteLength + ) as ArrayBuffer; + } + + async text(): Promise { + this._bodyUsed = true; + return new TextDecoder().decode(helloWorld); + } + + async startStreaming(): Promise { + return helloWorld; + } + + cancelStreaming() {} + } + class StubNativeRequest {} + return { ExpoFetchModule: { NativeRequest: StubNativeRequest, @@ -15,8 +70,191 @@ jest.mock('../ExpoFetchModule', () => { }; }); +function makeResponse(): FetchResponse { + return new FetchResponse(() => {}); +} + describe('FetchResponse', () => { it('identifies as a standard Response via Symbol.toStringTag', () => { expect(Object.prototype.toString.call(FetchResponse.prototype)).toBe('[object Response]'); }); + + describe('clone()', () => { + it('returns a Response that exposes the same metadata', () => { + const response = makeResponse(); + const cloned = response.clone(); + expect(cloned.status).toBe(response.status); + expect(cloned.statusText).toBe(response.statusText); + expect(cloned.url).toBe(response.url); + expect(cloned.redirected).toBe(response.redirected); + expect(cloned.ok).toBe(response.ok); + expect(cloned.type).toBe('default'); + expect(cloned.headers.get('content-type')).toBe('text/plain'); + expect(Object.prototype.toString.call(cloned)).toBe('[object Response]'); + }); + + it('lets the original and the clone read the body independently', async () => { + const response = makeResponse(); + const cloned = response.clone(); + + const [originalBytes, clonedBytes] = await Promise.all([ + response.arrayBuffer(), + cloned.arrayBuffer(), + ]); + + expect(originalBytes.byteLength).toBe(11); + expect(clonedBytes.byteLength).toBe(11); + expect(response.bodyUsed).toBe(true); + expect(cloned.bodyUsed).toBe(true); + }); + + it('supports cloning the clone', async () => { + const response = makeResponse(); + const cloned = response.clone(); + const reCloned = cloned.clone(); + const bytes = await reCloned.arrayBuffer(); + expect(bytes.byteLength).toBe(11); + }); + + it('throws a TypeError if the body has already been read', async () => { + const response = makeResponse(); + await response.arrayBuffer(); + expect(() => response.clone()).toThrow(TypeError); + }); + + it('throws a TypeError if the body stream is locked', () => { + const response = makeResponse(); + response.body!.getReader(); + expect(() => response.clone()).toThrow(TypeError); + }); + + it('throws a TypeError if the body has been partially read and released', async () => { + const response = makeResponse(); + const reader = response.body!.getReader(); + await reader.read(); + reader.releaseLock(); + expect(() => response.clone()).toThrow(TypeError); + }); + + it('keeps the original readable after the clone body is cancelled', async () => { + const response = makeResponse(); + const cloned = response.clone(); + await cloned.body!.cancel(); + expect((await response.arrayBuffer()).byteLength).toBe(11); + }); + + it('keeps the clone readable after the original body is cancelled', async () => { + const response = makeResponse(); + const cloned = response.clone(); + await response.body!.cancel(); + expect((await cloned.arrayBuffer()).byteLength).toBe(11); + }); + + it('reads the body of a clone via text()', async () => { + const response = makeResponse(); + const cloned = response.clone(); + expect(await cloned.text()).toBe('hello world'); + }); + + it('reads the body of a clone via blob()', async () => { + const response = makeResponse(); + const cloned = response.clone(); + const blob = await cloned.blob(); + expect(blob.size).toBe(11); + }); + + it('routes json() through the cloned body', async () => { + const response = makeResponse(); + const cloned = response.clone(); + await expect(cloned.json()).rejects.toThrow(SyntaxError); + }); + + it('routes formData() through the cloned body', async () => { + const response = makeResponse(); + const cloned = response.clone(); + const formData = await cloned.formData(); + expect(formData.get('hello world')).toBe(''); + }); + }); + + describe('body methods', () => { + it('rejects a second arrayBuffer() call with TypeError', async () => { + const response = makeResponse(); + await response.arrayBuffer(); + await expect(response.arrayBuffer()).rejects.toThrow(TypeError); + }); + + it('rejects a second text() call with TypeError', async () => { + const response = makeResponse(); + await response.arrayBuffer(); + await expect(response.text()).rejects.toThrow(TypeError); + }); + + it('rejects text() when the body stream is locked', async () => { + const response = makeResponse(); + response.body!.getReader(); + await expect(response.text()).rejects.toThrow(TypeError); + }); + + it('rejects arrayBuffer() when the body stream is locked', async () => { + const response = makeResponse(); + response.body!.getReader(); + await expect(response.arrayBuffer()).rejects.toThrow(TypeError); + }); + + it('rejects body methods on a clone after its body has been read', async () => { + const response = makeResponse(); + const cloned = response.clone(); + await cloned.arrayBuffer(); + await expect(cloned.text()).rejects.toThrow(TypeError); + }); + + it('flips bodyUsed on a clone after reading its body stream directly', async () => { + const response = makeResponse(); + const cloned = response.clone(); + const reader = cloned.body!.getReader(); + + while (true) { + const { done } = await reader.read(); + if (done) break; + } + + reader.releaseLock(); + expect(cloned.bodyUsed).toBe(true); + }); + + it('does not flip bodyUsed on the original when only the clone is read', async () => { + const response = makeResponse(); + const cloned = response.clone(); + await cloned.arrayBuffer(); + expect(response.bodyUsed).toBe(false); + }); + + it('lets both tee() branches read the body and flips bodyUsed', async () => { + const response = makeResponse(); + const [branchA, branchB] = response.body!.tee(); + + const drain = async (stream: ReadableStream>) => { + const reader = stream.getReader(); + let length = 0; + + while (true) { + const { done, value } = await reader.read(); + + if (!done) { + length += value.byteLength; + } else { + break; + } + } + + return length; + }; + + const [aLength, bLength] = await Promise.all([drain(branchA), drain(branchB)]); + expect(aLength).toBe(11); + expect(bLength).toBe(11); + expect(response.bodyUsed).toBe(true); + }); + }); }); From c9fc676ccce7030f2851af15b944326e887e7bb8 Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Fri, 15 May 2026 16:58:15 +0500 Subject: [PATCH 04/15] [docs][router] Fix typo in screen-tracking page description (#45825) --- docs/pages/router/reference/screen-tracking.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/router/reference/screen-tracking.mdx b/docs/pages/router/reference/screen-tracking.mdx index 3fcfe1e0ea75be..eeb6243f18ebf2 100644 --- a/docs/pages/router/reference/screen-tracking.mdx +++ b/docs/pages/router/reference/screen-tracking.mdx @@ -1,6 +1,6 @@ --- title: Screen tracking for analytics -description: Learn how to enable screen tracking for analytic when using Expo Router. +description: Learn how to enable screen tracking for analytics when using Expo Router. hideTOC: true --- From 6322cda59423fb225424360cfe0a14e9f36e5330 Mon Sep 17 00:00:00 2001 From: Stefano Volpatti Date: Fri, 15 May 2026 13:58:26 +0200 Subject: [PATCH 05/15] [docs] Add non-null assertion to Supabase URL and key (#45807) --- docs/pages/guides/using-supabase.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/guides/using-supabase.mdx b/docs/pages/guides/using-supabase.mdx index 7df9cba1ee551f..c977f29356a903 100644 --- a/docs/pages/guides/using-supabase.mdx +++ b/docs/pages/guides/using-supabase.mdx @@ -50,8 +50,8 @@ Create a helper file to initialize the Supabase client (`@supabase/supabase-js`) import 'expo-sqlite/localStorage/install'; import { createClient } from '@supabase/supabase-js'; -const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL; -const supabasePublishableKey = YOUR_REACT_NATIVE_SUPABASE_PUBLISHABLE_KEY; +const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL!; +const supabasePublishableKey = YOUR_REACT_NATIVE_SUPABASE_PUBLISHABLE_KEY!; export const supabase = createClient(supabaseUrl, supabasePublishableKey, { auth: { From d139acd4a4eab462c9b838c9b329b512c5a5a002 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Fri, 15 May 2026 14:11:19 +0100 Subject: [PATCH 06/15] [cli] Link to troubleshooting guide from simulator error (#43786) # Why Resolves ENG-11381 # How Link to https://docs.expo.dev/workflow/ios-simulator/#troubleshooting when opening iOS simulator errors. # Test Plan Unit test. # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/@expo/cli/CHANGELOG.md | 1 + .../platforms/ios/__tests__/simctl-test.ts | 21 +++++++++++++++++++ .../cli/src/start/platforms/ios/simctl.ts | 2 ++ 3 files changed, 24 insertions(+) diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index 75560760785a33..596bc7ebed7213 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -11,6 +11,7 @@ - Fix loader HMR when streaming SSR is enabled in dev mode ([#45702](https://github.com/expo/expo/pull/45702) by [@hassankhan](https://github.com/hassankhan)) - Fix long project paths overflowing the dev server interstitial page by making the path scroll horizontally. ([#45808](https://github.com/expo/expo/pull/45808) by [@EvanBacon](https://github.com/EvanBacon)) - Serve an unsigned Expo Go manifest instead of failing with HTTP 500 when `expo start` is unauthenticated in a non-interactive shell. ([#45809](https://github.com/expo/expo/pull/45809) by [@EvanBacon](https://github.com/EvanBacon)) +- Add troubleshooting guide link to simulator boot failure errors. ([#43786](https://github.com/expo/expo/pull/43786) by [@kadikraman](https://github.com/kadikraman)) ### 💡 Others diff --git a/packages/@expo/cli/src/start/platforms/ios/__tests__/simctl-test.ts b/packages/@expo/cli/src/start/platforms/ios/__tests__/simctl-test.ts index 804b2bad90842a..b90620285c6e7b 100644 --- a/packages/@expo/cli/src/start/platforms/ios/__tests__/simctl-test.ts +++ b/packages/@expo/cli/src/start/platforms/ios/__tests__/simctl-test.ts @@ -2,6 +2,7 @@ import spawnAsync from '@expo/spawn-async'; import * as Log from '../../../../log'; import { + bootDeviceAsync, getContainerPathAsync, getDevicesAsync, getInfoPlistValueAsync, @@ -75,6 +76,26 @@ describe(getInfoPlistValueAsync, () => { }); }); +describe(bootDeviceAsync, () => { + it(`does not throw when device is already booted`, async () => { + jest.mocked(spawnAsync).mockRejectedValueOnce({ + stderr: 'Unable to boot device in current state: Booted', + }); + + await expect(bootDeviceAsync({ udid: 'fake-udid' })).resolves.toBeUndefined(); + }); + + it(`throws with troubleshooting link when boot fails`, async () => { + jest.mocked(spawnAsync).mockRejectedValueOnce( + Object.assign(new Error('xcrun simctl boot exited with non-zero code: 148'), { + stderr: 'Unable to boot device in current state: Creating', + }) + ); + + await expect(bootDeviceAsync({ udid: 'fake-udid' })).rejects.toThrow(/Troubleshooting guide/); + }); +}); + describe(getContainerPathAsync, () => { it(`returns container path`, async () => { jest.mocked(spawnAsync).mockResolvedValueOnce({ diff --git a/packages/@expo/cli/src/start/platforms/ios/simctl.ts b/packages/@expo/cli/src/start/platforms/ios/simctl.ts index 21a7b58f172f80..55de785be4605d 100644 --- a/packages/@expo/cli/src/start/platforms/ios/simctl.ts +++ b/packages/@expo/cli/src/start/platforms/ios/simctl.ts @@ -9,6 +9,7 @@ import { isSpawnResultError, xcrunAsync } from './xcrun'; import * as Log from '../../../log'; import { CommandError } from '../../../utils/errors'; import { memoize } from '../../../utils/fn'; +import { learnMore } from '../../../utils/link'; import { parsePlistAsync } from '../../../utils/plist'; import { profile } from '../../../utils/profile'; @@ -275,6 +276,7 @@ export async function bootDeviceAsync(device: DeviceContext): Promise { await simctlAsync(['boot', device.udid]); } catch (error: any) { if (!error.stderr?.match(/Unable to boot device in current state: Booted/)) { + error.message += `\n${learnMore('https://docs.expo.dev/workflow/ios-simulator/#troubleshooting', { learnMoreMessage: 'Troubleshooting guide' })}`; throw error; } } From ae80c754544a0cab6b9719ecee36a98c78551cd9 Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Fri, 15 May 2026 16:06:54 +0200 Subject: [PATCH 07/15] [expo-app-metrics] fix concurrent access to FrameMetricsRecorder (#45840) # Why https://linear.app/expo/issue/ENG-21273/fix-android-crash-in-frameratemonitor # How Change `mutableListOf` to `CopyOnWriteArrayList` - the recorders array is not muted often. # Test Plan 1. CI - added concurrent access test 2. ObserveTester # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../appmetrics/frames/FrameMetricsRecorder.kt | 3 + .../appmetrics/frames/FrameRateMonitor.kt | 29 ++++++-- .../appmetrics/frames/FrameRateMonitorTest.kt | 73 +++++++++++++++++++ 3 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/frames/FrameRateMonitorTest.kt diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/frames/FrameMetricsRecorder.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/frames/FrameMetricsRecorder.kt index 1cc2a2f0046287..53f255f9b1f23d 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/frames/FrameMetricsRecorder.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/frames/FrameMetricsRecorder.kt @@ -9,6 +9,9 @@ import android.app.Activity * and retrieve the accumulated metrics. */ class FrameMetricsRecorder { + // Reassigned from JS thread in stop() while dispatchFrame mutates it on the + // main thread. @Volatile gives stop() a stable visible reference to swap. + @Volatile private var record = FrameMetricsRecord() /** Starts recording frame metrics for the given activity's window. */ diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/frames/FrameRateMonitor.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/frames/FrameRateMonitor.kt index 0f90ee1cf273db..970bb5cc3bc46f 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/frames/FrameRateMonitor.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/frames/FrameRateMonitor.kt @@ -5,24 +5,34 @@ import android.os.Handler import android.os.Looper import android.view.Window import java.lang.ref.WeakReference +import java.util.concurrent.CopyOnWriteArrayList /** * Internal singleton that manages the [Window.OnFrameMetricsAvailableListener]. * Auto-attaches to the window when the first recorder is added and * auto-detaches when the last is removed. + * + * The frame callback runs on the main thread while [addRecorder] / [removeRecorder] + * are typically invoked from the JS thread, so [recorders] must be safe for + * concurrent iteration. [CopyOnWriteArrayList] gives lock-free iteration on the + * hot frame path; writes are rare (per record start/stop). */ internal object FrameRateMonitor { private var listener: Window.OnFrameMetricsAvailableListener? = null private var currentActivity: WeakReference? = null - private val recorders = mutableListOf>() + private val recorders = CopyOnWriteArrayList>() + @Synchronized fun addRecorder(recorder: FrameMetricsRecorder, activity: Activity) { recorders.add(WeakReference(recorder)) startMonitoringIfNeeded(activity) } + @Synchronized fun removeRecorder(recorder: FrameMetricsRecorder) { - recorders.removeAll { it.get() === recorder || it.get() == null } + // Use CopyOnWriteArrayList's atomic removeIf — Kotlin's removeAll { } extension + // uses an indexed iterator that is not safe against concurrent add(). + recorders.removeIf { ref -> ref.get().let { it === recorder || it == null } } stopMonitoringIfEmpty() } @@ -33,11 +43,7 @@ internal object FrameRateMonitor { val newListener = Window.OnFrameMetricsAvailableListener { _, frameMetrics, _ -> val frameDurationMs = (frameMetrics.getMetric(android.view.FrameMetrics.TOTAL_DURATION) / 1_000_000.0).toLong() - - removeReleasedRecorders() - for (ref in recorders) { - ref.get()?.processFrame(frameDurationMs) - } + dispatchFrame(frameDurationMs) } listener = newListener @@ -57,6 +63,13 @@ internal object FrameRateMonitor { } private fun removeReleasedRecorders() { - recorders.removeAll { it.get() == null } + recorders.removeIf { it.get() == null } + } + + internal fun dispatchFrame(frameDurationMs: Long) { + removeReleasedRecorders() + for (ref in recorders) { + ref.get()?.processFrame(frameDurationMs) + } } } diff --git a/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/frames/FrameRateMonitorTest.kt b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/frames/FrameRateMonitorTest.kt new file mode 100644 index 00000000000000..08d2b1ef3e0397 --- /dev/null +++ b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/frames/FrameRateMonitorTest.kt @@ -0,0 +1,73 @@ +package expo.modules.appmetrics.frames + +import android.app.Activity +import android.view.Window +import io.mockk.every +import io.mockk.mockk +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.atomic.AtomicBoolean + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, sdk = [28]) +class FrameRateMonitorTest { + private val seed = FrameMetricsRecorder() + + @After + fun tearDown() { + FrameRateMonitor.removeRecorder(seed) + } + + @Test + fun `concurrent recorder churn during frame dispatch does not throw`() { + val window = mockk(relaxed = true) + val activity = mockk(relaxed = true) + every { activity.window } returns window + + // Seed with a long-lived recorder so the monitor attaches and stays attached + // throughout the test (otherwise the churn thread might briefly empty the list). + seed.start(activity) + + val running = AtomicBoolean(true) + val errors = ConcurrentLinkedQueue() + + val frameThread = Thread({ + while (running.get()) { + try { + FrameRateMonitor.dispatchFrame(16L) + } catch (t: Throwable) { + errors.add(t) + } + } + }, "frame-dispatch-thread") + + val recorderThread = Thread({ + while (running.get()) { + try { + val r = FrameMetricsRecorder() + r.start(activity) + r.stop() + } catch (t: Throwable) { + errors.add(t) + } + } + }, "recorder-churn-thread") + + frameThread.start() + recorderThread.start() + Thread.sleep(500) + running.set(false) + frameThread.join() + recorderThread.join() + + assertTrue( + "Expected no exceptions, got ${errors.size}; first: ${errors.firstOrNull()?.let { "${it.javaClass.simpleName}: ${it.message}" }}", + errors.isEmpty() + ) + } +} From dad3ce1469d54e34dcdde29078d8b5df0a9cd186 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Fri, 15 May 2026 16:17:07 +0200 Subject: [PATCH 08/15] [app-metrics] Extract MetricParamsBuilder + typed device/network state (#45440) --- .../appstartup/AppStartupManager.kt | 17 +-- .../appmetrics/utils/DeviceConditions.kt | 129 ++++++++++------ .../appmetrics/utils/MetricParamsBuilder.kt | 60 ++++++++ .../utils/MetricParamsBuilderTest.kt | 122 +++++++++++++++ .../ios/AppStartup/AppStartupMonitoring.swift | 22 ++- .../ios/Tests/MetricParamsBuilderTests.swift | 130 ++++++++++++++++ .../ios/Utils/DeviceConditions.swift | 139 ++++++++---------- .../ios/Utils/MetricParamsBuilder.swift | 69 +++++++++ 8 files changed, 546 insertions(+), 142 deletions(-) create mode 100644 packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/MetricParamsBuilder.kt create mode 100644 packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/utils/MetricParamsBuilderTest.kt create mode 100644 packages/expo-app-metrics/ios/Tests/MetricParamsBuilderTests.swift create mode 100644 packages/expo-app-metrics/ios/Utils/MetricParamsBuilder.swift diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/appstartup/AppStartupManager.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/appstartup/AppStartupManager.kt index c36c5364ef1487..c6358d78c707d9 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/appstartup/AppStartupManager.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/appstartup/AppStartupManager.kt @@ -13,6 +13,7 @@ import expo.modules.appmetrics.frames.FrameMetricsRecord import expo.modules.appmetrics.frames.FrameMetricsRecorder import expo.modules.appmetrics.storage.Metric import expo.modules.appmetrics.utils.DeviceConditions +import expo.modules.appmetrics.utils.MetricParamsBuilder import expo.modules.appmetrics.utils.TimeUtils.getCurrentTimeInMillis import expo.modules.appmetrics.utils.TimeUtils.getCurrentTimestampInISOFormat import expo.modules.appmetrics.utils.TimeUtils.getProcessStartTimeInMillis @@ -225,16 +226,12 @@ object AppStartupManager { frameMetrics: FrameMetricsRecord, userParams: Map? ): Map { - val merged = mutableMapOf() - userParams?.let { merged.putAll(it) } - if (frameMetrics.expectedFrames > 0) { - merged["expo.frameRate.slowFrames"] = frameMetrics.slowFrames - merged["expo.frameRate.frozenFrames"] = frameMetrics.frozenFrames - merged["expo.frameRate.totalDelay"] = frameMetrics.freezeTimeMs.toDouble() / 1000.0 - } - merged.putAll(DeviceConditions.deviceParams(context)) - merged.putAll(DeviceConditions.networkParams(context)) - return merged + return MetricParamsBuilder.build( + userParams = userParams, + frameMetrics = frameMetrics, + deviceState = DeviceConditions.deviceState(context), + networkState = DeviceConditions.networkState(context) + ) } fun markFirstRender() { diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/DeviceConditions.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/DeviceConditions.kt index 820ee1b2c873c4..aa6fa5c2c9a645 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/DeviceConditions.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/DeviceConditions.kt @@ -9,89 +9,132 @@ import android.os.BatteryManager import android.os.Build import android.os.PowerManager +enum class ThermalState { + NOMINAL, + FAIR, + SERIOUS, + CRITICAL, + UNKNOWN +} + +enum class NetworkTransport { + WIFI, + CELLULAR, + ETHERNET, + OTHER, + NONE +} + /** - * Snapshots of the device's environment (power, thermals, connectivity) - * attached to metrics like `timeToInteractive` so regressions can be - * correlated with conditions outside the app's control. + * A snapshot of the device's power, thermal, and battery state. + * + * Fields may be `null` when Android does not provide the data, + * such as on older OS versions or before battery info is available. */ -object DeviceConditions { - fun deviceParams(context: Context): Map { - val params = mutableMapOf() +data class DeviceState( + val lowPowerMode: Boolean? = null, + val thermalState: ThermalState? = null, + val batteryLevel: Double? = null, + val batteryCharging: Boolean? = null +) + +/** + * A snapshot of the device's network connectivity. `connected` is `false` + * and `transport` is `NONE` when no `ConnectivityManager` service is + * available or the active network has no capabilities. + */ +data class NetworkState( + val connected: Boolean, + val transport: NetworkTransport +) +/** + * Reads the device's environment (power, thermals, battery, connectivity) + * into typed `DeviceState` / `NetworkState` snapshots. The wire-format + * conversion to `expo.*` keys lives in `MetricParamsBuilder`. + */ +object DeviceConditions { + fun deviceState(context: Context): DeviceState { val powerManager = context.getSystemService(Context.POWER_SERVICE) as? PowerManager - if (powerManager != null) { - params["expo.device.lowPowerMode"] = powerManager.isPowerSaveMode - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - params["expo.device.thermalState"] = thermalStatusString(powerManager.currentThermalStatus) - } + val lowPowerMode = powerManager?.isPowerSaveMode + val thermalState = if (powerManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + mapThermalStatus(powerManager.currentThermalStatus) + } else { + null } val batteryStatus = context.registerReceiver( null, IntentFilter(Intent.ACTION_BATTERY_CHANGED) ) - if (batteryStatus != null) { - val level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) - val scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1) + val batteryLevel = batteryStatus?.let { + val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) + val scale = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1) if (level >= 0 && scale > 0) { - params["expo.device.batteryLevel"] = level.toDouble() / scale.toDouble() + level.toDouble() / scale.toDouble() + } else { + null } - val status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1) - if (status != -1) { - params["expo.device.batteryCharging"] = - status == BatteryManager.BATTERY_STATUS_CHARGING || + } + val batteryCharging = batteryStatus?.let { + val status = it.getIntExtra(BatteryManager.EXTRA_STATUS, -1) + if (status == -1) { + null + } else { + status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL } } - return params + return DeviceState( + lowPowerMode = lowPowerMode, + thermalState = thermalState, + batteryLevel = batteryLevel, + batteryCharging = batteryCharging + ) } - fun networkParams(context: Context): Map { + fun networkState(context: Context): NetworkState { val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager - ?: return mapOf( - "expo.network.connected" to false, - "expo.network.type" to "unknown" - ) + ?: return NetworkState(connected = false, transport = NetworkTransport.NONE) // Read capabilities once: `cm.activeNetwork` and `getNetworkCapabilities` // can drift if the connection changes between calls, and a single read // also halves the syscalls. val capabilities = cm.activeNetwork?.let { cm.getNetworkCapabilities(it) } val connected = capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true - - return mapOf( - "expo.network.connected" to connected, - "expo.network.type" to networkTypeString(capabilities) + return NetworkState( + connected = connected, + transport = mapTransport(capabilities) ) } - private fun thermalStatusString(status: Int): String { + private fun mapThermalStatus(status: Int): ThermalState { return when (status) { - PowerManager.THERMAL_STATUS_NONE -> "nominal" + PowerManager.THERMAL_STATUS_NONE -> ThermalState.NOMINAL PowerManager.THERMAL_STATUS_LIGHT, - PowerManager.THERMAL_STATUS_MODERATE -> "fair" - PowerManager.THERMAL_STATUS_SEVERE -> "serious" + PowerManager.THERMAL_STATUS_MODERATE -> ThermalState.FAIR + PowerManager.THERMAL_STATUS_SEVERE -> ThermalState.SERIOUS PowerManager.THERMAL_STATUS_CRITICAL, PowerManager.THERMAL_STATUS_EMERGENCY, - PowerManager.THERMAL_STATUS_SHUTDOWN -> "critical" - else -> "unknown" + PowerManager.THERMAL_STATUS_SHUTDOWN -> ThermalState.CRITICAL + else -> ThermalState.UNKNOWN } } - private fun networkTypeString(capabilities: NetworkCapabilities?): String { + private fun mapTransport(capabilities: NetworkCapabilities?): NetworkTransport { if (capabilities == null) { - return "none" + return NetworkTransport.NONE } // VPN tunnels over wifi/cellular, and Bluetooth tethering is rare enough - // that it's not worth a dedicated bucket — both fold into `other` so the + // that it's not worth a dedicated bucket — both fold into `OTHER` so the // value set matches iOS. If we ever surface VPN explicitly, it should be - // a separate boolean param rather than a transport value. + // a separate boolean field rather than a transport value. return when { - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "wifi" - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "cellular" - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "ethernet" - else -> "other" + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> NetworkTransport.WIFI + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> NetworkTransport.CELLULAR + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> NetworkTransport.ETHERNET + else -> NetworkTransport.OTHER } } } diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/MetricParamsBuilder.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/MetricParamsBuilder.kt new file mode 100644 index 00000000000000..014447a4b86f31 --- /dev/null +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/MetricParamsBuilder.kt @@ -0,0 +1,60 @@ +package expo.modules.appmetrics.utils + +import expo.modules.appmetrics.frames.FrameMetricsRecord + +/** + * Single source of truth for the `expo.*` keys we attach to metrics. Takes + * typed inputs (`DeviceState`, `NetworkState`, `FrameMetricsRecord`) and + * produces the flat `Map` map the metric envelope expects. + * + * Framework-emitted keys override user-supplied keys on collision so the OS + * readings always win — a user passing `expo.device.lowPowerMode` as a + * string doesn't get to overwrite the actual OS bool. + */ +object MetricParamsBuilder { + fun build( + userParams: Map? = null, + frameMetrics: FrameMetricsRecord? = null, + deviceState: DeviceState? = null, + networkState: NetworkState? = null + ): Map { + val params = mutableMapOf() + userParams?.let { params.putAll(it) } + if (frameMetrics != null && frameMetrics.expectedFrames > 0) { + params["expo.frameRate.slowFrames"] = frameMetrics.slowFrames + params["expo.frameRate.frozenFrames"] = frameMetrics.frozenFrames + params["expo.frameRate.totalDelay"] = frameMetrics.freezeTimeMs.toDouble() / 1000.0 + } + if (deviceState != null) { + deviceState.lowPowerMode?.let { params["expo.device.lowPowerMode"] = it } + deviceState.thermalState?.let { params["expo.device.thermalState"] = thermalStateString(it) } + deviceState.batteryLevel?.let { params["expo.device.batteryLevel"] = it } + deviceState.batteryCharging?.let { params["expo.device.batteryCharging"] = it } + } + if (networkState != null) { + params["expo.network.connected"] = networkState.connected + params["expo.network.type"] = networkTransportString(networkState.transport) + } + return params + } + + private fun thermalStateString(state: ThermalState): String { + return when (state) { + ThermalState.NOMINAL -> "nominal" + ThermalState.FAIR -> "fair" + ThermalState.SERIOUS -> "serious" + ThermalState.CRITICAL -> "critical" + ThermalState.UNKNOWN -> "unknown" + } + } + + private fun networkTransportString(transport: NetworkTransport): String { + return when (transport) { + NetworkTransport.WIFI -> "wifi" + NetworkTransport.CELLULAR -> "cellular" + NetworkTransport.ETHERNET -> "ethernet" + NetworkTransport.OTHER -> "other" + NetworkTransport.NONE -> "none" + } + } +} diff --git a/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/utils/MetricParamsBuilderTest.kt b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/utils/MetricParamsBuilderTest.kt new file mode 100644 index 00000000000000..cf0a0e4b6ce1ae --- /dev/null +++ b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/utils/MetricParamsBuilderTest.kt @@ -0,0 +1,122 @@ +package expo.modules.appmetrics.utils + +import expo.modules.appmetrics.frames.FrameMetricsRecord +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class MetricParamsBuilderTest { + @Test + fun `emits empty map with no inputs`() { + val params = MetricParamsBuilder.build() + assertEquals(emptyMap(), params) + } + + @Test + fun `passes user params through unchanged`() { + val params = MetricParamsBuilder.build( + userParams = mapOf("tenant" to "acme", "cohort" to 3) + ) + assertEquals("acme", params["tenant"]) + assertEquals(3, params["cohort"]) + } + + @Test + fun `maps connected wifi NetworkState to wifi keys`() { + val params = MetricParamsBuilder.build( + networkState = NetworkState(connected = true, transport = NetworkTransport.WIFI) + ) + assertEquals(true, params["expo.network.connected"]) + assertEquals("wifi", params["expo.network.type"]) + } + + @Test + fun `maps disconnected NetworkState to none keys`() { + val params = MetricParamsBuilder.build( + networkState = NetworkState(connected = false, transport = NetworkTransport.NONE) + ) + assertEquals(false, params["expo.network.connected"]) + assertEquals("none", params["expo.network.type"]) + } + + @Test + fun `omits frame keys when expectedFrames is zero`() { + val params = MetricParamsBuilder.build(frameMetrics = FrameMetricsRecord()) + assertNull(params["expo.frameRate.slowFrames"]) + assertNull(params["expo.frameRate.frozenFrames"]) + assertNull(params["expo.frameRate.totalDelay"]) + } + + @Test + fun `emits frame keys when expectedFrames is positive`() { + val params = MetricParamsBuilder.build( + frameMetrics = FrameMetricsRecord( + expectedFrames = 12, + slowFrames = 3, + frozenFrames = 1, + freezeTimeMs = 400 + ) + ) + assertEquals(3L, params["expo.frameRate.slowFrames"]) + assertEquals(1L, params["expo.frameRate.frozenFrames"]) + assertEquals(0.4, params["expo.frameRate.totalDelay"]) + } + + @Test + fun `emits all device keys when DeviceState fields are populated`() { + val params = MetricParamsBuilder.build( + deviceState = DeviceState( + lowPowerMode = true, + thermalState = ThermalState.SERIOUS, + batteryLevel = 0.42, + batteryCharging = false + ) + ) + assertEquals(true, params["expo.device.lowPowerMode"]) + assertEquals("serious", params["expo.device.thermalState"]) + assertEquals(0.42, params["expo.device.batteryLevel"]) + assertEquals(false, params["expo.device.batteryCharging"]) + } + + @Test + fun `omits device keys when DeviceState fields are null`() { + val params = MetricParamsBuilder.build(deviceState = DeviceState()) + assertNull(params["expo.device.lowPowerMode"]) + assertNull(params["expo.device.thermalState"]) + assertNull(params["expo.device.batteryLevel"]) + assertNull(params["expo.device.batteryCharging"]) + } + + @Test + fun `framework-emitted keys override user-supplied keys on collision`() { + val params = MetricParamsBuilder.build( + userParams = mapOf("expo.device.lowPowerMode" to "user-supplied"), + deviceState = DeviceState(lowPowerMode = true) + ) + assertEquals(true, params["expo.device.lowPowerMode"]) + } + + @Test + fun `maps every thermal state to its expo string`() { + fun stateFor(thermal: ThermalState) = MetricParamsBuilder.build( + deviceState = DeviceState(thermalState = thermal) + )["expo.device.thermalState"] + assertEquals("nominal", stateFor(ThermalState.NOMINAL)) + assertEquals("fair", stateFor(ThermalState.FAIR)) + assertEquals("serious", stateFor(ThermalState.SERIOUS)) + assertEquals("critical", stateFor(ThermalState.CRITICAL)) + assertEquals("unknown", stateFor(ThermalState.UNKNOWN)) + } + + @Test + fun `maps every transport to its expo string`() { + fun typeFor(transport: NetworkTransport) = MetricParamsBuilder.build( + networkState = NetworkState(connected = true, transport = transport) + )["expo.network.type"] + assertEquals("wifi", typeFor(NetworkTransport.WIFI)) + assertEquals("cellular", typeFor(NetworkTransport.CELLULAR)) + assertEquals("ethernet", typeFor(NetworkTransport.ETHERNET)) + assertEquals("other", typeFor(NetworkTransport.OTHER)) + assertEquals("none", typeFor(NetworkTransport.NONE)) + } +} diff --git a/packages/expo-app-metrics/ios/AppStartup/AppStartupMonitoring.swift b/packages/expo-app-metrics/ios/AppStartup/AppStartupMonitoring.swift index 9e63838182fbf9..a4aa47f4760891 100644 --- a/packages/expo-app-metrics/ios/AppStartup/AppStartupMonitoring.swift +++ b/packages/expo-app-metrics/ios/AppStartup/AppStartupMonitoring.swift @@ -102,25 +102,21 @@ final class AppStartupMonitoring: MetricReporter, @unchecked Sendable { markers.timeToInteractive = currentTime if let tti = markers.getTTI() { - var params = params let frameMetrics = frameMetricsRecorder.stop() - if frameMetrics.expectedFrames > 0 { - params["expo.frameRate.slowFrames"] = frameMetrics.slowFrames - params["expo.frameRate.frozenFrames"] = frameMetrics.frozenFrames - params["expo.frameRate.totalDelay"] = frameMetrics.freezeTime - } - for (key, value) in await DeviceConditions.deviceParams() { - params[key] = value - } - for (key, value) in await DeviceConditions.networkParams() { - params[key] = value - } + let deviceState = await DeviceConditions.deviceState() + let networkPath = await NetworkPathMonitor.shared.waitForFirstPath() + let mergedParams = MetricParamsBuilder.build( + userParams: params, + frameMetrics: frameMetrics, + deviceState: deviceState, + networkPath: networkPath + ) let metric = Metric( category: .appStartup, name: "timeToInteractive", value: tti, routeName: routeName, - params: params.isEmpty ? nil : params + params: mergedParams.isEmpty ? nil : mergedParams ) reportMetric(metric) } diff --git a/packages/expo-app-metrics/ios/Tests/MetricParamsBuilderTests.swift b/packages/expo-app-metrics/ios/Tests/MetricParamsBuilderTests.swift new file mode 100644 index 00000000000000..fb984cd1c31782 --- /dev/null +++ b/packages/expo-app-metrics/ios/Tests/MetricParamsBuilderTests.swift @@ -0,0 +1,130 @@ +import Foundation +import Testing + +@testable import ExpoAppMetrics + +@Suite("MetricParamsBuilder") +struct MetricParamsBuilderTests { + private let satisfiedWifi = NetworkPath( + status: .satisfied, + interfaceType: .wifi, + isExpensive: false, + isConstrained: false, + unsatisfiedReason: nil, + timestamp: 0 + ) + + private let unsatisfied = NetworkPath( + status: .unsatisfied, + interfaceType: .none, + isExpensive: false, + isConstrained: false, + unsatisfiedReason: .notAvailable, + timestamp: 0 + ) + + @Test + func `returns empty map when all inputs are nil`() { + let params = MetricParamsBuilder.build() + #expect(params.isEmpty) + } + + @Test + func `omits network keys when networkPath is nil`() { + let params = MetricParamsBuilder.build(userParams: ["tenant": "acme"]) + #expect(params["expo.network.connected"] == nil) + #expect(params["expo.network.type"] == nil) + } + + @Test + func `passes user params through unchanged`() { + let params = MetricParamsBuilder.build(userParams: ["tenant": "acme", "cohort": 3]) + #expect(params["tenant"] as? String == "acme") + #expect(params["cohort"] as? Int == 3) + } + + @Test + func `maps satisfied wifi path to connected wifi keys`() { + let params = MetricParamsBuilder.build(networkPath: satisfiedWifi) + #expect(params["expo.network.connected"] as? Bool == true) + #expect(params["expo.network.type"] as? String == "wifi") + } + + @Test + func `maps unsatisfied path to disconnected none keys`() { + let params = MetricParamsBuilder.build(networkPath: unsatisfied) + #expect(params["expo.network.connected"] as? Bool == false) + #expect(params["expo.network.type"] as? String == "none") + } + + @Test + func `omits frame keys when expectedFrames is zero`() { + let frameMetrics = FrameRateMetrics.zero + let params = MetricParamsBuilder.build(frameMetrics: frameMetrics) + #expect(params["expo.frameRate.slowFrames"] == nil) + #expect(params["expo.frameRate.frozenFrames"] == nil) + #expect(params["expo.frameRate.totalDelay"] == nil) + } + + @Test + func `emits frame keys when expectedFrames is positive`() { + let frameMetrics = FrameRateMetrics( + renderedFrames: 10, + expectedFrames: 12, + droppedFrames: 2, + frozenFrames: 1, + slowFrames: 3, + freezeTime: 0.4, + sessionDuration: 1.0 + ) + let params = MetricParamsBuilder.build(frameMetrics: frameMetrics) + #expect(params["expo.frameRate.slowFrames"] as? UInt == 3) + #expect(params["expo.frameRate.frozenFrames"] as? UInt == 1) + #expect(params["expo.frameRate.totalDelay"] as? TimeInterval == 0.4) + } + + @Test + func `emits all device keys when DeviceState fields are populated`() { + let deviceState = DeviceState( + lowPowerMode: true, + thermalState: .serious, + batteryLevel: 0.42, + batteryCharging: false + ) + let params = MetricParamsBuilder.build(deviceState: deviceState) + #expect(params["expo.device.lowPowerMode"] as? Bool == true) + #expect(params["expo.device.thermalState"] as? String == "serious") + #expect(params["expo.device.batteryLevel"] as? Double == 0.42) + #expect(params["expo.device.batteryCharging"] as? Bool == false) + } + + @Test + func `omits device keys when DeviceState fields are nil`() { + let deviceState = DeviceState( + lowPowerMode: nil, + thermalState: nil, + batteryLevel: nil, + batteryCharging: nil + ) + let params = MetricParamsBuilder.build(deviceState: deviceState) + #expect(params["expo.device.lowPowerMode"] == nil) + #expect(params["expo.device.thermalState"] == nil) + #expect(params["expo.device.batteryLevel"] == nil) + #expect(params["expo.device.batteryCharging"] == nil) + } + + @Test + func `framework-emitted device keys override user-supplied ones on collision`() { + let deviceState = DeviceState( + lowPowerMode: true, + thermalState: nil, + batteryLevel: nil, + batteryCharging: nil + ) + let params = MetricParamsBuilder.build( + userParams: ["expo.device.lowPowerMode": "user-supplied"], + deviceState: deviceState + ) + #expect(params["expo.device.lowPowerMode"] as? Bool == true) + } +} diff --git a/packages/expo-app-metrics/ios/Utils/DeviceConditions.swift b/packages/expo-app-metrics/ios/Utils/DeviceConditions.swift index 89d66c22ce3d06..de2bb4ac6e7a38 100644 --- a/packages/expo-app-metrics/ios/Utils/DeviceConditions.swift +++ b/packages/expo-app-metrics/ios/Utils/DeviceConditions.swift @@ -3,17 +3,40 @@ import ExpoModulesCore /** - Snapshots of the device's environment (power, thermals, connectivity) attached - to metrics like `timeToInteractive` so regressions can be correlated with - conditions outside the app's control. + A `Sendable` snapshot of the device's power and thermal state. Fields are + optional so missing OS data (Simulator, brief windows where the OS hasn't + published a value yet) can be expressed as `nil` rather than a sentinel. + */ +struct DeviceState: Sendable, Equatable { + /** + The raw values are part of the `expo.device.thermalState` wire contract + — `MetricParamsBuilder` emits them via `.rawValue`. Don't rename cases. + */ + enum ThermalState: String, Sendable { + case nominal + case fair + case serious + case critical + case unknown + } + + let lowPowerMode: Bool? + let thermalState: ThermalState? + let batteryLevel: Double? + let batteryCharging: Bool? +} + +/** + Reads the device's environment (power, thermals, battery) into a typed + `DeviceState`. The wire-format conversion to `expo.*` keys lives in + `MetricParamsBuilder`. */ enum DeviceConditions { /** - Reads device power and thermal state and returns them as a flat dictionary - keyed with `expo.device.*`. Returned keys are omitted (rather than emitting - sentinel values like `-1` or `.unknown`) when the OS does not report a - meaningful value — typically the Simulator, or the brief window after - battery monitoring is first enabled before the first reading is published. + Reads device power, thermal, and battery state. Returned fields are `nil` + when the OS does not report a meaningful value — typically the Simulator, + or the brief window after battery monitoring is first enabled before the + first reading is published. Pinned to `@MainActor` because `UIDevice` is part of UIKit and its battery state/level reads are not documented as thread-safe. `ProcessInfo` @@ -21,15 +44,20 @@ enum DeviceConditions { we pin the whole helper for simplicity at the cost of one main-actor hop. */ @MainActor - static func deviceParams() -> [String: any Sendable] { - var params: [String: any Sendable] = [:] - - params["expo.device.lowPowerMode"] = ProcessInfo.processInfo.isLowPowerModeEnabled - params["expo.device.thermalState"] = thermalStateString(ProcessInfo.processInfo.thermalState) + static func deviceState() -> DeviceState { + let lowPowerMode = ProcessInfo.processInfo.isLowPowerModeEnabled + let thermalState = thermalState(from: ProcessInfo.processInfo.thermalState) -#if !os(tvOS) +#if os(tvOS) + return DeviceState( + lowPowerMode: lowPowerMode, + thermalState: thermalState, + batteryLevel: nil, + batteryCharging: nil + ) +#else // tvOS devices are wall-powered, so `UIDevice` doesn't expose battery - // state/level there. Skip the battery section entirely on that platform. + // state/level there; the `#if` skips the battery section entirely. let device = UIDevice.current // Battery readings require `isBatteryMonitoringEnabled = true`. We turn it // on once and leave it on rather than save/restore it around the read: @@ -43,72 +71,31 @@ enum DeviceConditions { } let level = device.batteryLevel - if level >= 0 { - params["expo.device.batteryLevel"] = Double(level) - } + let batteryLevel: Double? = level >= 0 ? Double(level) : nil - switch device.batteryState { - case .charging, .full: - params["expo.device.batteryCharging"] = true - case .unplugged: - params["expo.device.batteryCharging"] = false - case .unknown: - break - @unknown default: - break + let batteryCharging: Bool? = switch device.batteryState { + case .charging, .full: true + case .unplugged: false + case .unknown: nil + @unknown default: nil } -#endif - return params - } - - /** - Reads `expo.network.connected` and `expo.network.type` from the snapshot - maintained by `NetworkPathMonitor`. Awaits the monitor's first path - delivery if it hasn't arrived yet — the TTI value is captured from a - synchronously-recorded timestamp before we get here, so the await only - delays the local-storage write, not the metric itself. - */ - @AppMetricsActor - static func networkParams() async -> [String: any Sendable] { - let path = await NetworkPathMonitor.shared.waitForFirstPath() - return [ - "expo.network.connected": path?.status == .satisfied, - "expo.network.type": networkTypeString(path) - ] - } - - private static func thermalStateString(_ state: ProcessInfo.ThermalState) -> String { - switch state { - case .nominal: - return "nominal" - case .fair: - return "fair" - case .serious: - return "serious" - case .critical: - return "critical" - @unknown default: - return "unknown" - } + return DeviceState( + lowPowerMode: lowPowerMode, + thermalState: thermalState, + batteryLevel: batteryLevel, + batteryCharging: batteryCharging + ) +#endif } - private static func networkTypeString(_ path: NetworkPath?) -> String { - guard let path else { - return "unknown" - } - if path.status != .satisfied { - return "none" - } - switch path.interfaceType { - case .wifi: - return "wifi" - case .cellular: - return "cellular" - case .ethernet: - return "ethernet" - case .other, .none: - return "other" + private static func thermalState(from state: ProcessInfo.ThermalState) -> DeviceState.ThermalState { + return switch state { + case .nominal: .nominal + case .fair: .fair + case .serious: .serious + case .critical: .critical + @unknown default: .unknown } } } diff --git a/packages/expo-app-metrics/ios/Utils/MetricParamsBuilder.swift b/packages/expo-app-metrics/ios/Utils/MetricParamsBuilder.swift new file mode 100644 index 00000000000000..6d9dc04b683f45 --- /dev/null +++ b/packages/expo-app-metrics/ios/Utils/MetricParamsBuilder.swift @@ -0,0 +1,69 @@ +// Copyright 2025-present 650 Industries. All rights reserved. + +import Foundation + +/** + Single source of truth for the `expo.*` keys we attach to metrics. Takes + typed inputs (`DeviceState`, `NetworkPath`, `FrameRateMetrics`) and produces + the flat `[String: Any]` map the metric envelope expects. + + Framework-emitted keys override user-supplied keys on collision so the OS + readings always win — a user passing `expo.device.lowPowerMode: "yes"` + doesn't get to overwrite the actual OS bool. + */ +enum MetricParamsBuilder { + /** + Builds the params map for a metric. All inputs are optional; any input + that is `nil` simply contributes no keys. + */ + static func build( + userParams: [String: Any] = [:], + frameMetrics: FrameRateMetrics? = nil, + deviceState: DeviceState? = nil, + networkPath: NetworkPath? = nil + ) -> [String: Any] { + var params: [String: Any] = userParams + if let frameMetrics, frameMetrics.expectedFrames > 0 { + params["expo.frameRate.slowFrames"] = frameMetrics.slowFrames + params["expo.frameRate.frozenFrames"] = frameMetrics.frozenFrames + params["expo.frameRate.totalDelay"] = frameMetrics.freezeTime + } + if let deviceState { + if let lowPowerMode = deviceState.lowPowerMode { + params["expo.device.lowPowerMode"] = lowPowerMode + } + if let thermalState = deviceState.thermalState { + // Raw values on `DeviceState.ThermalState` are part of the wire + // contract — see the enum's docs. Don't rename cases. + params["expo.device.thermalState"] = thermalState.rawValue + } + if let batteryLevel = deviceState.batteryLevel { + params["expo.device.batteryLevel"] = batteryLevel + } + if let batteryCharging = deviceState.batteryCharging { + params["expo.device.batteryCharging"] = batteryCharging + } + } + if let networkPath { + params["expo.network.connected"] = networkPath.status == .satisfied + params["expo.network.type"] = networkTypeString(networkPath) + } + return params + } + + private static func networkTypeString(_ path: NetworkPath) -> String { + if path.status != .satisfied { + return "none" + } + switch path.interfaceType { + case .wifi: + return "wifi" + case .cellular: + return "cellular" + case .ethernet: + return "ethernet" + case .other, .none: + return "other" + } + } +} From 23299b78dbd7b7e4620ae6db68a2779efba56f74 Mon Sep 17 00:00:00 2001 From: Hirbod <504909+hirbod@users.noreply.github.com> Date: Fri, 15 May 2026 17:03:59 +0200 Subject: [PATCH 09/15] [docs] Animate App.js Conf banner mount and dismiss (#45613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why The banner gates rendering on a post-hydration `localStorage` read, so it pops in late and instantly shoves the page down. Same on dismiss in reverse. https://github.com/user-attachments/assets/c86e8d44-86ec-48aa-b8bf-6f08962b1770 # How CSS `grid-template-rows: 0fr → 1fr` transition — pure CSS, no JS measurement. - After hydration, render at `grid-rows-[0fr]`, flip to `1fr` on the next frame (`requestAnimationFrame`) so the browser actually interpolates. - Inner `min-h-0 overflow-hidden` so the grid item collapses below content size; `mb-6` moves inside so the margin animates with the height. - Entrance is sequenced: 250ms delay → 350ms height → 250ms opacity (starts at 600ms). Dismiss is 400ms simultaneous and `onTransitionEnd` commits the `localStorage` flag once the collapse finishes. - `motion-reduce:transition-none` for `prefers-reduced-motion`. # Test Plan - Hard reload — banner expands smoothly, content fades in after the height settles. - Click X — collapses smoothly, stays dismissed across reloads. - Reduced motion — entrance and dismiss are instant. - Light/dark themes and `max-md-gutters` / `max-sm-gutters` breakpoints. https://github.com/user-attachments/assets/fc00b04b-ec42-4dbe-8816-23253661e0c4 --- docs/ui/components/AppJSBanner/index.tsx | 109 ++++++++++++++--------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/docs/ui/components/AppJSBanner/index.tsx b/docs/ui/components/AppJSBanner/index.tsx index 872cdfc210b982..22e68ec1048b79 100644 --- a/docs/ui/components/AppJSBanner/index.tsx +++ b/docs/ui/components/AppJSBanner/index.tsx @@ -10,6 +10,7 @@ import { AppJSIcon } from './AppJSIcon'; export function AppJSBanner() { const [isLoaded, setIsLoaded] = useState(false); + const [isOpen, setIsOpen] = useState(false); const [isAppJSBannerVisible, setIsAppJSBannerVisible] = useLocalStorage({ name: '2026-appjs-banner', defaultValue: true, @@ -22,55 +23,81 @@ export function AppJSBanner() { setIsLoaded(true); }, []); - if (!isAppJSBannerVisible || !showAppJSConfShoutout || !isLoaded) { + useEffect(() => { + if (isLoaded && isAppJSBannerVisible && showAppJSConfShoutout) { + const id = requestAnimationFrame(() => { + setIsOpen(true); + }); + return () => { + cancelAnimationFrame(id); + }; + } + }, [isLoaded, isAppJSBannerVisible, showAppJSConfShoutout]); + + if (!isLoaded || !isAppJSBannerVisible || !showAppJSConfShoutout) { return null; } return (
-
-
-
- -
-
-

- App.js Conf 2026 -

-

- Join us at the biggest React Native & Expo-focused conference. -

-
-
-
- - +
+
); From 5e52d10d9a3845d189f053f7c72dbe26092dbded Mon Sep 17 00:00:00 2001 From: Hirbod <504909+hirbod@users.noreply.github.com> Date: Fri, 15 May 2026 17:10:14 +0200 Subject: [PATCH 10/15] [docs] Tailwind v4 cleanup, Next.js 16.2.6, styleguide 14.1.5 (#45619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why Tailwind v3 leftovers in the docs codebase, plus the Next.js and styleguide bumps that depend on them. Folds in #45612. ## How - **Gutters → standard breakpoints.** `max-{sm,md,lg,xl,2xl}-gutters:` → `max-{sm,md,lg,xl,2xl}:`, `lg-gutters:` → `lg:` across 47 files. Drop the `max-md-gutters:` ignore from `eslint.config.mjs`. Fix `max-md-gutters::flex-col` typo in `Authentication/Box.tsx`. `.diff-code`: `word-break` → `overflow-wrap`. Snapshot updated. - **Next.js 16.2.3 → 16.2.6.** Static-export build-trace patch regenerated against new line numbers; patched conditions unchanged. - **Styleguide bump.** `@expo/styleguide` 14.1.3→14.1.5, `@expo/styleguide-icons` 4.2.3→4.2.4 (now dual ESM/CJS, no more Jest mocks under `--experimental-vm-modules`), `@expo/styleguide-search-ui` 9.1.3→9.1.6 (CMD+K flicker fix). Add `transformIgnorePatterns: ['\\.mjs$']` so `next/jest` SWC stops rewriting native ESM. Two snapshots refreshed for upstream Figma glyph updates. - **Drop `@custom-variant max-{sm,md,lg,xl,2xl}` compat block** from `tailwind-compat.css`. 14.1.5 dropped its v3 `screens` block, so v4 registers the standard min/max variants natively. - **Preserve gutter widths via local `screens` override.** Add `screens: { sm: 468, md: 788, lg: 1008, xl: 1328, 2xl: 1572 }` to `tailwind.config.cjs`. Without it, `max-{breakpoint}:` would resolve to TW4 defaults (640/768/1024/1280/1536), most noticeably widening `max-sm:` from 468px to 640px. Same pattern universe uses (`server/website`, `server/internal`). - **Fix misplaced `!` in arbitrary values.** `leading-[100!%]` → `leading-[100%]!`, `text-[90!%]` → `text-[90%]!` (3 files). Leftovers from the v3→v4 important-syntax flip; TW4 was emitting `line-height: 100!%` / `color: 90!%` and triggering CSS-optimizer warnings. ## Test Plan - [x] `pnpm lint`, `pnpm format`, `pnpm test` (337/337) - [x] `pnpm dev`, CMD+K opens without flicker - [ ] Sidebar, Footer at mobile and tablet - [ ] Home grid sections behave at the original gutter breakpoints - [ ] Search dialog mobile layout - [ ] `.diff-code` long-line wrapping --- .../DocumentationNestedScrollLayout.tsx | 11 +- docs/components/DocumentationPage.tsx | 6 +- docs/components/plugins/CodeBlocksTable.tsx | 10 +- .../__snapshots__/APISection.test.tsx.snap | 250 ++-- .../plugins/api/APISectionDeprecationNote.tsx | 2 +- .../plugins/api/APISectionEnums.tsx | 2 +- .../plugins/api/components/APIBoxHeader.tsx | 2 +- docs/eslint.config.mjs | 2 - docs/jest.config.js | 4 + docs/package.json | 18 +- .../{next@16.2.3.patch => next@16.2.6.patch} | 8 +- docs/pnpm-lock.yaml | 1079 ++++++++--------- .../ProjectStructure/index.tsx | 4 +- .../TemplateFeatures/index.tsx | 4 +- docs/styles/global.css | 2 +- docs/tailwind.config.cjs | 7 + docs/ui/components/AppJSBanner/index.tsx | 4 +- docs/ui/components/Authentication/Box.tsx | 2 +- docs/ui/components/ContentSpotlight/index.tsx | 2 +- docs/ui/components/DownloadSlide/index.tsx | 6 +- .../components/EASHostingShoutoutBanner.tsx | 4 +- docs/ui/components/FileTree/TextWithNote.tsx | 2 +- docs/ui/components/Footer/Footer.tsx | 11 +- .../ui/components/Footer/NewsletterSignUp.tsx | 2 +- docs/ui/components/Footer/PageVote.tsx | 4 +- docs/ui/components/Header/Header.tsx | 14 +- docs/ui/components/Header/Logo.tsx | 10 +- .../Home/components/GridContainer.tsx | 6 +- docs/ui/components/Home/components/Header.tsx | 2 +- .../Home/resources/DevicesImage.tsx | 4 +- .../components/Home/sections/DiscoverMore.tsx | 6 +- .../components/Home/sections/ExploreAPIs.tsx | 8 +- .../Home/sections/ExploreExamples.tsx | 8 +- .../Home/sections/JoinTheCommunity.tsx | 6 +- .../components/Home/sections/QuickStart.tsx | 6 +- docs/ui/components/Home/sections/Talks.tsx | 8 +- .../ui/components/LaunchPartyBanner/index.tsx | 8 +- docs/ui/components/Layout/Layout.tsx | 7 +- docs/ui/components/PageHeader/PageHeader.tsx | 20 +- .../PageHeader/SdkPackageButton.tsx | 2 +- docs/ui/components/ProgressTracker/index.tsx | 2 +- docs/ui/components/RouteUrl/RuntimePopup.tsx | 2 +- docs/ui/components/Select.tsx | 2 +- docs/ui/components/Separator.tsx | 2 +- .../components/Sidebar/ApiVersionSelect.tsx | 2 +- docs/ui/components/Sidebar/Sidebar.tsx | 4 +- .../components/Snippet/actions/CopyAction.tsx | 2 +- .../components/Snippet/blocks/SnackInline.tsx | 6 +- .../ui/components/Snippet/blocks/Terminal.tsx | 2 +- docs/ui/components/StateOfRNBanner/index.tsx | 4 +- .../TableOfContents.test.tsx.snap | 2 +- docs/ui/components/Tabs/TabButton.tsx | 2 +- .../TemplateBareMinimumDiffViewer/index.tsx | 6 +- docs/ui/components/Text/index.tsx | 12 +- docs/ui/components/VideoBoxLink/index.tsx | 6 +- 55 files changed, 772 insertions(+), 847 deletions(-) rename docs/patches/{next@16.2.3.patch => next@16.2.6.patch} (90%) diff --git a/docs/components/DocumentationNestedScrollLayout.tsx b/docs/components/DocumentationNestedScrollLayout.tsx index ba622db15d230c..dcaf3f7dce1409 100644 --- a/docs/components/DocumentationNestedScrollLayout.tsx +++ b/docs/components/DocumentationNestedScrollLayout.tsx @@ -82,9 +82,9 @@ export default function DocumentationNestedScrollLayout({ return (
-
{header}
+
{header}
-
+
{onSidebarToggle ? (
{children}
@@ -168,7 +167,7 @@ export default function DocumentationNestedScrollLayout({
{cloneElement(sidebarRight, { diff --git a/docs/components/DocumentationPage.tsx b/docs/components/DocumentationPage.tsx index a8ad97eb6aae32..4938c7868e7fe2 100644 --- a/docs/components/DocumentationPage.tsx +++ b/docs/components/DocumentationPage.tsx @@ -349,11 +349,7 @@ export default function DocumentationPage({ 'from-default bg-linear-to-b to-transparent opacity-90' )} /> -
+
{version && version === 'unversioned' && ( This is documentation for the next SDK version. For up-to-date documentation, see the{' '} diff --git a/docs/components/plugins/CodeBlocksTable.tsx b/docs/components/plugins/CodeBlocksTable.tsx index e640c76f3dddf2..8c21f89daef95e 100644 --- a/docs/components/plugins/CodeBlocksTable.tsx +++ b/docs/components/plugins/CodeBlocksTable.tsx @@ -41,16 +41,16 @@ export function CodeBlocksTable({ children, tabs, connected = true, ...rest }: P
div:nth-child(odd)>div]:lg-gutters:rounded-r-none! [&>div:nth-child(odd)>div]:lg-gutters:border-r-0', - connected && '[&>div:nth-child(even)>div]:lg-gutters:rounded-l-none!', + '[&>div:nth-child(odd)>div]:lg:rounded-r-none! [&>div:nth-child(odd)>div]:lg:border-r-0', + connected && '[&>div:nth-child(even)>div]:lg:rounded-l-none!', '[&_pre]:m-0 [&_pre]:border-0', - 'max-lg-gutters:grid-cols-1' + 'max-lg:grid-cols-1' )} {...rest}> {codeBlocks.map((codeBlock, index) => ( - + diff --git a/docs/components/plugins/__snapshots__/APISection.test.tsx.snap b/docs/components/plugins/__snapshots__/APISection.test.tsx.snap index 91c9ce2267c0ea..6b0bf0933c83a2 100644 --- a/docs/components/plugins/__snapshots__/APISection.test.tsx.snap +++ b/docs/components/plugins/__snapshots__/APISection.test.tsx.snap @@ -3,7 +3,7 @@ exports[`APISection expo-apple-authentication 1`] = `

@@ -46,10 +46,10 @@ exports[`APISection expo-apple-authentication 1`] = ` class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none shadow-none!" >

@@ -378,10 +378,10 @@ for more details. class="border-palette-gray4 border-t first:border-t-0" >

@@ -459,10 +459,10 @@ for more details. class="border-palette-gray4 border-t first:border-t-0" >

@@ -540,10 +540,10 @@ for more details. class="border-palette-gray4 border-t first:border-t-0" >

@@ -625,10 +625,10 @@ for more details. class="border-palette-gray4 border-t first:border-t-0" >

@@ -725,10 +725,10 @@ in here. class="border-palette-gray4 border-t first:border-t-0" >

@@ -940,7 +940,7 @@ properties.

@@ -983,10 +983,10 @@ properties. class="border-palette-gray4 overflow-hidden rounded-lg border [&_h2]:mt-0 [&_h3]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none mb-4 shadow-none [&_h4]:mt-0" >

@@ -1185,7 +1185,7 @@ properties. id="corner-down-right-outline-icon" >

@@ -1440,7 +1440,7 @@ This should come from the user field of an id="corner-down-right-outline-icon" >

@@ -1594,7 +1594,7 @@ value depending on the state of the credential. id="corner-down-right-outline-icon" >

@@ -1835,7 +1835,7 @@ Calling this method will show the sign in modal before actually refreshing the u id="corner-down-right-outline-icon" >

@@ -2139,7 +2139,7 @@ the user, since this remains the same for apps released by the same developer. id="corner-down-right-outline-icon" >

@@ -2426,7 +2426,7 @@ from using id="corner-down-right-outline-icon" >

@@ -2553,10 +2553,10 @@ sign-out operation. class="border-palette-gray4 overflow-hidden rounded-lg border [&_h2]:mt-0 [&_h3]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none mb-4 shadow-none [&_h4]:mt-0" >

@@ -2682,7 +2682,7 @@ sign-out operation. id="corner-down-right-outline-icon" >

@@ -2749,10 +2749,10 @@ sign-out operation. class="border-palette-gray4 rounded-lg border [&_h2]:mt-0 [&_h3]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none mb-4 shadow-none [&_h4]:mt-0 overflow-hidden" >

@@ -2856,10 +2856,10 @@ sign-out operation. class="border-palette-gray4 border-b last:border-b-0" >

@@ -2932,7 +2932,7 @@ After calling this function, the listener will no longer receive any events from id="corner-down-right-outline-icon" >

@@ -3000,10 +3000,10 @@ After calling this function, the listener will no longer receive any events from class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -3589,10 +3589,10 @@ developers. class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -3954,10 +3954,10 @@ may be class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -4148,10 +4148,10 @@ for more details. class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -4480,10 +4480,10 @@ OAuth 2.0 protocol class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -4832,10 +4832,10 @@ OAuth 2.0 protocol class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -5087,7 +5087,7 @@ OAuth 2.0 protocol

@@ -5130,10 +5130,10 @@ OAuth 2.0 protocol class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -5204,7 +5204,7 @@ OAuth 2.0 protocol class="border-t-palette-gray4 border-t px-4 pt-3 pb-0 [&_h4]:mb-0.5" >

@@ -5486,7 +5486,7 @@ OAuth 2.0 protocol class="border-t-palette-gray4 border-t px-4 pt-3 pb-0 [&_h4]:mb-0.5" >

@@ -5862,7 +5862,7 @@ for more details. class="border-t-palette-gray4 border-t px-4 pt-3 pb-0 [&_h4]:mb-0.5" >

@@ -6166,7 +6166,7 @@ for more details. class="border-t-palette-gray4 border-t px-4 pt-3 pb-0 [&_h4]:mb-0.5" >

@@ -6602,7 +6602,7 @@ for more details. class="border-t-palette-gray4 border-t px-4 pt-3 pb-0 [&_h4]:mb-0.5" >

@@ -6847,7 +6847,7 @@ for more details. class="border-t-palette-gray4 border-t px-4 pt-3 pb-0 [&_h4]:mb-0.5" >

@@ -7100,10 +7100,10 @@ exports[`APISection expo-pedometer 1`] = ` class="border-palette-gray4 overflow-hidden rounded-lg border [&_h2]:mt-0 [&_h3]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none mb-4 shadow-none [&_h4]:mt-0" >

@@ -7175,7 +7175,7 @@ exports[`APISection expo-pedometer 1`] = ` id="corner-down-right-outline-icon" >

@@ -7459,7 +7459,7 @@ exports[`APISection expo-pedometer 1`] = ` id="corner-down-right-outline-icon" >

@@ -7677,7 +7677,7 @@ a start date that is more than seven days in the past returns only the available id="corner-down-right-outline-icon" >

@@ -7821,7 +7821,7 @@ available on this device. id="corner-down-right-outline-icon" >

@@ -8034,7 +8034,7 @@ provided with a single argument that is id="corner-down-right-outline-icon" >

@@ -8205,10 +8205,10 @@ On iOS, the class="border-palette-gray4 rounded-lg border [&_h2]:mt-0 [&_h3]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none mb-4 shadow-none [&_h4]:mt-0 overflow-hidden" >

@@ -8312,10 +8312,10 @@ On iOS, the class="border-palette-gray4 border-b last:border-b-0" >

@@ -8388,7 +8388,7 @@ After calling this function, the listener will no longer receive any events from id="corner-down-right-outline-icon" >

@@ -8456,10 +8456,10 @@ After calling this function, the listener will no longer receive any events from class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -8586,10 +8586,10 @@ After calling this function, the listener will no longer receive any events from class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -8724,7 +8724,7 @@ After calling this function, the listener will no longer receive any events from id="corner-down-right-outline-icon" >

@@ -8858,10 +8858,10 @@ After calling this function, the listener will no longer receive any events from class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -9108,7 +9108,7 @@ in order to enable/disable the permission.

@@ -9151,10 +9151,10 @@ in order to enable/disable the permission. class="border-palette-gray4 mb-5 overflow-hidden rounded-lg border shadow-xs [&_h2]:mt-0 [&_h3]:mt-0 [&_h4]:mt-0 [&_h3]:mb-1.5 [&_h4]:mb-0 [&_li]:mb-0 [&_thead]:border-palette-gray4 [&_th]:text-tertiary [&_th]:px-4 [&_td]:border-palette-gray4 [&_td]:py-3 [&_.table-wrapper]:border-palette-gray4 [&_.table-wrapper]:mb-0 [&_.table-wrapper]:shadow-none" >

@@ -9206,7 +9206,7 @@ in order to enable/disable the permission. class="border-t-palette-gray4 border-t px-4 pt-3 pb-0 [&_h4]:mb-0.5" >

{content.length > 0 ? ( diff --git a/docs/components/plugins/api/APISectionEnums.tsx b/docs/components/plugins/api/APISectionEnums.tsx index 2fe4f1209d5447..38de31d17b2604 100644 --- a/docs/components/plugins/api/APISectionEnums.tsx +++ b/docs/components/plugins/api/APISectionEnums.tsx @@ -38,7 +38,7 @@ function APISectionEnum({ data: { name, children, comment } }: { data: EnumDefin className="border-t-palette-gray4 border-t px-4 pt-3 pb-0 [&_h4]:mb-0.5" key={enumValue.name}> -
+

{enumValue.name} diff --git a/docs/components/plugins/api/components/APIBoxHeader.tsx b/docs/components/plugins/api/components/APIBoxHeader.tsx index f340a95606bfab..44857cfa9c6afe 100644 --- a/docs/components/plugins/api/components/APIBoxHeader.tsx +++ b/docs/components/plugins/api/components/APIBoxHeader.tsx @@ -29,7 +29,7 @@ export function APIBoxHeader({
diff --git a/docs/eslint.config.mjs b/docs/eslint.config.mjs index 7e99f3552f1427..2f5b48f51aed38 100644 --- a/docs/eslint.config.mjs +++ b/docs/eslint.config.mjs @@ -127,9 +127,7 @@ export default defineConfig([ 'terminal-snippet', 'table-wrapper', 'tutorial-code-annotation', - 'max-sm:.+', 'max-medium:.+', - 'max-md-gutters:.+', '\\[table_&\\]:.+', '\\[table_&\\]', ], diff --git a/docs/jest.config.js b/docs/jest.config.js index ee6e8d68c5dc42..4e5cc103e8c738 100644 --- a/docs/jest.config.js +++ b/docs/jest.config.js @@ -25,6 +25,10 @@ const jestConfig = { '/node_modules/@expo/styleguide-cookie-consent/mock.js', }, transform: {}, + // next/jest's default `transform` runs SWC on `.mjs` and converts ESM + // exports to CJS-style `Object.defineProperty(exports, ...)`, which then + // links as zero named exports. Skip transforming native ESM files. + transformIgnorePatterns: ['\\.mjs$'], extensionsToTreatAsEsm: ['.ts', '.tsx'], }; diff --git a/docs/package.json b/docs/package.json index a1fef4bfd1cc58..1e93267f266e77 100644 --- a/docs/package.json +++ b/docs/package.json @@ -37,11 +37,11 @@ }, "packageManager": "pnpm@10.33.0", "dependencies": { - "@expo/styleguide": "^13.1.1", - "@expo/styleguide-base": "^3.0.4", - "@expo/styleguide-cookie-consent": "^2.0.3", - "@expo/styleguide-icons": "^4.0.6", - "@expo/styleguide-search-ui": "^8.1.1", + "@expo/styleguide": "^14.1.5", + "@expo/styleguide-base": "^3.1.2", + "@expo/styleguide-cookie-consent": "^2.1.2", + "@expo/styleguide-icons": "^4.2.4", + "@expo/styleguide-search-ui": "^9.1.6", "@kapaai/react-sdk": "^0.9.6", "@mdx-js/loader": "^3.1.1", "@mdx-js/mdx": "^3.1.1", @@ -62,16 +62,16 @@ "front-matter": "^4.0.2", "github-slugger": "^2.0.0", "lodash": "^4.18.1", - "next": "16.2.3", + "next": "16.2.6", "nprogress": "0.2.0", "path-browserify": "^1.0.1", "prism-react-renderer": "^2.4.1", "prismjs": "^1.30.0", "prop-types": "^15.8.1", - "react": "^19.2.5", + "react": "^19.2.6", "react-confetti-explosion": "^3.0.3", "react-diff-view": "^3.3.3", - "react-dom": "^19.2.5", + "react-dom": "^19.2.6", "react-markdown": "^10.1.0", "react-player": "^3.4.0", "react-qr-code": "^2.0.18", @@ -155,7 +155,7 @@ "baseline-browser-mapping": "2.9.15" }, "patchedDependencies": { - "next@16.2.3": "patches/next@16.2.3.patch" + "next@16.2.6": "patches/next@16.2.6.patch" } }, "volta": { diff --git a/docs/patches/next@16.2.3.patch b/docs/patches/next@16.2.6.patch similarity index 90% rename from docs/patches/next@16.2.3.patch rename to docs/patches/next@16.2.6.patch index 6b5fe69aac4217..3c7a68b43841b8 100644 --- a/docs/patches/next@16.2.3.patch +++ b/docs/patches/next@16.2.6.patch @@ -1,5 +1,5 @@ diff --git a/dist/build/index.js b/dist/build/index.js -index fdf399547b12224d3654c9219e7bfcb270868bf2..844c307575af595fe7c84a5f754491e70da6fbfc 100644 +index 065c9b6..a16d7ce 100644 --- a/dist/build/index.js +++ b/dist/build/index.js @@ -894,7 +894,7 @@ async function build(dir, experimentalAnalyze = false, reactProductionProfiling @@ -11,7 +11,7 @@ index fdf399547b12224d3654c9219e7bfcb270868bf2..844c307575af595fe7c84a5f754491e7 const buildTraceWorker = new _worker.Worker(require.resolve('./collect-build-traces'), { debuggerPortOffset: -1, isolatedMemory: false, -@@ -1534,7 +1534,7 @@ async function build(dir, experimentalAnalyze = false, reactProductionProfiling +@@ -1536,7 +1536,7 @@ async function build(dir, experimentalAnalyze = false, reactProductionProfiling await writeFunctionsConfigManifest(distDir, functionsConfigManifest); // #endregion // #region NFT @@ -21,7 +21,7 @@ index fdf399547b12224d3654c9219e7bfcb270868bf2..844c307575af595fe7c84a5f754491e7 const { collectBuildTraces } = require('./collect-build-traces'); return collectBuildTraces({ diff --git a/dist/esm/build/index.js b/dist/esm/build/index.js -index 6d035c4088b4621e9e9a9de702838537b8cf500a..68e820d5e92e505fa5c1fc4fbeb0595060cfc8c9 100644 +index bbfc94d..1af58dc 100644 --- a/dist/esm/build/index.js +++ b/dist/esm/build/index.js @@ -826,7 +826,7 @@ export default async function build(dir, experimentalAnalyze = false, reactProdu @@ -33,7 +33,7 @@ index 6d035c4088b4621e9e9a9de702838537b8cf500a..68e820d5e92e505fa5c1fc4fbeb05950 const buildTraceWorker = new Worker(require.resolve('./collect-build-traces'), { debuggerPortOffset: -1, isolatedMemory: false, -@@ -1466,7 +1466,7 @@ export default async function build(dir, experimentalAnalyze = false, reactProdu +@@ -1468,7 +1468,7 @@ export default async function build(dir, experimentalAnalyze = false, reactProdu await writeFunctionsConfigManifest(distDir, functionsConfigManifest); // #endregion // #region NFT diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 172c0da57eefe0..192b957b9f45a9 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -8,80 +8,80 @@ overrides: baseline-browser-mapping: 2.9.15 patchedDependencies: - next@16.2.3: - hash: 4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265 - path: patches/next@16.2.3.patch + next@16.2.6: + hash: 423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb + path: patches/next@16.2.6.patch importers: .: dependencies: '@expo/styleguide': - specifier: ^13.1.1 - version: 13.1.2(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.4) + specifier: ^14.1.5 + version: 14.1.5(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(tailwindcss@4.2.4) '@expo/styleguide-base': - specifier: ^3.0.4 - version: 3.0.4(react@19.2.5) + specifier: ^3.1.2 + version: 3.1.2(react@19.2.6) '@expo/styleguide-cookie-consent': - specifier: ^2.0.3 - version: 2.0.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)) + specifier: ^2.1.2 + version: 2.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6)) '@expo/styleguide-icons': - specifier: ^4.0.6 - version: 4.0.6(react@19.2.5)(tailwindcss@4.2.4) + specifier: ^4.2.4 + version: 4.2.4(react@19.2.6)(tailwindcss@4.2.4) '@expo/styleguide-search-ui': - specifier: ^8.1.1 - version: 8.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.4) + specifier: ^9.1.6 + version: 9.1.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(tailwindcss@4.2.4) '@kapaai/react-sdk': specifier: ^0.9.6 - version: 0.9.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 0.9.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@mdx-js/loader': specifier: ^3.1.1 - version: 3.1.1(webpack@5.106.2(postcss@8.5.10)) + version: 3.1.1(webpack@5.106.2) '@mdx-js/mdx': specifier: ^3.1.1 version: 3.1.1 '@mdx-js/react': specifier: ^3.1.1 - version: 3.1.1(@types/react@19.2.14)(react@19.2.5) + version: 3.1.1(@types/react@19.2.14)(react@19.2.6) '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.3.6) '@radix-ui/react-dialog': specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-dropdown-menu': specifier: ^2.1.16 - version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-select': specifier: ^2.2.6 - version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-tooltip': specifier: ^1.2.8 - version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@reach/tabs': specifier: ^0.18.0 - version: 0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@sentry/nextjs': specifier: ^10.48.0 - version: 10.49.0(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(webpack@5.106.2(postcss@8.5.10)) + version: 10.49.0(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(webpack@5.106.2) '@sentry/react': specifier: ^10.48.0 - version: 10.49.0(react@19.2.5) + version: 10.49.0(react@19.2.6) '@xyflow/react': specifier: ^12.10.2 - version: 12.10.2(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 12.10.2(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) clipboard-copy: specifier: ^4.0.1 version: 4.0.1 cmdk: specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) date-fns: specifier: ^4.1.0 version: 4.1.0 framer-motion: specifier: ^12.38.0 - version: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 12.38.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) front-matter: specifier: ^4.0.2 version: 4.0.2 @@ -92,8 +92,8 @@ importers: specifier: ^4.18.1 version: 4.18.1 next: - specifier: 16.2.3 - version: 16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + specifier: 16.2.6 + version: 16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) nprogress: specifier: 0.2.0 version: 0.2.0 @@ -102,7 +102,7 @@ importers: version: 1.0.1 prism-react-renderer: specifier: ^2.4.1 - version: 2.4.1(react@19.2.5) + version: 2.4.1(react@19.2.6) prismjs: specifier: ^1.30.0 version: 1.30.0 @@ -110,26 +110,26 @@ importers: specifier: ^15.8.1 version: 15.8.1 react: - specifier: ^19.2.5 - version: 19.2.5 + specifier: ^19.2.6 + version: 19.2.6 react-confetti-explosion: specifier: ^3.0.3 - version: 3.0.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react-diff-view: specifier: ^3.3.3 - version: 3.3.3(react@19.2.5) + version: 3.3.3(react@19.2.6) react-dom: - specifier: ^19.2.5 - version: 19.2.5(react@19.2.5) + specifier: ^19.2.6 + version: 19.2.6(react@19.2.6) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.14)(react@19.2.5) + version: 10.1.0(@types/react@19.2.14)(react@19.2.6) react-player: specifier: ^3.4.0 - version: 3.4.0(@svta/cml-cta@1.0.1(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1))(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.4.0(@svta/cml-cta@1.0.1(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1))(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react-qr-code: specifier: ^2.0.18 - version: 2.0.18(react@19.2.5) + version: 2.0.18(react@19.2.6) remark-frontmatter: specifier: ^5.0.0 version: 5.0.0 @@ -150,7 +150,7 @@ importers: version: 6.3.7 yet-another-react-lightbox: specifier: ^3.30.1 - version: 3.31.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 3.31.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) devDependencies: '@apidevtools/json-schema-ref-parser': specifier: ^11.9.3 @@ -172,7 +172,7 @@ importers: version: 6.9.1 '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) @@ -259,7 +259,7 @@ importers: version: 29.7.0 next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) + version: 0.9.13(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) oxfmt: specifier: 0.43.0 version: 0.43.0 @@ -274,7 +274,7 @@ importers: version: 8.5.10 react-test-renderer: specifier: ^19.2.5 - version: 19.2.5(react@19.2.5) + version: 19.2.5(react@19.2.6) rehype-slug: specifier: ^6.0.0 version: 6.0.0 @@ -758,33 +758,33 @@ packages: resolution: {integrity: sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==} engines: {node: '>=12'} - '@expo/styleguide-base@3.0.4': - resolution: {integrity: sha512-v0DuU6zAix96x5t3J+uXpqDHqH0ea60ywoDP57ik/A4rxEYNbn5x6gvLm28jNni2twimcnE7b1xV0uaGHhDHKA==} + '@expo/styleguide-base@3.1.2': + resolution: {integrity: sha512-t9k8fxFtcc4JJkttSjoZqJRthJLddJCGDQD4KvCxCiwFCVnFv+OwuhS0QDSxZ7n8NcOIZvzr1IQWMxOVSAIJEw==} peerDependencies: react: '>= 19' - '@expo/styleguide-cookie-consent@2.0.3': - resolution: {integrity: sha512-jkrNlG0/CBJUA22ylrVamBSOX+78EtXFE0QMNIzZMb356MnyGG95zdGm7rMH+4jRHla8wh0jcTAsCIqi1P/mmQ==} + '@expo/styleguide-cookie-consent@2.1.2': + resolution: {integrity: sha512-97X0ZCcV2Q1hNoDk70gfnqFuXdh3RvaHVfCjhktz1RIXWLJRM9CvsqgZWIl6DgCyb9xCnYL0RCZOpNPp2L614w==} peerDependencies: react: '>= 19' - '@expo/styleguide-icons@4.0.6': - resolution: {integrity: sha512-cNaz1kfnoDiPizEl9Q6C5PVKgClZZXRyiLEaNHM6INU4Xtq8E5Uuaqt5NQ8THYHRp9G1AdMIeNEiH11tOIZu5Q==} + '@expo/styleguide-icons@4.2.4': + resolution: {integrity: sha512-ibh6sVj+IsGkhdeyMfj28l7vb0JBUSSbrOeW2L2OJQ+hn29TonK9vg/HW1AaUEp9rEZAEUmLEEV4kjejvkOXIA==} peerDependencies: react: '>= 19' tailwindcss: ^4.2.2 - '@expo/styleguide-search-ui@8.1.2': - resolution: {integrity: sha512-7Vqra+jdMzG8MaL7yn0H9BWhK/Gc1V8Q7hWyvkkgHGEjLmuNqa+0lPWEnYjof6ER1Z7F3tveSfLhvBt7kqFOSw==} + '@expo/styleguide-search-ui@9.1.6': + resolution: {integrity: sha512-NAbjOl42ZsyWcOL0rXQ9c+DsxE1VUuXKgJm7YnRc5ksarsWqDVYWXv/v0FrQEuTgeAxe/pbE17jLnJlLGESzRw==} peerDependencies: - next: '>= 13' + next: '>= 16' react: '>= 19' tailwindcss: ^4.2.2 - '@expo/styleguide@13.1.2': - resolution: {integrity: sha512-6TfiHmp6LLtgmoG86dLGwmlfnZL74pqsKFCy6OAvDetFjH4UNQXNGtPjrf2mwrt/MNYim2xxlvBzPpu7N0oThQ==} + '@expo/styleguide@14.1.5': + resolution: {integrity: sha512-Aw+TyvHv2aM5PSQHYUFouISteBx0GBHhDrvHzrG02P6aPInSsWEpNjtTDfIPX9mxCXYYS0qwld2pHBkbT+6i9Q==} peerDependencies: - next: '>= 13' + next: '>= 16' react: '>= 19' tailwindcss: ^4.2.2 @@ -1172,57 +1172,57 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/env@16.2.3': - resolution: {integrity: sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==} + '@next/env@16.2.6': + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} - '@next/swc-darwin-arm64@16.2.3': - resolution: {integrity: sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==} + '@next/swc-darwin-arm64@16.2.6': + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.3': - resolution: {integrity: sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==} + '@next/swc-darwin-x64@16.2.6': + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.3': - resolution: {integrity: sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==} + '@next/swc-linux-arm64-gnu@16.2.6': + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.3': - resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==} + '@next/swc-linux-arm64-musl@16.2.6': + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.3': - resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==} + '@next/swc-linux-x64-gnu@16.2.6': + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.3': - resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==} + '@next/swc-linux-x64-musl@16.2.6': + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.3': - resolution: {integrity: sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==} + '@next/swc-win32-arm64-msvc@16.2.6': + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.3': - resolution: {integrity: sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==} + '@next/swc-win32-x64-msvc@16.2.6': + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2762,9 +2762,6 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/event-source-polyfill@1.0.5': resolution: {integrity: sha512-iaiDuDI2aIFft7XkcwMzDWLqo7LVDixd2sR6B4wxJut9xcp/Ev9bO4EFg4rm6S9QxATLBj5OPxdeocgmhjwKaw==} @@ -2837,9 +2834,6 @@ packages: '@types/node@22.19.17': resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} - '@types/node@22.19.18': - resolution: {integrity: sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ==} - '@types/node@24.12.2': resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} @@ -3204,9 +3198,6 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -3860,10 +3851,6 @@ packages: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} - enhanced-resolve@5.21.2: - resolution: {integrity: sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==} - engines: {node: '>=10.13.0'} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -3901,8 +3888,8 @@ packages: resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} engines: {node: '>= 0.4'} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -4298,9 +4285,6 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-uri@3.1.2: - resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} - fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} @@ -5237,8 +5221,8 @@ packages: load-plugin@6.0.3: resolution: {integrity: sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==} - loader-runner@4.3.2: - resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} engines: {node: '>=6.11.5'} localforage@1.10.0: @@ -5610,8 +5594,8 @@ packages: next: '>=10.0.0' react: '>=17.0.0' - next@16.2.3: - resolution: {integrity: sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==} + next@16.2.6: + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -6038,10 +6022,10 @@ packages: peerDependencies: react: '>=16.14.0' - react-dom@19.2.5: - resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: - react: ^19.2.5 + react: ^19.2.6 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -6108,8 +6092,8 @@ packages: peerDependencies: react: ^19.2.5 - react@19.2.5: - resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} read-package-json-fast@3.0.2: @@ -6589,51 +6573,24 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - terser-webpack-plugin@5.6.0: - resolution: {integrity: sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA==} + terser-webpack-plugin@5.4.0: + resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} engines: {node: '>= 10.13.0'} peerDependencies: - '@minify-html/node': '*' '@swc/core': '*' - '@swc/css': '*' - '@swc/html': '*' - clean-css: '*' - cssnano: '*' - csso: '*' esbuild: '*' - html-minifier-terser: '*' - lightningcss: '*' - postcss: '*' uglify-js: '*' webpack: ^5.1.0 peerDependenciesMeta: - '@minify-html/node': - optional: true '@swc/core': optional: true - '@swc/css': - optional: true - '@swc/html': - optional: true - clean-css: - optional: true - cssnano: - optional: true - csso: - optional: true esbuild: optional: true - html-minifier-terser: - optional: true - lightningcss: - optional: true - postcss: - optional: true uglify-js: optional: true - terser@5.47.1: - resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} + terser@5.46.1: + resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} engines: {node: '>=10'} hasBin: true @@ -7001,8 +6958,8 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - webpack-sources@3.4.1: - resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==} + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} webpack@5.106.2: @@ -7608,19 +7565,20 @@ snapshots: dependencies: cross-spawn: 7.0.6 - '@expo/styleguide-base@3.0.4(react@19.2.5)': + '@expo/styleguide-base@3.1.2(react@19.2.6)': dependencies: '@radix-ui/colors': 3.0.0 - react: 19.2.5 + react: 19.2.6 + tailwind-merge: 3.5.0 - '@expo/styleguide-cookie-consent@2.0.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))': + '@expo/styleguide-cookie-consent@2.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6))': dependencies: - '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) clsx: 2.1.1 - react: 19.2.5 - zustand: 5.0.12(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)) + react: 19.2.6 + zustand: 5.0.12(@types/react@19.2.14)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6)) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -7628,26 +7586,26 @@ snapshots: - react-dom - use-sync-external-store - '@expo/styleguide-icons@4.0.6(react@19.2.5)(tailwindcss@4.2.4)': + '@expo/styleguide-icons@4.2.4(react@19.2.6)(tailwindcss@4.2.4)': dependencies: - react: 19.2.5 - tailwind-merge: 3.5.0 + '@expo/styleguide-base': 3.1.2(react@19.2.6) + react: 19.2.6 tailwindcss: 4.2.4 - '@expo/styleguide-search-ui@8.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.4)': + '@expo/styleguide-search-ui@9.1.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(tailwindcss@4.2.4)': dependencies: - '@expo/styleguide': 13.1.2(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.4) - '@expo/styleguide-icons': 4.0.6(react@19.2.5)(tailwindcss@4.2.4) - '@kapaai/react-sdk': 0.9.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@expo/styleguide': 14.1.5(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(tailwindcss@4.2.4) + '@expo/styleguide-icons': 4.2.4(react@19.2.6)(tailwindcss@4.2.4) + '@kapaai/react-sdk': 0.9.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@sanity/client': 7.21.0 '@sanity/image-url': 2.1.1 - cmdk: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + cmdk: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) lodash.groupby: 4.6.0 - markdown-to-jsx: 9.7.16(react@19.2.5) - next: 16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - prism-react-renderer: 2.4.1(react@19.2.5) + markdown-to-jsx: 9.7.16(react@19.2.6) + next: 16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + prism-react-renderer: 2.4.1(react@19.2.6) prismjs: 1.30.0 - react: 19.2.5 + react: 19.2.6 tailwindcss: 4.2.4 transitivePeerDependencies: - '@types/react' @@ -7657,11 +7615,11 @@ snapshots: - solid-js - vue - '@expo/styleguide@13.1.2(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(tailwindcss@4.2.4)': + '@expo/styleguide@14.1.5(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(tailwindcss@4.2.4)': dependencies: - '@expo/styleguide-base': 3.0.4(react@19.2.5) - next: 16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 + '@expo/styleguide-base': 3.1.2(react@19.2.6) + next: 16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 tailwind-merge: 3.5.0 tailwindcss: 4.2.4 @@ -7696,22 +7654,22 @@ snapshots: '@floating-ui/core': 1.7.5 '@floating-ui/utils': 0.2.11 - '@floating-ui/react-dom@2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@floating-ui/dom': 1.7.6 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) '@floating-ui/utils@0.2.11': {} '@hcaptcha/loader@2.3.0': {} - '@hcaptcha/react-hcaptcha@1.17.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@hcaptcha/react-hcaptcha@1.17.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.2 '@hcaptcha/loader': 2.3.0 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) '@hono/node-server@1.19.14(hono@4.12.14)': dependencies: @@ -8041,22 +7999,22 @@ snapshots: '@jsdevtools/ono@7.1.3': {} - '@kapaai/react-sdk@0.9.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@kapaai/react-sdk@0.9.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@fingerprintjs/fingerprintjs-pro-react': 2.7.1 '@fingerprintjs/fingerprintjs-pro-spa': 1.3.3 - '@hcaptcha/react-hcaptcha': 1.17.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@hcaptcha/react-hcaptcha': 1.17.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6) js-cookie: 3.0.5 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) tldts: 7.0.28 - '@mdx-js/loader@3.1.1(webpack@5.106.2(postcss@8.5.10))': + '@mdx-js/loader@3.1.1(webpack@5.106.2)': dependencies: '@mdx-js/mdx': 3.1.1 source-map: 0.7.6 optionalDependencies: - webpack: 5.106.2(postcss@8.5.10) + webpack: 5.106.2 transitivePeerDependencies: - supports-color @@ -8090,11 +8048,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5)': + '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: '@types/mdx': 2.0.13 '@types/react': 19.2.14 - react: 19.2.5 + react: 19.2.6 '@mixmark-io/domino@2.2.0': {} @@ -8124,23 +8082,23 @@ snapshots: dependencies: mux-embed: 5.18.0 - '@mux/mux-player-react@3.11.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@mux/mux-player-react@3.11.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@mux/mux-player': 3.11.8(react@19.2.5) + '@mux/mux-player': 3.11.8(react@19.2.6) '@mux/playback-core': 0.34.0 prop-types: 15.8.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@mux/mux-player@3.11.8(react@19.2.5)': + '@mux/mux-player@3.11.8(react@19.2.6)': dependencies: '@mux/mux-video': 0.30.6 '@mux/playback-core': 0.34.0 - media-chrome: 4.18.3(react@19.2.5) - player.style: 0.3.4(react@19.2.5) + media-chrome: 4.18.3(react@19.2.6) + player.style: 0.3.4(react@19.2.6) transitivePeerDependencies: - react @@ -8164,30 +8122,30 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@16.2.3': {} + '@next/env@16.2.6': {} - '@next/swc-darwin-arm64@16.2.3': + '@next/swc-darwin-arm64@16.2.6': optional: true - '@next/swc-darwin-x64@16.2.3': + '@next/swc-darwin-x64@16.2.6': optional: true - '@next/swc-linux-arm64-gnu@16.2.3': + '@next/swc-linux-arm64-gnu@16.2.6': optional: true - '@next/swc-linux-arm64-musl@16.2.3': + '@next/swc-linux-arm64-musl@16.2.6': optional: true - '@next/swc-linux-x64-gnu@16.2.3': + '@next/swc-linux-x64-gnu@16.2.6': optional: true - '@next/swc-linux-x64-musl@16.2.3': + '@next/swc-linux-x64-musl@16.2.6': optional: true - '@next/swc-win32-arm64-msvc@16.2.3': + '@next/swc-win32-arm64-msvc@16.2.6': optional: true - '@next/swc-win32-x64-msvc@16.2.3': + '@next/swc-win32-x64-msvc@16.2.6': optional: true '@noble/ed25519@3.1.0': {} @@ -8665,423 +8623,423 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) aria-hidden: 1.2.6 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) aria-hidden: 1.2.6 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) '@radix-ui/rect': 1.1.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) aria-hidden: 1.2.6 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: '@radix-ui/rect': 1.1.1 - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.5)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) - react: 19.2.5 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) '@radix-ui/rect@1.1.1': {} - '@reach/auto-id@0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@reach/auto-id@0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@reach/utils': 0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@reach/utils': 0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@reach/descendants@0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@reach/descendants@0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@reach/utils': 0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@reach/utils': 0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@reach/polymorphic@0.18.0(react@19.2.5)': + '@reach/polymorphic@0.18.0(react@19.2.6)': dependencies: - react: 19.2.5 + react: 19.2.6 - '@reach/tabs@0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@reach/tabs@0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@reach/auto-id': 0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@reach/descendants': 0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@reach/polymorphic': 0.18.0(react@19.2.5) - '@reach/utils': 0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@reach/auto-id': 0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@reach/descendants': 0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@reach/polymorphic': 0.18.0(react@19.2.6) + '@reach/utils': 0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - '@reach/utils@0.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@reach/utils@0.18.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) '@rollup/plugin-commonjs@28.0.1(rollup@4.60.2)': dependencies: @@ -9290,7 +9248,7 @@ snapshots: '@sentry/core@10.49.0': {} - '@sentry/nextjs@10.49.0(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(webpack@5.106.2(postcss@8.5.10))': + '@sentry/nextjs@10.49.0(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(webpack@5.106.2)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.40.0 @@ -9300,10 +9258,10 @@ snapshots: '@sentry/core': 10.49.0 '@sentry/node': 10.49.0 '@sentry/opentelemetry': 10.49.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) - '@sentry/react': 10.49.0(react@19.2.5) + '@sentry/react': 10.49.0(react@19.2.6) '@sentry/vercel-edge': 10.49.0 - '@sentry/webpack-plugin': 5.2.0(webpack@5.106.2(postcss@8.5.10)) - next: 16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@sentry/webpack-plugin': 5.2.0(webpack@5.106.2) + next: 16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) rollup: 4.60.2 stacktrace-parser: 0.1.11 transitivePeerDependencies: @@ -9373,11 +9331,11 @@ snapshots: '@opentelemetry/semantic-conventions': 1.40.0 '@sentry/core': 10.49.0 - '@sentry/react@10.49.0(react@19.2.5)': + '@sentry/react@10.49.0(react@19.2.6)': dependencies: '@sentry/browser': 10.49.0 '@sentry/core': 10.49.0 - react: 19.2.5 + react: 19.2.6 '@sentry/vercel-edge@10.49.0': dependencies: @@ -9385,10 +9343,10 @@ snapshots: '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) '@sentry/core': 10.49.0 - '@sentry/webpack-plugin@5.2.0(webpack@5.106.2(postcss@8.5.10))': + '@sentry/webpack-plugin@5.2.0(webpack@5.106.2)': dependencies: '@sentry/bundler-plugin-core': 5.2.0 - webpack: 5.106.2(postcss@8.5.10) + webpack: 5.106.2 transitivePeerDependencies: - encoding - supports-color @@ -9547,12 +9505,12 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.2 '@testing-library/dom': 10.4.1 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) @@ -9627,11 +9585,11 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 - '@types/estree': 1.0.9 + '@types/estree': 1.0.8 '@types/eslint@9.6.1': dependencies: - '@types/estree': 1.0.9 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 '@types/estree-jsx@1.0.5': @@ -9640,8 +9598,6 @@ snapshots: '@types/estree@1.0.8': {} - '@types/estree@1.0.9': {} - '@types/event-source-polyfill@1.0.5': {} '@types/eventsource@1.1.15': {} @@ -9718,10 +9674,6 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@22.19.18': - dependencies: - undici-types: 6.21.0 - '@types/node@24.12.2': dependencies: undici-types: 7.16.0 @@ -10019,13 +9971,13 @@ snapshots: '@xtuc/long@4.2.2': {} - '@xyflow/react@12.10.2(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@xyflow/react@12.10.2(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@xyflow/system': 0.0.76 classcat: 5.0.5 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - zustand: 4.5.7(@types/react@19.2.14)(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + zustand: 4.5.7(@types/react@19.2.14)(react@19.2.6) transitivePeerDependencies: - '@types/react' - immer @@ -10080,17 +10032,17 @@ snapshots: transitivePeerDependencies: - supports-color - ajv-formats@2.1.1(ajv@8.20.0): + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.20.0 + ajv: 8.18.0 ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 - ajv-keywords@5.1.0(ajv@8.20.0): + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: - ajv: 8.20.0 + ajv: 8.18.0 fast-deep-equal: 3.1.3 ajv@6.14.0: @@ -10107,13 +10059,6 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - ajv@8.20.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -10398,9 +10343,9 @@ snapshots: ccount@2.0.1: {} - ce-la-react@0.3.2(react@19.2.5): + ce-la-react@0.3.2(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 chalk@4.1.2: dependencies: @@ -10488,14 +10433,14 @@ snapshots: clsx@2.1.1: {} - cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -10818,11 +10763,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 - enhanced-resolve@5.21.2: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - entities@4.5.0: {} entities@6.0.1: {} @@ -10917,7 +10857,7 @@ snapshots: iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 - es-module-lexer@2.1.0: {} + es-module-lexer@2.0.0: {} es-object-atoms@1.1.1: dependencies: @@ -11486,8 +11426,6 @@ snapshots: fast-uri@3.1.0: {} - fast-uri@3.1.2: {} - fault@2.0.1: dependencies: format: 0.2.2 @@ -11563,14 +11501,14 @@ snapshots: forwarded@0.2.0: {} - framer-motion@12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + framer-motion@12.38.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: motion-dom: 12.38.0 motion-utils: 12.36.0 tslib: 2.8.1 optionalDependencies: - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) fresh@2.0.0: {} @@ -12460,7 +12398,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.19.18 + '@types/node': 22.19.17 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -12651,7 +12589,7 @@ snapshots: transitivePeerDependencies: - bluebird - loader-runner@4.3.2: {} + loader-runner@4.3.1: {} localforage@1.10.0: dependencies: @@ -12703,9 +12641,9 @@ snapshots: markdown-table@3.0.4: {} - markdown-to-jsx@9.7.16(react@19.2.5): + markdown-to-jsx@9.7.16(react@19.2.6): optionalDependencies: - react: 19.2.5 + react: 19.2.6 math-intrinsics@1.1.0: {} @@ -12885,15 +12823,15 @@ snapshots: mdn-data@2.27.1: {} - media-chrome@4.18.3(react@19.2.5): + media-chrome@4.18.3(react@19.2.6): dependencies: - ce-la-react: 0.3.2(react@19.2.5) + ce-la-react: 0.3.2(react@19.2.6) transitivePeerDependencies: - react - media-chrome@4.19.0(react@19.2.5): + media-chrome@4.19.0(react@19.2.6): dependencies: - ce-la-react: 0.3.2(react@19.2.5) + ce-la-react: 0.3.2(react@19.2.6) transitivePeerDependencies: - react @@ -13255,30 +13193,30 @@ snapshots: neo-async@2.6.2: {} - next-router-mock@0.9.13(next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5): + next-router-mock@0.9.13(next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6): dependencies: - next: 16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 + next: 16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 - next@16.2.3(patch_hash=4172f4183cdf065811fdff7f735ccee387e83dfecd2646eadd41d0bd71629265)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + next@16.2.6(patch_hash=423eaa79c801dbfda09aef892b3236e0194d49baf618fb3c7a89ec95d486eaeb)(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@next/env': 16.2.3 + '@next/env': 16.2.6 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.9.15 caniuse-lite: 1.0.30001790 postcss: 8.4.31 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.6) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.3 - '@next/swc-darwin-x64': 16.2.3 - '@next/swc-linux-arm64-gnu': 16.2.3 - '@next/swc-linux-arm64-musl': 16.2.3 - '@next/swc-linux-x64-gnu': 16.2.3 - '@next/swc-linux-x64-musl': 16.2.3 - '@next/swc-win32-arm64-msvc': 16.2.3 - '@next/swc-win32-x64-msvc': 16.2.3 + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 '@opentelemetry/api': 1.9.1 sharp: 0.34.5 transitivePeerDependencies: @@ -13587,9 +13525,9 @@ snapshots: dependencies: find-up: 4.1.0 - player.style@0.3.4(react@19.2.5): + player.style@0.3.4(react@19.2.6): dependencies: - media-chrome: 4.19.0(react@19.2.5) + media-chrome: 4.19.0(react@19.2.6) transitivePeerDependencies: - react @@ -13651,11 +13589,11 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prism-react-renderer@2.4.1(react@19.2.5): + prism-react-renderer@2.4.1(react@19.2.6): dependencies: '@types/prismjs': 1.26.6 clsx: 2.1.1 - react: 19.2.5 + react: 19.2.6 prismjs@1.30.0: {} @@ -13719,24 +13657,24 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - react-confetti-explosion@3.0.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + react-confetti-explosion@3.0.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) - react-diff-view@3.3.3(react@19.2.5): + react-diff-view@3.3.3(react@19.2.6): dependencies: classnames: 2.5.1 diff-match-patch: 1.0.5 gitdiff-parser: 0.3.1 lodash: 4.18.1 - react: 19.2.5 + react: 19.2.6 shallow-equal: 3.1.0 warning: 4.0.3 - react-dom@19.2.5(react@19.2.5): + react-dom@19.2.6(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 scheduler: 0.27.0 react-is@16.13.1: {} @@ -13747,7 +13685,7 @@ snapshots: react-is@19.2.5: {} - react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.5): + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.6): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -13756,7 +13694,7 @@ snapshots: hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 mdast-util-to-hast: 13.2.1 - react: 19.2.5 + react: 19.2.6 remark-parse: 11.0.0 remark-rehype: 11.1.2 unified: 11.0.5 @@ -13765,15 +13703,15 @@ snapshots: transitivePeerDependencies: - supports-color - react-player@3.4.0(@svta/cml-cta@1.0.1(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1))(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + react-player@3.4.0(@svta/cml-cta@1.0.1(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1))(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@mux/mux-player-react': 3.11.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mux/mux-player-react': 3.11.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/react': 19.2.14 cloudflare-video-element: 1.3.5 dash-video-element: 0.3.2(@svta/cml-cta@1.0.1(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1))(@svta/cml-structured-field-values@1.0.1(@svta/cml-utils@1.0.1))(@svta/cml-utils@1.0.1) hls-video-element: 1.5.11 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) spotify-audio-element: 1.0.4 tiktok-video-element: 0.1.2 twitch-video-element: 0.1.6 @@ -13786,46 +13724,46 @@ snapshots: - '@svta/cml-utils' - '@types/react-dom' - react-qr-code@2.0.18(react@19.2.5): + react-qr-code@2.0.18(react@19.2.6): dependencies: prop-types: 15.8.1 qr.js: 0.0.0 - react: 19.2.5 + react: 19.2.6 - react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.5): + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.6): dependencies: - react: 19.2.5 - react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.6 + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.6) tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.14 - react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.5): + react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.6): dependencies: - react: 19.2.5 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.5) - react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.6 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.6) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.6) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.5) - use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.5) + use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.6) + use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.5): + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.6): dependencies: get-nonce: 1.0.1 - react: 19.2.5 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.14 - react-test-renderer@19.2.5(react@19.2.5): + react-test-renderer@19.2.5(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 react-is: 19.2.5 scheduler: 0.27.0 - react@19.2.5: {} + react@19.2.6: {} read-package-json-fast@3.0.2: dependencies: @@ -14151,9 +14089,9 @@ snapshots: schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) - ajv-keywords: 5.1.0(ajv@8.20.0) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) secure-compare@3.0.1: {} @@ -14451,10 +14389,10 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.5): + styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.6): dependencies: client-only: 0.0.1 - react: 19.2.5 + react: 19.2.6 optionalDependencies: '@babel/core': 7.29.0 @@ -14488,17 +14426,15 @@ snapshots: tapable@2.3.3: {} - terser-webpack-plugin@5.6.0(postcss@8.5.10)(webpack@5.106.2(postcss@8.5.10)): + terser-webpack-plugin@5.4.0(webpack@5.106.2): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.47.1 - webpack: 5.106.2(postcss@8.5.10) - optionalDependencies: - postcss: 8.5.10 + terser: 5.46.1 + webpack: 5.106.2 - terser@5.47.1: + terser@5.46.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.16.0 @@ -14859,24 +14795,24 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.5): + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.14 - use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.5): + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.6): dependencies: detect-node-es: 1.1.0 - react: 19.2.5 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.14 - use-sync-external-store@1.6.0(react@19.2.5): + use-sync-external-store@1.6.0(react@19.2.6): dependencies: - react: 19.2.5 + react: 19.2.6 user-agent-data-types@0.4.3: {} @@ -14974,12 +14910,12 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-sources@3.4.1: {} + webpack-sources@3.3.4: {} - webpack@5.106.2(postcss@8.5.10): + webpack@5.106.2: dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.9 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -14988,32 +14924,23 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.21.2 - es-module-lexer: 2.1.0 + enhanced-resolve: 5.20.1 + es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - loader-runner: 4.3.2 + loader-runner: 4.3.1 mime-db: 1.54.0 neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(postcss@8.5.10)(webpack@5.106.2(postcss@8.5.10)) + terser-webpack-plugin: 5.4.0(webpack@5.106.2) watchpack: 2.5.1 - webpack-sources: 3.4.1 + webpack-sources: 3.3.4 transitivePeerDependencies: - - '@minify-html/node' - '@swc/core' - - '@swc/css' - - '@swc/html' - - clean-css - - cssnano - - csso - esbuild - - html-minifier-terser - - lightningcss - - postcss - uglify-js whatwg-encoding@2.0.0: @@ -15164,10 +15091,10 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yet-another-react-lightbox@3.31.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + yet-another-react-lightbox@3.31.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) @@ -15197,17 +15124,17 @@ snapshots: zod@4.3.6: {} - zustand@4.5.7(@types/react@19.2.14)(react@19.2.5): + zustand@4.5.7(@types/react@19.2.14)(react@19.2.6): dependencies: - use-sync-external-store: 1.6.0(react@19.2.5) + use-sync-external-store: 1.6.0(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - react: 19.2.5 + react: 19.2.6 - zustand@5.0.12(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)): + zustand@5.0.12(@types/react@19.2.14)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6)): optionalDependencies: '@types/react': 19.2.14 - react: 19.2.5 - use-sync-external-store: 1.6.0(react@19.2.5) + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) zwitch@2.0.4: {} diff --git a/docs/scenes/get-started/start-developing/ProjectStructure/index.tsx b/docs/scenes/get-started/start-developing/ProjectStructure/index.tsx index 8c273783e016a1..f7ee44e64bc1a0 100644 --- a/docs/scenes/get-started/start-developing/ProjectStructure/index.tsx +++ b/docs/scenes/get-started/start-developing/ProjectStructure/index.tsx @@ -22,11 +22,11 @@ export function ProjectStructure() {
Files
-
+
Default project
-
+
-
+
diff --git a/docs/ui/components/Authentication/Box.tsx b/docs/ui/components/Authentication/Box.tsx index fa967ca7b9e22a..8fff56294bd082 100644 --- a/docs/ui/components/Authentication/Box.tsx +++ b/docs/ui/components/Authentication/Box.tsx @@ -15,7 +15,7 @@ type BoxProps = PropsWithChildren<{ export const Box = ({ name, image, createUrl, children }: BoxProps) => ( -
+

{name}

diff --git a/docs/ui/components/ContentSpotlight/index.tsx b/docs/ui/components/ContentSpotlight/index.tsx index b8af01e460e358..a2625c6751f229 100644 --- a/docs/ui/components/ContentSpotlight/index.tsx +++ b/docs/ui/components/ContentSpotlight/index.tsx @@ -103,7 +103,7 @@ export function ContentSpotlight({ />
diff --git a/docs/ui/components/DownloadSlide/index.tsx b/docs/ui/components/DownloadSlide/index.tsx index 3e6cfcfd1d0bea..efa7b476f548ef 100644 --- a/docs/ui/components/DownloadSlide/index.tsx +++ b/docs/ui/components/DownloadSlide/index.tsx @@ -17,7 +17,7 @@ export function DownloadSlide({ title, description, imageUrl, className }: Props className={mergeClasses( 'border-default bg-default relative flex items-stretch overflow-hidden rounded-lg border shadow-xs transition', 'hocus:opacity-80 hocus:shadow-sm', - 'max-sm-gutters:flex-col', + 'max-sm:flex-col', '[&+hr]:mt-6!', className )} @@ -25,7 +25,7 @@ export function DownloadSlide({ title, description, imageUrl, className }: Props