diff --git a/shared/chat/conversation/input-area/normal/index.tsx b/shared/chat/conversation/input-area/normal/index.tsx index afc2bad9f6cb..4201177d775a 100644 --- a/shared/chat/conversation/input-area/normal/index.tsx +++ b/shared/chat/conversation/input-area/normal/index.tsx @@ -198,12 +198,24 @@ const ConnectedPlatformInput = function ConnectedPlatformInput() { const {scrollToBottom} = React.useContext(ThreadRefsContext) const onSubmit = (text: string) => { if (!text) return + // Clearing the composer shrinks it back to one line, which grows the thread's viewport. Sending in + // the same tick makes that growth and the new row a single change for the list to resolve its end + // against, and it lands short — 8 of 8 at one, two and six lines, worse the longer the message. So + // clear first and let that land before the row arrives. legend-list's own chat example does both at + // once, which works there because its composer is a single-line input that never resizes the list. + // + // A timeout rather than requestAnimationFrame: this callback owns the only copy of the text, and + // frames stop in a hidden or backgrounded window, which would drop the message with the composer + // already emptied. injectText('', true) - sendComposerText(text) - if (hasCenter) { - toggleThreadSearch(true) - jumpToRecent() - } else { + setTimeout(() => { + sendComposerText(text) + if (hasCenter) { + toggleThreadSearch(true) + jumpToRecent() + } + }, 0) + if (!hasCenter) { scrollToBottom() } } diff --git a/shared/chat/conversation/list-area/index.tsx b/shared/chat/conversation/list-area/index.tsx index 40a7a078a1b8..dda21755fdf5 100644 --- a/shared/chat/conversation/list-area/index.tsx +++ b/shared/chat/conversation/list-area/index.tsx @@ -139,6 +139,8 @@ const usePagination = (p: { } const centerTolerancePx = 8 +// A scroller within this many pixels of its end counts as at the end. +const endTolerancePx = 2 // When a centeredOrdinal is set at mount, start there; otherwise start at the end (newest). const useInitialScrollIndex = ( @@ -237,10 +239,29 @@ const DesktopThreadWrapper = function DesktopThreadWrapper() { const getItemType = useGetItemType() - // Imperative scroll for ThreadRefsContext + // Asks the scroller, not the list's own isAtEnd: that flag comes from the content size and viewport + // the list has recorded, and both lag a composer collapse, so it reads not-at-end while the scroller + // is in fact at its end. + const isScrolledToEnd = React.useCallback(() => { + type ElLike = {children: ArrayLike; clientHeight: number; scrollHeight: number; scrollTop: number} + const wrapper = wrapperRef.current as unknown as ElLike | null + if (!wrapper) return false + for (const child of Array.from(wrapper.children)) { + if (child.scrollHeight - child.clientHeight > 1) { + return child.scrollHeight - child.clientHeight - child.scrollTop <= endTolerancePx + } + } + return false + }, []) + + // Imperative scroll for ThreadRefsContext: for coming back from somewhere else in the thread, which + // is the only case that needs it. While the list is at the end maintainScrollAtEnd owns the position, + // and scrolling here only displaces it — the target resolves before the new row has measured, so it + // lands short, and while it counts as in flight the list declines its own end anchor and abandons it. const scrollToBottom = React.useCallback(() => { + if (isScrolledToEnd()) return void listRef.current?.scrollToEnd({animated: false}) - }, []) + }, [isScrolledToEnd]) const scrollUp = React.useCallback(() => { const state = listRef.current?.getState() @@ -536,12 +557,18 @@ const DesktopThreadWrapper = function DesktopThreadWrapper() { drawDistance={250} estimatedItemSize={72} style={Kb.Styles.castStyleDesktop(desktopStyles.list)} + // Short threads sit at the bottom rather than the top. Inert once the content is taller than + // the viewport: the padding it adds is max(0, viewport - content). + alignItemsAtEnd={true} initialScrollAtEnd={initialScrollIndex === undefined} initialScrollIndex={initialScrollIndex} maintainScrollAtEnd={ centeredOrdinal !== undefined ? false - : {on: {dataChange: true, footerLayout: true, itemLayout: true}} + : // The documented form, which enables every trigger. It was a narrowed {on: {...}} list + // before, and naming any trigger opts out of the ones left unnamed — that is how the + // layout trigger went missing and a window resize lost the end. + true } // Stays on while centered: the full thread response lands after the cached one and // re-measures rows above the target, which slides it out of view unless anchored. @@ -565,6 +592,12 @@ const useDesktopStyles = Kb.Styles.createStyleHook( isElectron: { ...Kb.Styles.globalStyles.fillAbsolute, overflow: 'hidden', + // The gap above the input lives out here, not as the list's own paddingBottom: the list + // feeds its padding into every scroll-offset calculation it makes (content size, the end + // target, the at-end threshold), so keeping it outside the scroller keeps that math on + // message sizes alone. Deliberately 8 rather than the 16 it used to be — half the gap reads + // better with the messages sitting closer to the composer. + paddingBottom: 8, }, }), list: Kb.Styles.platformStyles({ @@ -573,7 +606,6 @@ const useDesktopStyles = Kb.Styles.createStyleHook( outline: 'none', overflowY: 'auto', overscrollBehavior: 'contain', - paddingBottom: 16, scrollbarGutter: 'stable', willChange: 'transform', }, diff --git a/shared/chat/conversation/messages/emoji-row.tsx b/shared/chat/conversation/messages/emoji-row.tsx index 66549c77d9a9..101df01d04f2 100644 --- a/shared/chat/conversation/messages/emoji-row.tsx +++ b/shared/chat/conversation/messages/emoji-row.tsx @@ -12,6 +12,9 @@ import { useConversationThreadMessageActions, } from '../thread-context' +// A raised hover bar stops this far above the scroller's bottom edge instead of flush against it. +const edgeClearance = 2 + type OwnProps = { className?: string hasUnfurls: boolean @@ -74,7 +77,6 @@ function EmojiRowContainer(p: OwnProps) { // on every pointer re-entry into the row. Typed structurally since the native tsconfig has no // DOM lib; on desktop Box2's MeasureRef is the backing div. const [raiseBy, setRaiseBy] = React.useState(0) - const raiseByRef = React.useRef(0) React.useLayoutEffect(() => { if (isMobile || !keepWithinList) { return @@ -83,24 +85,45 @@ function EmojiRowContainer(p: OwnProps) { addEventListener: (t: string, cb: () => void) => void removeEventListener: (t: string, cb: () => void) => void getBoundingClientRect: () => {bottom: number; height: number} + parentElement: ElLike | null } const el = popupAnchor.current as null | (ElLike & {closest?: (sel: string) => ElLike | null}) const row = el?.closest?.('.WrapperMessage-hoverBox') - const list = el?.closest?.('.chat-message-list') + const outer = el?.closest?.('.chat-message-list') + // Measure against the scroller, not the outer box: the gap above the input area is padding on + // the outer box, so the scroller ends above it and clips a bar the outer box still had room + // for. The scroller is the one ancestor sitting directly inside the outer box — don't look for + // "nearest scrollable ancestor", every row container reports as one. + let list = outer + for (let node = el?.parentElement; node; node = node.parentElement) { + if (node.parentElement === outer) { + list = node + break + } + } if (!el || !row || !list) { return } + // Read back the raise the DOM is actually carrying rather than the one we last asked for: the two + // differ for as long as it takes React to paint our state, and measuring twice inside that gap + // (which mount + the enter event do) would otherwise stack the raise onto itself. + const appliedRaise = () => { + const getStyle = (globalThis as unknown as {getComputedStyle?: (e: unknown) => {transform: string}}) + .getComputedStyle + const matrix = /matrix\([^)]*,\s*(-?[\d.]+)\)\s*$/.exec(getStyle?.(el).transform ?? '') + return matrix ? -Number(matrix[1]) : 0 + } const measure = () => { const rect = el.getBoundingClientRect() // zero height means we're display:none and the rect is meaningless if (rect.height === 0) { return } - // add back the raise we're currently applying so overflow is computed from our natural spot - const overflow = rect.bottom + raiseByRef.current - list.getBoundingClientRect().bottom - const next = Math.max(0, overflow) - raiseByRef.current = next - setRaiseBy(next) + // add back the raise in effect so overflow is computed from our natural spot + const overflow = rect.bottom + appliedRaise() - list.getBoundingClientRect().bottom + // clear the edge rather than sitting flush on it, so a raised bar doesn't read as tight + // against the input area + setRaiseBy(overflow > 0 ? overflow + edgeClearance : 0) } // the enter event that mounted us already fired, so measure directly too measure() diff --git a/shared/ios/Podfile.lock b/shared/ios/Podfile.lock index 841aa0feb7e1..a6e60b8201c2 100644 --- a/shared/ios/Podfile.lock +++ b/shared/ios/Podfile.lock @@ -1,7 +1,7 @@ PODS: - - EXConstants (57.0.11): + - EXConstants (57.0.12): - ExpoModulesCore - - Expo (57.0.13): + - Expo (57.0.14): - ExpoModulesCore - ExpoModulesJSI - hermes-engine @@ -27,7 +27,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoAsset (57.0.11): + - ExpoAsset (57.0.12): - ExpoModulesCore - ExpoAudio (57.0.3): - ExpoModulesCore @@ -58,15 +58,15 @@ PODS: - SDWebImageAVIFCoder (~> 0.11.0) - SDWebImageSVGCoder (~> 1.7.0) - SDWebImageWebPCoder (~> 0.14.6) - - ExpoImagePicker (57.0.10): + - ExpoImagePicker (57.0.11): - ExpoModulesCore - ExpoKeepAwake (57.0.1): - ExpoModulesCore - ExpoLocalization (57.0.1): - ExpoModulesCore - - ExpoLocation (57.0.10): + - ExpoLocation (57.0.11): - ExpoModulesCore - - ExpoLogBox (57.0.2): + - ExpoLogBox (57.0.3): - React-Core - ExpoMailComposer (57.0.1): - ExpoModulesCore @@ -112,7 +112,7 @@ PODS: - ExpoModulesCore - ExpoSMS (57.0.1): - ExpoModulesCore - - ExpoTaskManager (57.0.10): + - ExpoTaskManager (57.0.11): - ExpoModulesCore - UMAppLoader - ExpoVideo (57.0.2): @@ -2891,9 +2891,9 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - EXConstants: 13cc6ad9324d81223386074a523d4e16aed32cd2 - Expo: 02ec524ae0eda32b052decb8ea9b6c2f1c85e456 - ExpoAsset: e046c820a585f46969182c2aa03c3a822dd55b6b + EXConstants: 092a945e490d473e0b6aec724684b36c447dd62a + Expo: 0a22949b8fd3e8e544aa8bd5a75cb36f932289ea + ExpoAsset: 1a913b91ccf6db8a5f68ba0c3ab75b8683766cf1 ExpoAudio: b69f36525132edba8d87a715b3a20c678ef78b30 ExpoCamera: a5e9d9ce2fc06689eb2dd676f1f8d5f22f8aa292 ExpoCameraBarcodeScanning: b869ac87ebe30bb99b26c55b0e0471301383c90b @@ -2905,11 +2905,11 @@ SPEC CHECKSUMS: ExpoFont: 59e1faf66ba9bcd232ae1e2ce58d202a48b6f65e ExpoHaptics: 4503d2da51ff7109712ccdfe0e4939f66818183e ExpoImage: b37accc4b12647e5cf939b2335eea2bdb874ad7f - ExpoImagePicker: 945c7f41749d99266d220a67118ae769b4d2af19 + ExpoImagePicker: b6f0ce415819462b7cf28c47e7a9e147624ef29e ExpoKeepAwake: c26f14275017370cc8a4b7b43a0e23361f2053a5 ExpoLocalization: 659243b03b3a3e9589793cee2f728a899e181329 - ExpoLocation: 1da1cdf04d24886ca57a98fa36704fa3d5e23629 - ExpoLogBox: 6bb73c341aca22e699bd4da6cc61afc844e1a648 + ExpoLocation: 30cc8a3337823bf3725a332a96c07ce1d1521875 + ExpoLogBox: 2c4225a62f1cfb943e6b2df4fa4fe2d136e06fb6 ExpoMailComposer: 101933dc6bdb4d7a46495d1dd87420365325adb9 ExpoMediaLibrary: 344f5b528a71f34794e9d7396e32589531df146e ExpoModulesCore: 3b0be7a40e69428089fe7ac7054586a20778d276 @@ -2918,7 +2918,7 @@ SPEC CHECKSUMS: ExpoModulesWorkletsAdapter: 4825908c915e61b9e4408c893a66307221269c8f ExpoScreenCapture: 6b82aba55db3ecc6f124ebd45163d6db9b916b2d ExpoSMS: ac35f6c85b72ee5b18a7c6bb06550fbd4a683775 - ExpoTaskManager: 83460ba590ec911d8eb66c38a49de817b127e0e8 + ExpoTaskManager: 764fd9262a9db7a194b58eba220035a7fbd825e8 ExpoVideo: b1d9a325ddcd4f3ed8ccb374c01b688e98bc6a04 FBLazyVector: 3c3be9a019176b5699455f7f66c5444b78a411c6 hermes-engine: 034fad1f720e9fb9fe1b5ae5ac0c217531216c50 diff --git a/shared/package.json b/shared/package.json index eba3b73efdff..372a848dab54 100644 --- a/shared/package.json +++ b/shared/package.json @@ -92,7 +92,7 @@ "@gorhom/bottom-sheet": "5.2.14", "@gorhom/portal": "1.0.14", "@khanacademy/simple-markdown": "3.0.1", - "@legendapp/list": "3.3.5", + "@legendapp/list": "3.3.6", "@msgpack/msgpack": "3.1.3", "@react-native-community/netinfo": "12.0.1", "@react-native-masked-view/masked-view": "0.3.2", @@ -104,8 +104,8 @@ "date-fns": "4.4.0", "emoji-datasource-apple": "16.0.0", "emoji-regex": "10.6.0", - "expo": "57.0.13", - "expo-asset": "57.0.11", + "expo": "57.0.14", + "expo-asset": "57.0.12", "expo-audio": "57.0.3", "expo-camera": "57.0.3", "expo-clipboard": "57.0.1", @@ -114,17 +114,17 @@ "expo-file-system": "57.0.4", "expo-haptics": "57.0.1", "expo-image": "57.0.3", - "expo-image-picker": "57.0.10", + "expo-image-picker": "57.0.11", "expo-localization": "57.0.1", - "expo-location": "57.0.10", + "expo-location": "57.0.11", "expo-mail-composer": "57.0.1", "expo-media-library": "57.0.4", "expo-screen-capture": "57.0.2", "expo-sms": "57.0.1", - "expo-task-manager": "57.0.10", + "expo-task-manager": "57.0.11", "expo-video": "57.0.2", "google-libphonenumber": "3.2.46", - "immer": "11.1.16", + "immer": "11.1.17", "lodash": "4.18.1", "lottie-react-native": "7.4.0", "lottie-web": "5.13.0", @@ -204,6 +204,7 @@ }, "resolutions": { "**/@types/react": "19.2.18", + "**/deepmerge-ts": "8.0.1", "**/serialize-javascript": "7.1.0", "**/xcode/uuid": "14.0.1" }, diff --git a/shared/patches/expo+57.0.13.patch b/shared/patches/expo+57.0.14.patch similarity index 100% rename from shared/patches/expo+57.0.13.patch rename to shared/patches/expo+57.0.14.patch diff --git a/shared/tests/e2e/electron/flows/chat-send-message.test.ts b/shared/tests/e2e/electron/flows/chat-send-message.test.ts index 8838361f657b..1cadc95cabb0 100644 --- a/shared/tests/e2e/electron/flows/chat-send-message.test.ts +++ b/shared/tests/e2e/electron/flows/chat-send-message.test.ts @@ -2,6 +2,10 @@ import {test, expect} from '@/tests/e2e/electron/helpers/fixtures' import {navigateToChat} from '@/tests/e2e/electron/helpers/navigate' import {CHAT_INBOX_ROW, CHAT_MESSAGE_LIST, CHAT_INPUT} from '@/tests/e2e/shared/test-ids' +// The same tolerance the thread itself treats as at-end (list-area's endTolerancePx), so a pass here +// means what the app means by it. +const endTolerancePx = 2 + test('send a message to KB_SMOKE_USER', async ({page}, testInfo) => { testInfo.annotations.push({type: 'account', description: process.env['KB_SMOKE_USER']!}) @@ -33,4 +37,86 @@ test('send a message to KB_SMOKE_USER', async ({page}, testInfo) => { page.locator(`[data-testid="${CHAT_MESSAGE_LIST}"]`).getByText(testMessage) ).toBeVisible({timeout: 5_000}) }) + + // Reads the scroller rather than the message's visibility: a thread parked one row short of the + // end still shows the message it just sent, so only the distance catches it. Three sends because + // the regression this covers alternated — a keep-at-end request that moved nothing left the next + // one to be dropped, so every second send landed correctly. + await test.step('list stays at the end across sends', async () => { + // The shared tsconfig stubs `document` for react-native, so the browser side of evaluate() + // describes only the nodes it touches. + type ScrollNode = { + clientHeight: number + querySelectorAll: (selector: string) => ArrayLike + scrollHeight: number + scrollTop: number + } + const distanceFromEnd = async () => + page.evaluate(testId => { + const doc = document as unknown as {querySelector: (s: string) => ScrollNode | null} + const wrapper = doc.querySelector(`[data-testid="${testId}"]`) + if (!wrapper) return undefined + const scroller = [wrapper, ...Array.from(wrapper.querySelectorAll('*'))].find( + el => el.scrollHeight - el.clientHeight > 4 + ) + if (!scroller) return undefined + return scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop + }, CHAT_MESSAGE_LIST) + + // A thread shorter than its viewport has no end to fall short of, so it would pass vacuously. + const initial = await distanceFromEnd() + expect(initial, 'conversation has no scrollable history to test against').not.toBeUndefined() + + const input = page.getByTestId(CHAT_INPUT) + for (let i = 0; i < 3; i++) { + await input.click() + await input.fill(`e2e-stick-${Date.now()}-${i}`) + await input.press('Enter') + // Waits for the list to come to rest rather than for the first reading that looks right: the + // failures this covers are late ones, where a row re-measures after the list already reported + // itself at the end, so polling until the first pass would accept exactly the bug. + await expect + .poll( + async () => { + const first = await distanceFromEnd() + await page.waitForTimeout(250) + const second = await distanceFromEnd() + return first === second ? second : undefined + }, + {message: `send ${i} did not settle at the end`, timeout: 8_000} + ) + .toBeLessThanOrEqual(endTolerancePx) + } + + // A multi-line message is its own case, and the sharper one: composing it shrinks the thread's + // viewport a line at a time and sending it grows that back, so the collapse and the new row land + // together unless the send waits for the collapse. A single-line send never exercises that. + await test.step('a multi-line send settles at the end too', async () => { + const stamp = `e2e-stick-multi-${Date.now()}` + await input.click() + // Twelve lines, not two or six: the shortfall scales with how much the composer collapses, and + // the short ones only fail intermittently, which would make this a flaky canary rather than a + // failing one. + for (let line = 0; line < 12; line++) { + await page.keyboard.type(`${stamp} line ${line}`) + if (line < 11) { + await page.keyboard.down('Shift') + await page.keyboard.press('Enter') + await page.keyboard.up('Shift') + } + } + await page.keyboard.press('Enter') + await expect + .poll( + async () => { + const first = await distanceFromEnd() + await page.waitForTimeout(250) + const second = await distanceFromEnd() + return first === second ? second : undefined + }, + {message: 'a multi-line send did not settle at the end', timeout: 8_000} + ) + .toBeLessThanOrEqual(endTolerancePx) + }) + }) }) diff --git a/shared/yarn.lock b/shared/yarn.lock index 77fff162f9e5..0ea622f5131c 100644 --- a/shared/yarn.lock +++ b/shared/yarn.lock @@ -1568,20 +1568,20 @@ "@eslint/core" "^1.2.1" levn "^0.4.1" -"@expo/cli@^57.0.15": - version "57.0.15" - resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-57.0.15.tgz#8387c9018feeef82bdd22b8e57e4e0326103dbe1" - integrity sha512-YIE5xgZlkwx6OyjjVq/zAfqtYvczsWLEkOukDJwaZDaMQl8x5/CjoqC/Oe670DFQc5xjqguH6cmZUWHHL7yH3g== +"@expo/cli@^57.0.16": + version "57.0.16" + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-57.0.16.tgz#751579acf585ec078a31c65a26600822e91d05a2" + integrity sha512-+HyMY2nAS6QBJb0nSeMz92p11bZ1AtWSRnhWnklznN07IRoQirisnKq2vr18Ayjb/fnb5F/um+n6FRhF0fZm1Q== dependencies: "@expo/code-signing-certificates" "^0.0.6" - "@expo/config" "~57.0.7" + "@expo/config" "~57.0.8" "@expo/config-plugins" "~57.0.8" "@expo/devcert" "^1.2.1" "@expo/env" "~2.4.2" "@expo/image-utils" "^0.11.4" - "@expo/inline-modules" "^0.1.5" + "@expo/inline-modules" "^0.1.6" "@expo/json-file" "^11.0.1" - "@expo/log-box" "^57.0.2" + "@expo/log-box" "^57.0.3" "@expo/metro" "~56.0.0" "@expo/metro-config" "~57.0.8" "@expo/metro-file-map" "^57.0.1" @@ -1699,6 +1699,22 @@ semver "^7.6.0" slugify "^1.3.4" +"@expo/config@~57.0.8": + version "57.0.8" + resolved "https://registry.yarnpkg.com/@expo/config/-/config-57.0.8.tgz#3a75f70d436e3e9e48140e8008fdac1d4fe44d3b" + integrity sha512-7VpAu2ZMXNZI0+Kn+nD3vQBIyST89LATNkZpnp/cXh8/aj7zUXO8d/T+hOxsvhOFwR8eQO+jKXEw8tQbidiMxA== + dependencies: + "@expo/config-plugins" "~57.0.8" + "@expo/config-types" "^57.0.2" + "@expo/json-file" "^11.0.1" + "@expo/require-utils" "^57.0.4" + deepmerge "^4.3.1" + getenv "^2.0.0" + glob "^13.0.0" + resolve-workspace-root "^2.0.0" + semver "^7.6.0" + slugify "^1.3.4" + "@expo/devcert@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@expo/devcert/-/devcert-1.2.1.tgz#1a687985bea1670866e54d5ba7c0ced963c354f4" @@ -1733,10 +1749,10 @@ resolved "https://registry.yarnpkg.com/@expo/expo-modules-macros-plugin/-/expo-modules-macros-plugin-0.6.1.tgz#5581bbe09e0aedb39ea787a9960eb0a9ee07d2ff" integrity sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA== -"@expo/fingerprint@^0.20.7": - version "0.20.7" - resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.20.7.tgz#9fbf057dd113398445a4475ca839c713e7b5cf67" - integrity sha512-tYyZD4XZSn1C30pr9IvjN/BjAqpf6r9e1NL09lPvheO1DMLByOVdmMHFLSwW8Pu6veVgzue7GDWmq9P8mc8GMg== +"@expo/fingerprint@^0.20.8": + version "0.20.8" + resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.20.8.tgz#a02741143749f417ad8fd663139b075a8d8d6a2d" + integrity sha512-0UOg9dVepR0LO4iUacoFuWZ9GNSUdUkdx9GX+Rj0mRpOTAtavyiem8IfewCAHij3cppqjBic134JD2lUxsZcJw== dependencies: "@expo/env" "^2.4.2" "@expo/spawn-async" "^1.8.0" @@ -1763,12 +1779,12 @@ parse-png "^2.1.0" semver "^7.6.0" -"@expo/inline-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@expo/inline-modules/-/inline-modules-0.1.5.tgz#a9dfbd7c92ef9fd1986845aabe7c1a196e68555e" - integrity sha512-LC+kWeIwnvsGIvDaFBd8uzleWzWZiZTCG7CmtfxBLjW/Gify596X67ZqTi76iHzm5KToAfwwX0nppAKPDA9vQw== +"@expo/inline-modules@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@expo/inline-modules/-/inline-modules-0.1.6.tgz#9f7fb2adead2dfb8b4456e01a315a676b6e08332" + integrity sha512-5f6EiOIKsFj9zlrCBet4ZIQRPEa9dBUdQgTzpjYwdZ8Z/M8W5lqMVL9dEs4HYKvEmDnqv0dQuDkFLyRSwu/DAQ== dependencies: - "@expo/config-plugins" "~57.0.7" + "@expo/config-plugins" "~57.0.8" "@expo/json-file@^11.0.1", "@expo/json-file@~11.0.1": version "11.0.1" @@ -1786,10 +1802,10 @@ "@expo/config" "~57.0.7" chalk "^4.1.2" -"@expo/log-box@^57.0.2": - version "57.0.2" - resolved "https://registry.yarnpkg.com/@expo/log-box/-/log-box-57.0.2.tgz#2185dc3484ee81c63c3e7be5dc280d6300a15872" - integrity sha512-ZsFyfIR7YCbQAdVLzuTUmMHofZC7ZS9ywYCJNPlLc78x59cI8GwXFEIVbRjjC0uJERpNtXx/tsNNnkhexXlzMw== +"@expo/log-box@^57.0.3": + version "57.0.3" + resolved "https://registry.yarnpkg.com/@expo/log-box/-/log-box-57.0.3.tgz#89c81b30083b98c11d5d2cffba535a0fcab8d144" + integrity sha512-qv/cMliNax6es07Un/4IGJIcs4PUuhTKKTrJZX/0X2YRcOT5cqm/QicibZwTIbiZWi1i8P4YBRQTfFT2B17giw== dependencies: "@expo/dom-webview" "^57.0.1" anser "^1.4.9" @@ -2662,10 +2678,10 @@ dependencies: "@khanacademy/perseus-utils" "2.1.5" -"@legendapp/list@3.3.5": - version "3.3.5" - resolved "https://registry.yarnpkg.com/@legendapp/list/-/list-3.3.5.tgz#a2a7b8e0c3897fc5bd159d7b87e0b62ef45a4f74" - integrity sha512-XTsLYtpg41SVb5uLBYA+YcDSA3w0tgoPq/W8ZggQ2tx+3lrC/rf+ehTP9KYHea9oFaZIuePAgzACs5/auVMJlQ== +"@legendapp/list@3.3.6": + version "3.3.6" + resolved "https://registry.yarnpkg.com/@legendapp/list/-/list-3.3.6.tgz#38bc9dbd50d66c689afcda7766a34644e901f90b" + integrity sha512-XlDxqur/A65qURIHJy9VlNW9b7Bvb+m4cidcryOBqHRa8I0MYlLtptkWBysgcwrHLwo2+fVFyeDvy7nQytsIQg== dependencies: use-sync-external-store "^1.5.0" @@ -6227,10 +6243,10 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge-ts@^7.0.3: - version "7.1.5" - resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz#ff818564007f5c150808d2b7b732cac83aa415ab" - integrity sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw== +deepmerge-ts@8.0.1, deepmerge-ts@^7.0.3: + version "8.0.1" + resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-8.0.1.tgz#755f118cd798df500bfc0b0aad3861dd89cc904b" + integrity sha512-szCXE7YLCvLKR9bFPJcvsezOShdalctSvrgN/LM/QGUEPZQajwjmsMObZ6/DuANT5lxzM/wtO8Feubwdkz8myA== deepmerge@^4.3.0, deepmerge@^4.3.1: version "4.3.1" @@ -7113,13 +7129,13 @@ expect@30.4.1, expect@^30.0.0, expect@^30.4.1: jest-mock "30.4.1" jest-util "30.4.1" -expo-asset@57.0.11, expo-asset@~57.0.11: - version "57.0.11" - resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-57.0.11.tgz#0ee7a64850bf22eef9e4d1ddd211af9b46be3df5" - integrity sha512-tDaExt9fOKeSc51mmZNqfejyBt9argzo14XP9OmKEdAk2E/mWKJnUNj6rfUzHC5MhG120xuZC/d1ncqq0aC7Pg== +expo-asset@57.0.12, expo-asset@~57.0.12: + version "57.0.12" + resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-57.0.12.tgz#ab78e5d96b28ffccbe5ce4b8577662af68c2fb55" + integrity sha512-DbNxGsmqCaE2LWyabXEkDqV/8/mrZ2ikmVzi1V1Dp4MaQO9Kde6VcvQk7h6NVsklmo4xPT1J2Lnrs8Vd18sXOg== dependencies: "@expo/image-utils" "^0.11.4" - expo-constants "~57.0.11" + expo-constants "~57.0.12" expo-audio@57.0.3: version "57.0.3" @@ -7138,10 +7154,10 @@ expo-clipboard@57.0.1: resolved "https://registry.yarnpkg.com/expo-clipboard/-/expo-clipboard-57.0.1.tgz#d0550de548d313793d2240250081e9e453dbaf71" integrity sha512-HWICri4+1ao7S6QEfcorxVumXDiDnx1guGGewjZgGJWLGxFYs0RgH8ujBs+lkTzBkMmlwADaWSlaesR+nDJt5Q== -expo-constants@~57.0.11: - version "57.0.11" - resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-57.0.11.tgz#7a51e8d588e267641e2390148f46fdb0f266f940" - integrity sha512-gpSZ13Nfx0BOc9ALjG1SMhks5csb8Z6ncEVdhDgv0NRrmJcHPRvyUlWaKjRjzwW730XrQ6PIXFRcnNmAxOLj5A== +expo-constants@~57.0.12: + version "57.0.12" + resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-57.0.12.tgz#f9de7d1d89227cda2ff9b89a3e7aacdbaf83860c" + integrity sha512-js9qxZw4G9ulSc72GGpYGwtmk/IzFcf6/tTcEG6wB8HTlkunMS4W86R+4e6oHIrIkHA7W6s/79Se+E+DC3RAqA== dependencies: "@expo/env" "~2.4.2" @@ -7155,7 +7171,7 @@ expo-document-picker@57.0.1: resolved "https://registry.yarnpkg.com/expo-document-picker/-/expo-document-picker-57.0.1.tgz#c5a2bd3c431fcce1f3cf5b98661500f08d5b3285" integrity sha512-qBwM5oxDZ3I9kwFD3pUE1oK/WNv9artoEKO6UpqhQgNRr0XA1ALRVWYjkF4+ge9lUNDRehjTm/jenINkzqg84g== -expo-file-system@57.0.4, expo-file-system@~57.0.3: +expo-file-system@57.0.4, expo-file-system@~57.0.4: version "57.0.4" resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-57.0.4.tgz#cc58cff9236820b6ee8da3dcb38dc980844e51b1" integrity sha512-h8qDOxSW1SQfnZyvH0+HeOfXCr2X4v3x50+d19JuyQcjDR+JA3tng20TlQxIHazzLPPbwtSKA1Dj3+XIOpnoKg== @@ -7177,10 +7193,10 @@ expo-image-loader@~57.0.1: resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-57.0.1.tgz#b031dc3e62ac09004f3e436d7081487236791f53" integrity sha512-uhrZKLT/cTl2mXyR28kPpVkS5O+PK9N1QA/07IFM4f5T4g0lTW1JHT3NEWwEEsGFldPmVX4j7LwUVVZxE+woug== -expo-image-picker@57.0.10: - version "57.0.10" - resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-57.0.10.tgz#609c80d30d554d19547b2bfc9d783a6da33466c7" - integrity sha512-Zr00cd4RuUJlke8yN8/lKdl55UauoacF45j86zJa6oxJoe+wjlt9kkf0aGxQeoPW1JILT5Ikr3Os4+a9ROJYEw== +expo-image-picker@57.0.11: + version "57.0.11" + resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-57.0.11.tgz#4c9eab85bae9d73a6070350451d27899f38e2739" + integrity sha512-CyeQuGeA2Jei8zQb8S2fxmK6OIvdvUOYpxXegy8Dyc2WsI0GfWcgH8f+6uZmBaMlxakMPP4cfNvT1LeSMbB0PA== dependencies: expo-image-loader "~57.0.1" @@ -7203,10 +7219,10 @@ expo-localization@57.0.1: dependencies: rtl-detect "^1.0.2" -expo-location@57.0.10: - version "57.0.10" - resolved "https://registry.yarnpkg.com/expo-location/-/expo-location-57.0.10.tgz#11d324bdc7ac12d834a3b0f459ceb2c82464b31c" - integrity sha512-E/NdVxmd2+lODFgMc8CC6L0Zl+w20L1VEwPBGdJXblGew6Sv70FA3NlVP73Hi/QWmjECvbYTjkKR7OnrVnmaLA== +expo-location@57.0.11: + version "57.0.11" + resolved "https://registry.yarnpkg.com/expo-location/-/expo-location-57.0.11.tgz#8af48f59a4bc70eaa69b2cfb911b5c67fee86144" + integrity sha512-MwUroPjIWAnwac6aNHeGcqq9Il6/Mqrz36uVrbBi6MOjABcaeQK96WJo7+hbJP/CGZSa9enxxWpROgvH3m0DlQ== dependencies: "@expo/image-utils" "^0.11.4" @@ -7259,10 +7275,10 @@ expo-sms@57.0.1: resolved "https://registry.yarnpkg.com/expo-sms/-/expo-sms-57.0.1.tgz#64cf22ca1f5a3678bfa7b05a9b9314dec53c4696" integrity sha512-N5WGAA2Hw0nCGQIE8MAaL74ITwRhJdhtjIKryvExaGBxLXHZMgDD3kGdP4+feTo310kZBsGmXC5EtDqcrizE5Q== -expo-task-manager@57.0.10: - version "57.0.10" - resolved "https://registry.yarnpkg.com/expo-task-manager/-/expo-task-manager-57.0.10.tgz#27dc1958de11da62d7dc1cd6d41aa8797a47bdbb" - integrity sha512-K43+CdKXOvFteatTFzP9lOtpLq0Rzm7VWxIGTPe2Eue61Q/IBHdBuyK7ElKdsXAt0zUFeZDEuIVoLhJLMSwGYQ== +expo-task-manager@57.0.11: + version "57.0.11" + resolved "https://registry.yarnpkg.com/expo-task-manager/-/expo-task-manager-57.0.11.tgz#14c7a29847645a3b2274542c0fa9a272214420e9" + integrity sha512-d1W2RDoiGXqlfouDDQYWl3Mx6IuuZHnM5HS+Nd9CLZd3JFMJulZGTAGaBi0FyCt0LWKEEZCly8Z0JXTW6uIWDQ== dependencies: unimodules-app-loader "~57.0.1" @@ -7271,27 +7287,27 @@ expo-video@57.0.2: resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-57.0.2.tgz#e54c5d17e48ce7b6fb87fba6f36310b54a3a9c97" integrity sha512-s8KDRpGNWOE7Vx7+1o89hGeXZ3evx00SrQ2+5odYcmvUikNvF59/VuFkg55+YIS981wVQz/07csK6hLAK/w4bQ== -expo@57.0.13: - version "57.0.13" - resolved "https://registry.yarnpkg.com/expo/-/expo-57.0.13.tgz#6b0d1338b9af54fe25aab3c634d2ceb684245b22" - integrity sha512-oJnPSAcQYDIXTJowL9fm6SvAU5CU0P+u1WtWM9a/7uUmeCrkcVKoaGstwF+UY3GoM11S7FO4KKmRl1aU12CTKA== +expo@57.0.14: + version "57.0.14" + resolved "https://registry.yarnpkg.com/expo/-/expo-57.0.14.tgz#b59764a64e7e4f850ee0b4de5bc99d24bf88c047" + integrity sha512-vJ7V/VNr2IGxhhBdRs1qDExPbEe+DvfqVGp3818gyfr4k0l7mdBb1QuDFH0VCX6wT9/18EYxW7SQca4tXkzDYw== dependencies: "@babel/runtime" "^7.20.0" - "@expo/cli" "^57.0.15" - "@expo/config" "~57.0.7" + "@expo/cli" "^57.0.16" + "@expo/config" "~57.0.8" "@expo/config-plugins" "~57.0.8" "@expo/devtools" "~57.0.1" "@expo/dom-webview" "~57.0.1" - "@expo/fingerprint" "^0.20.7" + "@expo/fingerprint" "^0.20.8" "@expo/local-build-cache-provider" "^57.0.6" - "@expo/log-box" "^57.0.2" + "@expo/log-box" "^57.0.3" "@expo/metro" "~56.0.0" "@expo/metro-config" "~57.0.8" "@ungap/structured-clone" "^1.3.0" babel-preset-expo "~57.0.7" - expo-asset "~57.0.11" - expo-constants "~57.0.11" - expo-file-system "~57.0.3" + expo-asset "~57.0.12" + expo-constants "~57.0.12" + expo-file-system "~57.0.4" expo-font "~57.0.1" expo-keep-awake "~57.0.1" expo-modules-autolinking "~57.0.10" @@ -8246,10 +8262,10 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== -immer@11.1.16: - version "11.1.16" - resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.16.tgz#66702b60de57fda6a0b57eef52c84258cc4aac9b" - integrity sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q== +immer@11.1.17: + version "11.1.17" + resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.17.tgz#ba8a16e3b801b6dcb0fc753e043348a8ec42b696" + integrity sha512-8Vu44Y0MuMBlTQz/jQ8HEMYNq/bBqk87MnBwYR5mC8AthfhEXidZ5aT/oA/CUqboa8THKltnD9L3xyqhU/Sy1Q== import-fresh@^3.3.0: version "3.3.1"