diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml
index 983494dd18..5f1f0a4e28 100644
--- a/.github/workflows/bundle-deploy-eas-update.yml
+++ b/.github/workflows/bundle-deploy-eas-update.yml
@@ -71,18 +71,18 @@ jobs:
profile: ${{ inputs.channel || 'testflight' }}
previous-commit-tag: ${{ inputs.runtimeVersion }}
- - name: Lint check
- run: pnpm lint
-
- - name: Prettier check
- run: pnpm prettier --check .
-
- name: 🔤 Compile translations
run: pnpm intl:build 2>&1 | tee i18n.log
- name: Check for i18n compilation errors
run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi
+ - name: Lint check
+ run: pnpm lint
+
+ - name: Prettier check
+ run: pnpm prettier --check .
+
- name: Type check
run: pnpm typecheck
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 93729874b6..aa6c92765d 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- job: [lint, prettier]
+ job: [lint, prettier, typecheck]
steps:
- name: Check out Git repository
uses: actions/checkout@v5
@@ -62,6 +62,8 @@ jobs:
command: pnpm install --frozen-lockfile
attempt_limit: 3
attempt_delay: 2000
+ - name: Check & compile i18n
+ run: pnpm intl:build
- name: Lint checks
run: pnpm ${{ matrix.job }}
# Aggregates the matrix results into a single stable check name so branch
@@ -80,28 +82,6 @@ jobs:
run: |
echo "linting result: $RESULT"
test "$RESULT" = "success"
- typechecking:
- name: Run typecheck
- runs-on: ubuntu-latest
- steps:
- - name: Check out Git repository
- uses: actions/checkout@v5
- - uses: pnpm/action-setup@v6
- - name: Install node
- uses: actions/setup-node@v6
- with:
- node-version-file: package.json
- cache: pnpm
- - name: pnpm install
- uses: Wandalen/wretry.action@master
- with:
- command: pnpm install --frozen-lockfile
- attempt_limit: 3
- attempt_delay: 2000
- - name: Check & compile i18n
- run: pnpm intl:build
- - name: Type check
- run: pnpm typecheck
testing:
name: Run tests
runs-on: ubuntu-latest
diff --git a/.github/workflows/pull-request-comment.yml b/.github/workflows/pull-request-comment.yml
index a497a5625d..8ede462392 100644
--- a/.github/workflows/pull-request-comment.yml
+++ b/.github/workflows/pull-request-comment.yml
@@ -128,15 +128,15 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- - name: Lint check
- run: pnpm lint
-
- name: 🔤 Compile translations
run: pnpm intl:build 2>&1 | tee i18n.log
- name: Check for i18n compilation errors
run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi
+ - name: Lint check
+ run: pnpm lint
+
- name: Type check
run: pnpm typecheck
diff --git a/eslint-suppressions.json b/eslint-suppressions.json
index 5b7eef4c94..f2e677dc79 100644
--- a/eslint-suppressions.json
+++ b/eslint-suppressions.json
@@ -1,12 +1,72 @@
{
+ "src/Navigation.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/Splash.android.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/Splash.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/ageAssurance/components/RedirectOverlay.tsx": {
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/ageAssurance/util.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
"src/alf/util/flatten.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 9
+ }
+ },
+ "src/alf/util/systemUI.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
+ "src/alf/util/useGutters.ts": {
+ "react-hooks/immutability": {
+ "count": 5
}
},
"src/analytics/PassiveAnalytics.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "react-hooks/purity": {
+ "count": 1
+ }
+ },
+ "src/analytics/features/index.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/analytics/identifiers/session.test.ts": {
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 9
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 9
}
},
"src/analytics/metadata.ts": {
@@ -17,16 +77,84 @@
"src/analytics/metrics/client.test.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 8
+ },
+ "@typescript-eslint/require-await": {
+ "count": 5
}
},
"src/analytics/metrics/client.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 5
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/analytics/utils.ts": {
+ "react-hooks/immutability": {
+ "count": 1
+ }
+ },
+ "src/components/AppLanguageDropdown.tsx": {
+ "@typescript-eslint/no-unsafe-enum-comparison": {
+ "count": 1
+ }
+ },
+ "src/components/Autocomplete/Autocomplete.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/Autocomplete/useAutocomplete/index.ts": {
+ "react-hooks/immutability": {
+ "count": 1
+ }
+ },
+ "src/components/BotAccountAlert.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/Button.tsx": {
+ "react-hooks/immutability": {
+ "count": 1
}
},
"src/components/Composer/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-redundant-type-constituents": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 7
+ },
+ "react-hooks/immutability": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
+ "src/components/DebugFieldDisplay.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
}
},
"src/components/Dialog/index.tsx": {
@@ -37,6 +165,18 @@
"src/components/Dialog/index.web.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 4
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
}
},
"src/components/DraggableList/index.web.tsx": {
@@ -44,6 +184,11 @@
"count": 2
}
},
+ "src/components/EmojiPicker/preload.web.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
"src/components/FeedCard.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
@@ -52,16 +197,53 @@
"src/components/FocusScope/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/components/InternationalPhoneCodeSelect.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/components/KnownFollowers.tsx": {
+ "react-hooks/refs": {
+ "count": 6
+ }
+ },
+ "src/components/Lightbox/Lightbox.web.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx": {
+ "react-hooks/immutability": {
+ "count": 1
+ }
+ },
+ "src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx": {
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
+ "src/components/LikedByList.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
}
},
"src/components/Lists.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/components/Menu/types.ts": {
@@ -74,34 +256,129 @@
"count": 1
}
},
+ "src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ }
+ },
+ "src/components/Post/Embed/ExternalEmbed/index.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
"src/components/Post/Embed/ImageEmbed.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/Post/Embed/StandardSiteEmbed/index.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ }
+ },
+ "src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-enum-comparison": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ },
+ "react-hooks/immutability": {
+ "count": 1
}
},
"src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
}
},
"src/components/PostControls/BookmarkButton.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ }
+ },
+ "src/components/PostControls/DiscoverDebug.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/PostControls/ShareMenu/ShareMenuItems.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ }
+ },
+ "src/components/PostControls/ShareMenu/index.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
}
},
"src/components/ProfileHoverCard/index.web.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ },
+ "react-hooks/immutability": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 2
+ }
+ },
+ "src/components/ProgressGuide/FollowDialog.tsx": {
+ "react-hooks/refs": {
+ "count": 2
+ }
+ },
+ "src/components/RichTextTag.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/components/Select/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 4
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
}
},
"src/components/Select/index.web.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/components/Select/types.ts": {
@@ -109,9 +386,44 @@
"count": 3
}
},
+ "src/components/SendErrorReportDialog.tsx": {
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/components/StarterPack/Main/ProfilesList.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
"src/components/StarterPack/ProfileStarterPacks.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ }
+ },
+ "src/components/StarterPack/QrCodeDialog.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 5
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/require-await": {
+ "count": 4
+ }
+ },
+ "src/components/StarterPack/ShareDialog.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
}
},
"src/components/StarterPack/Wizard/WizardEditListDialog.tsx": {
@@ -119,9 +431,109 @@
"count": 1
}
},
+ "src/components/Tooltip/index.tsx": {
+ "@typescript-eslint/no-base-to-string": {
+ "count": 1
+ }
+ },
+ "src/components/Tooltip/index.web.tsx": {
+ "@typescript-eslint/no-base-to-string": {
+ "count": 1
+ }
+ },
+ "src/components/WhoCanReply.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
+ "src/components/ageAssurance/AgeAssuranceErrors.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/components/ageAssurance/AgeAssuranceInitDialog.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "react-hooks/purity": {
+ "count": 1
+ }
+ },
+ "src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx": {
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/components/contacts/components/OTPInput.tsx": {
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
+ "src/components/contacts/screens/GetContacts.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/contacts/screens/VerifyNumber.tsx": {
+ "@typescript-eslint/require-await": {
+ "count": 1
+ },
+ "react-hooks/purity": {
+ "count": 1
+ }
+ },
+ "src/components/contacts/screens/ViewMatches.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
"src/components/dialogs/DeviceLocationRequestDialog.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/components/dialogs/EmailDialog/components/ResendEmailText.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/dialogs/EmailDialog/data/useAccountEmailState.ts": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ }
+ },
+ "src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/components/dialogs/EmailDialog/screens/Update.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ }
+ },
+ "src/components/dialogs/EmailDialog/screens/Verify.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ }
+ },
+ "src/components/dialogs/InAppBrowserConsent.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/components/dialogs/LanguageSelectDialog.tsx": {
@@ -129,27 +541,105 @@
"count": 1
}
},
+ "src/components/dialogs/LinkWarning.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
"src/components/dialogs/MutedWords.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
}
},
"src/components/dialogs/PostInteractionSettingsDialog.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
},
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ },
"react-hooks/preserve-manual-memoization": {
"count": 1
}
},
+ "src/components/dialogs/ServerInput.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/dialogs/StarterPackDialog.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/components/dialogs/SwitchAccount.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
"src/components/dialogs/lists/CreateOrEditListDialog.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/components/dialogs/nuxs/FindContactsAnnouncement.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/dialogs/nuxs/index.tsx": {
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ },
+ "react-hooks/immutability": {
+ "count": 1
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/components/dms/MessageItem.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/components/forms/DateField/index.web.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/components/forms/SearchInput.tsx": {
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/components/forms/SegmentedControl.tsx": {
@@ -157,6 +647,14 @@
"count": 1
}
},
+ "src/components/forms/TextField.tsx": {
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
+ },
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
"src/components/forms/ToggleButton.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
@@ -165,6 +663,37 @@
"src/components/hooks/useFollowMethods.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/components/hooks/useFullscreen.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/components/hooks/useLandingEntry.native.ts": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/components/hooks/useLandingEntry.ts": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/components/hooks/useRefreshOnFocus.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/hooks/useRichText.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
}
},
"src/components/images/AutoSizedImage.tsx": {
@@ -175,6 +704,15 @@
"src/components/images/Gallery/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 5
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 4
+ },
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/components/images/Gallery/useKeyboardHandlers.ts": {
@@ -197,6 +735,51 @@
"count": 2
}
},
+ "src/components/intents/VerifyEmailIntentDialog.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/components/interstitials/TrendingVideos.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/components/moderation/ContentHider.tsx": {
+ "react-hooks/immutability": {
+ "count": 1
+ }
+ },
+ "src/components/moderation/LabelsOnMeDialog.tsx": {
+ "react-hooks/purity": {
+ "count": 1
+ }
+ },
+ "src/components/moderation/ModerationDetailsDialog.tsx": {
+ "react-hooks/purity": {
+ "count": 1
+ }
+ },
+ "src/components/verification/VerificationCreatePrompt.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/components/verification/VerificationRemovePrompt.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/features/liveEvents/preferences.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/features/liveNow/components/LiveStatusDialog.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
"src/features/liveNow/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 3
@@ -205,6 +788,12 @@
"src/geolocation/service.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
}
},
"src/lib/ScrollContext.tsx": {
@@ -212,9 +801,22 @@
"count": 3
}
},
+ "src/lib/api/feed/demo.ts": {
+ "@typescript-eslint/require-await": {
+ "count": 2
+ }
+ },
+ "src/lib/api/feed/posts.ts": {
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
"src/lib/api/index.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 5
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
}
},
"src/lib/async/retry.ts": {
@@ -232,9 +834,20 @@
"count": 1
}
},
+ "src/lib/custom-animations/CountWheel.web.tsx": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
"src/lib/functions.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 6
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 25
}
},
"src/lib/getUserDisplayName.ts": {
@@ -242,19 +855,80 @@
"count": 1
}
},
+ "src/lib/haptics.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
"src/lib/hooks/useAccountSwitcher.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/lib/hooks/useAnimatedValue.ts": {
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
+ "src/lib/hooks/useDraggableScrollView.ts": {
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
+ "src/lib/hooks/useIntentHandler.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/lib/hooks/useNonReactiveCallback.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ }
+ },
+ "src/lib/hooks/useNotificationHandler.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 16
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
}
},
"src/lib/hooks/useOTAUpdates.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 4
+ }
+ },
+ "src/lib/hooks/useOpenLink.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/lib/hooks/usePermissions.ts": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-enum-comparison": {
+ "count": 5
+ }
+ },
+ "src/lib/hooks/usePermissions.web.ts": {
+ "@typescript-eslint/require-await": {
+ "count": 3
}
},
"src/lib/hooks/useRequireEmailVerification.tsx": {
@@ -262,14 +936,33 @@
"count": 2
}
},
+ "src/lib/hooks/useTLDs.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/lib/hooks/useTabFocusEffect.ts": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
"src/lib/hooks/useToggleMutationQueue.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 2
}
},
"src/lib/hooks/useWebScrollRestoration.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "react-hooks/immutability": {
+ "count": 1
}
},
"src/lib/international-telephone-codes.ts": {
@@ -277,9 +970,30 @@
"count": 1
}
},
+ "src/lib/link-meta/link-meta.ts": {
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 9
+ }
+ },
"src/lib/media/manip.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 5
+ }
+ },
+ "src/lib/media/manip.web.ts": {
+ "@typescript-eslint/prefer-promise-reject-errors": {
+ "count": 1
+ },
+ "@typescript-eslint/require-await": {
+ "count": 3
+ }
+ },
+ "src/lib/media/picker.web.tsx": {
+ "@typescript-eslint/require-await": {
+ "count": 2
}
},
"src/lib/media/save-image.ios.ts": {
@@ -297,9 +1011,34 @@
"count": 1
}
},
+ "src/lib/notifications/notifications.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 5
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-enum-comparison": {
+ "count": 2
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
"src/lib/react-query.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
+ "src/lib/routes/links.ts": {
+ "@typescript-eslint/no-redundant-type-constituents": {
+ "count": 1
}
},
"src/lib/routes/router.ts": {
@@ -312,82 +1051,298 @@
"count": 1
}
},
+ "src/lib/sharing.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
"src/lib/strings/errors.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 14
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 14
+ }
+ },
+ "src/lib/translation/index.tsx": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/lib/useGetEmojis/getEmojis.ts": {
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/logger/transports/sentry.ts": {
+ "@typescript-eslint/no-unsafe-enum-comparison": {
+ "count": 3
+ }
+ },
+ "src/logger/types.ts": {
+ "@typescript-eslint/no-redundant-type-constituents": {
+ "count": 1
}
},
"src/screens/Bookmarks/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
}
},
"src/screens/Deactivated.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/screens/E2E/SharedPreferencesTesterScreen.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 6
+ },
+ "@typescript-eslint/require-await": {
+ "count": 6
+ }
+ },
+ "src/screens/Feeds/NoFollowingFeed.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Feeds/NoSavedFeedsOfAnyType.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Hashtag.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Home/NoFeedsPinned.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/List/ListHiddenScreen.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Log.tsx": {
+ "@typescript-eslint/no-unsafe-enum-comparison": {
+ "count": 2
}
},
"src/screens/Login/ChooseAccountForm.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/screens/Login/ForgotPasswordForm.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/screens/Login/LoginForm.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 4
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 4
+ },
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/screens/Login/SetNewPasswordForm.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/screens/Login/index.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "react-hooks/purity": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 1
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/screens/Messages/ChatList.tsx": {
+ "react-hooks/immutability": {
+ "count": 1
}
},
"src/screens/Moderation/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
}
},
"src/screens/ModerationInteractionSettings/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx": {
+ "react-hooks/refs": {
+ "count": 4
}
},
"src/screens/Onboarding/StepFinished/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/screens/Onboarding/StepInterests/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/screens/Onboarding/StepProfile/index.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 4
}
},
"src/screens/PostThread/index.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
},
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
+ },
"react-hooks/preserve-manual-memoization": {
"count": 1
+ },
+ "react-hooks/refs": {
+ "count": 4
}
},
"src/screens/Profile/Header/EditProfileDialog.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 3
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/screens/Profile/Header/ProfileHeaderLabeler.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
}
},
"src/screens/Profile/Header/Shell.tsx": {
@@ -395,29 +1350,164 @@
"count": 1
}
},
+ "src/screens/Profile/KnownFollowers.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ }
+ },
"src/screens/Profile/Sections/Feed.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
}
},
"src/screens/Profile/components/GermButton.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/screens/Profile/components/ProfileFeedHeader.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 5
+ }
+ },
+ "src/screens/ProfileList/FeedSection.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/ProfileList/components/Header.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ }
+ },
+ "src/screens/ProfileList/components/MoreOptionsMenu.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 5
+ }
+ },
+ "src/screens/ProfileList/components/SubscribeMenu.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ }
+ },
+ "src/screens/ProfileList/index.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/SavedFeeds.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ }
+ },
+ "src/screens/Search/Shell.tsx": {
+ "react-hooks/refs": {
+ "count": 4
+ }
+ },
+ "src/screens/Search/modules/ExploreSuggestedAccounts.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Search/modules/ExploreTrendingTopics.tsx": {
+ "react-hooks/purity": {
+ "count": 2
+ }
+ },
+ "src/screens/Search/modules/ExploreTrendingVideos.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Settings/AboutSettings.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Settings/AppPasswords.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Settings/AutomationLabelSettings.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/screens/Settings/FindContactsSettings.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/screens/Settings/InterestsSettings.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/screens/Settings/components/ChangeHandleDialog.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 4
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/screens/Settings/components/ChangePasswordDialog.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/screens/Settings/components/CopyButton.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
}
},
"src/screens/Settings/components/DeactivateAccountDialog.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/screens/Settings/components/DeleteAccountDialog.tsx": {
@@ -425,14 +1515,114 @@
"count": 2
}
},
+ "src/screens/Settings/components/DisableEmail2FADialog.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 4
+ }
+ },
+ "src/screens/Settings/components/OTAInfo.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/Signup/StepCaptcha/CaptchaWebView.tsx": {
+ "react-hooks/purity": {
+ "count": 1
+ }
+ },
+ "src/screens/Signup/StepHandle/index.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
"src/screens/Signup/StepInfo/Policies.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
+ "src/screens/Signup/StepInfo/index.tsx": {
+ "react-hooks/refs": {
+ "count": 3
+ }
+ },
"src/screens/SignupQueued.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/screens/StarterPack/StarterPackLandingScreen.tsx": {
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/screens/StarterPack/StarterPackScreen.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/screens/StarterPack/Wizard/StepFeeds.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/StarterPack/Wizard/StepProfiles.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/screens/StarterPack/Wizard/index.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/screens/Topic.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/state/a11y.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/cache/profile-shadow.ts": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/state/cache/thread-mutes.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/state/cache/types.ts": {
@@ -443,31 +1633,213 @@
"src/state/feed-feedback.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 3
+ },
+ "react-hooks/refs": {
+ "count": 2
+ }
+ },
+ "src/state/gallery.ts": {
+ "@typescript-eslint/prefer-promise-reject-errors": {
+ "count": 1
}
},
"src/state/messages/events/agent.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 4
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/state/messages/message-drafts.tsx": {
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/state/persisted/index.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/state/persisted/index.web.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 4
}
},
"src/state/persisted/util.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/alt-text-required.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/autoplay.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/disable-haptics.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/external-embeds-prefs.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/hidden-posts.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/in-app-browser.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/kawaii.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/languages.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/large-alt-badge.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/subtitles.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/trending.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/preferences/used-starter-packs.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/queries/activity-subscriptions.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/queries/actor-autocomplete.ts": {
+ "react-hooks/immutability": {
+ "count": 2
+ }
+ },
+ "src/state/queries/app-passwords.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
+ "src/state/queries/bookmarks/useBookmarkMutation.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
+ "src/state/queries/bookmarks/useBookmarksQuery.ts": {
+ "@typescript-eslint/require-await": {
+ "count": 2
+ }
+ },
+ "src/state/queries/explore-feed-previews.tsx": {
+ "react-hooks/refs": {
+ "count": 4
+ }
+ },
+ "src/state/queries/feed.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/queries/handle.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/queries/list-memberships.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
+ "src/state/queries/list.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 7
+ }
+ },
+ "src/state/queries/messages/accept-conversation.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
+ "src/state/queries/messages/update-all-read.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ }
+ },
+ "src/state/queries/my-lists.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/state/queries/notifications/feed.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/state/queries/notifications/unread.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 4
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 4
+ },
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/state/queries/nuxs/definitions.ts": {
@@ -478,109 +1850,386 @@
"src/state/queries/pinned-post.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/state/queries/post-feed.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 3
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
}
},
"src/state/queries/postgate/index.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
+ },
+ "@typescript-eslint/require-await": {
+ "count": 2
+ }
+ },
+ "src/state/queries/preferences/index.ts": {
+ "react-hooks/immutability": {
+ "count": 1
+ }
+ },
+ "src/state/queries/preferences/useThreadPreferences.ts": {
+ "react-hooks/refs": {
+ "count": 2
}
},
"src/state/queries/search-posts.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/state/queries/starter-packs.ts": {
+ "@typescript-eslint/require-await": {
+ "count": 1
+ },
+ "react-hooks/immutability": {
+ "count": 1
+ }
+ },
+ "src/state/queries/suggested-follows.ts": {
+ "@typescript-eslint/no-unused-vars": {
+ "count": 1
}
},
"src/state/queries/threadgate/index.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ },
+ "@typescript-eslint/require-await": {
+ "count": 3
}
},
"src/state/queries/util.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/state/session/agent.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ }
+ },
+ "src/state/shell/color-mode.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
+ "src/state/shell/composer/index.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/shell/onboarding.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 5
}
},
"src/state/shell/progress-guide.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 4
+ }
+ },
+ "src/state/shell/reminders.ts": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/state/shell/tick-every-minute.tsx": {
+ "react-hooks/purity": {
+ "count": 1
+ }
+ },
+ "src/storage/archive/index.ts": {
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/storage/index.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 8
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/view/com/auth/SplashScreen.web.tsx": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
}
},
"src/view/com/composer/Composer.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
},
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 4
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ },
+ "react-hooks/immutability": {
+ "count": 2
+ },
"react-hooks/preserve-manual-memoization": {
"count": 3
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/view/com/composer/SelectMediaButton.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/view/com/composer/drafts/DraftsButton.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/view/com/composer/drafts/state/queries.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/view/com/composer/drafts/state/storage.ts": {
+ "@typescript-eslint/require-await": {
+ "count": 3
+ }
+ },
+ "src/view/com/composer/photos/EditImageDialog.web.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/view/com/composer/photos/Gallery.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
}
},
"src/view/com/composer/photos/OpenCameraBtn.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/view/com/composer/select-language/SuggestedLanguage.tsx": {
+ "react-hooks/refs": {
+ "count": 1
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/view/com/composer/text-input/TextInput.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/view/com/composer/text-input/TextInput.web.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 1
+ }
+ },
+ "src/view/com/composer/text-input/web/Autocomplete.tsx": {
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
+ }
+ },
+ "src/view/com/composer/videos/pickVideo.web.ts": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
}
},
"src/view/com/feeds/ComposerPrompt.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ }
+ },
+ "src/view/com/feeds/FeedPage.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/view/com/feeds/ProfileFeedgens.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 3
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
}
},
"src/view/com/lists/ListMembers.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 3
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
}
},
"src/view/com/lists/MyLists.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 6
}
},
"src/view/com/lists/ProfileLists.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 3
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/view/com/modals/Modal.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/require-await": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/view/com/notifications/NotificationFeed.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 6
+ },
+ "react-hooks/set-state-in-effect": {
+ "count": 1
}
},
"src/view/com/notifications/NotificationFeedItem.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 3
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
}
},
"src/view/com/pager/Pager.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 4
+ },
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/view/com/pager/Pager.web.tsx": {
+ "react-hooks/immutability": {
+ "count": 1
+ },
"react-hooks/preserve-manual-memoization": {
"count": 1
+ },
+ "react-hooks/refs": {
+ "count": 1
}
},
"src/view/com/pager/PagerWithHeader.tsx": {
@@ -593,14 +2242,46 @@
"count": 2
}
},
+ "src/view/com/pager/TabBar.tsx": {
+ "react-hooks/immutability": {
+ "count": 1
+ }
+ },
"src/view/com/pager/TabBar.web.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 4
+ }
+ },
+ "src/view/com/post-thread/PostLikedBy.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ }
+ },
+ "src/view/com/post-thread/PostQuotes.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/view/com/post-thread/PostRepostedBy.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
}
},
"src/view/com/posts/FeedShutdownMsg.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
}
},
"src/view/com/posts/PostFeed.tsx": {
@@ -611,16 +2292,47 @@
"src/view/com/posts/PostFeedErrorMessage.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 8
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 9
+ }
+ },
+ "src/view/com/profile/ProfileFollowers.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ }
+ },
+ "src/view/com/profile/ProfileFollows.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
}
},
"src/view/com/profile/ProfileMenu.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 6
+ },
+ "@typescript-eslint/no-floating-promises": {
+ "count": 4
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 6
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 12
}
},
"src/view/com/testing/TestCtrls.e2e.tsx": {
"@typescript-eslint/no-explicit-any": {
- "count": 2
+ "count": 1
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 9
}
},
"src/view/com/util/EmptyState.tsx": {
@@ -631,16 +2343,36 @@
"src/view/com/util/ErrorBoundary.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/view/com/util/EventStopper.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/view/com/util/Link.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 2
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 2
+ }
+ },
+ "src/view/com/util/LoadingPlaceholder.tsx": {
+ "react-hooks/purity": {
+ "count": 1
}
},
"src/view/com/util/MainScrollProvider.tsx": {
@@ -648,9 +2380,22 @@
"count": 2
}
},
+ "src/view/com/util/UserAvatar.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ }
+ },
+ "src/view/com/util/UserBanner.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ }
+ },
"src/view/com/util/ViewSelector.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 6
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
}
},
"src/view/com/util/Views.tsx": {
@@ -658,14 +2403,101 @@
"count": 1
}
},
+ "src/view/com/util/Views.web.tsx": {
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/view/com/util/forms/Button.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
"src/view/screens/Debug.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 4
+ }
+ },
+ "src/view/screens/Feeds.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/view/screens/Home.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 1
+ }
+ },
+ "src/view/screens/ModerationBlockedAccounts.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ }
+ },
+ "src/view/screens/ModerationMutedAccounts.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 1
+ }
+ },
+ "src/view/screens/Notifications.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 3
+ }
+ },
+ "src/view/screens/Storybook/ListContained.tsx": {
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 2
+ }
+ },
+ "src/view/screens/Storybook/Storybook.tsx": {
+ "@typescript-eslint/no-misused-promises": {
+ "count": 1
+ }
+ },
+ "src/view/shell/Drawer.tsx": {
+ "@typescript-eslint/no-floating-promises": {
+ "count": 2
}
},
"src/view/shell/createNativeStackNavigatorWithAuth.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+ },
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 1
+ },
+ "react-hooks/refs": {
+ "count": 13
+ }
+ },
+ "src/view/shell/desktop/Search.tsx": {
+ "@typescript-eslint/no-unsafe-call": {
+ "count": 3
+ },
+ "@typescript-eslint/no-unsafe-member-access": {
+ "count": 3
+ },
+ "react-hooks/refs": {
+ "count": 3
+ }
+ },
+ "src/view/shell/index.web.tsx": {
+ "react-hooks/set-state-in-effect": {
+ "count": 1
}
}
}
\ No newline at end of file
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 50ab8e5da4..c8addf66ae 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -134,11 +134,10 @@ export default defineConfig(
'react-native/no-inline-styles': 'off',
...reactNativeA11y.configs.all.rules,
'react-compiler/react-compiler': 'warn',
- // TODO: Fix these and set to error
- 'react-hooks/set-state-in-effect': 'warn',
- 'react-hooks/purity': 'warn',
- 'react-hooks/refs': 'warn',
- 'react-hooks/immutability': 'warn',
+ 'react-hooks/set-state-in-effect': 'error',
+ 'react-hooks/purity': 'error',
+ 'react-hooks/refs': 'error',
+ 'react-hooks/immutability': 'error',
/**
* Import sorting
@@ -235,9 +234,8 @@ export default defineConfig(
},
],
/**
- * Maintain previous behavior - these are stricter in typescript-eslint
- * v8 `warn` ones are probably worth fixing. `off` ones are a bit too
- * nit-picky
+ * Maintain previous behavior via eslint-suppressions.json - these are
+ * stricter in typescript-eslint v8. `off` ones are a bit too nit-picky.
*/
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/ban-ts-comment': 'off',
@@ -247,18 +245,18 @@ export default defineConfig(
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
- '@typescript-eslint/no-unsafe-member-access': 'warn',
- '@typescript-eslint/no-unsafe-call': 'warn',
- '@typescript-eslint/no-floating-promises': 'warn',
- '@typescript-eslint/no-misused-promises': 'warn',
- '@typescript-eslint/require-await': 'warn',
- '@typescript-eslint/no-unsafe-enum-comparison': 'warn',
- '@typescript-eslint/no-unnecessary-type-assertion': 'warn',
- '@typescript-eslint/no-redundant-type-constituents': 'warn',
- '@typescript-eslint/no-duplicate-type-constituents': 'warn',
- '@typescript-eslint/no-base-to-string': 'warn',
- '@typescript-eslint/prefer-promise-reject-errors': 'warn',
- '@typescript-eslint/await-thenable': 'warn',
+ '@typescript-eslint/no-unsafe-member-access': 'error',
+ '@typescript-eslint/no-unsafe-call': 'error',
+ '@typescript-eslint/no-floating-promises': 'error',
+ '@typescript-eslint/no-misused-promises': 'error',
+ '@typescript-eslint/require-await': 'error',
+ '@typescript-eslint/no-unsafe-enum-comparison': 'error',
+ '@typescript-eslint/no-unnecessary-type-assertion': 'error',
+ '@typescript-eslint/no-redundant-type-constituents': 'error',
+ '@typescript-eslint/no-duplicate-type-constituents': 'error',
+ '@typescript-eslint/no-base-to-string': 'error',
+ '@typescript-eslint/prefer-promise-reject-errors': 'error',
+ '@typescript-eslint/await-thenable': 'error',
'no-restricted-imports': [
'error',
diff --git a/package.json b/package.json
index b8bd26e241..c0670da958 100644
--- a/package.json
+++ b/package.json
@@ -93,7 +93,7 @@
"prettier": "prettier --check ."
},
"dependencies": {
- "@atproto/api": "0.20.8",
+ "@atproto/api": "0.20.9",
"@atproto/syntax": "0.6.1",
"@bitdrift/react-native": "^0.6.8",
"@braintree/sanitize-url": "^6.0.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b3d0003107..564e39f8f0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -242,8 +242,8 @@ importers:
.:
dependencies:
'@atproto/api':
- specifier: 0.20.8
- version: 0.20.8
+ specifier: 0.20.9
+ version: 0.20.9
'@atproto/syntax':
specifier: 0.6.1
version: 0.6.1
@@ -877,8 +877,8 @@ packages:
graphql:
optional: true
- '@atproto/api@0.20.8':
- resolution: {integrity: sha512-rTkA6kOmA2axSrg6VgpdXpsCFWpofnHBOn6pKg69Ju5MpIHqk4haQMgjBcVh1G3kUxzwgSAr7SYrPS3dFe5Etg==}
+ '@atproto/api@0.20.9':
+ resolution: {integrity: sha512-Yuw7Ewn+yMJZ8GskbuvI3lKPW65rsXic1xjFA2Dpq6H8WjVYs6xNZ31bkwtTYDDwjKIZcJmAVbAVgdfjo4T9iw==}
engines: {node: '>=22'}
'@atproto/common-web@0.5.0':
@@ -9493,7 +9493,7 @@ snapshots:
'@0no-co/graphql.web@1.2.0': {}
- '@atproto/api@0.20.8':
+ '@atproto/api@0.20.9':
dependencies:
'@atproto/common-web': 0.5.0
'@atproto/lexicon': 0.7.1
diff --git a/src/components/MediaPreview.tsx b/src/components/MediaPreview.tsx
index 996e3b18e8..de07a03b23 100644
--- a/src/components/MediaPreview.tsx
+++ b/src/components/MediaPreview.tsx
@@ -1,6 +1,10 @@
import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native'
import {Image} from 'expo-image'
-import {type AppBskyEmbedImages, type AppBskyFeedDefs} from '@atproto/api'
+import {
+ AppBskyEmbedGallery,
+ type AppBskyEmbedImages,
+ type AppBskyFeedDefs,
+} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {shareImageModal} from '#/lib/media/manip'
@@ -52,23 +56,26 @@ export function Embed({
// a 10-image gallery doesn't blow out the row width.
return (
- {e.view.items.slice(0, 4).map(item => {
- const image: AppBskyEmbedImages.ViewImage = {
- thumb: item.thumbnail,
- fullsize: item.fullsize,
- alt: item.alt,
- aspectRatio: item.aspectRatio,
- }
- return peekable ? (
-
- ) : (
-
- )
- })}
+ {e.view.items
+ .filter(AppBskyEmbedGallery.isViewImage)
+ .slice(0, 4)
+ .map(item => {
+ const image: AppBskyEmbedImages.ViewImage = {
+ thumb: item.thumbnail,
+ fullsize: item.fullsize,
+ alt: item.alt,
+ aspectRatio: item.aspectRatio,
+ }
+ return peekable ? (
+
+ ) : (
+
+ )
+ })}
)
} else if (e.type === 'link') {
diff --git a/src/components/Post/Embed/ImageEmbed.tsx b/src/components/Post/Embed/ImageEmbed.tsx
index 5b51c29f5b..fca9cec5c8 100644
--- a/src/components/Post/Embed/ImageEmbed.tsx
+++ b/src/components/Post/Embed/ImageEmbed.tsx
@@ -2,7 +2,7 @@ import {useRef} from 'react'
import {InteractionManager, View} from 'react-native'
import {type AnimatedRef} from 'react-native-reanimated'
import {Image} from 'expo-image'
-import {type AppBskyEmbedImages} from '@atproto/api'
+import {AppBskyEmbedGallery, type AppBskyEmbedImages} from '@atproto/api'
import {atoms as a, tokens} from '#/alf'
import {AutoSizedImage} from '#/components/images/AutoSizedImage'
@@ -28,7 +28,7 @@ export function ImageEmbed({
const {openLightbox} = useLightboxControls()
const images: AppBskyEmbedImages.ViewImage[] =
embed.type === 'gallery'
- ? embed.view.items.map(item => ({
+ ? embed.view.items.filter(AppBskyEmbedGallery.isViewImage).map(item => ({
thumb: item.thumbnail,
fullsize: item.fullsize,
alt: item.alt,
@@ -101,8 +101,7 @@ export function ImageEmbed({
crop={
rest.viewContext === PostEmbedViewContext.ThreadHighlighted
? 'none'
- : rest.viewContext ===
- PostEmbedViewContext.FeedEmbedRecordWithMedia
+ : rest.isWithinQuote
? 'square'
: 'constrained'
}
@@ -117,10 +116,7 @@ export function ImageEmbed({
onPress(0, [containerRef], [dims])
}
onPressIn={() => onPressIn(0)}
- hideBadge={
- rest.viewContext ===
- PostEmbedViewContext.FeedEmbedRecordWithMedia
- }
+ hideBadge={rest.isWithinQuote}
/>
@@ -135,6 +131,7 @@ export function ImageEmbed({
onPress={onPress}
onPressIn={onPressIn}
viewContext={rest.viewContext}
+ isWithinQuote={rest.isWithinQuote}
/>
)
diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
index 916a7ec2d9..5fdf18e2a1 100644
--- a/src/components/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -224,6 +224,7 @@ export function Basic({
cancelButtonCta,
confirmButtonCta,
onConfirm,
+ onClose,
confirmButtonColor,
showCancel = true,
}: React.PropsWithChildren<{
@@ -240,11 +241,12 @@ export function Basic({
* should NOT close the dialog as a side effect of this method.
*/
onConfirm: (e: GestureResponderEvent) => void
+ onClose?: () => void
confirmButtonColor?: ButtonColor
showCancel?: boolean
}>) {
return (
-
+
{title}
{description && {description}}
diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx
index ac5bc4889a..3e11cf051e 100644
--- a/src/components/Toast/Toast.tsx
+++ b/src/components/Toast/Toast.tsx
@@ -8,8 +8,10 @@ import {
type UninheritableButtonProps,
} from '#/components/Button'
import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck'
-import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
-import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
+import {
+ CircleInfo_Stroke2_Corner0_Rounded as CircleInfo,
+ CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon,
+} from '#/components/icons/CircleInfo'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
import {dismiss} from '#/components/Toast/sonner'
diff --git a/src/components/dms/AfterReportDialog.tsx b/src/components/dms/AfterReportDialog.tsx
index 3df194fc8b..060ea1fcfb 100644
--- a/src/components/dms/AfterReportDialog.tsx
+++ b/src/components/dms/AfterReportDialog.tsx
@@ -33,14 +33,19 @@ export const AfterReportDialog = memo(function BlockOrDeleteDialogInner({
control,
params,
currentScreen,
+ onClose,
}: {
control: Dialog.DialogControlProps
params: ReportDialogParams
currentScreen: 'list' | 'conversation'
+ onClose?: () => void
}): React.ReactNode {
const {t: l} = useLingui()
return (
-
+
{
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
- convo
- .deleteMessage(message.id)
- .then(() => Toast.show(l({message: 'Message deleted', context: 'toast'})))
- .catch(() => Toast.show(l`Failed to delete message`))
- }, [l, convo, message.id])
-
const onEmojiSelect = useCallback(
(emoji: string) => {
if (
@@ -128,104 +112,72 @@ export let MessageContextMenu = ({
const sender = senderProfile
return (
- <>
-
- {IS_NATIVE && reactionsAvailable && (
-
-
-
- )}
-
-
- {children}
-
-
-
+ {IS_NATIVE && reactionsAvailable && (
+
- {message.text.length > 0 && (
- <>
-
-
- {l`Translate`}
-
-
-
-
- {l`Copy message text`}
-
-
- >
- )}
+
+
+ )}
+
+
+ {children}
+
+
+
+ {message.text.length > 0 && (
+ <>
+
+
+ {l`Translate`}
+
+
+
+
+ {l`Copy message text`}
+
+
+ >
+ )}
+ openDeleteMessage(message)}>
+
+ {l`Delete for me`}
+
+ {!isFromSelf && (
deleteControl.open()}>
-
- {l`Delete for me`}
+ testID="messageDropdownReportBtn"
+ label={l`Report message`}
+ onPress={() => openReportMessage(message, senderProfile)}>
+
+ {l`Report`}
- {!isFromSelf && (
- reportControl.open()}>
-
- {l`Report`}
-
- )}
-
-
- {
- if (sender) {
- unstableCacheProfileView(queryClient, sender)
- }
- blockOrDeleteControl.open()
- }}
- />
-
-
- >
+ )}
+
+
)
}
MessageContextMenu = memo(MessageContextMenu)
diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx
index 6f1819ada2..ac6acb5891 100644
--- a/src/components/dms/MessageItem.tsx
+++ b/src/components/dms/MessageItem.tsx
@@ -40,8 +40,8 @@ import {useSession} from '#/state/session'
import {atoms as a, native, platform, useTheme} from '#/alf'
import {isOnlyEmoji} from '#/alf/typography'
import {Button} from '#/components/Button'
-import {useDialogControl} from '#/components/Dialog'
import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
+import {useMessageDialogs} from '#/components/dms/MessageOverlays'
import {InlineLinkText, Link} from '#/components/Link'
import * as ProfileCard from '#/components/ProfileCard'
import * as Prompt from '#/components/Prompt'
@@ -49,7 +49,7 @@ import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
import {DateDivider} from './DateDivider'
import {MessageItemEmbed} from './MessageItemEmbed'
-import {ReactionsDialog} from './ReactionsDialog'
+import {groupReactions} from './ReactionsDialog'
import {CLUSTERED_MESSAGE_THRESHOLD_MS, MESSAGE_GAP_THRESHOLD_MS} from './util'
const AVATAR_SIZE = 28
@@ -118,7 +118,7 @@ let MessageItem = ({
const {message} = item
const profile = useMaybeProfileShadow(relatedProfiles.get(message.sender.did))
- const reactionsControl = useDialogControl()
+ const {openReactions} = useMessageDialogs()
const isPending = item.type === 'pending-message'
@@ -243,34 +243,10 @@ let MessageItem = ({
)
- const groupedReactions = useMemo(() => {
- const reactions = message.reactions ?? []
- const grouped = new Map<
- string,
- {
- key: string
- value: string
- senders: ChatBskyConvoDefs.ReactionViewSender[]
- count: number
- }
- >()
- for (const reaction of reactions) {
- if (!reaction) continue
- const existing = grouped.get(reaction.value)
- if (existing) {
- existing.senders.push(reaction.sender)
- existing.count++
- } else {
- grouped.set(reaction.value, {
- key: reaction.value,
- value: reaction.value,
- senders: [reaction.sender],
- count: 1,
- })
- }
- }
- return Array.from(grouped.values())
- }, [message.reactions])
+ const groupedReactions = useMemo(
+ () => groupReactions(message.reactions),
+ [message.reactions],
+ )
const reactions = useMemo(() => message.reactions ?? [], [message.reactions])
@@ -336,7 +312,7 @@ let MessageItem = ({
transform: [{translateY: -8}],
},
]}
- onPress={isGroupChat ? reactionsControl.open : undefined}>
+ onPress={isGroupChat ? () => openReactions(message) : undefined}>
{groupedReactions.map(group => (
) : null}
-
)
diff --git a/src/components/dms/MessageOverlays.tsx b/src/components/dms/MessageOverlays.tsx
new file mode 100644
index 0000000000..41228f2899
--- /dev/null
+++ b/src/components/dms/MessageOverlays.tsx
@@ -0,0 +1,175 @@
+import {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react'
+import {LayoutAnimation} from 'react-native'
+import {type ChatBskyConvoDefs} from '@atproto/api'
+import {useLingui} from '@lingui/react/macro'
+import {useQueryClient} from '@tanstack/react-query'
+
+import {useConvoActive} from '#/state/messages/convo'
+import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
+import {useDialogControl} from '#/components/Dialog'
+import {AfterReportDialog} from '#/components/dms/AfterReportDialog'
+import {ReactionsDialog} from '#/components/dms/ReactionsDialog'
+import {ReportDialog} from '#/components/moderation/ReportDialog'
+import * as Prompt from '#/components/Prompt'
+import {usePromptControl} from '#/components/Prompt'
+import * as Toast from '#/components/Toast'
+import type * as bsky from '#/types/bsky'
+
+type MessageDialogsContextType = {
+ openDeleteMessage: (message: ChatBskyConvoDefs.MessageView) => void
+ openReportMessage: (
+ message: ChatBskyConvoDefs.MessageView,
+ senderProfile: bsky.profile.AnyProfileView | undefined,
+ ) => void
+ openReactions: (message: ChatBskyConvoDefs.MessageView) => void
+}
+
+const Context = createContext(null)
+
+export function useMessageDialogs() {
+ const ctx = useContext(Context)
+ if (!ctx) {
+ throw new Error('useMessageDialogs must be used within a MessageOverlays')
+ }
+ return ctx
+}
+
+export function MessageOverlays({children}: {children: React.ReactNode}) {
+ const {t: l} = useLingui()
+ const queryClient = useQueryClient()
+ const convo = useConvoActive()
+
+ const deleteControl = usePromptControl()
+ const reportControl = usePromptControl()
+ const afterReportControl = usePromptControl()
+ const reactionsControl = useDialogControl()
+
+ const [deleteTarget, setDeleteTarget] =
+ useState(null)
+ const [reportTarget, setReportTarget] = useState<{
+ message: ChatBskyConvoDefs.MessageView
+ senderProfile: bsky.profile.AnyProfileView | undefined
+ } | null>(null)
+ const [afterReportTarget, setAfterReportTarget] =
+ useState(null)
+ const [reactionsTarget, setReactionsTarget] =
+ useState(null)
+
+ const openDeleteMessage = useCallback(
+ (message: ChatBskyConvoDefs.MessageView) => {
+ setDeleteTarget(message)
+ deleteControl.open()
+ },
+ [deleteControl],
+ )
+
+ const openReportMessage = useCallback(
+ (
+ message: ChatBskyConvoDefs.MessageView,
+ senderProfile: bsky.profile.AnyProfileView | undefined,
+ ) => {
+ setReportTarget({message, senderProfile})
+ reportControl.open()
+ },
+ [reportControl],
+ )
+
+ const openReactions = useCallback(
+ (message: ChatBskyConvoDefs.MessageView) => {
+ setReactionsTarget(message)
+ },
+ [],
+ )
+
+ // These dialogs are conditionally mounted, so we can't open them in the same
+ // tick that we set their targets - the control refs aren't attached yet. Open
+ // in an effect after the dialog has mounted.
+ useEffect(() => {
+ if (reactionsTarget) {
+ reactionsControl.open()
+ }
+ }, [reactionsTarget, reactionsControl])
+
+ useEffect(() => {
+ if (afterReportTarget) {
+ afterReportControl.open()
+ }
+ }, [afterReportTarget, afterReportControl])
+
+ const onConfirmDelete = useCallback(() => {
+ if (!deleteTarget) return
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
+ convo
+ .deleteMessage(deleteTarget.id)
+ .then(() => Toast.show(l({message: 'Message deleted', context: 'toast'})))
+ .catch(() => Toast.show(l`Failed to delete message`))
+ }, [l, convo, deleteTarget])
+
+ const onAfterReportSubmit = useCallback(() => {
+ if (!reportTarget) return
+ if (reportTarget.senderProfile) {
+ unstableCacheProfileView(queryClient, reportTarget.senderProfile)
+ }
+ setAfterReportTarget(reportTarget.message)
+ }, [queryClient, reportTarget])
+
+ const ctx = useMemo(
+ () => ({openDeleteMessage, openReportMessage, openReactions}),
+ [openDeleteMessage, openReportMessage, openReactions],
+ )
+
+ const reportSubject = reportTarget
+ ? ({
+ view: 'message',
+ convoId: convo.convo.view.id,
+ message: reportTarget.message,
+ } as const)
+ : undefined
+
+ return (
+
+ {children}
+ setReportTarget(null)}
+ />
+ {afterReportTarget && (
+ setAfterReportTarget(null)}
+ />
+ )}
+ {reactionsTarget && (
+ setReactionsTarget(null)}
+ />
+ )}
+ setDeleteTarget(null)}
+ />
+
+ )
+}
diff --git a/src/components/dms/ReactionsDialog.tsx b/src/components/dms/ReactionsDialog.tsx
index 576ef4cead..8119cc1b7c 100644
--- a/src/components/dms/ReactionsDialog.tsx
+++ b/src/components/dms/ReactionsDialog.tsx
@@ -1,4 +1,4 @@
-import {useRef, useState} from 'react'
+import {useMemo, useRef, useState} from 'react'
import {
LayoutAnimation,
Pressable,
@@ -37,14 +37,12 @@ export function ReactionsDialog({
control,
relatedProfiles,
message,
- reactions,
- groupedReactions,
+ onClose,
}: {
control: Dialog.DialogControlProps
relatedProfiles: Map
message: ChatBskyConvoDefs.MessageView
- reactions?: ChatBskyConvoDefs.ReactionView[]
- groupedReactions?: Reaction[]
+ onClose?: () => void
}) {
const {t: l} = useLingui()
@@ -54,6 +52,9 @@ export function ReactionsDialog({
const [selected, setSelected] = useState('all')
+ const reactions = message.reactions
+ const groupedReactions = useMemo(() => groupReactions(reactions), [reactions])
+
const filteredReactions = reactions?.filter(
r => selected === 'all' || r.value === selected,
)
@@ -78,7 +79,10 @@ export function ReactionsDialog({
return (
setSelected('all')}
+ onClose={() => {
+ setSelected('all')
+ onClose?.()
+ }}
nativeOptions={{
preventExpansion: true,
minHeight: screenHeight / 2,
@@ -388,3 +392,25 @@ function ReactionTab({
)
}
+
+export function groupReactions(
+ reactions: ChatBskyConvoDefs.ReactionView[] | undefined,
+): Reaction[] {
+ const grouped = new Map()
+ for (const reaction of reactions ?? []) {
+ if (!reaction) continue
+ const existing = grouped.get(reaction.value)
+ if (existing) {
+ existing.senders.push(reaction.sender)
+ existing.count++
+ } else {
+ grouped.set(reaction.value, {
+ key: reaction.value,
+ value: reaction.value,
+ senders: [reaction.sender],
+ count: 1,
+ })
+ }
+ }
+ return Array.from(grouped.values())
+}
diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx
index f4ffcd7159..fe2b63e606 100644
--- a/src/components/images/Gallery/index.tsx
+++ b/src/components/images/Gallery/index.tsx
@@ -54,6 +54,7 @@ interface GalleryProps {
) => void
onPressIn?: (index: number) => void
viewContext?: PostEmbedViewContext
+ isWithinQuote?: boolean
}
const Context = createContext<{
@@ -97,6 +98,7 @@ export function Gallery({
onPress,
onPressIn,
viewContext,
+ isWithinQuote,
}: GalleryProps) {
const {t: l} = useLingui()
const ax = useAnalytics()
@@ -104,14 +106,21 @@ export function Gallery({
const largeAltBadge = useLargeAltBadgeEnabled()
const bps = useBreakpoints()
const window = useWindowDimensions()
- const isWithinQuote =
- viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
const isWithinChat = viewContext === PostEmbedViewContext.ChatMessage
const hideBadges = isWithinQuote
const contentHeight = useMemo(() => {
if (isWithinChat) {
return 120
}
+ if (isWithinQuote) {
+ if (bps.gtMobile) {
+ return 220
+ } else if (bps.gtPhone) {
+ return 190
+ } else {
+ return 150
+ }
+ }
if (bps.gtMobile) {
return 300
} else if (bps.gtPhone) {
@@ -119,7 +128,7 @@ export function Gallery({
} else {
return 200
}
- }, [bps, isWithinChat])
+ }, [bps, isWithinChat, isWithinQuote])
/*
* Container overflow styles
@@ -220,7 +229,7 @@ export function Gallery({
crop={
viewContext === PostEmbedViewContext.ThreadHighlighted
? 'none'
- : viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
+ : isWithinQuote
? 'square'
: 'constrained'
}
@@ -229,9 +238,7 @@ export function Gallery({
onPress?.(index, [containerRef], [dims])
}
onPressIn={() => onPressIn?.(index)}
- hideBadge={
- viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
- }
+ hideBadge={isWithinQuote}
/>
))}
diff --git a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts
index 6b0d8067bb..1749daf7ea 100644
--- a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts
+++ b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts
@@ -1,4 +1,5 @@
import {
+ AppBskyEmbedGallery,
AppBskyEmbedImages,
AppBskyEmbedRecordWithMedia,
type AppBskyFeedDefs,
@@ -7,7 +8,6 @@ import {
type ModerationUI,
} from '@atproto/api'
-import {AppBskyEmbedGallery} from '#/lib/api/gallery-embed-shim'
import {unique} from '#/lib/moderation'
import {type AppModerationCause} from '#/components/Pills'
import {Features, features} from '#/analytics/features'
diff --git a/src/components/moderation/ReportDialog/index.tsx b/src/components/moderation/ReportDialog/index.tsx
index c24c6feae0..d1d3aa5584 100644
--- a/src/components/moderation/ReportDialog/index.tsx
+++ b/src/components/moderation/ReportDialog/index.tsx
@@ -75,9 +75,11 @@ export function ReportDialog(
() => (props.subject ? parseReportSubject(props.subject) : undefined),
[props.subject],
)
+ const propsOnClose = props.onClose
const onClose = useCallback(() => {
ax.metric('reportDialog:close', {})
- }, [ax])
+ propsOnClose?.()
+ }, [ax, propsOnClose])
return (
diff --git a/src/components/moderation/ReportDialog/types.ts b/src/components/moderation/ReportDialog/types.ts
index 89089d138b..57ed3cd279 100644
--- a/src/components/moderation/ReportDialog/types.ts
+++ b/src/components/moderation/ReportDialog/types.ts
@@ -88,4 +88,8 @@ export type ReportDialogProps = {
* Called if the report was successfully submitted.
*/
onAfterSubmit?: () => void
+ /**
+ * Called after the dialog finishes closing.
+ */
+ onClose?: () => void
}
diff --git a/src/lib/api/gallery-embed-shim.impl.ts b/src/lib/api/gallery-embed-shim.impl.ts
deleted file mode 100644
index 3788eb9e7b..0000000000
--- a/src/lib/api/gallery-embed-shim.impl.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * Implementation backing `AppBskyEmbedGallery`. See gallery-embed-shim.ts.
- */
-import {type AppBskyEmbedDefs, type BlobRef} from '@atproto/api'
-
-export interface Main {
- $type?: 'app.bsky.embed.gallery'
- items: Image[]
-}
-
-export interface Image {
- $type?: 'app.bsky.embed.gallery#image'
- image: BlobRef
- alt: string
- aspectRatio: AppBskyEmbedDefs.AspectRatio
-}
-
-export interface View {
- $type?: 'app.bsky.embed.gallery#view'
- items: ViewImage[]
-}
-
-export interface ViewImage {
- $type?: 'app.bsky.embed.gallery#viewImage'
- thumbnail: string
- fullsize: string
- alt: string
- aspectRatio: AppBskyEmbedDefs.AspectRatio
-}
-
-export function isMain(
- v: V,
-): v is V & Main & {$type: 'app.bsky.embed.gallery'} {
- return (
- typeof v === 'object' &&
- v !== null &&
- (v as {$type?: string}).$type === 'app.bsky.embed.gallery'
- )
-}
-
-export function isImage(
- v: V,
-): v is V & Image & {$type: 'app.bsky.embed.gallery#image'} {
- return (
- typeof v === 'object' &&
- v !== null &&
- (v as {$type?: string}).$type === 'app.bsky.embed.gallery#image'
- )
-}
-
-export function isView(
- v: V,
-): v is V & View & {$type: 'app.bsky.embed.gallery#view'} {
- return (
- typeof v === 'object' &&
- v !== null &&
- (v as {$type?: string}).$type === 'app.bsky.embed.gallery#view'
- )
-}
-
-export function isViewImage(
- v: V,
-): v is V & ViewImage & {$type: 'app.bsky.embed.gallery#viewImage'} {
- return (
- typeof v === 'object' &&
- v !== null &&
- (v as {$type?: string}).$type === 'app.bsky.embed.gallery#viewImage'
- )
-}
diff --git a/src/lib/api/gallery-embed-shim.ts b/src/lib/api/gallery-embed-shim.ts
deleted file mode 100644
index b65a098985..0000000000
--- a/src/lib/api/gallery-embed-shim.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * Local shim for `app.bsky.embed.gallery` until @atproto/api ships the
- * generated types. Mirrors the shape from atproto PR #4827:
- * https://github.com/bluesky-social/atproto/pull/4827
- *
- * Once the lexicon ships and we bump @atproto/api, delete this file and
- * replace `import {AppBskyEmbedGallery} from '#/lib/api/gallery-embed-shim'`
- * with `import {AppBskyEmbedGallery} from '@atproto/api'`.
- */
-import * as gallery from './gallery-embed-shim.impl'
-
-export {gallery as AppBskyEmbedGallery}
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 283f552ec6..4e0a8d47c9 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -1,6 +1,7 @@
import {
type $Typed,
type AppBskyEmbedExternal,
+ type AppBskyEmbedGallery,
type AppBskyEmbedImages,
type AppBskyEmbedRecord,
type AppBskyEmbedRecordWithMedia,
@@ -21,7 +22,6 @@ import {sha256} from 'js-sha256'
import {CID} from 'multiformats/cid'
import * as Hasher from 'multiformats/hashes/hasher'
-import {type AppBskyEmbedGallery} from '#/lib/api/gallery-embed-shim'
import {isNetworkError} from '#/lib/strings/errors'
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
import {logger} from '#/logger'
@@ -347,14 +347,14 @@ async function resolveMedia(
count: imagesDraft.length,
})
onStateChange?.(t`Uploading images...`)
- const items: AppBskyEmbedGallery.Image[] = await Promise.all(
+ const items: $Typed[] = await Promise.all(
imagesDraft.map(async (image, i) => {
logger.debug(`Compressing gallery image #${i}`)
const {path, width, height, mime} = await compressImage(image)
logger.debug(`Uploading gallery image #${i}`)
const res = await uploadBlob(agent, path, mime)
return {
- $type: 'app.bsky.embed.gallery#image',
+ $type: 'app.bsky.embed.gallery#image' as const,
image: res.data.blob,
alt: image.alt,
aspectRatio: {width, height},
diff --git a/src/lib/hooks/useTimeAgo.ts b/src/lib/hooks/useTimeAgo.ts
index 67398ec547..ffc808a91f 100644
--- a/src/lib/hooks/useTimeAgo.ts
+++ b/src/lib/hooks/useTimeAgo.ts
@@ -58,12 +58,12 @@ export function dateDiff(
if (diffSeconds < NOW) {
diff = {
value: 0,
- unit: 'now' as DateDiff['unit'],
+ unit: 'now',
}
} else if (diffSeconds < MINUTE) {
diff = {
value: diffSeconds,
- unit: 'second' as DateDiff['unit'],
+ unit: 'second',
}
} else if (diffSeconds < HOUR) {
const value =
@@ -72,7 +72,7 @@ export function dateDiff(
: Math.floor(diffSeconds / MINUTE)
diff = {
value,
- unit: 'minute' as DateDiff['unit'],
+ unit: 'minute',
}
} else if (diffSeconds < DAY) {
const value =
@@ -81,7 +81,7 @@ export function dateDiff(
: Math.floor(diffSeconds / HOUR)
diff = {
value,
- unit: 'hour' as DateDiff['unit'],
+ unit: 'hour',
}
} else if (diffSeconds < MONTH_30) {
const value =
@@ -90,7 +90,7 @@ export function dateDiff(
: Math.floor(diffSeconds / DAY)
diff = {
value,
- unit: 'day' as DateDiff['unit'],
+ unit: 'day',
}
} else {
const value =
@@ -99,7 +99,7 @@ export function dateDiff(
: Math.floor(diffSeconds / MONTH_30)
diff = {
value,
- unit: 'month' as DateDiff['unit'],
+ unit: 'month',
}
}
diff --git a/src/lib/routes/links.ts b/src/lib/routes/links.ts
index 908db7cba7..c87ccb5a39 100644
--- a/src/lib/routes/links.ts
+++ b/src/lib/routes/links.ts
@@ -19,8 +19,8 @@ export function makeProfileLink(
export function makeCustomFeedLink(
did: string,
rkey: string,
- segment?: string | undefined,
- feedCacheKey?: 'discover' | 'explore' | undefined,
+ segment?: string,
+ feedCacheKey?: 'discover' | 'explore',
) {
return (
[`/profile`, did, 'feed', rkey, ...(segment ? [segment] : [])].join('/') +
diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx
index 99b725267e..fbebdc0745 100644
--- a/src/screens/Messages/components/MessagesList.tsx
+++ b/src/screens/Messages/components/MessagesList.tsx
@@ -56,6 +56,7 @@ import {MessageListError} from '#/screens/Messages/components/MessageListError'
import {atoms as a, platform, tokens, useTheme, web} from '#/alf'
import {DateDivider} from '#/components/dms/DateDivider'
import {MessageItem} from '#/components/dms/MessageItem'
+import {MessageOverlays} from '#/components/dms/MessageOverlays'
import {NewMessagesPill} from '#/components/dms/NewMessagesPill'
import {SystemMessageGroup} from '#/components/dms/SystemMessageGroup'
import {SystemMessageItem} from '#/components/dms/SystemMessageItem'
@@ -498,127 +499,133 @@ export function MessagesList({
return (
-
- {/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */}
-
-
-
-
- {convoState.hasAllHistory ? (
- convoState.convo?.kind === 'group' ? (
-
- ) : (
-
- )
- ) : null}
- >
- }
- // native only (prop is not supported on web)
- renderScrollComponent={renderScrollComponent}
- contentContainerStyle={{
- paddingBottom: platform({
- // ios is slightly larger as the input has no top padding
- ios: tokens.space.lg,
- android: tokens.space.md,
- web: 0, // web uses ListFooterComponent instead for scroll reasons
- }),
- }}
- ListFooterComponent={
-
- }
- style={[
- web({
- scrollbarWidth: 'thin',
- scrollbarColor: `${t.palette.contrast_100} transparent`,
- scrollbarGutter: 'stable',
- }),
- ]}
- pointerEvents={!hasScrolled ? 'none' : 'auto'}
- contentInset={{top: transparentHeaderHeight}}
- scrollIndicatorInsets={{top: transparentHeaderHeight}}
- />
-
-
-
- {footer ?? (
-
- {({loading}) =>
- ax.features.enabled(ax.features.DmsNewMessageComposerEnable) ? (
-
- void onSendMessage(message)
- }
- hasEmbed={!!embedUri}
- setEmbed={setEmbed}
- loading={loading}>
-
+
+ {/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */}
+
+
+
+
+ {convoState.hasAllHistory ? (
+ convoState.convo?.kind === 'group' ? (
+
+ ) : (
+
+ )
+ ) : null}
+ >
+ }
+ // native only (prop is not supported on web)
+ renderScrollComponent={renderScrollComponent}
+ contentContainerStyle={{
+ paddingBottom: platform({
+ // ios is slightly larger as the input has no top padding
+ ios: tokens.space.lg,
+ android: tokens.space.md,
+ web: 0, // web uses ListFooterComponent instead for scroll reasons
+ }),
+ }}
+ ListFooterComponent={
+
+ }
+ style={[
+ web({
+ scrollbarWidth: 'thin',
+ scrollbarColor: `${t.palette.contrast_100} transparent`,
+ scrollbarGutter: 'stable',
+ }),
+ ]}
+ pointerEvents={!hasScrolled ? 'none' : 'auto'}
+ contentInset={{top: transparentHeaderHeight}}
+ scrollIndicatorInsets={{top: transparentHeaderHeight}}
+ />
+
+
+
+ {footer ?? (
+
+ {({loading}) =>
+ ax.features.enabled(
+ ax.features.DmsNewMessageComposerEnable,
+ ) ? (
+
+ void onSendMessage(message)
+ }
+ hasEmbed={!!embedUri}
setEmbed={setEmbed}
- />
-
- ) : (
-
-
+
+
+ ) : (
+
-
- )
- }
-
- )}
-
-
+ loading={loading}>
+
+
+ )
+ }
+
+ )}
+
+
- {newMessagesPill.show && }
+ {newMessagesPill.show && (
+
+ )}
+
)
}
diff --git a/src/screens/Messages/components/RequestListItem.tsx b/src/screens/Messages/components/RequestListItem.tsx
index 64dbebb164..79eab480ae 100644
--- a/src/screens/Messages/components/RequestListItem.tsx
+++ b/src/screens/Messages/components/RequestListItem.tsx
@@ -28,6 +28,9 @@ export function RequestListItem({
const isDeletedAccount =
!convo.primaryMember || convo.primaryMember.handle === 'missing.invalid'
+ const canAcceptRequest =
+ convo.kind === 'direct' || convo.details.lockStatus === 'unlocked'
+
return (
@@ -65,7 +68,9 @@ export function RequestListItem({
]}>
{convo.primaryMember && !isDeletedAccount ? (
<>
-
+ {canAcceptRequest ? (
+
+ ) : null}
{
- if (vals[0]) onChange(vals[0] as DynamicAppIcon.IconName)
+ if (vals[0]) onChange(vals[0])
}}>
{children}
diff --git a/src/screens/VideoFeed/components/Scrubber.tsx b/src/screens/VideoFeed/components/Scrubber.tsx
index 85bd4fddfd..acaf02453c 100644
--- a/src/screens/VideoFeed/components/Scrubber.tsx
+++ b/src/screens/VideoFeed/components/Scrubber.tsx
@@ -23,8 +23,7 @@ import {
import {useEventListener} from 'expo'
import {type VideoPlayer} from 'expo-video'
-import {tokens} from '#/alf'
-import {atoms as a} from '#/alf'
+import {atoms as a, tokens} from '#/alf'
import {formatTime} from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils'
import {Text} from '#/components/Typography'
diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts
index 57d83cb5bb..ca885107d8 100644
--- a/src/state/queries/notifications/feed.ts
+++ b/src/state/queries/notifications/feed.ts
@@ -298,7 +298,7 @@ export function* findAllPostsInQueryData(
if (AppBskyFeedDefs.isPostView(item.subject)) {
const quotedPost = getEmbeddedPost(item.subject?.embed)
if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) {
- yield embedViewRecordToPostView(quotedPost!)
+ yield embedViewRecordToPostView(quotedPost)
}
}
}
diff --git a/src/state/queries/nuxs/util.ts b/src/state/queries/nuxs/util.ts
index ef323b422e..dea35a9166 100644
--- a/src/state/queries/nuxs/util.ts
+++ b/src/state/queries/nuxs/util.ts
@@ -34,7 +34,7 @@ export function parseAppNux(nux: AppBskyActorDefs.Nux): AppNux | undefined {
export function serializeAppNux(nux: AppNux): AppBskyActorDefs.Nux {
const {data, ...rest} = nux
- const schema = NuxSchemas[nux.id as Nux]
+ const schema = NuxSchemas[nux.id]
const result: AppBskyActorDefs.Nux = {
...rest,
diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts
index c1ec4a12a7..7197eb2980 100644
--- a/src/state/queries/suggested-follows.ts
+++ b/src/state/queries/suggested-follows.ts
@@ -85,7 +85,7 @@ export function useSuggestedFollowsByActorWithDismiss({
const profiles = useMemo(() => {
return (data?.suggestions ?? []).map(profile => ({
- actor: profile as bsky.profile.AnyProfileView,
+ actor: profile,
recId: data?.recId,
}))
}, [data?.suggestions, data?.recId])
diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts
index 889c208c35..81abd958fb 100644
--- a/src/state/queries/usePostThread/traversal.ts
+++ b/src/state/queries/usePostThread/traversal.ts
@@ -196,7 +196,7 @@ export function sortAndAnnotateThreadItems(
* `repliesSeenCounter` later on, since `repliesSeenCounter`
* is 1-indexed and `replyIndex` is 0-indexed.
*/
- childMetadata!.replyIndex =
+ childMetadata.replyIndex =
childParentMetadata.repliesSeenCounter
}
diff --git a/src/types/bsky/post.ts b/src/types/bsky/post.ts
index 4020c1632d..43621ef63b 100644
--- a/src/types/bsky/post.ts
+++ b/src/types/bsky/post.ts
@@ -1,6 +1,7 @@
import {
type $Typed,
AppBskyEmbedExternal,
+ AppBskyEmbedGallery,
AppBskyEmbedImages,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
@@ -10,8 +11,6 @@ import {
AppBskyLabelerDefs,
} from '@atproto/api'
-import {AppBskyEmbedGallery} from '#/lib/api/gallery-embed-shim'
-
export type Embed =
| {
type: 'post'
diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx
index 8a889d7f9c..131337f955 100644
--- a/src/view/com/composer/ComposerReplyTo.tsx
+++ b/src/view/com/composer/ComposerReplyTo.tsx
@@ -2,6 +2,7 @@ import {useCallback, useMemo, useState} from 'react'
import {LayoutAnimation, Pressable, View} from 'react-native'
import {Image} from 'expo-image'
import {
+ AppBskyEmbedGallery,
AppBskyEmbedImages,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
@@ -10,7 +11,6 @@ import {
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
-import {AppBskyEmbedGallery} from '#/lib/api/gallery-embed-shim'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {type ComposerOptsPostRef} from '#/state/shell/composer'
@@ -134,16 +134,19 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
}
function galleryItemsToImages(
- items: AppBskyEmbedGallery.ViewImage[],
+ items: AppBskyEmbedGallery.View['items'],
): AppBskyEmbedImages.ViewImage[] {
// The reply-to thumbnail only renders up to 4 tiles; slicing here keeps
// the existing layout switch valid for galleries up to 10 items.
- return items.slice(0, 4).map(item => ({
- thumb: item.thumbnail,
- fullsize: item.fullsize,
- alt: item.alt,
- aspectRatio: item.aspectRatio,
- }))
+ return items
+ .filter(AppBskyEmbedGallery.isViewImage)
+ .slice(0, 4)
+ .map(item => ({
+ thumb: item.thumbnail,
+ fullsize: item.fullsize,
+ alt: item.alt,
+ aspectRatio: item.aspectRatio,
+ }))
}
function ComposerReplyToImages({
diff --git a/src/view/com/composer/drafts/state/api.ts b/src/view/com/composer/drafts/state/api.ts
index cca5f2b855..75475fc142 100644
--- a/src/view/com/composer/drafts/state/api.ts
+++ b/src/view/com/composer/drafts/state/api.ts
@@ -1,16 +1,9 @@
/**
* Type converters for Draft API - convert between ComposerState and server Draft types.
*/
-import {type AppBskyDraftDefs, AtUri, RichText} from '@atproto/api'
+import {AppBskyDraftDefs, AtUri, RichText} from '@atproto/api'
import {nanoid} from 'nanoid/non-secure'
-// Shim: AppBskyDraftDefs.DraftPost gains an `embedGallery` field in atproto
-// PR #4827. Until @atproto/api ships those types, we widen the shape locally.
-// Delete this once the lexicon publishes.
-type DraftPostWithGallery = AppBskyDraftDefs.DraftPost & {
- embedGallery?: AppBskyDraftDefs.DraftEmbedImage[]
-}
-
import {resolveLink} from '#/lib/api/resolve'
import {getDeviceName} from '#/lib/deviceName'
import {getImageDim} from '#/lib/media/manip'
@@ -123,10 +116,15 @@ async function postDraftToServerPost(
localRefPaths,
)
} else if (post.embed.media.type === 'gallery') {
- ;(draftPost as DraftPostWithGallery).embedGallery = serializeImages(
- post.embed.media.images,
- localRefPaths,
- )
+ draftPost.embedGallery = {
+ $type: 'app.bsky.draft.defs#draftEmbedGallery',
+ items: serializeImages(post.embed.media.images, localRefPaths).map(
+ img => ({
+ $type: 'app.bsky.draft.defs#draftEmbedImage' as const,
+ ...img,
+ }),
+ ),
+ }
} else if (post.embed.media.type === 'video') {
const video = await serializeVideo(post.embed.media.video, localRefPaths)
if (video) {
@@ -326,11 +324,11 @@ async function restoreDraftImages(
height,
mime: 'image/jpeg',
},
- } as ComposerImage
+ } satisfies ComposerImage
})
return (await Promise.all(imagePromises)).filter(
- (img): img is ComposerImage => img !== null,
+ (img): img is NonNullable => img !== null,
)
}
@@ -380,18 +378,18 @@ export function draftViewToSummary({
}
// Process gallery
- const summaryEmbedGallery = (post as DraftPostWithGallery).embedGallery
- if (summaryEmbedGallery) {
- for (const img of summaryEmbedGallery) {
+ if (post.embedGallery) {
+ for (const item of post.embedGallery.items) {
+ if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue
meta.mediaCount++
meta.hasMedia = true
- const exists = storage.mediaExists(img.localRef.path)
+ const exists = storage.mediaExists(item.localRef.path)
if (!exists) {
meta.hasMissingMedia = true
}
images.push({
- localPath: img.localRef.path,
- altText: img.alt || '',
+ localPath: item.localRef.path,
+ altText: item.alt || '',
exists,
})
}
@@ -523,9 +521,11 @@ export async function draftToComposerPosts(
}
// Restore gallery
- const embedGallery = (post as DraftPostWithGallery).embedGallery
- if (embedGallery && embedGallery.length > 0) {
- const images = await restoreDraftImages(embedGallery, loadedMedia)
+ if (post.embedGallery && post.embedGallery.items.length > 0) {
+ const galleryImages = post.embedGallery.items.filter(
+ AppBskyDraftDefs.isDraftEmbedImage,
+ )
+ const images = await restoreDraftImages(galleryImages, loadedMedia)
if (images.length > 0) {
embed.media = {type: 'gallery', images}
}
@@ -561,7 +561,7 @@ export async function draftToComposerPosts(
tinygif: mediaObject,
preview: mediaObject,
},
- } as Gif,
+ },
alt: gifData.alt,
}
break
@@ -680,10 +680,10 @@ export function extractLocalRefs(draft: AppBskyDraftDefs.Draft): Set {
refs.add(img.localRef.path)
}
}
- const embedGallery = (post as DraftPostWithGallery).embedGallery
- if (embedGallery) {
- for (const img of embedGallery) {
- refs.add(img.localRef.path)
+ if (post.embedGallery) {
+ for (const item of post.embedGallery.items) {
+ if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue
+ refs.add(item.localRef.path)
}
}
if (post.embedVideos) {
diff --git a/src/view/com/composer/drafts/state/queries.ts b/src/view/com/composer/drafts/state/queries.ts
index c0d896da61..07dd67dd88 100644
--- a/src/view/com/composer/drafts/state/queries.ts
+++ b/src/view/com/composer/drafts/state/queries.ts
@@ -1,10 +1,4 @@
-import {AppBskyDraftCreateDraft, type AppBskyDraftDefs} from '@atproto/api'
-
-// Shim: AppBskyDraftDefs.DraftPost gains `embedGallery` in atproto PR #4827.
-// Delete once @atproto/api publishes the new lexicon.
-type DraftPostWithGallery = AppBskyDraftDefs.DraftPost & {
- embedGallery?: AppBskyDraftDefs.DraftEmbedImage[]
-}
+import {AppBskyDraftCreateDraft, AppBskyDraftDefs} from '@atproto/api'
import {
useInfiniteQuery,
useMutation,
@@ -81,15 +75,15 @@ export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{
}
}
// Load gallery
- const embedGallery = (post as DraftPostWithGallery).embedGallery
- if (embedGallery) {
- for (const img of embedGallery) {
+ if (post.embedGallery) {
+ for (const item of post.embedGallery.items) {
+ if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue
try {
- const url = await storage.loadMediaFromLocal(img.localRef.path)
- loadedMedia.set(img.localRef.path, url)
+ const url = await storage.loadMediaFromLocal(item.localRef.path)
+ loadedMedia.set(item.localRef.path, url)
} catch (e) {
logger.error('Failed to load draft gallery image', {
- path: img.localRef.path,
+ path: item.localRef.path,
safeMessage: e instanceof Error ? e.message : String(e),
})
}
@@ -247,10 +241,10 @@ export function useDeleteDraftMutation() {
await storage.deleteMediaFromLocal(img.localRef.path)
}
}
- const embedGallery = (post as DraftPostWithGallery).embedGallery
- if (embedGallery) {
- for (const img of embedGallery) {
- await storage.deleteMediaFromLocal(img.localRef.path)
+ if (post.embedGallery) {
+ for (const item of post.embedGallery.items) {
+ if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue
+ await storage.deleteMediaFromLocal(item.localRef.path)
}
}
if (post.embedVideos) {
diff --git a/src/view/com/testing/TestCtrls.e2e.tsx b/src/view/com/testing/TestCtrls.e2e.tsx
index b01651d7b1..2110c9540e 100644
--- a/src/view/com/testing/TestCtrls.e2e.tsx
+++ b/src/view/com/testing/TestCtrls.e2e.tsx
@@ -55,7 +55,7 @@ export function TestCtrls() {
accessibilityLabel="Text input field"
accessibilityHint="Enter proxy header"
testID="e2eProxyHeaderInput"
- onChangeText={val => setProxyHeader(val as any)}
+ onChangeText={val => setProxyHeader(val)}
autoComplete="off"
autoCorrect={false}
autoCapitalize="none"
diff --git a/src/view/com/util/PressableWithHover.tsx b/src/view/com/util/PressableWithHover.tsx
index f4006a5268..a4a2d194fd 100644
--- a/src/view/com/util/PressableWithHover.tsx
+++ b/src/view/com/util/PressableWithHover.tsx
@@ -3,9 +3,9 @@ import {
Pressable,
type PressableProps,
type StyleProp,
+ type View,
type ViewStyle,
} from 'react-native'
-import {type View} from 'react-native'
import {addStyle} from '#/lib/styles'
import {useInteractionState} from '#/components/hooks/useInteractionState'
diff --git a/src/view/shell/BlockDrawerGesture.tsx b/src/view/shell/BlockDrawerGesture.tsx
index bae9a8db82..93ef18cfae 100644
--- a/src/view/shell/BlockDrawerGesture.tsx
+++ b/src/view/shell/BlockDrawerGesture.tsx
@@ -4,6 +4,8 @@ import {Gesture, GestureDetector} from 'react-native-gesture-handler'
export function BlockDrawerGesture({children}: {children: React.ReactNode}) {
const drawerGesture = useContext(DrawerGestureContext) ?? Gesture.Native() // noop for web
- const scrollGesture = Gesture.Native().blocksExternalGesture(drawerGesture)
+ let scrollGesture = Gesture.Native()
+ .shouldCancelWhenOutside(false) // for some reason defaults to true on Android
+ .blocksExternalGesture(drawerGesture)
return {children}
}
diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx
index 4191f94176..a23ed693ec 100644
--- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx
+++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx
@@ -171,7 +171,7 @@ function NativeStackNavigator({
}
// Evicted screens get a lightweight placeholder instead of their full tree
- finalDescriptors = {} as typeof descriptors
+ finalDescriptors = {}
for (const key in descriptors) {
if (mountSet.has(key)) {
finalDescriptors[key] = descriptors[key]
diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx
index 827ab092c4..9b215d9f13 100644
--- a/src/view/shell/index.tsx
+++ b/src/view/shell/index.tsx
@@ -174,8 +174,15 @@ function DrawerLayout({children}: {children: React.ReactNode}) {
// so fail the drawer gesture immediately.
.failOffsetX(-1)
// Don't rush declaring that a movement to the right
- // is a drawer swipe. It could be a vertical scroll.
- .activeOffsetX(5)
+ // is a drawer swipe. It could be a vertical scroll, or a
+ // slow horizontal carousel swipe. On Android a child
+ // `blocksExternalGesture` only holds the drawer off once the
+ // native scroll has activated, which on a slow swipe doesn't
+ // happen until movement crosses the native touch slop
+ // (~8-16px). Activating the drawer below that lets a slow
+ // carousel swipe pop the drawer open (APP-2119), so require
+ // more travel before claiming on Android.
+ .activeOffsetX(IS_ANDROID ? 20 : 5)
)
}
} else {