diff --git a/apps/mobile/src/features/agent-awareness/agentLiveActivity.ios.ts b/apps/mobile/src/features/agent-awareness/agentLiveActivity.ios.ts index ab078b29a665..da5a35b672a1 100644 --- a/apps/mobile/src/features/agent-awareness/agentLiveActivity.ios.ts +++ b/apps/mobile/src/features/agent-awareness/agentLiveActivity.ios.ts @@ -1,9 +1,16 @@ import AgentActivity, { type AgentActivityProps } from "../../widgets/AgentActivity"; +/** Dismisses ended T3 cards still visible on the Lock Screen, preserving active and stale cards. */ +export function dismissEndedAgentLiveActivities(): Promise { + return AgentActivity.dismissEndedInstances(); +} + +/** Returns active and stale T3 cards; ended cards require the separate native dismissal pass. */ export function getAgentLiveActivities() { return AgentActivity.getInstances(); } +/** Starts a T3 card while the app is foregrounded so its update token can be registered. */ export function startAgentLiveActivity(props: AgentActivityProps) { return AgentActivity.start(props); } diff --git a/apps/mobile/src/features/agent-awareness/agentLiveActivity.ts b/apps/mobile/src/features/agent-awareness/agentLiveActivity.ts index 44ac017984d9..473ebda851a4 100644 --- a/apps/mobile/src/features/agent-awareness/agentLiveActivity.ts +++ b/apps/mobile/src/features/agent-awareness/agentLiveActivity.ts @@ -1,10 +1,17 @@ import type { LiveActivity } from "expo-widgets"; import type { AgentActivityProps } from "../../widgets/AgentActivity"; +/** Resolves without native work on platforms that do not support iOS Live Activities. */ +export function dismissEndedAgentLiveActivities(): Promise { + return Promise.resolve(); +} + +/** Returns no cards on platforms without iOS Live Activity support. */ export function getAgentLiveActivities(): Array> { return []; } +/** Returns null without creating a card on platforms without iOS Live Activity support. */ export function startAgentLiveActivity( _props: AgentActivityProps, ): LiveActivity | null { diff --git a/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts b/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts index df8e023710bd..04d5723f78db 100644 --- a/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts +++ b/apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts @@ -8,6 +8,7 @@ import Constants from "expo-constants"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import { Cookies, @@ -62,6 +63,7 @@ vi.mock("./androidNotifications", () => ({ const secureStore = vi.hoisted(() => new Map()); const widgetMocks = vi.hoisted(() => ({ + dismissEndedInstances: vi.fn(() => Promise.resolve()), getInstances: vi.fn(() => []), start: vi.fn(() => ({})), })); @@ -102,6 +104,7 @@ vi.mock("expo-widgets", () => ({ })); vi.mock("./agentLiveActivity", () => ({ + dismissEndedAgentLiveActivities: widgetMocks.dismissEndedInstances, getAgentLiveActivities: widgetMocks.getInstances, startAgentLiveActivity: widgetMocks.start, })); @@ -274,6 +277,8 @@ describe("makeRelayDeviceRegistrationRequest", () => { vi.mocked(loadOrCreateAgentAwarenessDeviceId).mockResolvedValue("device-1"); widgetMocks.getInstances.mockReset(); widgetMocks.getInstances.mockReturnValue([]); + widgetMocks.dismissEndedInstances.mockReset(); + widgetMocks.dismissEndedInstances.mockResolvedValue(); widgetMocks.start.mockClear(); environmentConfigsMock.configs.clear(); }); @@ -474,6 +479,176 @@ describe("makeRelayDeviceRegistrationRequest", () => { }, ); + it.effect( + "waits for ended-card cleanup before discovering and registering the active card", + () => { + const cleanup = Promise.withResolvers(); + const cleanupStarted = Promise.withResolvers(); + const activitiesRead = Promise.withResolvers(); + widgetMocks.getInstances.mockImplementation(() => { + activitiesRead.resolve(); + return []; + }); + widgetMocks.dismissEndedInstances.mockImplementation(() => { + cleanupStarted.resolve(); + return cleanup.promise; + }); + const activity = { + getPushToken: vi.fn(() => Promise.resolve("current-activity-token")), + addPushTokenListener: vi.fn(), + end: vi.fn(), + }; + setAgentAwarenessRelayTokenProvider(() => Promise.resolve("clerk-token-user-a")); + + return Effect.gen(function* () { + const refresh = yield* refreshActiveLiveActivityRemoteRegistration().pipe(Effect.forkChild); + const firstStep = yield* Effect.promise(() => + Promise.race([ + cleanupStarted.promise.then(() => "cleanup"), + activitiesRead.promise.then(() => "activities"), + ]), + ); + expect(firstStep).toBe("cleanup"); + expect(widgetMocks.getInstances).not.toHaveBeenCalled(); + expect(widgetMocks.start).not.toHaveBeenCalled(); + + // Another foreground path armed the live card while native cleanup was + // pending. Discover it after cleanup instead of starting a second one. + widgetMocks.getInstances.mockReturnValue([activity] as never); + cleanup.resolve(); + yield* Fiber.join(refresh); + + expect(activity.getPushToken).toHaveBeenCalledOnce(); + expect(activity.end).not.toHaveBeenCalled(); + expect(widgetMocks.start).not.toHaveBeenCalled(); + }).pipe(Effect.provide(relayTestLayer)); + }, + ); + + it.effect("still registers the active card when ended-card cleanup fails", () => { + widgetMocks.dismissEndedInstances.mockRejectedValueOnce(new Error("native cleanup failed")); + const activity = { + getPushToken: vi.fn(() => Promise.resolve("current-activity-token")), + addPushTokenListener: vi.fn(), + end: vi.fn(), + }; + widgetMocks.getInstances.mockReturnValue([activity] as never); + setAgentAwarenessRelayTokenProvider(() => Promise.resolve("clerk-token-user-a")); + + return Effect.gen(function* () { + yield* refreshActiveLiveActivityRemoteRegistration(); + expect(activity.getPushToken).toHaveBeenCalledOnce(); + expect(activity.end).not.toHaveBeenCalled(); + expect(widgetMocks.start).not.toHaveBeenCalled(); + }).pipe(Effect.provide(relayTestLayer)); + }); + + it("rechecks for an active card after cleanup when arming local work", async () => { + const cleanup = Promise.withResolvers(); + const cleanupStarted = Promise.withResolvers(); + const activitiesRead = Promise.withResolvers(); + widgetMocks.getInstances.mockImplementation(() => { + activitiesRead.resolve(); + return [{ getPushToken: vi.fn() }] as never; + }); + widgetMocks.dismissEndedInstances.mockImplementation(() => { + cleanupStarted.resolve(); + return cleanup.promise; + }); + setAgentAwarenessRelayTokenProvider(() => Promise.resolve("clerk-token-user-a")); + vi.mocked(loadPreferences).mockResolvedValueOnce({ + liveActivitiesEnabled: true, + } as Preferences); + + armAgentAwarenessLiveActivityForLocalWork({ + environmentId: "env-1" as EnvironmentId, + threadTitle: "New work", + projectTitle: "t3code", + }); + expect( + await Promise.race([ + cleanupStarted.promise.then(() => "cleanup"), + activitiesRead.promise.then(() => "activities"), + ]), + ).toBe("cleanup"); + expect(widgetMocks.getInstances).not.toHaveBeenCalled(); + expect(widgetMocks.start).not.toHaveBeenCalled(); + + cleanup.resolve(); + await activitiesRead.promise; + expect(widgetMocks.start).not.toHaveBeenCalled(); + }); + + it.each([ + ["preferences", "sign-out"], + ["preferences", "account change"], + ["preferences", "provider teardown"], + ["cleanup", "sign-out"], + ["cleanup", "account change"], + ["cleanup", "provider teardown"], + ])("cancels local arming pending %s after %s", async (pendingStep, sessionChange) => { + const resume = Promise.withResolvers(); + const paused = Promise.withResolvers(); + const preferences = { liveActivitiesEnabled: true } as Preferences; + setAgentAwarenessRelayTokenProvider(() => Promise.resolve("token-a"), "user-a"); + if (pendingStep === "preferences") { + vi.mocked(loadPreferences).mockImplementationOnce(async () => { + paused.resolve(); + await resume.promise; + return preferences; + }); + } else { + vi.mocked(loadPreferences).mockResolvedValueOnce(preferences); + widgetMocks.dismissEndedInstances.mockImplementationOnce(() => { + paused.resolve(); + return resume.promise; + }); + } + + const arming = armAgentAwarenessLiveActivityForLocalWork({ + environmentId: "env-1" as EnvironmentId, + threadTitle: "Private work", + projectTitle: "Private project", + }); + await paused.promise; + if (sessionChange === "sign-out") { + setAgentAwarenessRelayTokenProvider(null); + } else if (sessionChange === "account change") { + setAgentAwarenessRelayTokenProvider(() => Promise.resolve("token-b"), "user-b"); + } else { + releaseAgentAwarenessRelayTokenProvider(); + } + resume.resolve(); + await arming; + + expect(widgetMocks.start).not.toHaveBeenCalled(); + }); + + it("still arms local work after a same-account token refresh during cleanup", async () => { + const cleanup = Promise.withResolvers(); + const cleanupStarted = Promise.withResolvers(); + widgetMocks.dismissEndedInstances.mockImplementationOnce(() => { + cleanupStarted.resolve(); + return cleanup.promise; + }); + setAgentAwarenessRelayTokenProvider(() => Promise.resolve("token-a"), "user-a"); + vi.mocked(loadPreferences).mockResolvedValueOnce({ + liveActivitiesEnabled: true, + } as Preferences); + + const arming = armAgentAwarenessLiveActivityForLocalWork({ + environmentId: "env-1" as EnvironmentId, + threadTitle: "New work", + projectTitle: "t3code", + }); + await cleanupStarted.promise; + setAgentAwarenessRelayTokenProvider(() => Promise.resolve("refreshed-token-a"), "user-a"); + cleanup.resolve(); + await arming; + + expect(widgetMocks.start).toHaveBeenCalledOnce(); + }); + it.effect( "re-registers active Live Activity tokens when the app returns to the foreground", () => { diff --git a/apps/mobile/src/features/agent-awareness/remoteRegistration.ts b/apps/mobile/src/features/agent-awareness/remoteRegistration.ts index a6c31594327a..cbc09a5f7689 100644 --- a/apps/mobile/src/features/agent-awareness/remoteRegistration.ts +++ b/apps/mobile/src/features/agent-awareness/remoteRegistration.ts @@ -36,7 +36,11 @@ import { saveAgentAwarenessRegistrationRecord, } from "../../persistence/imperative"; import type { AgentActivityProps } from "../../widgets/AgentActivity"; -import { getAgentLiveActivities, startAgentLiveActivity } from "./agentLiveActivity"; +import { + dismissEndedAgentLiveActivities, + getAgentLiveActivities, + startAgentLiveActivity, +} from "./agentLiveActivity"; import { resolveCloudPublicConfig } from "../cloud/publicConfig"; import { supportsAgentAwarenessPush } from "./capabilities"; import { makeRelayDeviceRegistrationRequest, resolveApsEnvironment } from "./registrationPayload"; @@ -487,18 +491,17 @@ function environmentPublishesAgentActivity(environmentId: EnvironmentId): boolea ); } -// Arms the lock-screen card the moment the user starts agent work from this -// phone, while the app is still foregrounded and the fresh activity's token -// can be registered immediately. The seeded row is a best-effort placeholder; -// the relay's registration replay repaints it with the authoritative -// aggregate within seconds. No-ops when a card is already armed, and skips -// environments that report publishing disabled — the seed would sit on -// "Connecting" forever with no update ever arriving to repaint or end it. -export function armAgentAwarenessLiveActivityForLocalWork(input: { +/** + * Arms a foreground card for local work; relay registration replaces its placeholder + * with the current aggregate. Skips disabled preferences, existing cards, and + * environments that cannot publish updates. Session changes cancel pending arming. + * Resolves after the arming attempt; token registration continues in the background. + */ +export async function armAgentAwarenessLiveActivityForLocalWork(input: { readonly environmentId: EnvironmentId; readonly threadTitle: string; readonly projectTitle: string; -}): void { +}): Promise { if (!canRegisterRemoteLiveActivities() || !relayTokenProvider) { return; } @@ -508,21 +511,32 @@ export function armAgentAwarenessLiveActivityForLocalWork(input: { }); return; } - void loadPreferences() - .catch(() => null) - .then((preferences) => { - if (preferences?.liveActivitiesEnabled === false) { - return; - } - armAgentAwarenessLiveActivityForLocalWorkNow(input); - }); + const generation = deviceRegistrationGeneration; + const preferences = await loadPreferences().catch(() => null); + if (preferences?.liveActivitiesEnabled === false || generation !== deviceRegistrationGeneration) { + return; + } + await armAgentAwarenessLiveActivityForLocalWorkNow(input, generation); } -function armAgentAwarenessLiveActivityForLocalWorkNow(input: { - readonly threadTitle: string; - readonly projectTitle: string; -}): void { +/** + * Removes ended cards before seeding local work for the captured session generation. + * Rechecks the session and existing cards after cleanup to avoid orphaned or duplicate cards. + */ +async function armAgentAwarenessLiveActivityForLocalWorkNow( + input: { + readonly threadTitle: string; + readonly projectTitle: string; + }, + expectedGeneration: number, +): Promise { try { + await dismissEndedLocalLiveActivities(); + // Sign-out or an account change may have happened while native cleanup ran. + // The old session must not leave a new card that its relay can no longer end. + if (expectedGeneration !== deviceRegistrationGeneration) { + return; + } if (getAgentLiveActivities().length > 0) { return; } @@ -841,10 +855,24 @@ function ensureAppStateListener(): void { }); } +/** + * Dismisses ended cards hidden by getInstances() but still visible on the Lock Screen. + * Logs native cleanup failures so active-card registration can continue. + */ +async function dismissEndedLocalLiveActivities(): Promise { + try { + await dismissEndedAgentLiveActivities(); + } catch (error) { + logRegistrationError("ended live activity cleanup failed", error); + } +} + +/** Starts best-effort dismissal of ended and live cards on sign-out, logging failures with context. */ function endLocalLiveActivities(context: string): void { if (!canRegisterRemoteLiveActivities()) { return; } + void dismissEndedLocalLiveActivities(); try { for (const activity of getAgentLiveActivities()) { activity.end("immediate").catch((error: unknown) => { @@ -1062,6 +1090,10 @@ function scheduleActiveLiveActivityRegistrationRetry(): void { }, REMOTE_ACTIVITY_REGISTRATION_RETRY_MS); } +/** + * Reconciles foreground iOS cards with the relay after dismissing ended cards. + * Keeps one live card, primes it from current work when needed, and registers its update token. + */ export function refreshActiveLiveActivityRemoteRegistration(): Effect.Effect< void, never, @@ -1072,6 +1104,8 @@ export function refreshActiveLiveActivityRemoteRegistration(): Effect.Effect< return; } + yield* Effect.promise(dismissEndedLocalLiveActivities); + let activities = yield* Effect.try({ try: () => getAgentLiveActivities(), catch: (cause) => diff --git a/patches/expo-widgets@57.0.15.patch b/patches/expo-widgets@57.0.15.patch index f50a3fa4f661..e6f19d6f63b6 100644 --- a/patches/expo-widgets@57.0.15.patch +++ b/patches/expo-widgets@57.0.15.patch @@ -1,5 +1,47 @@ +diff --git a/build/ExpoWidgets.js b/build/ExpoWidgets.js +index aabbb41259038993305302607e0156e4d61cf4bd..867327294a649b6c8d596454d0fc4c2d39dd1805 100644 +--- a/build/ExpoWidgets.js ++++ b/build/ExpoWidgets.js +@@ -30,6 +30,9 @@ class LiveActivityFactoryStub { + getInstances() { + return []; + } ++ dismissEndedInstances() { ++ return Promise.resolve(); ++ } + } + const ExpoWidgetsModule = { + widgetsDirectory: '', +diff --git a/build/Widgets.d.ts b/build/Widgets.d.ts +index d4b5aacee380c056855f68c5ca62c9af24304745..e0a95729c5f3b89599b1b814937ab8a7e9f3e537 100644 +--- a/build/Widgets.d.ts ++++ b/build/Widgets.d.ts +@@ -83,6 +83,8 @@ export declare class LiveActivityFactory { + * Returns all currently active instances of this Live Activity type. + */ + getInstances(): LiveActivity[]; ++ /** Removes ended instances that are still visible without ending active or stale ones. */ ++ dismissEndedInstances(): Promise; + } + /** + * Creates a dismissal policy that removes the Live Activity at the specified time within a four-hour window. +diff --git a/build/Widgets.js b/build/Widgets.js +index 9099a1bb3c10eaddc41518ba5b82117284f82135..c35bbad071006501994afebf9a30016dfb0f6d4c 100644 +--- a/build/Widgets.js ++++ b/build/Widgets.js +@@ -113,6 +113,10 @@ export class LiveActivityFactory { + start(props, url, staleDate) { + return new LiveActivity(this.nativeLiveActivityFactory.start(JSON.stringify(props), url, staleDate?.getTime())); + } ++ /** Removes ended instances that are still visible without ending active or stale ones. */ ++ dismissEndedInstances() { ++ return this.nativeLiveActivityFactory.dismissEndedInstances(); ++ } + /** + * Returns all currently active instances of this Live Activity type. + */ diff --git a/build/Widgets.types.d.ts b/build/Widgets.types.d.ts -index 15e026868625ce20a3e359e5cc8e5c7e937c4650..f5cc83174ede4fb6e01cbce494f61e088eb4eed8 100644 +index 15e026868625ce20a3e359e5cc8e5c7e937c4650..1f63ec87c54fc329812e3fe1ec1f0eece0ef515b 100644 --- a/build/Widgets.types.d.ts +++ b/build/Widgets.types.d.ts @@ -90,6 +90,10 @@ export type LiveActivityEnvironment = { @@ -13,6 +55,36 @@ index 15e026868625ce20a3e359e5cc8e5c7e937c4650..f5cc83174ede4fb6e01cbce494f61e08 /** * Whether the activity is displayed in a context with reduced luminance. * @platform iOS 16+ +@@ -263,6 +267,7 @@ export declare class NativeLiveActivityFactory extends SharedObject { + constructor(name: string, layout: string); + start(props: string, url?: string, staleDate?: number): NativeLiveActivity; + getInstances(): NativeLiveActivity[]; ++ dismissEndedInstances(): Promise; + } + export declare class NativeLiveActivity extends SharedObject { + getId(): string; +diff --git a/ios/LiveActivityFactory.swift b/ios/LiveActivityFactory.swift +index 4b8a632965ddb507aeeb54a4d8944f455e5dffd7..9a6604e09c2e8f2811a512ee04a2ca12ce46a397 100644 +--- a/ios/LiveActivityFactory.swift ++++ b/ios/LiveActivityFactory.swift +@@ -62,6 +62,17 @@ final class LiveActivityFactory: SharedObject { + } + } + ++ func dismissEndedInstances() async throws { ++ guard #available(iOS 16.2, *) else { throw LiveActivitiesNotSupportedException() } ++ ++ // Ended activities can remain visible for hours, but getInstances() ++ // deliberately excludes them because they can no longer receive updates. ++ for activity in Activity.activities ++ where activity.content.state.name == name && activity.activityState == .ended { ++ await activity.end(nil, dismissalPolicy: .immediate) ++ } ++ } ++ + override func sharedObjectWillRelease() { + instances.removeAll() + } diff --git a/ios/Widgets/Utils.swift b/ios/Widgets/Utils.swift index 2b4c044b597de7e0fab42dbc47af14d8bb521838..801f0aed65ea2659f221d477dd841787d9146c26 100644 --- a/ios/Widgets/Utils.swift @@ -131,8 +203,53 @@ index 24d4571a52d3ebe9d785b5d049d8e134a149d513..7051c23b17ac91552e5b5c54606c5708 if #available(iOS 18.0, *) { LiveActivityBanner(context: context, nodes: nodes) } else if let node = nodes["banner"] as? [String: Any] { +diff --git a/ios/WidgetsModule.swift b/ios/WidgetsModule.swift +index b1b4295e22726e6fe501aa5e514b56061dea57a1..8252ffe389c853a0bc9f1f369abf43d856c93847 100644 +--- a/ios/WidgetsModule.swift ++++ b/ios/WidgetsModule.swift +@@ -93,6 +93,10 @@ public final class WidgetsModule: Module { + Function("getInstances") { (liveActivity: LiveActivityFactory) in + try liveActivity.getInstances() + } ++ ++ AsyncFunction("dismissEndedInstances") { (liveActivity: LiveActivityFactory) in ++ try await liveActivity.dismissEndedInstances() ++ } + } + + Class("LiveActivity", LiveActivity.self) { +diff --git a/src/ExpoWidgets.ts b/src/ExpoWidgets.ts +index b118f9142e1a2a97c0b3683ee8a42a642a955e4d..e888e14544d6eb80fddc42c78f50e30ce332cb81 100644 +--- a/src/ExpoWidgets.ts ++++ b/src/ExpoWidgets.ts +@@ -48,6 +48,9 @@ class LiveActivityFactoryStub { + getInstances(): NativeLiveActivity[] { + return []; + } ++ dismissEndedInstances(): Promise { ++ return Promise.resolve(); ++ } + } + + const ExpoWidgetsModule = { +diff --git a/src/Widgets.ts b/src/Widgets.ts +index 51ea140b4309c347d6f98290051be1c5062bff70..9063111e47b2f1ba80d4fe26441461db298f6a04 100644 +--- a/src/Widgets.ts ++++ b/src/Widgets.ts +@@ -168,6 +168,11 @@ export class LiveActivityFactory { + ); + } + ++ /** Removes ended instances that are still visible without ending active or stale ones. */ ++ dismissEndedInstances(): Promise { ++ return this.nativeLiveActivityFactory.dismissEndedInstances(); ++ } ++ + /** + * Returns all currently active instances of this Live Activity type. + */ diff --git a/src/Widgets.types.ts b/src/Widgets.types.ts -index fee5c7475b52218fa0e192a1d347dd32cb0fe1f8..1d3a097b45d8eb5d13e5cedd26f0414caa83a5d6 100644 +index fee5c7475b52218fa0e192a1d347dd32cb0fe1f8..eca20f9d294a0c10b6c8fbdd4431211563aa9878 100644 --- a/src/Widgets.types.ts +++ b/src/Widgets.types.ts @@ -104,6 +104,10 @@ export type LiveActivityEnvironment = { @@ -146,3 +263,11 @@ index fee5c7475b52218fa0e192a1d347dd32cb0fe1f8..1d3a097b45d8eb5d13e5cedd26f0414c /** * Whether the activity is displayed in a context with reduced luminance. * @platform iOS 16+ +@@ -292,6 +296,7 @@ export declare class NativeLiveActivityFactory extends SharedObject { + constructor(name: string, layout: string); + start(props: string, url?: string, staleDate?: number): NativeLiveActivity; + getInstances(): NativeLiveActivity[]; ++ dismissEndedInstances(): Promise; + } + + export declare class NativeLiveActivity extends SharedObject { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0daed3ff962..d18f19ef1e46 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,7 +105,7 @@ patchedDependencies: expo-modules-core@57.0.14: 6be7092f8c6f03a57b75c797d98b5f19ed62d2dd205bfc4c8adf4cef45655d7e expo-notifications@57.0.15: f89252c2c08dc7b3acb8415f319ebbe15fff8c9f88a64ebe1830cd5ac5c9ac40 expo-sharing@57.0.17: 8d2e3b10eb3f52036a9a086800180ec6cebf3b75bccc5b1775117a7244d4ac45 - expo-widgets@57.0.15: 319a9ded5db49c5b5215c511a138b33f44c7ea2972eb418192e8d5342fe75ce6 + expo-widgets@57.0.15: b641ea58b1e4cc9c0f57cd3db2c55d6cddfc2094e8b69d1ff3a369ad66914979 react-native-gesture-handler@2.32.0: 0579f8e4dad02bf3183d95b02620358412983c36f9bda7425dc8bcb9643b5ce2 react-native-keyboard-controller@1.21.13: 6e4339347bc5bb3c9ea67d85ff5c814058b211c5750f247aba59d07869a2e787 react-native-nitro-modules@0.35.9: 825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675 @@ -424,7 +424,7 @@ importers: version: 57.0.2(expo@57.0.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) expo-widgets: specifier: 57.0.15 - version: 57.0.15(patch_hash=319a9ded5db49c5b5215c511a138b33f44c7ea2972eb418192e8d5342fe75ce6)(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(expo@57.0.18)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) + version: 57.0.15(patch_hash=b641ea58b1e4cc9c0f57cd3db2c55d6cddfc2094e8b69d1ff3a369ad66914979)(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(expo@57.0.18)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) react: specifier: 19.2.3 version: 19.2.3 @@ -16271,7 +16271,7 @@ snapshots: optionalDependencies: '@babel/runtime': 7.29.7 expo: 57.0.18(41fd11498a34454c91128cdad32f22f8) - expo-widgets: 57.0.15(patch_hash=319a9ded5db49c5b5215c511a138b33f44c7ea2972eb418192e8d5342fe75ce6)(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(expo@57.0.18)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) + expo-widgets: 57.0.15(patch_hash=b641ea58b1e4cc9c0f57cd3db2c55d6cddfc2094e8b69d1ff3a369ad66914979)(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(expo@57.0.18)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) transitivePeerDependencies: - '@babel/core' - supports-color @@ -17574,7 +17574,7 @@ snapshots: expo: 57.0.18(41fd11498a34454c91128cdad32f22f8) react-native: 0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) - expo-widgets@57.0.15(patch_hash=319a9ded5db49c5b5215c511a138b33f44c7ea2972eb418192e8d5342fe75ce6)(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(expo@57.0.18)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3): + expo-widgets@57.0.15(patch_hash=b641ea58b1e4cc9c0f57cd3db2c55d6cddfc2094e8b69d1ff3a369ad66914979)(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(expo@57.0.18)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3): dependencies: '@expo/plist': 0.8.1 '@expo/ui': 57.0.14(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(expo@57.0.18)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.11.4(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)