diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index eb2c9b7bbb..8fffe1d17b 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: 🔧 Setup Docker buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: 🔑 Log into registry ${{ env.REGISTRY }} uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 diff --git a/.github/workflows/build-and-push-bskyweb-ghcr.yaml b/.github/workflows/build-and-push-bskyweb-ghcr.yaml index 66652d5c9d..d996cebc6a 100644 --- a/.github/workflows/build-and-push-bskyweb-ghcr.yaml +++ b/.github/workflows/build-and-push-bskyweb-ghcr.yaml @@ -41,7 +41,7 @@ jobs: - name: ⬇️ Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: 🔧 Setup Docker buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Build ${{ matrix.image }} uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: @@ -65,7 +65,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: 🔧 Setup Docker buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: 🔑 Log into registry ${{ env.REGISTRY }} uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 diff --git a/.github/workflows/build-and-push-embedr-aws.yaml b/.github/workflows/build-and-push-embedr-aws.yaml index 9947935261..59daa8b4a0 100644 --- a/.github/workflows/build-and-push-embedr-aws.yaml +++ b/.github/workflows/build-and-push-embedr-aws.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: 🔧 Setup Docker buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: 🔑 Log into registry ${{ env.REGISTRY }} uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 diff --git a/.github/workflows/build-and-push-link-aws.yaml b/.github/workflows/build-and-push-link-aws.yaml index 60a043faa6..11c335c307 100644 --- a/.github/workflows/build-and-push-link-aws.yaml +++ b/.github/workflows/build-and-push-link-aws.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: 🔧 Setup Docker buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: 🔑 Log into registry ${{ env.REGISTRY }} uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 diff --git a/.github/workflows/build-and-push-ogcard-aws.yaml b/.github/workflows/build-and-push-ogcard-aws.yaml index ec5eee6e83..79f70e2b0c 100644 --- a/.github/workflows/build-and-push-ogcard-aws.yaml +++ b/.github/workflows/build-and-push-ogcard-aws.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: 🔧 Setup Docker buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: 🔑 Log into registry ${{ env.REGISTRY }} uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index 7899b62269..d4e18cc510 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -225,31 +225,6 @@ jobs: SENTRY_DIST=${{ steps.env.outputs.bundle-identifier }} pnpm export - # Pin ONE bundle version for both publishes below. Each script used to call - # `date +%s` itself, so the same bytes reached denis and ota1 under versions - # seconds apart (observed: 1785102575 vs 1785102614). The version is part of - # the asset URL path, so each origin then served a manifest referencing a - # path only it had -- meaning a manifest fetched from one origin and assets - # fetched from the other 404. Both scripts fall back to `date +%s` when this - # is unset, so single-publisher callers are unaffected. - - name: 🔢 Pin bundle version - if: ${{ !steps.fingerprint.outputs.includes-changes && - !steps.version.outputs.version-changed }} - run: echo "BUNDLE_VERSION=$(date +%s)" >> "$GITHUB_ENV" - - # denis on EKS has been the sole origin for updates.bsky.app since - # 2026-07-26, so it publishes FIRST: it is the path that actually serves - # clients. The legacy ota1 upload runs after it, and exists only so that - # rolling the Bunny origin back to ota1 would find current bundles there. - # - # The ordering is load-bearing, not cosmetic. While the legacy step ran - # first, its failure skipped these steps and nothing reached EITHER origin - # -- the dual-write took down the working path with it. Both steps are - # still required to pass, so a stale ota1 remains a loud failure, but the - # publish that serves users has already landed before the legacy one can - # fail. - # - # Both halves are removed together when ota1 is decommissioned (Phase 5). - name: ☁️ Configure AWS credentials (denis) if: ${{ !steps.fingerprint.outputs.includes-changes && !steps.version.outputs.version-changed }} @@ -279,19 +254,6 @@ jobs: BSKY_IOS_BUILD_NUMBER: ${{ inputs.iosBuildNumber }} BSKY_ANDROID_VERSION_CODE: ${{ inputs.androidVersionCode }} - - name: 📦 Package Bundle and 🚀 Deploy (legacy ota1) - if: ${{ !steps.fingerprint.outputs.includes-changes && - !steps.version.outputs.version-changed }} - run: pnpm use-build-number bash scripts/bundleUpdate.sh - env: - DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }} - RUNTIME_VERSION: ${{ inputs.runtimeVersion }} - CHANNEL_NAME: ${{ inputs.channel || 'testflight' }} - # When set (required for production), these take precedence over the - # global EAS counters inside the use-build-number wrapper - BSKY_IOS_BUILD_NUMBER: ${{ inputs.iosBuildNumber }} - BSKY_ANDROID_VERSION_CODE: ${{ inputs.androidVersionCode }} - buildIfNecessaryIOS: name: Build and Submit iOS needs: [bundleDeploy] diff --git a/.github/workflows/claude-mention.yml b/.github/workflows/claude-mention.yml index e76e8cea45..9c06aa5886 100644 --- a/.github/workflows/claude-mention.yml +++ b/.github/workflows/claude-mention.yml @@ -60,7 +60,7 @@ jobs: fetch-depth: 1 - name: 🤖 Claude - uses: anthropics/claude-code-action@5ef2e550a465a721f4f45e4a7d3c340c873e1dcc # v1.0.190 + uses: anthropics/claude-code-action@459ad358ae43fea66bfefd0a1f8d840b4b9791fb # v1.0.194 env: ANTHROPIC_BASE_URL: https://agentgateway.k1.prod.bsky.dev with: diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index ba82c487e4..309ae4807d 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -45,7 +45,7 @@ jobs: fetch-depth: 1 - name: 🤖 Claude review - uses: anthropics/claude-code-action@5ef2e550a465a721f4f45e4a7d3c340c873e1dcc # v1.0.190 + uses: anthropics/claude-code-action@459ad358ae43fea66bfefd0a1f8d840b4b9791fb # v1.0.194 env: ANTHROPIC_BASE_URL: https://agentgateway.k1.prod.bsky.dev with: diff --git a/CLAUDE.md b/CLAUDE.md index fbe87d5287..d90cbf7cb3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,8 +8,8 @@ Bluesky Social is a cross-platform social media application built with React Nat **Tech Stack:** -- React 19.1 -- React Native 0.81 with Expo 54 +- React 19.2 +- React Native 0.86 with Expo 57 - TypeScript 7 - React Navigation 7 for routing - TanStack Query (React Query) for data fetching @@ -561,7 +561,7 @@ Only use `useMemo`/`useCallback` when you have a specific reason, such as: 1. **Accessibility**: Always provide `label` prop for interactive elements, use `accessibilityHint` where helpful -2. **Translations**: Wrap ALL user-facing strings with ` `l` `` or `` +2. **Translations**: Wrap ALL user-facing strings with the `` l`…` `` macro or the `` component 3. **Styling**: Combine static atoms with theme atoms, use platform utilities for platform-specific styles diff --git a/jest/jestSetup.js b/jest/jestSetup.js index 2a1d3f3293..44a2c985f5 100644 --- a/jest/jestSetup.js +++ b/jest/jestSetup.js @@ -56,6 +56,16 @@ jest.mock('expo-media-library', () => ({ __esModule: true, // this property makes it work default: jest.fn(), usePermissions: jest.fn(() => [true]), + requestPermissionsAsync: jest.fn().mockResolvedValue({granted: true}), + saveToLibraryAsync: jest.fn().mockResolvedValue(undefined), +})) + +jest.mock('expo-media-library/legacy', () => ({ + __esModule: true, + default: jest.fn(), + usePermissions: jest.fn(() => [true]), + requestPermissionsAsync: jest.fn().mockResolvedValue({granted: true}), + saveToLibraryAsync: jest.fn().mockResolvedValue(undefined), })) jest.mock('@bsky.app/expo-guess-language', () => ({ diff --git a/lexicons.json b/lexicons.json index 5fa2fdb0bb..ca0e64f570 100644 --- a/lexicons.json +++ b/lexicons.json @@ -426,11 +426,11 @@ }, "app.bsky.embed.video": { "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.video", - "cid": "bafyreie3nug4ezpwodl6yrpgv5edkazzn22t7ea4yaeuun4rctyekkngai" + "cid": "bafyreiaqos23yv3t4ptrxily6s6qea5fcxfjzjlm42zq46xweby2mkgr4m" }, "app.bsky.feed.defs": { "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.defs", - "cid": "bafyreibg2zvz4n25vtd5yfqlodqra72s4h4lz7vwdexoutv3523wbw6jca" + "cid": "bafyreihxsdr5eig2gntbwn5duiskdae6lb7xyrtvump6nzzuz7sspyqv3e" }, "app.bsky.feed.describeFeedGenerator": { "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.describeFeedGenerator", diff --git a/lexicons/app/bsky/embed/video.json b/lexicons/app/bsky/embed/video.json index 67278da9c2..5033261982 100644 --- a/lexicons/app/bsky/embed/video.json +++ b/lexicons/app/bsky/embed/video.json @@ -18,8 +18,8 @@ "accept": [ "video/mp4" ], - "maxSize": 100000000, - "description": "The mp4 video file. May be up to 100mb, formerly limited to 50mb." + "maxSize": 300000000, + "description": "The mp4 video file. May be up to 300mb, formerly limited to 100mb." }, "captions": { "type": "array", diff --git a/lexicons/app/bsky/feed/defs.json b/lexicons/app/bsky/feed/defs.json index 436782d4ec..a59b32914d 100644 --- a/lexicons/app/bsky/feed/defs.json +++ b/lexicons/app/bsky/feed/defs.json @@ -202,10 +202,37 @@ }, "embeddingDisabled": { "type": "boolean" + }, + "knownLikers": { + "description": "This property is present only in selected cases, as an optimization.", + "type": "ref", + "ref": "#knownLikers" } }, "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests." }, + "knownLikers": { + "type": "object", + "description": "The post's likers whom you also follow", + "required": [ + "count", + "actors" + ], + "properties": { + "count": { + "type": "integer" + }, + "actors": { + "type": "array", + "minLength": 0, + "maxLength": 5, + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic" + } + } + } + }, "feedViewPost": { "type": "object", "required": [ diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index e04604a747..bef12f4397 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -60,11 +60,6 @@ "count": 1 } }, - "src/Navigation.tsx": { - "typescript/no-floating-promises": { - "count": 1 - } - }, "src/Splash.android.tsx": { "typescript/no-floating-promises": { "count": 1 @@ -80,14 +75,6 @@ "count": 1 } }, - "src/alf/util/flatten.ts": { - "typescript/no-explicit-any": { - "count": 1 - }, - "typescript/no-unsafe-member-access": { - "count": 9 - } - }, "src/alf/util/systemUI.ts": { "typescript/no-floating-promises": { "count": 2 @@ -167,9 +154,6 @@ "typescript/no-explicit-any": { "count": 4 }, - "typescript/no-misused-promises": { - "count": 1 - }, "typescript/no-unsafe-call": { "count": 2 }, @@ -224,9 +208,6 @@ "src/components/Lists.tsx": { "typescript/no-explicit-any": { "count": 1 - }, - "typescript/no-misused-promises": { - "count": 1 } }, "src/components/Menu/types.ts": { @@ -261,9 +242,6 @@ "src/components/PostControls/BookmarkButton.tsx": { "typescript/no-explicit-any": { "count": 2 - }, - "typescript/no-misused-promises": { - "count": 1 } }, "src/components/PostControls/DiscoverDebug.tsx": { @@ -335,9 +313,6 @@ "typescript/no-floating-promises": { "count": 5 }, - "typescript/no-misused-promises": { - "count": 2 - }, "typescript/require-await": { "count": 4 } @@ -346,9 +321,6 @@ "typescript/no-floating-promises": { "count": 1 }, - "typescript/no-misused-promises": { - "count": 2 - }, "typescript/require-await": { "count": 1 } @@ -363,16 +335,6 @@ "count": 1 } }, - "src/components/ageAssurance/AgeAssuranceErrors.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, - "src/components/ageAssurance/AgeAssuranceInitDialog.tsx": { - "typescript/no-misused-promises": { - "count": 2 - } - }, "src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx": { "typescript/require-await": { "count": 1 @@ -388,18 +350,10 @@ "count": 1 } }, - "src/components/contacts/screens/ViewMatches.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, "src/components/dialogs/DeviceLocationRequestDialog.tsx": { "typescript/no-explicit-any": { "count": 1 }, - "typescript/no-misused-promises": { - "count": 1 - }, "typescript/no-unsafe-member-access": { "count": 2 } @@ -409,26 +363,6 @@ "count": 1 } }, - "src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx": { - "typescript/no-misused-promises": { - "count": 3 - } - }, - "src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, - "src/components/dialogs/EmailDialog/screens/Update.tsx": { - "typescript/no-misused-promises": { - "count": 3 - } - }, - "src/components/dialogs/EmailDialog/screens/Verify.tsx": { - "typescript/no-misused-promises": { - "count": 3 - } - }, "src/components/dialogs/LanguageSelectDialog.tsx": { "typescript/no-explicit-any": { "count": 1 @@ -447,7 +381,7 @@ "count": 2 }, "typescript/no-misused-promises": { - "count": 3 + "count": 1 }, "typescript/no-unsafe-member-access": { "count": 2 @@ -490,9 +424,6 @@ "src/components/dialogs/lists/CreateOrEditListDialog.tsx": { "typescript/no-explicit-any": { "count": 2 - }, - "typescript/no-misused-promises": { - "count": 1 } }, "src/components/dialogs/nuxs/FindContactsAnnouncement.tsx": { @@ -580,16 +511,6 @@ "count": 2 } }, - "src/components/intents/VerifyEmailIntentDialog.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, - "src/components/verification/VerificationCreatePrompt.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, "src/components/verification/VerificationRemovePrompt.tsx": { "typescript/no-misused-promises": { "count": 1 @@ -696,13 +617,10 @@ "count": 1 }, "typescript/no-misused-promises": { - "count": 3 + "count": 2 } }, "src/lib/hooks/usePermissions.ts": { - "typescript/no-misused-promises": { - "count": 1 - }, "typescript/no-unsafe-enum-comparison": { "count": 5 } @@ -845,15 +763,9 @@ }, "typescript/no-floating-promises": { "count": 1 - }, - "typescript/no-misused-promises": { - "count": 1 } }, "src/screens/E2E/SharedPreferencesTesterScreen.tsx": { - "typescript/no-misused-promises": { - "count": 6 - }, "typescript/require-await": { "count": 6 } @@ -863,22 +775,9 @@ "count": 1 } }, - "src/screens/Feeds/NoSavedFeedsOfAnyType.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, - "src/screens/Home/NoFeedsPinned.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, "src/screens/List/ListHiddenScreen.tsx": { "typescript/no-floating-promises": { "count": 3 - }, - "typescript/no-misused-promises": { - "count": 1 } }, "src/screens/Log.tsx": { @@ -886,20 +785,6 @@ "count": 2 } }, - "src/screens/Moderation/index.tsx": { - "typescript/no-explicit-any": { - "count": 2 - }, - "typescript/no-floating-promises": { - "count": 1 - }, - "typescript/no-misused-promises": { - "count": 2 - }, - "typescript/no-unsafe-member-access": { - "count": 2 - } - }, "src/screens/ModerationInteractionSettings/index.tsx": { "typescript/no-explicit-any": { "count": 1 @@ -922,19 +807,6 @@ "src/screens/Onboarding/StepProfile/index.tsx": { "typescript/no-floating-promises": { "count": 2 - }, - "typescript/no-misused-promises": { - "count": 1 - } - }, - "src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, - "src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx": { - "typescript/no-misused-promises": { - "count": 1 } }, "src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx": { @@ -962,18 +834,12 @@ "src/screens/Profile/Header/EditProfileDialog.tsx": { "typescript/no-explicit-any": { "count": 3 - }, - "typescript/no-misused-promises": { - "count": 1 } }, "src/screens/Profile/Header/ProfileHeaderLabeler.tsx": { "typescript/no-explicit-any": { "count": 2 }, - "typescript/no-misused-promises": { - "count": 1 - }, "typescript/no-unsafe-member-access": { "count": 3 } @@ -1004,11 +870,6 @@ "count": 1 } }, - "src/screens/ProfileList/components/Header.tsx": { - "typescript/no-misused-promises": { - "count": 3 - } - }, "src/screens/ProfileList/components/MoreOptionsMenu.tsx": { "typescript/no-floating-promises": { "count": 1 @@ -1027,11 +888,6 @@ "count": 1 } }, - "src/screens/SavedFeeds.tsx": { - "typescript/no-misused-promises": { - "count": 2 - } - }, "src/screens/Search/modules/ExploreSuggestedAccounts.tsx": { "typescript/no-floating-promises": { "count": 1 @@ -1093,9 +949,6 @@ "src/screens/Settings/components/ChangePasswordDialog.tsx": { "typescript/no-explicit-any": { "count": 2 - }, - "typescript/no-misused-promises": { - "count": 2 } }, "src/screens/Settings/components/CopyButton.tsx": { @@ -1107,9 +960,6 @@ "typescript/no-explicit-any": { "count": 1 }, - "typescript/no-misused-promises": { - "count": 1 - }, "typescript/no-unsafe-member-access": { "count": 1 } @@ -1119,11 +969,6 @@ "count": 2 } }, - "src/screens/Settings/components/DisableEmail2FADialog.tsx": { - "typescript/no-misused-promises": { - "count": 4 - } - }, "src/screens/Settings/components/OTAInfo.tsx": { "typescript/no-floating-promises": { "count": 1 @@ -1147,7 +992,7 @@ "count": 1 }, "typescript/no-misused-promises": { - "count": 2 + "count": 1 }, "typescript/no-unsafe-call": { "count": 1 @@ -1168,9 +1013,6 @@ "typescript/no-floating-promises": { "count": 1 }, - "typescript/no-misused-promises": { - "count": 2 - }, "typescript/require-await": { "count": 1 } @@ -1474,9 +1316,6 @@ "src/view/com/composer/SelectMediaButton.tsx": { "typescript/no-floating-promises": { "count": 1 - }, - "typescript/no-misused-promises": { - "count": 1 } }, "src/view/com/composer/drafts/DraftsButton.tsx": { @@ -1500,11 +1339,6 @@ "count": 2 } }, - "src/view/com/composer/photos/EditImageDialog.web.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, "src/view/com/composer/photos/Gallery.tsx": { "typescript/no-floating-promises": { "count": 1 @@ -1513,9 +1347,6 @@ "src/view/com/composer/photos/OpenCameraBtn.tsx": { "typescript/no-explicit-any": { "count": 1 - }, - "typescript/no-misused-promises": { - "count": 1 } }, "src/view/com/composer/text-input/TextInput.web.tsx": { @@ -1560,7 +1391,7 @@ "count": 2 }, "typescript/no-misused-promises": { - "count": 3 + "count": 2 }, "typescript/no-unsafe-member-access": { "count": 6 @@ -1598,12 +1429,6 @@ "src/view/com/pager/TabBar.web.tsx": { "typescript/no-explicit-any": { "count": 1 - }, - "typescript/no-unsafe-call": { - "count": 2 - }, - "typescript/no-unsafe-member-access": { - "count": 4 } }, "src/view/com/post-thread/PostLikedBy.tsx": { @@ -1627,9 +1452,6 @@ "src/view/com/posts/FeedShutdownMsg.tsx": { "typescript/no-explicit-any": { "count": 2 - }, - "typescript/no-misused-promises": { - "count": 2 } }, "src/view/com/posts/PostFeedErrorMessage.tsx": { @@ -1693,9 +1515,6 @@ "src/view/com/util/Views.tsx": { "react/display-name": { "count": 1 - }, - "typescript/no-explicit-any": { - "count": 1 } }, "src/view/com/util/Views.web.tsx": { @@ -1703,11 +1522,6 @@ "count": 1 } }, - "src/view/com/util/forms/Button.tsx": { - "typescript/no-misused-promises": { - "count": 1 - } - }, "src/view/screens/Feeds.tsx": { "typescript/no-floating-promises": { "count": 3 @@ -1770,5 +1584,16 @@ "typescript/no-unsafe-member-access": { "count": 3 } + }, + "webpack.config.js": { + "import/no-nodejs-modules": { + "count": 1 + }, + "typescript/no-unsafe-call": { + "count": 12 + }, + "typescript/no-unsafe-member-access": { + "count": 33 + } } } \ No newline at end of file diff --git a/package.json b/package.json index 3fc47a26e5..38e149c632 100644 --- a/package.json +++ b/package.json @@ -93,17 +93,16 @@ "update-extensions": "bash scripts/updateExtensions.sh", "export": "expo export --dump-sourcemap && pnpm upload-native-sourcemaps", "upload-native-sourcemaps": "pnpm exec sentry-expo-upload-sourcemaps dist", - "make-deploy-bundle": "bash scripts/bundleUpdate.sh", "generate-webpack-stats-file": "EXPO_PUBLIC_GENERATE_STATS=1 pnpm build-web", "open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 pnpm build-web", "icons:optimize": "svgo -f ./assets/icons", "prettier": "prettier --check ." }, "dependencies": { - "@atproto/common-web": "0.5.9", - "@atproto/lex": "0.3.6", - "@atproto/lex-password-session": "0.2.0", - "@atproto/syntax": "0.7.4", + "@atproto/common-web": "0.5.10", + "@atproto/lex": "0.3.8", + "@atproto/lex-password-session": "0.2.2", + "@atproto/syntax": "0.7.5", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@bsky.app/alf": "^0.1.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7de34eb0a6..1f74136ded 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -243,17 +243,17 @@ importers: .: dependencies: '@atproto/common-web': - specifier: 0.5.9 - version: 0.5.9 + specifier: 0.5.10 + version: 0.5.10 '@atproto/lex': - specifier: 0.3.6 - version: 0.3.6 + specifier: 0.3.8 + version: 0.3.8 '@atproto/lex-password-session': - specifier: 0.2.0 - version: 0.2.0 + specifier: 0.2.2 + version: 0.2.2 '@atproto/syntax': - specifier: 0.7.4 - version: 0.7.4 + specifier: 0.7.5 + version: 0.7.5 '@bitdrift/react-native': specifier: ^0.6.8 version: 0.6.14(react-native@0.86.0(patch_hash=dd549527bb84c88acc7b0b1d521c9f4b666fda484c75cd0b282f8e9838524909)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1) @@ -298,7 +298,7 @@ importers: version: 0.2.0(expo@57.0.8)(react-native@0.86.0(patch_hash=dd549527bb84c88acc7b0b1d521c9f4b666fda484c75cd0b282f8e9838524909)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) '@bsky/sdk': specifier: 1.0.1 - version: 1.0.1(@atproto/lex@0.3.6) + version: 1.0.1(@atproto/lex@0.3.8) '@emoji-mart/data': specifier: ^1.2.1 version: 1.2.1 @@ -900,12 +900,12 @@ packages: resolution: {integrity: sha512-vfvoDhu6ds6BT3Pqe+d2/LBU1WpDRtt69y6zltlfMCoucPm82m1j50/Wb6xTvv+oZ+2j0JyZVXsmXQ12SWYQJg==} engines: {node: '>=22'} - '@atproto/common-web@0.5.9': - resolution: {integrity: sha512-2c5C6YV8352JJz9Z5fNfcsstllaKGAb3cQW9aO7unMAGh/FzTGFq+xOwNMW2lTeagO6z530uxYm8AiSRM6O+DQ==} + '@atproto/common-web@0.5.10': + resolution: {integrity: sha512-w4JUdsJ3VXt8ewkavYh5m/u0UbxDtFdCKhNZPEpJ0U1vcWIjDaSInIexteETDgD7fBJAhidIEi30MGz1kk49ug==} engines: {node: '>=22'} - '@atproto/common@0.8.0': - resolution: {integrity: sha512-fBtd08LdouLqNaL8SaGLEIa0l3Ue27yR810jhUu+iBv/xdoXp+1G0pxvPe6JUStH7YGtf/v/v0w/cRGh1xUOaA==} + '@atproto/common@0.8.1': + resolution: {integrity: sha512-WvgY0CDgodmTS7B1KDVFt868h1b85iQtQZeoDVI+6oOZmg1jINWUl+bZ1rwf59Tk1PqcMz5OzH6OS2J7Mb9/Iw==} engines: {node: '>=22'} '@atproto/crypto@0.5.4': @@ -916,57 +916,57 @@ packages: resolution: {integrity: sha512-BlnwQ+obL+4ZA71KH/EzZ3TY+cpSxnLiUI85mjBJIQwvDF/oN2sQA18wIj9jpduviIt2b/cMtlJuzHjzBkfXvw==} engines: {node: '>=22'} - '@atproto/lex-builder@0.1.12': - resolution: {integrity: sha512-uo3mcdoOgqBfQnf+PhYYLNEBdUTgoNe2ARYb3cUaiec8O0oritOeZndUDgmths06ACrlqFkZNTV1YJX6CsH71g==} + '@atproto/lex-builder@0.1.13': + resolution: {integrity: sha512-YHSONY88fWdMcN6Xa/Noap98DNW2m5zVZGspQWaROsX2lFx1mLf7g/XKLBcPRVDpE3OWRZ98QkkSZN3SgbOXrg==} engines: {node: '>=22'} '@atproto/lex-cbor@0.1.6': resolution: {integrity: sha512-PxLQy7jzV8u9zTGURNF8ZczLPk+ZH+WNx/2z/z7RacJbu7sWNpx26U2pC7cIkWtt4jyDaruaLhssw3w3atLotA==} engines: {node: '>=22'} - '@atproto/lex-client@0.3.3': - resolution: {integrity: sha512-sWChe7Jukm9uxDfxT6ElICp6vMTqAFmwcHdSLdMDtsKBZSrwO7vWL9F6rpoMPyxS29Ob9nNAhMg30kKfZrb+uw==} + '@atproto/lex-client@0.3.5': + resolution: {integrity: sha512-IOYjgQ2H+pw8QhV0vMMG9PTRg+oh5In9APbGBvotNZlbF4SCZNDVd7dONFY4rU2Dg6BHSkiqbqoNUHNgTC+ITA==} engines: {node: '>=22'} '@atproto/lex-data@0.1.7': resolution: {integrity: sha512-kW/dPLqo/WgCLV+XESR4JKwV6c1rZWJGOfuPupZGTjEDAKoBbKXdaEzX9/1vKQYbZ9U3j0DS/n7OFFK7wBugyQ==} engines: {node: '>=22'} - '@atproto/lex-document@0.1.9': - resolution: {integrity: sha512-TmpwttQ9pKCXlvwUlJ/5HMZPtJMFBvgZ1bunEeQD5JfU6n/sgK/fob8tfjhTdL2QrpZMtM6opMEk3w5STm1RDQ==} + '@atproto/lex-document@0.1.10': + resolution: {integrity: sha512-7ivrxWttSxInDksR2rtM24xGdntY/3Ew5j08W5SiG45B+T2nyARL5EOk2xMXEm3MLjPrpNYAybA21ZEDYhGG0Q==} engines: {node: '>=22'} - '@atproto/lex-installer@0.1.15': - resolution: {integrity: sha512-4/xDyY7yxxs66vF0WZB+7fcuEy4I5IRDyDoQFzLQx7C6PlQ76/e/PxvYTff7x0/N6hknmOlANODnnxo3rtdnCA==} + '@atproto/lex-installer@0.1.17': + resolution: {integrity: sha512-XSXiFLoDT7YEe+GuUdwczKQmhuLrA2CHVVlL8s5U8FLtWC5Cr3ELmz1t1b/Y8dV+R31hWyvdsk/t992B6+WW3Q==} engines: {node: '>=22'} '@atproto/lex-json@0.1.6': resolution: {integrity: sha512-mvrAd0lbyuecIHjyld8QN6MN6CBf4j0GCxLzegsvLh0SvDf+GbYWklkcQqmITL44yFQOwmA/QNIQj0Uvh7+R/g==} engines: {node: '>=22'} - '@atproto/lex-password-session@0.2.0': - resolution: {integrity: sha512-u958Etax/bPVr9FWko+/B5nRcU1klQHvrdiVtYb4lkgrM6uvV7BvaFJZYEu5DzlEeqNAZ9a65PmbC1F5oyUBjQ==} + '@atproto/lex-password-session@0.2.2': + resolution: {integrity: sha512-lifxp9sMdOGP1AfPhjNUMson6yzqEbS0PS8Q7UCVQucXbBlM89r5ff9DMBJF2sTiH5Qt8yXXsthI543OT9FXgg==} engines: {node: '>=22'} - '@atproto/lex-resolver@0.2.8': - resolution: {integrity: sha512-j3dIxeEAStp/RzJS4MECRDPuOFX41mX9fnteGgrQhe9053eRuTnl2oTyJbV7rKgzy+hEI4jH6X6Y4wOStjUb/Q==} + '@atproto/lex-resolver@0.2.10': + resolution: {integrity: sha512-0Ml37emPqi6zr7hBAVR8fi7XDgG6xWsksBotoMU4Doh/PNFNt/H79Zopt/jnmDRcl8DGj9PNqZOZjzcAr2IuFw==} engines: {node: '>=22'} - '@atproto/lex-schema@0.2.5': - resolution: {integrity: sha512-/tP3dRqaZu0EIoq0BSVzhvFGL6u0uu2KFDV21i73DpFsg6TXOTddKJ9bvgK83XdAJYL7PWi703XsaR31v9u5Qg==} + '@atproto/lex-schema@0.2.6': + resolution: {integrity: sha512-8fe/gmhjMImBsy073jMANkbHHQLe/w4s9XLATpAz+Qd6WrR9RYTqc2DU8uZDL13F5bfdGTj8KECt/Xs4CoPXdQ==} engines: {node: '>=22'} - '@atproto/lex@0.3.6': - resolution: {integrity: sha512-8Hpc6MbfkPJKaO5CvvIerEylbihI5GfrY5DzapZD4rV2TWfm3RU+DzeRpsxQJ0W9WOZRYvp1JnBcq/gZ48Qb0g==} + '@atproto/lex@0.3.8': + resolution: {integrity: sha512-8X7f6wxVDm4IVNR80Yg3BV3IXb89sKo/KIZeYb4or/EHP7ljazVU4qU+RPXmlELnWiIcw+PiJE+hPSkGLnMPOA==} engines: {node: '>=22'} hasBin: true - '@atproto/repo@0.10.11': - resolution: {integrity: sha512-QqDzEv8d6NUuqZ0xvWG+n8DfWd4Vuka04nyuD9oglKsj5avB1zbEsWx3Oit1oYtZFUf9JSVWMDqMXhu3UbB+lA==} + '@atproto/repo@0.10.12': + resolution: {integrity: sha512-SnDSoFi1bRAfN0IcDjSPFcefknDCIIjKgJXgFsd5jvktCkopmzml8BpQEP5t2/mcZ7NvEn5onQ0kaWkXhgL+5g==} engines: {node: '>=22'} - '@atproto/syntax@0.7.4': - resolution: {integrity: sha512-EHsEHtasH/DGPljBYecVDjweGMQ5eTu6Ns0GZ5z0qdqpOI8ipBvldWnMIxWkA+5HfZ9Dsu4V1MX0WP7wgL8cuA==} + '@atproto/syntax@0.7.5': + resolution: {integrity: sha512-6vnLQK8OAzg0dO6z/xnvuXn5zMV0UMI54zbxk7G7BXhGlIlLMb1yo+JVYtAlv8Nxzr+AaFdNZ8AJt+L/QhJMFQ==} engines: {node: '>=22'} '@babel/code-frame@7.10.4': @@ -9758,16 +9758,16 @@ snapshots: '@atproto-labs/simple-store@0.5.1': {} - '@atproto/common-web@0.5.9': + '@atproto/common-web@0.5.10': dependencies: '@atproto/lex-data': 0.1.7 '@atproto/lex-json': 0.1.6 - '@atproto/syntax': 0.7.4 + '@atproto/syntax': 0.7.5 zod: 3.25.76 - '@atproto/common@0.8.0': + '@atproto/common@0.8.1': dependencies: - '@atproto/common-web': 0.5.9 + '@atproto/common-web': 0.5.10 '@atproto/lex-cbor': 0.1.6 '@atproto/lex-data': 0.1.7 multiformats: 13.4.2 @@ -9783,10 +9783,10 @@ snapshots: dependencies: zod: 3.25.76 - '@atproto/lex-builder@0.1.12': + '@atproto/lex-builder@0.1.13': dependencies: - '@atproto/lex-document': 0.1.9 - '@atproto/lex-schema': 0.2.5 + '@atproto/lex-document': 0.1.10 + '@atproto/lex-schema': 0.2.6 prettier: 3.9.6 ts-morph: 27.0.2 tslib: 2.8.1 @@ -9797,11 +9797,11 @@ snapshots: cborg: 4.5.8 tslib: 2.8.1 - '@atproto/lex-client@0.3.3': + '@atproto/lex-client@0.3.5': dependencies: '@atproto/lex-data': 0.1.7 '@atproto/lex-json': 0.1.6 - '@atproto/lex-schema': 0.2.5 + '@atproto/lex-schema': 0.2.6 tslib: 2.8.1 '@atproto/lex-data@0.1.7': @@ -9810,21 +9810,21 @@ snapshots: tslib: 2.8.1 unicode-segmenter: 0.14.5 - '@atproto/lex-document@0.1.9': + '@atproto/lex-document@0.1.10': dependencies: - '@atproto/lex-schema': 0.2.5 + '@atproto/lex-schema': 0.2.6 core-js: 3.50.0 tslib: 2.8.1 - '@atproto/lex-installer@0.1.15': + '@atproto/lex-installer@0.1.17': dependencies: - '@atproto/lex-builder': 0.1.12 + '@atproto/lex-builder': 0.1.13 '@atproto/lex-cbor': 0.1.6 '@atproto/lex-data': 0.1.7 - '@atproto/lex-document': 0.1.9 - '@atproto/lex-resolver': 0.2.8 - '@atproto/lex-schema': 0.2.5 - '@atproto/syntax': 0.7.4 + '@atproto/lex-document': 0.1.10 + '@atproto/lex-resolver': 0.2.10 + '@atproto/lex-schema': 0.2.6 + '@atproto/syntax': 0.7.5 tslib: 2.8.1 '@atproto/lex-json@0.1.6': @@ -9832,54 +9832,54 @@ snapshots: '@atproto/lex-data': 0.1.7 tslib: 2.8.1 - '@atproto/lex-password-session@0.2.0': + '@atproto/lex-password-session@0.2.2': dependencies: - '@atproto/lex-client': 0.3.3 - '@atproto/lex-schema': 0.2.5 + '@atproto/lex-client': 0.3.5 + '@atproto/lex-schema': 0.2.6 tslib: 2.8.1 - '@atproto/lex-resolver@0.2.8': + '@atproto/lex-resolver@0.2.10': dependencies: '@atproto-labs/did-resolver': 0.3.7 '@atproto/crypto': 0.5.4 - '@atproto/lex-client': 0.3.3 + '@atproto/lex-client': 0.3.5 '@atproto/lex-data': 0.1.7 - '@atproto/lex-document': 0.1.9 - '@atproto/lex-schema': 0.2.5 - '@atproto/repo': 0.10.11 - '@atproto/syntax': 0.7.4 + '@atproto/lex-document': 0.1.10 + '@atproto/lex-schema': 0.2.6 + '@atproto/repo': 0.10.12 + '@atproto/syntax': 0.7.5 tslib: 2.8.1 - '@atproto/lex-schema@0.2.5': + '@atproto/lex-schema@0.2.6': dependencies: '@atproto/lex-data': 0.1.7 - '@atproto/syntax': 0.7.4 + '@atproto/syntax': 0.7.5 '@standard-schema/spec': 1.1.0 tslib: 2.8.1 - '@atproto/lex@0.3.6': + '@atproto/lex@0.3.8': dependencies: - '@atproto/lex-builder': 0.1.12 - '@atproto/lex-client': 0.3.3 + '@atproto/lex-builder': 0.1.13 + '@atproto/lex-client': 0.3.5 '@atproto/lex-data': 0.1.7 - '@atproto/lex-installer': 0.1.15 + '@atproto/lex-installer': 0.1.17 '@atproto/lex-json': 0.1.6 - '@atproto/lex-schema': 0.2.5 + '@atproto/lex-schema': 0.2.6 tslib: 2.8.1 yargs: 18.1.0 - '@atproto/repo@0.10.11': + '@atproto/repo@0.10.12': dependencies: - '@atproto/common': 0.8.0 - '@atproto/common-web': 0.5.9 + '@atproto/common': 0.8.1 + '@atproto/common-web': 0.5.10 '@atproto/crypto': 0.5.4 '@atproto/lex-cbor': 0.1.6 '@atproto/lex-data': 0.1.7 - '@atproto/syntax': 0.7.4 + '@atproto/syntax': 0.7.5 varint: 6.0.0 zod: 3.25.76 - '@atproto/syntax@0.7.4': + '@atproto/syntax@0.7.5': dependencies: iso-datestring-validator: 2.2.2 tslib: 2.8.1 @@ -10842,11 +10842,11 @@ snapshots: react: 19.2.3 react-native: 0.86.0(patch_hash=dd549527bb84c88acc7b0b1d521c9f4b666fda484c75cd0b282f8e9838524909)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1) - '@bsky/sdk@1.0.1(@atproto/lex@0.3.6)': + '@bsky/sdk@1.0.1(@atproto/lex@0.3.8)': dependencies: '@atproto-labs/handle-resolver': 0.4.8 - '@atproto/lex': 0.3.6 - '@atproto/syntax': 0.7.4 + '@atproto/lex': 0.3.8 + '@atproto/syntax': 0.7.5 tlds: 1.261.0 '@crowdin/cli@4.14.2': diff --git a/scripts/bundleUpdate.sh b/scripts/bundleUpdate.sh deleted file mode 100644 index 35bed14ed0..0000000000 --- a/scripts/bundleUpdate.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -set -o errexit -set -o pipefail -set -o nounset - -rm -rf bundleTempDir -rm -rf bundle.tar.gz - -echo "Creating tarball..." -node scripts/bundleUpdate.js - -if [ -z "$RUNTIME_VERSION" ]; then - RUNTIME_VERSION=$(cat package.json | jq '.version' -r) -fi - -cd bundleTempDir || exit - -# Shared with denisPublish.sh when both run in one job -- see the note there. -# Both origins must receive the same bundle version for the same bytes, because -# the version is part of the asset URL path. -BUNDLE_VERSION="${BUNDLE_VERSION:-$(date +%s)}" - -# This MUST address ota1's own origin hostname, never updates.bsky.app. -# -# Since the 2026-07-26 cutover updates.bsky.app resolves to denis on EKS, which -# deliberately has no /v1/upload route -- publishing there is out-of-band via -# `denis publish` (see denisPublish.sh). Posting to the CDN hostname therefore -# returns 404, which is what broke this step the first time it ran after the -# flip. The dual-write was never independent of the cutover precisely because it -# addressed the hostname being cut over. -# -# This upload exists only to keep ota1 carrying current bundles so a rollback of -# the Bunny origin remains useful. It goes away with this whole script when ota1 -# is decommissioned (Phase 5). -OTA1_ORIGIN="${OTA1_ORIGIN:-https://ota1.us-east.updates.bsky.network}" -DEPLOYMENT_URL="$OTA1_ORIGIN/v1/upload?runtime-version=$RUNTIME_VERSION&bundle-version=$BUNDLE_VERSION&channel=$CHANNEL_NAME&ios-build-number=$BSKY_IOS_BUILD_NUMBER&android-build-number=$BSKY_ANDROID_VERSION_CODE" - -tar czvf bundle.tar.gz ./* - -echo "Deploying to $DEPLOYMENT_URL..." -echo " runtime-version: $RUNTIME_VERSION" -echo " bundle-version: $BUNDLE_VERSION" -echo " channel: $CHANNEL_NAME" -echo " ios-build-number: $BSKY_IOS_BUILD_NUMBER" -echo " android-build-number: $BSKY_ANDROID_VERSION_CODE" - -curl --fail-with-body -o - --form "bundle=@./bundle.tar.gz" --user "bsky:$DENIS_API_KEY" --basic "$DEPLOYMENT_URL" - -cd .. - -rm -rf bundleTempDir -rm -rf bundle.tar.gz diff --git a/scripts/denisPublish.sh b/scripts/denisPublish.sh index c6a30b77b9..2b4624eca0 100755 --- a/scripts/denisPublish.sh +++ b/scripts/denisPublish.sh @@ -4,8 +4,7 @@ set -o pipefail set -o nounset # Publishes the just-exported Expo bundle to the denis OTA service (S3) via the -# `denis publish` CLI. Mirrors bundleUpdate.sh's inputs (runtime version, bundle -# version, build numbers) but targets denis instead of the legacy ota1 upload. +# `denis publish` CLI. # Expects: the `denis` binary on PATH (setup-denis action), ambient AWS creds # (configure-aws-credentials OIDC), and BSKY_IOS_BUILD_NUMBER / # BSKY_ANDROID_VERSION_CODE from the use-build-number wrapper. @@ -19,16 +18,7 @@ if [ -z "$RUNTIME_VERSION" ]; then RUNTIME_VERSION=$(cat package.json | jq '.version' -r) fi -# Accept a caller-supplied bundle version so that a dual-write publishes the SAME -# version to every origin. When this script and bundleUpdate.sh each called -# `date +%s` independently they produced versions seconds apart for identical -# bytes -- observed 1785102575 (denis) vs 1785102614 (ota1) for one commit. Since -# the version is part of the asset URL path, the two origins then served -# manifests pointing at paths only one of them had, so the manifest and its -# assets had to come from the same origin or the fetch 404s. Falling back to -# `date +%s` keeps standalone callers (PR previews, `pnpm make-deploy-bundle`) -# working unchanged. -BUNDLE_VERSION="${BUNDLE_VERSION:-$(date +%s)}" +BUNDLE_VERSION=$(date +%s) DENIS_CDN_DOMAIN="${DENIS_CDN_DOMAIN:-updates.bsky.app}" DENIS_S3_BUCKET="${DENIS_S3_BUCKET:-bsky-denis-ota-prod}" diff --git a/src/Navigation.tsx b/src/Navigation.tsx index ec3072f46d..c03e65af69 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -87,6 +87,7 @@ import {MessagesJoinRequestsScreen} from '#/screens/Messages/JoinRequests' import {MessagesSettingsScreen} from '#/screens/Messages/Settings' import {ModerationScreen} from '#/screens/Moderation' import {Screen as ModerationVerificationSettings} from '#/screens/Moderation/VerificationSettings' +import {ModerationInboxScreen} from '#/screens/ModerationInbox' import {Screen as ModerationInteractionSettings} from '#/screens/ModerationInteractionSettings' import {NotificationsActivityListScreen} from '#/screens/Notifications/ActivityList' import {PostLikedByScreen} from '#/screens/Post/PostLikedBy' @@ -178,6 +179,11 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { getComponent={() => ModerationScreen} options={{title: title(msg`Moderation`), requireAuth: true}} /> + ModerationInboxScreen} + options={{title: title(msg`Moderation inbox`), requireAuth: true}} + /> ModerationModlistsScreen} @@ -919,7 +925,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { // chat-removed-from-group, chat-join-request-rejected: the convo is // no longer accessible to the recipient, so just open the list. // @ts-expect-error nested navigators aren't typed -sfn - navigate('MessagesTab', {screen: 'Messages'}) + void navigate('MessagesTab', {screen: 'Messages'}) } }, ) @@ -1006,7 +1012,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { ax.metric('init', { initMs: Math.round( - // @ts-ignore Emitted by Metro in the bundle prelude + // @ts-expect-error Emitted by Metro in the bundle prelude performance.now() - global.__BUNDLE_START_TIME__, ), }) @@ -1081,7 +1087,7 @@ function navigate( } navigationRef.addListener('state', handler) - // @ts-ignore I don't know what would make typescript happy but I have a life -prf + // @ts-expect-error I don't know what would make typescript happy but I have a life -prf navigationRef.navigate(name, params) }), timeout(1e3), diff --git a/src/Splash.tsx b/src/Splash.tsx index 4a4dea831a..76c9c7de82 100644 --- a/src/Splash.tsx +++ b/src/Splash.tsx @@ -20,14 +20,15 @@ import * as SplashScreen from 'expo-splash-screen' import {Logotype} from '#/view/icons/Logotype' import {atoms as a} from '#/alf' -// @ts-ignore +// @ts-expect-error import splashImagePointer from '../assets/splash/splash.png' -// @ts-ignore +// @ts-expect-error import darkSplashImagePointer from '../assets/splash/splash-dark.png' -const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri + +const splashImageUri = RNImage.resolveAssetSource(splashImagePointer)!.uri const darkSplashImageUri = RNImage.resolveAssetSource( darkSplashImagePointer, -).uri +)!.uri export const Logo = forwardRef(function LogoImpl(props: SvgProps, ref) { const width = 1000 @@ -35,7 +36,7 @@ export const Logo = forwardRef(function LogoImpl(props: SvgProps, ref) { return ( diff --git a/src/Splash.web.tsx b/src/Splash.web.tsx index ff86c9a770..44e14baa33 100644 --- a/src/Splash.web.tsx +++ b/src/Splash.web.tsx @@ -7,7 +7,7 @@ import {useEffect, useRef, useState} from 'react' import Svg, {Path} from 'react-native-svg' -import {atoms as a, flatten} from '#/alf' +import {atoms as a, flattenToCSS} from '#/alf' const size = 100 const ratio = 57 / 64 @@ -72,7 +72,7 @@ export function Splash({ {!isAnimationComplete && (
, ) { - const s = flatten(styles) ?? {} + const s: MutableTextStyle = {...flatten(styles)} // should always be defined on these components s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale diff --git a/src/alf/util/dimensions.ts b/src/alf/util/dimensions.ts index 31af2ffa99..e5d60055c1 100644 --- a/src/alf/util/dimensions.ts +++ b/src/alf/util/dimensions.ts @@ -1,5 +1,5 @@ import {useEffect, useState} from 'react' -import {Dimensions} from 'react-native' +import {Dimensions, type DimensionsPayload} from 'react-native' /** * Same as `useWindowDimensions().fontScale`, but avoids rerendering @@ -9,9 +9,12 @@ export function useNativeFontScale() { const [fontScale, setFontScale] = useState(Dimensions.get('window').fontScale) useEffect(() => { - const sub = Dimensions.addEventListener('change', evt => { - setFontScale(evt.window.fontScale) - }) + const sub = Dimensions.addEventListener( + 'change', + (evt: DimensionsPayload) => { + if (evt.window) setFontScale(evt.window.fontScale) + }, + ) return () => sub.remove() }, []) diff --git a/src/alf/util/flatten.ts b/src/alf/util/flatten.ts index 6d49ce6e51..b218c8879e 100644 --- a/src/alf/util/flatten.ts +++ b/src/alf/util/flatten.ts @@ -1,6 +1,19 @@ -import {type DimensionValue, StyleSheet} from 'react-native' +import {type DimensionValue, type StyleProp, StyleSheet} from 'react-native' -export const flatten = StyleSheet.flatten +export function flatten( + style?: StyleProp, +): T extends (infer U)[] ? U : T { + return (StyleSheet.flatten( + style as unknown as Parameters[0], + ) ?? {}) as T extends (infer U)[] ? U : T +} + +/** Flatten React Native styles passed directly to a web-only DOM component. */ +export function flattenToCSS(style: unknown): React.CSSProperties { + return (StyleSheet.flatten( + style as Parameters[0], + ) ?? {}) as React.CSSProperties +} /** * Coerce a style value to a number. Padding values are typed as @@ -28,7 +41,7 @@ interface PaddingStyle { * non-numeric `DimensionValue` (e.g. percentages) is treated as 0. */ export function extractPadding(style: PaddingStyle | PaddingStyle[]) { - const s = flatten(style as any) ?? {} + const s = flatten(style) const base = num(s.padding) return { paddingTop: num(s.paddingTop) || num(s.paddingVertical) || base, diff --git a/src/alf/util/useColorModeTheme.ts b/src/alf/util/useColorModeTheme.ts index 7cb9b12723..dfcc57b53a 100644 --- a/src/alf/util/useColorModeTheme.ts +++ b/src/alf/util/useColorModeTheme.ts @@ -24,7 +24,7 @@ export function useThemeName(): ThemeName { } function getThemeName( - colorScheme: ColorSchemeName, + colorScheme: ColorSchemeName | null | undefined, colorMode: 'system' | 'light' | 'dark', darkTheme?: ThemeName, ) { @@ -39,11 +39,8 @@ function getThemeName( } function updateDocument(theme: ThemeName) { - // @ts-ignore web only if (IS_WEB && typeof window !== 'undefined') { - // @ts-ignore web only const html = window.document.documentElement - // @ts-ignore web only const meta = window.document.querySelector('meta[name="theme-color"]') // remove any other color mode classes diff --git a/src/analytics/features/index.ts b/src/analytics/features/index.ts index 7b1d9cbbaf..6cbafeaa22 100644 --- a/src/analytics/features/index.ts +++ b/src/analytics/features/index.ts @@ -77,22 +77,6 @@ export function getFeatures() { export function getFeatureDescription(feature: Features, i18n: I18n) { switch (feature) { - case Features.VideoAllow10MinuteEnable: - return { - key: feature, - name: i18n._( - msg({ - message: 'Longer videos', - comment: 'Name for a feature flag (longer videos)', - }), - ), - description: i18n._( - msg({ - message: 'Enable 10-minute video uploads.', - comment: 'Description of a feature flag (10-minute video uploads)', - }), - ), - } case Features.CanonicalPostNumberingEnable: return { key: feature, diff --git a/src/analytics/features/types.ts b/src/analytics/features/types.ts index d86ad83ff7..4d3cf5e197 100644 --- a/src/analytics/features/types.ts +++ b/src/analytics/features/types.ts @@ -17,15 +17,13 @@ export enum Features { ComposerLanguageDetectionEnable = 'composer:language_detection:enable', PostGalleryEmbedEnable = 'post_gallery_embed:enable', PostThreadKnownLikersEnable = 'post_thread:known_likers:enable', - PostThreadKnownLikersFetchEnable = 'post_thread:known_likers:fetch:enable', CustomLogoJapanEnable = 'custom_logo:japan:enable', - VideoAllow10MinuteEnable = 'video:allow-10-minute:enable', - VideoMultipartUploadEnable = 'video:multipart_upload:enable', SearchStarterPacksV2Enable = 'search_starter_packs_v2:enable', FollowSortEnable = 'follow_sort:enable', OnboardingInterestsRequiredEnable = 'onboarding:interests:required:enable', CanonicalPostNumberingEnable = 'canonical_post_numbering:enable', ContentVisibilitySettingsEnable = 'content_visibility_settings:enable', + ModerationInboxEnable = 'moderation_inbox:enable', // values TrendingDiscoverValues = 'trending_discover:values', diff --git a/src/analytics/index.tsx b/src/analytics/index.tsx index d765c46e2a..07af41473e 100644 --- a/src/analytics/index.tsx +++ b/src/analytics/index.tsx @@ -9,10 +9,6 @@ import {Platform} from 'react-native' import {type Result, type WidenPrimitives} from '@growthbook/growthbook-react' import {Logger} from '#/logger' -import { - getCachedIsBetaUser, - subscribeToCachedIsBetaUser, -} from '#/state/preferences/beta-user-cache' import { Features, features as feats, @@ -37,7 +33,7 @@ import {type Metrics, metrics} from '#/analytics/metrics' import * as refParams from '#/analytics/misc/refParams' import * as env from '#/env' import {useGeolocationServiceResponse} from '#/geolocation/service' -import {device} from '#/storage' +import {account, device} from '#/storage' export * as utils from '#/analytics/utils' export const features = {init, refresh} @@ -135,7 +131,7 @@ export const setupDeviceId = getAndMigrateDeviceId() /** * Reads the per-account cached `isBetaUser` flag for `did`, kept in sync with - * PDS preference query results and the beta settings toggle. + * writes from `BetaUserStorageSync` and the beta settings toggle. * * This deliberately does not use `useStorage`, whose `useState` seeds once and * only updates via the change listener. The consuming `AnalyticsContext` lives @@ -150,13 +146,17 @@ function useAccountIsBetaUser(did: string | undefined): boolean | undefined { const subscribe = useCallback( (onChange: () => void) => { if (!did) return () => {} - return subscribeToCachedIsBetaUser(did, onChange) + const sub = account.addOnValueChangedListener( + [did, 'isBetaUser'], + onChange, + ) + return () => sub.remove() }, [did], ) const getSnapshot = useCallback(() => { if (!did) return undefined - return getCachedIsBetaUser(did) + return account.get([did, 'isBetaUser']) }, [did]) return useSyncExternalStore(subscribe, getSnapshot) } diff --git a/src/analytics/metrics/types.ts b/src/analytics/metrics/types.ts index 8e43589281..78a4574f0f 100644 --- a/src/analytics/metrics/types.ts +++ b/src/analytics/metrics/types.ts @@ -5,10 +5,7 @@ import {type Platform} from 'react-native' import {type NotificationReason} from '#/lib/hooks/useNotificationHandler' -import { - type VideoCompressSkipReason, - type VideoUploadTransport, -} from '#/lib/media/video/types' +import {type VideoCompressSkipReason} from '#/lib/media/video/types' import {type NotificationType} from '#/state/queries/notifications/types' import {type FeedDescriptor} from '#/state/queries/post-feed' import {type LiveEventFeedMetricContext} from '#/features/liveEvents/types' @@ -1473,7 +1470,6 @@ export type Events = { bytes: number elapsedMs: number throughputBytesPerSec: number - transport: VideoUploadTransport } 'video:upload:uploadFailed': { uploadId: string @@ -1481,7 +1477,6 @@ export type Events = { bytes: number errorClass: string elapsedMs: number - transport: VideoUploadTransport } 'video:upload:processingStarted': { uploadId: string diff --git a/src/analytics/utils.ts b/src/analytics/utils.ts index 95d2efb589..b20b3719c9 100644 --- a/src/analytics/utils.ts +++ b/src/analytics/utils.ts @@ -14,7 +14,7 @@ import { export function useMeta(metadata?: MergeableMetadata) { const m = useMemo(() => metadata, [metadata]) if (!m) return - // @ts-ignore + // @ts-expect-error m.__meta = true return m } diff --git a/src/components/AltBadgeWithDialog.tsx b/src/components/AltBadgeWithDialog.tsx index 07cf9544f0..4ae078e12b 100644 --- a/src/components/AltBadgeWithDialog.tsx +++ b/src/components/AltBadgeWithDialog.tsx @@ -1,9 +1,9 @@ -import {Pressable} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' import {HITSLOP_20} from '#/lib/constants' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' import {atoms as a, useTheme} from '#/alf' +import {Pressable} from '#/components/Pressable' import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' @@ -49,7 +49,7 @@ export function AltBadgeWithDialog({ accessibilityHint="" hitSlop={HITSLOP_20} onPress={control.open} - style={s => [ + style={({pressed, hovered}) => [ a.justify_center, a.rounded_sm, a.p_xs, @@ -62,7 +62,7 @@ export function AltBadgeWithDialog({ opacity: 0.8, }, pos, - s.hovered || s.pressed + hovered || pressed ? [ { opacity: 1, diff --git a/src/components/BetaBadge.tsx b/src/components/BetaBadge.tsx index 115f8d0046..c8c0ddd2bf 100644 --- a/src/components/BetaBadge.tsx +++ b/src/components/BetaBadge.tsx @@ -1,11 +1,12 @@ import {useState} from 'react' -import {type Insets, Pressable, View} from 'react-native' +import {type Insets, View} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' import {usePreferencesQuery} from '#/state/queries/preferences' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker' +import {Pressable} from '#/components/Pressable' import * as Tooltip from '#/components/Tooltip' import type * as bsky from '#/types/bsky' diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 54332fbc07..2c1f7ff3f8 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -101,6 +101,8 @@ export type ButtonProps = Pick< | 'onPressOut' | 'onFocus' | 'onBlur' + | 'onAccessibilityAction' + | 'onAccessibilityEscape' > & AccessibilityProps & VariantProps & { @@ -131,7 +133,7 @@ export function useButtonContext() { return useContext(Context) } -export const Button = forwardRef( +export const Button = forwardRef, ButtonProps>( ( { children, @@ -575,7 +577,6 @@ export const Button = forwardRef( role="button" accessibilityHint={undefined} // optional {...rest} - // @ts-ignore - this will always be a pressable ref={ref} aria-label={label} aria-pressed={state.pressed} diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index f4a0302ff7..b2266db5a1 100644 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -39,6 +39,9 @@ import { import {Span, Text} from '#/components/Typography' import {IS_IOS, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' +type TextInputInstance = React.ComponentRef +type ViewInstance = React.ComponentRef + export type SubmitRequest = | { platform: 'web' @@ -60,7 +63,7 @@ export type ComposerInternalApi = { input?: ReturnType['input'] clear: () => void insert(text: string): void - setAutocompleteAnchor: (node: View | null) => void + setAutocompleteAnchor: (node: ViewInstance | null) => void } export function useComposerInternalApiRef() { @@ -82,7 +85,7 @@ export type ComposerProps = Omit< | 'onSubmitEditing' > & { label: string - ref?: React.RefObject + ref?: React.RefObject internalApiRef?: React.Ref outerStyle?: ViewStyleProp['style'] contentTextStyle?: TextStyleProp['style'] @@ -139,6 +142,11 @@ export function Composer({ placement: autocompletePlacement, dynamicWidth: IS_WEB, }) + const inputRef = mergeRefs([ + ref, + tapper.inputProps.ref as React.Ref, + sift.targetProps.ref as React.Ref, + ]) /* * Active facet state for controlling the visibility of the Autocomplete. @@ -306,7 +314,7 @@ export function Composer({ style={[a.absolute, a.inset_0, a.z_10, {overflow: 'hidden'}]} ref={node => { if (IS_WEB && node) { - // @ts-ignore web only a11y + // @ts-expect-error web only a11y node.setAttribute('inert', '') } }}> @@ -345,7 +353,7 @@ export function Composer({ {...rest} {...tapper.inputProps} {...sift.targetProps} - ref={mergeRefs([ref, tapper.inputProps.ref, sift.targetProps.ref])} + ref={inputRef} rawValue={tapper.state.text} onBlur={e => { rest.onBlur?.(e) @@ -359,11 +367,10 @@ export function Composer({ inputScrollSharedValue.value = e.nativeEvent.contentOffset.y } }} - // @ts-ignore web only + // @ts-expect-error web only onCompositionStart={() => { isComposing.current = true }} - // @ts-ignore web only onCompositionEnd={() => { isComposing.current = false }} diff --git a/src/components/ContextMenu/index.tsx b/src/components/ContextMenu/index.tsx index d09e0e60ec..95492a385a 100644 --- a/src/components/ContextMenu/index.tsx +++ b/src/components/ContextMenu/index.tsx @@ -244,7 +244,7 @@ export function Trigger({ const context = useContextMenuContext() const playHaptic = useHaptics() const insets = useSafeAreaInsets() - const ref = useRef(null) + const ref = useRef>(null) const isFocused = useIsFocused() const [image, setImage] = useState(null) const [pendingMeasurement, setPendingMeasurement] = useState<{ @@ -971,7 +971,10 @@ export function Divider() { ) } -function measureView(view: View | null, insets: EdgeInsets) { +function measureView( + view: React.ComponentRef | null, + insets: EdgeInsets, +) { if (!view) return Promise.resolve(null) return new Promise(resolve => { view?.measureInWindow((x, y, width, height) => diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 4f6984a4a0..5cfa2d1c10 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -8,7 +8,7 @@ import { } from 'react' import { Keyboard, - type KeyboardEventListener, + type KeyboardEvent, type LayoutChangeEvent, type NativeScrollEvent, type NativeSyntheticEvent, @@ -203,80 +203,85 @@ export function Inner(props: DialogInnerProps) { return } -export const ScrollableInner = forwardRef( - function ScrollableInner( - {children, contentContainerStyle, header, footer, style, ...props}, - ref, - ) { - const {nativeSnapPoint, disableDrag, setDisableDrag, isHeightConstrained} = - useDialogContext() - const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full - const insets = useSafeAreaInsets() - const [keyboardHeight, setKeyboardHeight] = useState(() => - IS_ANDROID ? (Keyboard.metrics()?.height ?? 0) : 0, - ) +export function ScrollableInner({ + ref, + children, + contentContainerStyle, + header, + footer, + style, + ...props +}: DialogInnerProps & { + ref?: React.Ref> +}) { + const {nativeSnapPoint, disableDrag, setDisableDrag, isHeightConstrained} = + useDialogContext() + const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full + const insets = useSafeAreaInsets() + const [keyboardHeight, setKeyboardHeight] = useState(() => + IS_ANDROID ? (Keyboard.metrics()?.height ?? 0) : 0, + ) - const keyboardEventHandler = useCallback(e => { - setKeyboardHeight(e.endCoordinates.height) - }, []) - useOnKeyboard('keyboardDidShow', keyboardEventHandler) - useOnKeyboard('keyboardDidHide', keyboardEventHandler) + const keyboardEventHandler = useCallback((e: KeyboardEvent) => { + setKeyboardHeight(e.endCoordinates.height) + }, []) + useOnKeyboard('keyboardDidShow', keyboardEventHandler) + useOnKeyboard('keyboardDidHide', keyboardEventHandler) - const onScroll = (e: NativeSyntheticEvent) => { - if (!IS_ANDROID) { - return - } - const {contentOffset} = e.nativeEvent - if (contentOffset.y > 0 && !disableDrag) { - setDisableDrag(true) - } else if (contentOffset.y <= 1 && disableDrag) { - setDisableDrag(false) - } + const onScroll = (e: NativeSyntheticEvent) => { + if (!IS_ANDROID) { + return } + const {contentOffset} = e.nativeEvent + if (contentOffset.y > 0 && !disableDrag) { + setDisableDrag(true) + } else if (contentOffset.y <= 1 && disableDrag) { + setDisableDrag(false) + } + } - return ( - <> - - {header} - {children} - - {footer} - - ) - }, -) + return ( + <> + + {header} + {children} + + {footer} + + ) +} export const InnerFlatList = forwardRef< ListMethods, diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index c26a71825b..e867acd60a 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -193,7 +193,6 @@ export function Inner({ aria-label={label} aria-labelledby={accessibilityLabelledBy} aria-describedby={accessibilityDescribedBy} - // @ts-expect-error web only -prf onClick={stopPropagation} onStartShouldSetResponder={_ => true} onTouchEnd={stopPropagation} @@ -239,7 +238,9 @@ export function Inner({ export function ScrollableInner({ ref: _ref, ...props -}: DialogInnerProps & {ref?: React.Ref}) { +}: DialogInnerProps & { + ref?: React.Ref> +}) { return } diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index aa77a7b36b..59bbe85752 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -217,7 +217,7 @@ export function ProfileGrid({ // Track seen profiles const seenProfilesRef = useRef>(new Set()) - const containerRef = useRef(null) + const containerRef = useRef>(null) const hasTrackedRef = useRef(false) const logContext: Metrics['suggestedUser:seen']['logContext'] = isFeedContext ? 'DiscoverInterstitial' @@ -276,7 +276,7 @@ export function ProfileGrid({ }, {threshold: 0.5}, ) - // @ts-ignore - web only + // @ts-expect-error - web only observer.observe(node) return () => observer.disconnect() } else { diff --git a/src/components/FocusScope/index.tsx b/src/components/FocusScope/index.tsx index 48e013500e..598c3cd6ca 100644 --- a/src/components/FocusScope/index.tsx +++ b/src/components/FocusScope/index.tsx @@ -41,7 +41,7 @@ export function FocusScope({children}: {children: React.ReactNode}) { */ function FocusTrap({children}: {children: React.ReactNode}) { const {_} = useLingui() - const child = useRef(null) + const child = useRef>(null) /* * Here we add a ref to the first child of this component. This currently @@ -66,13 +66,16 @@ function FocusTrap({children}: {children: React.ReactNode}) { }) }, [children]) - const focusNode = useCallback((ref: View | null) => { - if (!ref) return - const node = findNodeHandle(ref) - if (node) { - AccessibilityInfo.setAccessibilityFocus(node) - } - }, []) + const focusNode = useCallback( + (ref: React.ComponentRef | null) => { + if (!ref) return + const node = findNodeHandle(ref) + if (node) { + AccessibilityInfo.setAccessibilityFocus(node) + } + }, + [], + ) useEffect(() => { setTimeout(() => { diff --git a/src/components/GlassView.tsx b/src/components/GlassView.tsx index cac5bd268d..ddbcc07d80 100644 --- a/src/components/GlassView.tsx +++ b/src/components/GlassView.tsx @@ -20,7 +20,7 @@ export const IS_GLASS_AVAILABLE = */ export const GlassView = IS_GLASS_AVAILABLE ? InnerGlassView : FallbackView -export type GlassViewProps = ExpoGlassViewProps & { +export type GlassViewProps = Omit & { fallbackStyle?: StyleProp } diff --git a/src/components/InterestTabs.tsx b/src/components/InterestTabs.tsx index 5df1fb62e3..5a99a36992 100644 --- a/src/components/InterestTabs.tsx +++ b/src/components/InterestTabs.tsx @@ -47,7 +47,7 @@ export function InterestTabs({ }) { const t = useTheme() const {_} = useLingui() - const listRef = useRef(null) + const listRef = useRef>(null) const [totalWidth, setTotalWidth] = useState(0) const [scrollX, setScrollX] = useState(0) const [contentWidth, setContentWidth] = useState(0) diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index a0bd45c8fc..c70cebd703 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -42,7 +42,7 @@ export function Outer({ }: { children: React.ReactNode noBottomBorder?: boolean - headerRef?: React.RefObject + headerRef?: React.RefObject | null> sticky?: boolean }) { const t = useTheme() diff --git a/src/components/Lightbox/Lightbox.web.tsx b/src/components/Lightbox/Lightbox.web.tsx index 0222aa3156..cccbe4f851 100644 --- a/src/components/Lightbox/Lightbox.web.tsx +++ b/src/components/Lightbox/Lightbox.web.tsx @@ -446,9 +446,7 @@ function LightboxGalleryItem({ const styles = StyleSheet.create({ avi: { - // @ts-ignore web-only maxWidth: `calc(min(400px, 100vw))`, - // @ts-ignore web-only maxHeight: `calc(min(400px, 100vh))`, padding: 16, boxSizing: 'border-box', @@ -458,7 +456,6 @@ const styles = StyleSheet.create({ // column via ScrollView's default flexGrow. flexGrow: 0, flexShrink: 0, - // @ts-ignore web-only -sfn maxHeight: '50vh', }, menuBtn: { diff --git a/src/components/Lightbox/chrome/ImageMenu.tsx b/src/components/Lightbox/chrome/ImageMenu.tsx index 9088b9ded9..14ce100c0f 100644 --- a/src/components/Lightbox/chrome/ImageMenu.tsx +++ b/src/components/Lightbox/chrome/ImageMenu.tsx @@ -35,7 +35,7 @@ const TIMING_OUT = {duration: 150} export function ImageMenu({onPressShare, onPressSave}: Props) { const {t: l} = useLingui() - const triggerRef = useRef(null) + const triggerRef = useRef>(null) const [isMounted, setIsMounted] = useState(false) const [anchor, setAnchor] = useState(null) const progress = useSharedValue(0) diff --git a/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx b/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx index 9cf088f864..c4a842a74e 100644 --- a/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx +++ b/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx @@ -82,7 +82,7 @@ const ImageItem = ({ const scrollHandler = useAnimatedScrollHandler({ onScroll(e) { 'worklet' - const nextIsScaled = e.zoomScale > 1 + const nextIsScaled = (e.zoomScale ?? 1) > 1 if (scaled !== nextIsScaled) { scheduleOnRN(handleZoom, nextIsScaled) } @@ -109,7 +109,6 @@ const ImageItem = ({ height: number }) { const scrollResponderRef = scrollViewRef?.current?.getScrollResponder() - // @ts-ignore scrollResponderRef?.scrollResponderZoomTo({ ...nextZoomRect, // This rect is in screen coordinates animated: true, @@ -218,7 +217,6 @@ const ImageItem = ({ return ( { + onPageSelected={(e: PagerViewOnPageSelectedEvent) => { const next = e.nativeEvent.position setImageIndex(prev => { if (metricsContext && prev !== next) { @@ -401,7 +404,7 @@ function ImageView({ }) setIsScaled(false) }} - onPageScrollStateChanged={e => { + onPageScrollStateChanged={(e: PageScrollStateChangedNativeEvent) => { setIsDragging(e.nativeEvent.pageScrollState !== 'idle') }} overdrag={true} diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 6d60edb615..8e5891f3b2 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -478,7 +478,7 @@ export function InlineLinkText({ onIn: onInteract, onOut: onInteractOut, } = useInteractionState() - const flattenedStyle = flatten(style) || {} + const flattenedStyle = flatten(style) return ( void + onKeyDown: PressableProps['onKeyDown'] /** * Radix provides this, but we override on web to use `onPress` instead, * which is less sensitive while scrolling. diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx index d20cc0d90a..947d4ac3d6 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx @@ -149,7 +149,7 @@ function canPlayBskyVideoCodecs(): boolean { type CachedPromise = Promise & {value: undefined | T} const promiseForHls = import( - // @ts-ignore + // @ts-expect-error 'hls.js/dist/hls.min' // oxlint-disable-next-line typescript/no-unsafe-member-access ).then(mod => mod.default) as CachedPromise diff --git a/src/components/Post/Translated/index.tsx b/src/components/Post/Translated/index.tsx index 849222df03..6d44e9ce75 100644 --- a/src/components/Post/Translated/index.tsx +++ b/src/components/Post/Translated/index.tsx @@ -267,7 +267,7 @@ function TranslationResult({ ? codeToLanguageName(resultSourceLanguage, i18n.locale) : undefined - const flattenedStyle = flatten(postTextStyle) ?? {} + const flattenedStyle = flatten(postTextStyle) const fontSize = flattenedStyle.fontSize return ( diff --git a/src/components/PostControls/PostControlButton.tsx b/src/components/PostControls/PostControlButton.tsx index 3ea85e2811..e65a931f7d 100644 --- a/src/components/PostControls/PostControlButton.tsx +++ b/src/components/PostControls/PostControlButton.tsx @@ -27,7 +27,7 @@ export function PostControlButton({ activeColor, ...props }: Omit & { - ref?: React.Ref + ref?: React.Ref> active?: boolean big?: boolean color?: string diff --git a/src/components/Pressable.tsx b/src/components/Pressable.tsx new file mode 100644 index 0000000000..5fc96e3047 --- /dev/null +++ b/src/components/Pressable.tsx @@ -0,0 +1,37 @@ +import { + Pressable as NativePressable, + type PressableStateCallbackType as NativePressableStateCallbackType, + type StyleProp, + type ViewStyle, +} from 'react-native' + +export interface PressableStateCallbackType extends NativePressableStateCallbackType { + /** Provided by react-native-web. */ + readonly focused?: boolean + /** Provided by react-native-web. */ + readonly hovered?: boolean +} + +export type PressableProps = Omit< + React.ComponentProps, + 'children' | 'style' +> & { + children?: + React.ReactNode | ((state: PressableStateCallbackType) => React.ReactNode) + style?: + | StyleProp + | ((state: PressableStateCallbackType) => StyleProp) +} + +/** + * React Native Pressable with react-native-web's callback state represented in + * its types. The web-only fields are optional because native does not provide + * them at runtime. + */ +export function Pressable({children, style, ...props}: PressableProps) { + return ( + + {children} + + ) +} diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index ccc48b8951..a5f47be28b 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -318,11 +318,10 @@ export function ProfileHoverCardInner(props: ProfileHoverCardProps) { return ( {props.children} diff --git a/src/components/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx index d7fa9c1fab..1ab2668ae5 100644 --- a/src/components/ProgressGuide/FollowDialog.tsx +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -1,5 +1,5 @@ import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react' -import {TextInput, View, type ViewToken} from 'react-native' +import {type ListViewToken as ViewToken, TextInput, View} from 'react-native' import {type ModerationOpts} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' @@ -139,7 +139,7 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) { const [searchText, setSearchText] = useState(lastSearchText) const moderationOpts = useModerationOpts() const listRef = useRef(null) - const inputRef = useRef(null) + const inputRef = useRef>(null) const [headerHeight, setHeaderHeight] = useState(0) const {currentAccount} = useSession() @@ -374,7 +374,7 @@ let Header = ({ interestsDisplayNames, }: { guide?: Follow10ProgressGuide - inputRef: React.RefObject + inputRef: React.RefObject | null> listRef: React.RefObject onSelectTab: (v: string) => void searchText: string @@ -679,7 +679,7 @@ function SearchInput({ }: { onChangeText: (text: string) => void onEscape: () => void - inputRef: React.RefObject + inputRef: React.RefObject | null> defaultValue: string }) { const t = useTheme() diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 0266dae0ce..1fa020f2a9 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -93,7 +93,7 @@ export function RichText({ if (!facets?.length) { if (isOnlyEmoji(text)) { - const flattenedStyle = flatten(style) ?? {} + const flattenedStyle = flatten(style) const fontSize = (flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier return ( @@ -104,7 +104,6 @@ export function RichText({ style={[plainStyles, {fontSize}, suffixStyles]} onLayout={onLayout} onTextLayout={onTextLayout} - // @ts-ignore web only -prf dataSet={WORD_WRAP}> {text} {suffix ? ' ' : null} @@ -121,7 +120,6 @@ export function RichText({ numberOfLines={numberOfLines} onLayout={onLayout} onTextLayout={onTextLayout} - // @ts-ignore web only -prf dataSet={WORD_WRAP}> {text} {suffix ? ' ' : null} @@ -150,7 +148,7 @@ export function RichText({ selectable={selectable} to={`/profile/${mention.did}`} style={interactiveStyles} - // @ts-ignore TODO + // @ts-expect-error TODO dataSet={WORD_WRAP} shouldProxy={shouldProxyLinks} onPress={onLinkPress}> @@ -169,7 +167,7 @@ export function RichText({ key={key} to={link.uri} style={interactiveStyles} - // @ts-ignore TODO + // @ts-expect-error TODO dataSet={WORD_WRAP} shareOnLongPress shouldProxy={shouldProxyLinks} @@ -209,7 +207,6 @@ export function RichText({ numberOfLines={numberOfLines} onLayout={onLayout} onTextLayout={onTextLayout} - // @ts-ignore web only -prf dataSet={WORD_WRAP}> {els} {suffix ? ' ' : null} diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index a7dd70d8e7..af5849346b 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -100,7 +100,16 @@ export function Trigger({children, hitSlop, label}: TriggerProps) { } else { return (