From 5d95a91437efbefd8bc13595ba6ececa24e2d6c3 Mon Sep 17 00:00:00 2001 From: Oleksii Bulenok Date: Thu, 30 Jul 2026 18:05:34 +0200 Subject: [PATCH] lint and typecheck fixes --- app.config.js | 4 -- oxlint-suppressions.json | 2 +- patches/expo@57.0.8.patch | 8 ++++ patches/expo@57.0.8.patch.md | 26 ++++++++++ pnpm-workspace.yaml | 1 + src/components/StarterPack/QrCode.tsx | 9 ++-- src/components/StarterPack/QrCodeDialog.tsx | 4 +- .../InviteFriendsDialogInner.tsx | 4 +- .../inviteFriends/components/ThemedQrCard.tsx | 9 ++-- src/platform/misc.web-check.d.ts | 48 +++++++++++++++---- .../StepProfile/PlaceholderCanvas.tsx | 9 ++-- src/screens/Signup/StepInfo/index.tsx | 3 +- src/view/com/composer/Composer.tsx | 2 +- src/view/com/composer/drafts/state/storage.ts | 2 +- tsconfig.check.web.json | 15 ++++++ tsconfig.json | 6 +++ 16 files changed, 113 insertions(+), 39 deletions(-) create mode 100644 patches/expo@57.0.8.patch create mode 100644 patches/expo@57.0.8.patch.md diff --git a/app.config.js b/app.config.js index cdb308eb88..22787090bb 100644 --- a/app.config.js +++ b/app.config.js @@ -184,10 +184,6 @@ module.exports = function (_config) { androidStatusBar: { barStyle: 'light-content', }, - // Dark nav bar in light mode is better than light nav bar in dark mode - androidNavigationBar: { - barStyle: 'light-content', - }, android: { icon: './assets/app-icons/android_icon_default_next.png', adaptiveIcon: { diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 1cb148b151..7b387e8e8b 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -1502,7 +1502,7 @@ }, "src/view/com/composer/drafts/state/storage.ts": { "typescript/require-await": { - "count": 3 + "count": 2 } }, "src/view/com/composer/photos/EditImageDialog.web.tsx": { diff --git a/patches/expo@57.0.8.patch b/patches/expo@57.0.8.patch new file mode 100644 index 0000000000..1fe8af5078 --- /dev/null +++ b/patches/expo@57.0.8.patch @@ -0,0 +1,8 @@ +diff --git a/build/winter/runtime.native.d.ts b/build/winter/runtime.native.d.ts +index 85c4227e4e37003dd3769a590e73a69874de5fac..27bd245675d09b906c2474d4da9efd073dd4f72c 100644 +--- a/build/winter/runtime.native.d.ts ++++ b/build/winter/runtime.native.d.ts +@@ -1,3 +1,2 @@ + import 'react-native/Libraries/Core/InitializeCore'; +-import '../../types'; + //# sourceMappingURL=runtime.native.d.ts.map diff --git a/patches/expo@57.0.8.patch.md b/patches/expo@57.0.8.patch.md new file mode 100644 index 0000000000..239fd3e958 --- /dev/null +++ b/patches/expo@57.0.8.patch.md @@ -0,0 +1,26 @@ +# expo + +## build/winter/runtime.native.d.ts + +Type-check-only change; no runtime impact (only a `.d.ts` is modified). + +Expo 57 added `import '../../types'` to `build/winter/runtime.native.d.ts` +(in Expo 54 the file was an empty `export {}`). That pulls +`expo/types/react-native-web.d.ts` into every native type-check pass via the +chain `expo/build/Expo.fx.d.ts -> ./winter -> runtime.native.d.ts -> +expo/types/index.d.ts`. + +`react-native-web.d.ts` augments react-native's `TextStyle` with web-only +props, including `cursor?: string`, which conflicts with react-native 0.86's +own `cursor?: CursorValue`. The merged declaration makes `TextStyle` no +longer assignable to `ViewStyle`, which in turn poisons `StyleSheet.create` +inference (values widen to `ViewStyle | TextStyle | ImageStyle`) and produced +~60 errors in `pnpm typecheck:ios` / `typecheck:android`. + +The patch drops the `import '../../types'` line so the web-only augmentation +stays out of the native passes, matching Expo 54 behavior. The web pass is +unaffected: it resolves `runtime.d.ts` (not `.native`), which never had this +import. + +Can be removed if Expo stops referencing `./react-native-web` from the types +loaded by the native winter runtime, or guards the augmentation to web. diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index efb74975c1..b771c23525 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -37,6 +37,7 @@ patchedDependencies: 'expo-modules-core@57.0.7': patches/expo-modules-core@57.0.7.patch 'expo-notifications@57.0.7': patches/expo-notifications@57.0.7.patch 'expo-updates@57.0.10': patches/expo-updates@57.0.10.patch + expo@57.0.8: patches/expo@57.0.8.patch 'react-native-compressor@1.13.0': patches/react-native-compressor@1.13.0.patch 'react-native-date-picker@5.0.13': patches/react-native-date-picker@5.0.13.patch 'react-native-drawer-layout@4.2.3': patches/react-native-drawer-layout@4.2.3.patch diff --git a/src/components/StarterPack/QrCode.tsx b/src/components/StarterPack/QrCode.tsx index 8a05a767b1..3321ac6057 100644 --- a/src/components/StarterPack/QrCode.tsx +++ b/src/components/StarterPack/QrCode.tsx @@ -2,7 +2,7 @@ import {lazy, useState} from 'react' import {View} from 'react-native' // @ts-expect-error missing types import QRCode from 'react-native-qrcode-styled' -import type ViewShot from 'react-native-view-shot' +import {type ViewShotRef} from 'react-native-view-shot' import {Trans} from '@lingui/react/macro' import {Logo} from '#/view/icons/Logo' @@ -14,10 +14,7 @@ import {IS_WEB} from '#/env' import {app} from '#/lexicons' import * as bsky from '#/types/bsky' -const LazyViewShot = lazy( - // @ts-expect-error dynamic import - () => import('react-native-view-shot/src/index'), -) +const LazyViewShot = lazy(() => import('react-native-view-shot')) export function QrCode({ starterPack, @@ -26,7 +23,7 @@ export function QrCode({ }: { starterPack: app.bsky.graph.defs.StarterPackView link: string - ref: React.Ref + ref: React.Ref }) { const {record} = starterPack diff --git a/src/components/StarterPack/QrCodeDialog.tsx b/src/components/StarterPack/QrCodeDialog.tsx index 483224f9bc..51e95fa292 100644 --- a/src/components/StarterPack/QrCodeDialog.tsx +++ b/src/components/StarterPack/QrCodeDialog.tsx @@ -1,6 +1,6 @@ import {Suspense, useRef, useState} from 'react' import {View} from 'react-native' -import type ViewShot from 'react-native-view-shot' +import {type ViewShotRef} from 'react-native-view-shot' import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library' import * as Sharing from 'expo-sharing' import {msg} from '@lingui/core/macro' @@ -38,7 +38,7 @@ export function QrCodeDialog({ const [isSaveProcessing, setIsSaveProcessing] = useState(false) const [isCopyProcessing, setIsCopyProcessing] = useState(false) - const ref = useRef(null) + const ref = useRef(null) const getCanvas = (base64: string): Promise => { return new Promise(resolve => { diff --git a/src/features/inviteFriends/InviteFriendsDialogInner.tsx b/src/features/inviteFriends/InviteFriendsDialogInner.tsx index d8d88b7482..00fd9ac072 100644 --- a/src/features/inviteFriends/InviteFriendsDialogInner.tsx +++ b/src/features/inviteFriends/InviteFriendsDialogInner.tsx @@ -1,6 +1,6 @@ import {Suspense, useRef} from 'react' import {Pressable, View} from 'react-native' -import type ViewShot from 'react-native-view-shot' +import {type ViewShotRef} from 'react-native-view-shot' import {setStringAsync} from 'expo-clipboard' import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library' import {useLingui} from '@lingui/react/macro' @@ -41,7 +41,7 @@ export function InviteFriendsDialogInner({ const {currentAccount} = useSession() const profileQuery = useProfileQuery({did: currentAccount?.did}) const [themeKey, setThemeKey] = useInviteThemeKey() - const captureRef = useRef(null) + const captureRef = useRef(null) const theme = getInviteTheme(themeKey) const variant = t.name === 'light' ? theme.light : theme.dark diff --git a/src/features/inviteFriends/components/ThemedQrCard.tsx b/src/features/inviteFriends/components/ThemedQrCard.tsx index 82a0ce687c..5fb6168008 100644 --- a/src/features/inviteFriends/components/ThemedQrCard.tsx +++ b/src/features/inviteFriends/components/ThemedQrCard.tsx @@ -2,7 +2,7 @@ import {lazy} from 'react' import {View} from 'react-native' // @ts-expect-error missing types import QRCode from 'react-native-qrcode-styled' -import type ViewShot from 'react-native-view-shot' +import {type ViewShotRef} from 'react-native-view-shot' import {Image} from 'expo-image' import {LinearGradient} from 'expo-linear-gradient' @@ -12,10 +12,7 @@ import {hexToRgb, rgbToHex} from '#/alf/util/colorGeneration' import {Text} from '#/components/Typography' import {type InviteThemeVariant} from '../themes' -const LazyViewShot = lazy( - // @ts-expect-error dynamic import - () => import('react-native-view-shot/src/index'), -) +const LazyViewShot = lazy(() => import('react-native-view-shot')) const CARD_WIDTH = 278 const CARD_GRADIENT_PADDING = 12 @@ -37,7 +34,7 @@ export function ThemedQrCard({ shareUrl: string handle: string avatarUri?: string - captureRef: React.Ref + captureRef: React.Ref }) { const t = useTheme() return ( diff --git a/src/platform/misc.web-check.d.ts b/src/platform/misc.web-check.d.ts index a592015d56..d3eaf3e971 100644 --- a/src/platform/misc.web-check.d.ts +++ b/src/platform/misc.web-check.d.ts @@ -18,27 +18,42 @@ declare module '*.css' /* * expo-file-system's declarations build File/Directory on top of * `./ExpoFileSystem`, which remaps to a web shim whose classes are empty. - * The fully-typed base classes live in ExpoFileSystem.types (no .web - * sibling), so mirror the FileSystem.d.ts wrapper classes on top of those. + * As of SDK 57 the fully-typed base classes live in + * internal/NativeFileSystem.types (no .web sibling), so mirror the File.d.ts + * and Directory.d.ts wrapper classes on top of those. */ declare module 'expo-file-system' { import { - Directory as ExpoFileSystemDirectory, - File as ExpoFileSystemFile, - } from 'expo-file-system/build/ExpoFileSystem.types' + NativeFileSystemDirectory as ExpoFileSystemDirectory, + NativeFileSystemFile as ExpoFileSystemFile, + } from 'expo-file-system/build/internal/NativeFileSystem.types' export { type DirectoryCreateOptions, type DirectoryInfo, type DownloadOptions, EncodingType, type FileCreateOptions, - FileHandle, + type FileHandle, type FileInfo, type FileWriteOptions, type InfoOptions, type PathInfo, - } from 'expo-file-system/build/ExpoFileSystem.types' - import {type PathInfo as ExpoPathInfo} from 'expo-file-system/build/ExpoFileSystem.types' + } from 'expo-file-system/build/FileSystem.types' + import {type PathInfo as ExpoPathInfo} from 'expo-file-system/build/FileSystem.types' + import { + type WatchEvent, + type WatchOptions, + type WatchSubscription, + } from 'expo-file-system/build/FileSystemWatcher.types' + import { + type DownloadTask, + type UploadTask, + } from 'expo-file-system/build/NetworkTasks' + import { + type DownloadTaskOptions, + type UploadOptions, + type UploadResult, + } from 'expo-file-system/build/NetworkTasks.types' import {PathUtilities} from 'expo-file-system/build/pathUtilities' export class Paths extends PathUtilities { @@ -59,8 +74,21 @@ declare module 'expo-file-system' { readableStream(): ReadableStream> writableStream(): WritableStream> arrayBuffer(): Promise + json(): Promise + formData(): ReturnType stream(): ReadableStream> slice(start?: number, end?: number, contentType?: string): Blob + upload(url: string, options?: UploadOptions): Promise + createUploadTask(url: string, options?: UploadOptions): UploadTask + static createDownloadTask( + url: string, + destination: File | Directory, + options?: DownloadTaskOptions, + ): DownloadTask + watch( + callback: (event: WatchEvent) => void, + options?: WatchOptions, + ): WatchSubscription } export class Directory extends ExpoFileSystemDirectory { @@ -70,6 +98,10 @@ declare module 'expo-file-system' { get name(): string createFile(name: string, mimeType: string | null): File createDirectory(name: string): Directory + watch( + callback: (event: WatchEvent) => void, + options?: WatchOptions, + ): WatchSubscription } } diff --git a/src/screens/Onboarding/StepProfile/PlaceholderCanvas.tsx b/src/screens/Onboarding/StepProfile/PlaceholderCanvas.tsx index 45de1f1355..e48ca54285 100644 --- a/src/screens/Onboarding/StepProfile/PlaceholderCanvas.tsx +++ b/src/screens/Onboarding/StepProfile/PlaceholderCanvas.tsx @@ -7,15 +7,12 @@ import { useRef, } from 'react' import {View} from 'react-native' -import type ViewShot from 'react-native-view-shot' +import {type ViewShotRef} from 'react-native-view-shot' import {useAvatar} from '#/screens/Onboarding/StepProfile/index' import {atoms as a} from '#/alf' -const LazyViewShot = lazy( - // @ts-expect-error dynamic import - () => import('react-native-view-shot/src/index'), -) +const LazyViewShot = lazy(() => import('react-native-view-shot')) const SIZE_MULTIPLIER = 5 @@ -28,7 +25,7 @@ export interface PlaceholderCanvasRef { export const PlaceholderCanvas = forwardRef( function PlaceholderCanvas({}, ref) { const {avatar} = useAvatar() - const viewshotRef = useRef(null) + const viewshotRef = useRef(null) const Icon = avatar.placeholder.component const styles = useMemo( diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx index 116b4cfc28..40600c3b7c 100644 --- a/src/screens/Signup/StepInfo/index.tsx +++ b/src/screens/Signup/StepInfo/index.tsx @@ -95,8 +95,7 @@ export function StepInfo({ tldtsRef.current = tldts }) // This will get used in the avatar creator a few steps later, so lets preload it now - // @ts-expect-error - valid path - void import('react-native-view-shot/src/index') + void import('react-native-view-shot') }, []) const onNextPress = () => { diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 08efc5c696..6b4bf9a63f 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -567,7 +567,7 @@ export const ComposePost = ({ FileSystem.Paths.cache, tempFileName, ) - sourceFile.copy(tempFile) + await sourceFile.copy(tempFile) logger.debug('restoreVideo: copied to temp file', { source: videoInfo.uri, temp: tempFile.uri, diff --git a/src/view/com/composer/drafts/state/storage.ts b/src/view/com/composer/drafts/state/storage.ts index 4a9b8b5847..b062b5a56d 100644 --- a/src/view/com/composer/drafts/state/storage.ts +++ b/src/view/com/composer/drafts/state/storage.ts @@ -50,7 +50,7 @@ export async function saveMediaToLocal( try { const sourceFile = new File(normalizedSource) - sourceFile.copy(destFile) + await sourceFile.copy(destFile) // Update cache after successful save mediaExistsCache.set(localRefPath, true) } catch (error) { diff --git a/tsconfig.check.web.json b/tsconfig.check.web.json index 156e51f648..4b2ae580f2 100644 --- a/tsconfig.check.web.json +++ b/tsconfig.check.web.json @@ -12,6 +12,21 @@ */ "expo-file-system/legacy": [ "./node_modules/expo-file-system/build/legacy/index.d.ts" + ], + /* + * Mirrors the root tsconfig mapping (paths does not merge across + * extends): expo-file-system 57's `exports` map blocks the deep type + * imports in src/platform/misc.web-check.d.ts. + */ + "expo-file-system/build/*": ["./node_modules/expo-file-system/build/*"], + /* + * The react-native exports condition resolves to the package's raw + * TypeScript source, whose findNodeHandle usage breaks under the web + * pass. Point it at the compiled declarations, where skipLibCheck + * applies. + */ + "react-native-view-shot": [ + "./node_modules/react-native-view-shot/lib/index.d.ts" ] } }, diff --git a/tsconfig.json b/tsconfig.json index 2f69ed70af..eab917283b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,12 @@ "paths": { "#/*": ["./src/*"], "crypto": ["./src/platform/crypto.ts"], + /* + * expo-file-system 57 ships an `exports` map without `./build/*`, which + * blocks the deep type imports in src/platform/misc.web-check.d.ts + * under `moduleResolution: "bundler"`. Type-check-only escape hatch. + */ + "expo-file-system/build/*": ["./node_modules/expo-file-system/build/*"], }, "plugins": [ {