Fix mobile legend anchor under automatic iOS insets#3684
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
ApprovabilityVerdict: Needs human review 6 blocking correctness issues found. This PR includes ~900-line patches to third-party scroll libraries, a new production CI workflow with an unresolved shell injection vulnerability, and multiple identified bugs affecting scroll positioning and animation timing. The scope and unresolved security concern require human review. You can customize Macroscope's approvability policy. Learn more. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Header blur uses wrong inset
- handleScroll now measures the scroll threshold with anchorTopInset instead of the zeroed topContentInset, so header material visibility toggles at the true rest position under iOS automatic content insets while non-automatic behavior is unchanged.
Or push these changes by commenting:
@cursor push 18fb3f23d9
Preview (18fb3f23d9)
diff --git a/apps/mobile/src/features/threads/ThreadFeed.tsx b/apps/mobile/src/features/threads/ThreadFeed.tsx
--- a/apps/mobile/src/features/threads/ThreadFeed.tsx
+++ b/apps/mobile/src/features/threads/ThreadFeed.tsx
@@ -1282,11 +1282,15 @@
},
[props.onHeaderMaterialVisibilityChange],
);
+ // anchorTopInset, not topContentInset: with automatic insets the list rests
+ // at contentOffset.y = -headerHeight (the inset only exists in UIKit's
+ // adjustedContentInset), so the header inset must be added back for the
+ // material to toggle relative to the true rest position.
const handleScroll = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
- reportHeaderMaterialVisibility(event.nativeEvent.contentOffset.y + topContentInset > 6);
+ reportHeaderMaterialVisibility(event.nativeEvent.contentOffset.y + anchorTopInset > 6);
},
- [reportHeaderMaterialVisibility, topContentInset],
+ [reportHeaderMaterialVisibility, anchorTopInset],
);
const handleViewportLayout = useCallback((event: LayoutChangeEvent) => {
const nextWidth = Math.round(event.nativeEvent.layout.width);You can send follow-ups to the cloud agent here.
- Fold header inset into the anchor offset when iOS automatic content insets are enabled - Prevent new messages from anchoring under the header and snapping past earlier messages
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Work pill height estimate stale
- Increased WORKING_INDICATOR_HEIGHT from 44 to 52 so the overlay height estimate includes the pill wrapper's new pt-2 (8px) padding, keeping the pre-measurement inset math consistent with the measured layout.
Or push these changes by commenting:
@cursor push 8b598399e0
Preview (8b598399e0)
diff --git a/apps/mobile/src/features/threads/ThreadDetailScreen.tsx b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx
--- a/apps/mobile/src/features/threads/ThreadDetailScreen.tsx
+++ b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx
@@ -174,7 +174,10 @@
}, [threadId, feed]);
}
-const WORKING_INDICATOR_HEIGHT = 44;
+// Estimated height of WorkingDurationPill including its pt-2/pb-2 wrapper
+// padding; must track the pill layout so the pre-measurement overlay height
+// estimate matches what onComposerLayout later reports.
+const WORKING_INDICATOR_HEIGHT = 52;
const WorkingDurationPill = memo(function WorkingDurationPill(props: {
readonly startedAt: string;You can send follow-ups to the cloud agent here.
- Patch LegendList and keyboard inset handling for transparent headers - Animate composer, feed, and work-log transitions more smoothly - Add tests and dependency patches for the mobile scroll fixes
| // composer-height short of the end. Layout effect: it must land before the | ||
| // list's first positioning tick or the one-shot initial scroll misses it. | ||
| const listMountKey = `${props.threadId}:${props.feed.length === 0 ? "empty" : "filled"}`; | ||
| useLayoutEffect(() => { |
There was a problem hiding this comment.
🟡 Medium threads/ThreadFeed.tsx:1330
The new useLayoutEffect keyed on listMountKey reads props.contentInsetEndAdjustment.value and reports it to the remounted list, but that shared value still holds the previous thread's composer-overlay height when the thread changes (because useKeyboardChatComposerInset deduplicates by height and hasn't re-measured the fresh overlay yet). initialScrollAtEnd is one-shot during attach, so the first scroll-to-end on a thread switch computes with a stale bottom inset and rests too high or too low by the overlay-height delta. Consider resetting contentInsetEndAdjustment.value to 0 on thread change before the list remounts, or guarding the effect so it only reports the inset once the composer overlay has re-measured for the current thread.
Also found in 1 other location(s)
apps/mobile/src/features/threads/ThreadDetailScreen.tsx:195
Adding
pt-2toWorkingDurationPillmakes the overlay 8 px taller, but the parent still budgets a fixedWORKING_INDICATOR_HEIGHT = 44whenactiveWorkStartedAtis set. That leaves the thread feed's estimated bottom inset/anchor space too small whenever this pill is shown, so the last message or empty-state content can end up partially underneath the floating overlay until later layout correction.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/src/features/threads/ThreadFeed.tsx around line 1330:
The new `useLayoutEffect` keyed on `listMountKey` reads `props.contentInsetEndAdjustment.value` and reports it to the remounted list, but that shared value still holds the previous thread's composer-overlay height when the thread changes (because `useKeyboardChatComposerInset` deduplicates by height and hasn't re-measured the fresh overlay yet). `initialScrollAtEnd` is one-shot during attach, so the first scroll-to-end on a thread switch computes with a stale bottom inset and rests too high or too low by the overlay-height delta. Consider resetting `contentInsetEndAdjustment.value` to 0 on thread change before the list remounts, or guarding the effect so it only reports the inset once the composer overlay has re-measured for the current thread.
Also found in 1 other location(s):
- apps/mobile/src/features/threads/ThreadDetailScreen.tsx:195 -- Adding `pt-2` to `WorkingDurationPill` makes the overlay 8 px taller, but the parent still budgets a fixed `WORKING_INDICATOR_HEIGHT = 44` when `activeWorkStartedAt` is set. That leaves the thread feed's estimated bottom inset/anchor space too small whenever this pill is shown, so the last message or empty-state content can end up partially underneath the floating overlay until later layout correction.
- Pass safe-area inset compensation through the mobile thread list - Adjust keyboard controller and legend list math so end anchors stay aligned - Fix overlay spacing and drag handling to prevent end-position drift
8bcfdef to
60867bd
Compare
| onMomentumScrollEnd(event); | ||
| } | ||
| }, | ||
| + onScrollBeginDrag: (event) => { |
There was a problem hiding this comment.
🟡 Medium patches/@legendapp__list@3.2.0.patch:481
The onScrollBeginDrag handler is forwarded through the fns object, but fns is created with an empty dependency array []. After mount, updates to the onScrollBeginDrag prop are never captured — drags always invoke the callback captured on first render, so any parent state that the drag callback depends on goes stale. Consider adding the prop (or a ref holding it) to the dependency array so the handler stays current.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @patches/@legendapp__list@3.2.0.patch around line 481:
The `onScrollBeginDrag` handler is forwarded through the `fns` object, but `fns` is created with an empty dependency array `[]`. After mount, updates to the `onScrollBeginDrag` prop are never captured — drags always invoke the callback captured on first render, so any parent state that the drag callback depends on goes stale. Consider adding the prop (or a ref holding it) to the dependency array so the handler stays current.
| + blankSpace.value = size > 0 ? Math.max(0, size - (adjustedInsetCompensation || 0)) : 0; | ||
| (_a = anchoredEndSpace.onSizeChanged) == null ? void 0 : _a.call(anchoredEndSpace, size); |
There was a problem hiding this comment.
🟡 Medium patches/@legendapp__list@3.2.0.patch:74
onSizeChanged receives the original unadjusted size, but the spacer actually applied to blankSpace.value is size - adjustedInsetCompensation. Any caller tracking the real anchored spacer via onSizeChanged will be off by the compensation amount, causing overlays or end-of-list calculations to drift by the safe-area inset. Pass the same adjusted value to onSizeChanged that is written to blankSpace.value.
| + blankSpace.value = size > 0 ? Math.max(0, size - (adjustedInsetCompensation || 0)) : 0; | |
| (_a = anchoredEndSpace.onSizeChanged) == null ? void 0 : _a.call(anchoredEndSpace, size); | |
| blankSpace.value = size > 0 ? Math.max(0, size - (adjustedInsetCompensation || 0)) : 0; | |
| const adjustedSize = blankSpace.value; | |
| (_a = anchoredEndSpace.onSizeChanged) == null ? void 0 : _a.call(anchoredEndSpace, adjustedSize); |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @patches/@legendapp__list@3.2.0.patch around lines 74-75:
`onSizeChanged` receives the original unadjusted `size`, but the spacer actually applied to `blankSpace.value` is `size - adjustedInsetCompensation`. Any caller tracking the real anchored spacer via `onSizeChanged` will be off by the compensation amount, causing overlays or end-of-list calculations to drift by the safe-area inset. Pass the same adjusted value to `onSizeChanged` that is written to `blankSpace.value`.
| <ControlPillMenu | ||
| actions={optionsMenuActions} | ||
| onPressAction={({ nativeEvent }) => handleOptionsMenuAction(nativeEvent.event)} | ||
| <Animated.View entering={FadeIn.duration(160)} exiting={FadeOut.duration(120)}> |
There was a problem hiding this comment.
🟡 Medium threads/ThreadComposer.tsx:843
The exiting={FadeOut} wrappers on the expanded-only sections (toolbar row, queue count, attachment strip) keep those elements mounted and occupying space until the exit animation finishes (~120–180 ms). ThreadDetailScreen measures the overlay height via onComposerLayout and uses it for the feed's bottom inset and anchor-scroll math, so when the composer collapses after send the measured height still reflects the expanded layout. This causes the feed to compute its resting position against the wrong inset and anchor the newly sent message too high relative to the collapsed composer. Consider removing the exiting animations on these sections (or using a layout-only transition) so the height measurement updates immediately when isExpanded flips to false.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/src/features/threads/ThreadComposer.tsx around line 843:
The `exiting={FadeOut}` wrappers on the expanded-only sections (toolbar row, queue count, attachment strip) keep those elements mounted and occupying space until the exit animation finishes (~120–180 ms). `ThreadDetailScreen` measures the overlay height via `onComposerLayout` and uses it for the feed's bottom inset and anchor-scroll math, so when the composer collapses after send the measured height still reflects the expanded layout. This causes the feed to compute its resting position against the wrong inset and anchor the newly sent message too high relative to the collapsed composer. Consider removing the `exiting` animations on these sections (or using a layout-only transition) so the height measurement updates immediately when `isExpanded` flips to false.
Gate LegendList's readyToRender on a new insetEndRevealHold flag owned by the inset-end settle watchdog: content stays hidden while the initial end-scroll chases late inset/size reports, upgrades adaptiveRender to normal while still hidden (the light->normal re-render wave was causing ~100ms of visible drift), then reveals only after the offset holds stable for 3 consecutive frames (40-frame cap, released early on user drag). First visible frame is the final resting position. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| + const maintainAnchoredEndSpace = state.props.anchoredEndSpace; | ||
| + const maintainAnchorIndex = maintainAnchoredEndSpace == null ? void 0 : maintainAnchoredEndSpace.anchorIndex; | ||
| + if (maintainAnchorIndex !== void 0 && maintainAnchorIndex >= 0 && maintainAnchorIndex < state.props.data.length && !areKnownOrFixedItemSizesAvailable(ctx, maintainAnchorIndex, state.props.data.length - 1)) { | ||
| + return false; |
There was a problem hiding this comment.
🟡 Medium patches/@legendapp__list@3.2.0.patch:277
When contentSize < state.scrollLength and maintainInsetStartAdjustment > 0, doMaintainScrollAtEnd sets state.scroll = -maintainInsetStartAdjustment and returns true without calling scrollTo on the native scroller. So when a list shrinks from overflowing to underflow while maintainScrollAtEnd is active, the internal scroll state says the feed is at -headerInset but the native scroll view stays at its old offset, leaving the viewport and end-anchor out of sync. Consider dispatching a scrollTo({ animated, y: -maintainInsetStartAdjustment }) (or falling through to the normal scrollTo path) so the native scroller matches state.scroll.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @patches/@legendapp__list@3.2.0.patch around line 277:
When `contentSize < state.scrollLength` and `maintainInsetStartAdjustment > 0`, `doMaintainScrollAtEnd` sets `state.scroll = -maintainInsetStartAdjustment` and returns `true` without calling `scrollTo` on the native scroller. So when a list shrinks from overflowing to underflow while `maintainScrollAtEnd` is active, the internal scroll state says the feed is at `-headerInset` but the native scroll view stays at its old offset, leaving the viewport and end-anchor out of sync. Consider dispatching a `scrollTo({ animated, y: -maintainInsetStartAdjustment })` (or falling through to the normal `scrollTo` path) so the native scroller matches `state.scroll`.
- Point production iOS submit config at the new ASC app ID - Keeps EAS submission aligned with the current app record
- Update `apps/mobile/eas.json` so production Android builds target the internal track - Leave iOS release config unchanged
| "ascAppId": "6787819824" | ||
| }, | ||
| "android": { | ||
| "track": "internal" |
There was a problem hiding this comment.
🟠 High mobile/eas.json:52
The submit.production.android.track is set to "internal", so eas submit --profile production -p android uploads the release to Google Play's internal testing track instead of the production track. Because production is the only Android submit profile, production Android releases never reach end users — they go only to internal testers. If internal testing is intended as a staging step before promotion, consider documenting that workflow; otherwise set track to "production" (or remove it so the default production track is used).
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/eas.json around line 52:
The `submit.production.android.track` is set to `"internal"`, so `eas submit --profile production -p android` uploads the release to Google Play's internal testing track instead of the production track. Because `production` is the only Android submit profile, production Android releases never reach end users — they go only to internal testers. If internal testing is intended as a staging step before promotion, consider documenting that workflow; otherwise set `track` to `"production"` (or remove it so the default production track is used).
Address two PR review findings on the automatic-inset work: - ThreadFeed: the header-material scroll check used topContentInset (0 under automatic insets) instead of anchorTopInset, so the header blur toggled a full header height too late. Use anchorTopInset to match the true rest position. - ThreadDetailScreen: WORKING_INDICATOR_HEIGHT (44) no longer matched the pill's rendered height after the pt-2 padding was added; bump to 52 so the pre-measurement overlay estimate seeds an accurate bottom inset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- app.config.ts: default runtimeVersion policy to fingerprint (was appVersion) for every environment. appVersion made all 0.1.0 builds share a runtime version, so an OTA could land on a binary missing the native changes it needs and crash. Fingerprint ties the runtime version to the native project (deps, config plugins, patches/) so updates only reach compatible binaries. Previews already ran fingerprint. - Settings: replace the hardcoded "Alpha" version label with the real app version + variant, plus which JS bundle is running (embedded vs OTA <id>) so "am I on the right build?" is answerable at a glance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Wrong variant fallback in settings
- Replaced the "development" fallback and unchecked string cast with a typeof guard that defaults to "production", matching resolveAppVariant() in app.config.ts and preventing a potential capitalize crash on non-string values.
Or push these changes by commenting:
@cursor push b5d3470297
Preview (b5d3470297)
diff --git a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx
--- a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx
+++ b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx
@@ -425,7 +425,10 @@
const icon = useThemeColor("--color-icon");
const version = Constants.expoConfig?.version ?? "0.0.0";
- const variant = (Constants.expoConfig?.extra?.appVariant as string | undefined) ?? "development";
+ // app.config.ts resolves unknown/missing APP_VARIANT to "production", so a
+ // missing value here must not be mislabeled as a development build.
+ const rawVariant = Constants.expoConfig?.extra?.appVariant;
+ const variant = typeof rawVariant === "string" ? rawVariant : "production";
const variantLabel = variant === "production" ? "" : capitalize(variant);
const versionLabel = variantLabel ? `${version} · ${variantLabel}` : version;
// Which JS is actually running: the bundle shipped in the binary, or an OTAYou can send follow-ups to the cloud agent here.
Match resolveAppVariant in app.config.ts, which treats a missing variant as production. The version row previously fell back to "development", which could mislabel a production build (Cursor Bugbot finding). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EAS Build was using its image-default pnpm (9.x, which names virtual-store dirs as <pkg>@<ver>_<full-hash>) instead of the pinned pnpm@10.24.0 (which uses the readable-prefix scheme). That made EAS's fingerprint diverge from the locally-computed one, so `eas build` under the fingerprint runtime policy errored with a runtime-version mismatch. corepack:true makes EAS honor the packageManager pin so both sides run pnpm 10.24 and fingerprints match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Production builds and OTA updates must run from Linux CI, not a laptop: under the fingerprint runtime policy the fingerprint is computed at both `eas build` time and on the EAS builder, and a macOS-computed fingerprint never matches EAS's Linux one (platform-specific deps + pnpm version), so local `eas build` errors. This workflow_dispatch job runs on Linux, uses corepack-pinned pnpm 10.24, and does either a production build (auto-submit to TestFlight) or an OTA update on the production channel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: OTA message shell injection
- The user-controlled message input is now passed via a step-level OTA_MESSAGE env var and referenced as "$OTA_MESSAGE" at runtime instead of being interpolated into the shell script.
- ✅ Fixed: Missing fingerprint env override
- Added MOBILE_VERSION_POLICY: fingerprint to the production job's env block, matching the preview workflow so remote EAS environment values cannot override the intended runtime-version policy.
Or push these changes by commenting:
@cursor push c592a9e02e
Preview (c592a9e02e)
diff --git a/.github/workflows/mobile-eas-production.yml b/.github/workflows/mobile-eas-production.yml
--- a/.github/workflows/mobile-eas-production.yml
+++ b/.github/workflows/mobile-eas-production.yml
@@ -39,6 +39,7 @@
env:
APP_VARIANT: production
NODE_OPTIONS: --max-old-space-size=8192
+ MOBILE_VERSION_POLICY: fingerprint
steps:
- id: expo-token
name: Check for EXPO_TOKEN
@@ -101,10 +102,11 @@
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
+ OTA_MESSAGE: ${{ inputs.message || format('Production OTA ({0})', github.sha) }}
run: |
eas update \
--channel production \
--environment production \
--platform ${{ inputs.platform }} \
- --message "${{ inputs.message || format('Production OTA ({0})', github.sha) }}" \
+ --message "$OTA_MESSAGE" \
--non-interactiveYou can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit ab86724. Configure here.
| --channel production \ | ||
| --environment production \ | ||
| --platform ${{ inputs.platform }} \ | ||
| --message "${{ inputs.message || format('Production OTA ({0})', github.sha) }}" \ |
There was a problem hiding this comment.
OTA message shell injection
High Severity
The Publish OTA update step is vulnerable to shell injection. The workflow_dispatch message input is directly interpolated into the eas update --message argument, allowing metacharacters to execute arbitrary commands on the runner. This could expose EXPO_TOKEN and impact production.
Reviewed by Cursor Bugbot for commit ab86724. Configure here.
| contents: read | ||
| env: | ||
| APP_VARIANT: production | ||
| NODE_OPTIONS: --max-old-space-size=8192 |
There was a problem hiding this comment.
Missing fingerprint env override
Low Severity
The production job sets APP_VARIANT but not MOBILE_VERSION_POLICY, while the preview workflow pins MOBILE_VERSION_POLICY to fingerprint. After eas env:pull production, a MOBILE_VERSION_POLICY value from the remote environment can override the app.config.ts default and change runtime versioning for builds and OTAs triggered here.
Reviewed by Cursor Bugbot for commit ab86724. Configure here.
Upstream pingdotgg#3684 made maintainScrollAtEnd animated, but on native an in-flight animated scrollToEnd is not reconciled with maintainVisibleContentPosition: each streamed token has MVCP restore the anchor (up) then the ~500ms animated end-scroll fire (down), looping before it settles. Keep the end pin instant during streaming (resolves in the same frame as the MVCP restore) and animate only for a short window after a new send anchor so the just-sent message still glides into place.
* Add middle-click close for right panel tabs (pingdotgg#3161) Co-authored-by: Julius Marminge <jmarminge@gmail.com> * fix: warm WSL before preflight in WSL-only backend mode (pingdotgg#3588) * Add Claude Sonnet 5 as the default Claude model (pingdotgg#3620) * Restore the ultrathink frame border effect (pingdotgg#3625) * fix(dev): Fix electron dev launch and add test (pingdotgg#3662) * Add adaptive split-view layout for iPad/mobile workspace (pingdotgg#3514) Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Julius Marminge <julius@mac.lan> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(mobile): compile patched native pods from source on EAS (pingdotgg#3667) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Make the thread composer read as elevated liquid glass (pingdotgg#3668) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Upgrade Vite Plus and enable bundled dev opt-in (pingdotgg#3679) * Surface pending tasks in mobile home and draft flow (pingdotgg#3670) * fix(mobile): combined test branch — scroll, back-swipe, thread lists, computer switching (pingdotgg#3687) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * Add repo-root favicon.svg so t3 code shows its own icon (pingdotgg#3683) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Load thread snapshots over HTTP before live sync (pingdotgg#3719) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * Fix mobile legend anchor under automatic iOS insets (pingdotgg#3684) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Improve live activity routing and diagnostics (pingdotgg#3685) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * Prevent Add Project sheet from collapsing on relayout (pingdotgg#3759) * Use variant-specific splash icons in mobile app (pingdotgg#3762) * Fix Expo widget asset wiring order (pingdotgg#3763) * Extend Done display to 15 minutes and show up to 5 Live Activity banner rows (pingdotgg#3761) * Clear VCS presentation state on finish (pingdotgg#3764) * Lead with the outcome when no agents are active in the Live Activity (pingdotgg#3768) * Add T3 Connect onboarding for mobile and web (pingdotgg#3765) * Revert "Add T3 Connect onboarding for mobile and web" (pingdotgg#3776) * Expose Clerk Google sign-in env vars to Expo (pingdotgg#3772) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Set up Cursor Cloud dev environment (web + Android toolchain) (pingdotgg#3755) Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Theo Browne <t3dotgg@users.noreply.github.com> * Revert "Revert "Add T3 Connect onboarding for mobile and web"" (pingdotgg#3777) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Use rounded depth logo for production splash screen (pingdotgg#3780) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * fix(release): stage pnpm 11 allowBuilds for desktop installs (pingdotgg#3781) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * Upgrade Clerk toolchain to latest versions (pingdotgg#3785) * fix(release): bump electron-builder so pnpm 11 deduped deps land in the asar (pingdotgg#3790) * Fix desktop native optional dependency packaging (pingdotgg#3816) * [codex] Upgrade Clerk stack (pingdotgg#3821) Co-authored-by: codex <codex@users.noreply.github.com> * [codex] Preserve worktree metadata during branch sync (pingdotgg#3822) Co-authored-by: codex <codex@users.noreply.github.com> * feat(client): persist offline environment data and mobile preferences (pingdotgg#3795) Co-authored-by: Julius Marminge <julius@mac.lan> Co-authored-by: codex <codex@users.noreply.github.com> * [codex] Label max and ultra reasoning (pingdotgg#3824) Co-authored-by: codex <codex@users.noreply.github.com> * fix(mobile): embed fonts and render project favicons reliably (pingdotgg#3823) Co-authored-by: codex <codex@users.noreply.github.com> * Show compact PR number badges in mobile thread rows (pingdotgg#3827) Co-authored-by: codex <codex@users.noreply.github.com> * Expose mobile PR indicator labels to accessibility (pingdotgg#3828) Co-authored-by: codex <codex@users.noreply.github.com> * Fix truncated chat error alert layout (pingdotgg#3899) * fix(marketing): show platform-appropriate commit shortcut on the website (pingdotgg#3644) * [codex] Add Android mobile support (pingdotgg#3579) Co-authored-by: Horus Lugo <horusgoul@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: maria-rcks <maria@kuuro.net> Co-authored-by: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> Co-authored-by: Ben Davis <45952064+bmdavis419@users.noreply.github.com> Co-authored-by: Alex <me@pixp.cc> Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Julius Marminge <julius@mac.lan> --------- Co-authored-by: Hugo Blom <6117705+huxcrux@users.noreply.github.com> Co-authored-by: Julius Marminge <jmarminge@gmail.com> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Julius Marminge <julius@mac.lan> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Theo Browne <t3dotgg@users.noreply.github.com> Co-authored-by: Rowan <rowan@cardow.co> Co-authored-by: Patricio Gómez Meneses <107218376+Prgm-code@users.noreply.github.com> Co-authored-by: Jake Leventhal <jakeleventhal@me.com> Co-authored-by: Vedank Purohit <VedankPurohit2@gmail.com> Co-authored-by: Horus Lugo <horusgoul@gmail.com> Co-authored-by: maria-rcks <maria@kuuro.net> Co-authored-by: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> Co-authored-by: Ben Davis <45952064+bmdavis419@users.noreply.github.com> Co-authored-by: Alex <me@pixp.cc>



Summary
maintainScrollAtEndfrom snapping to the wrong position when UIKit-managed insets are in play.Testing
apps/mobile/src/features/threads/ThreadFeed.tsxanchor calculations.Note
High Risk
Touches core chat scrolling via vendored patches and changes OTA runtime matching (fingerprint), so regressions could break thread UX or mis-deliver updates; production CI/submit config also affects store releases.
Overview
Fixes iOS thread chat layout when the list uses automatic safe-area insets under a transparent header: composer overlay height is adjusted for double-counted bottom inset,
ThreadFeedfeeds LegendList new inset/anchor props (contentInsetStartAdjustment,contentInsetEndStaticAdjustment,adjustedInsetCompensation, remount inset re-report), and maintainScrollAtEnd is animated again. Large patches to@legendapp/listandreact-native-keyboard-controllerimplement the underlying scroll/clamp/end-pin behavior.Adds Reanimated enter/layout transitions on the composer, feed rows, work log, and pending cards; Settings shows version, variant, and embedded vs OTA bundle info.
Release pipeline: new manual GitHub workflow for Linux EAS production build+submit or OTA; default
runtimeVersionpolicy →fingerprint(wasappVersion); EAS profiles enable corepack; production submit ASC app id and Android internal track updated.Reviewed by Cursor Bugbot for commit ab86724. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix mobile legend anchor position under automatic iOS safe-area insets
usesAutomaticContentInsetsis active, preventing the legend anchor from being offset incorrectly.anchorTopInsetfromHeaderHeightContextwhen automatic content insets are active, fixing header-material visibility reporting and anchored end-space calculations.eas.jsonwith Corepack support and corrected App Store Connect submission IDs.runtimeVersionpolicy now defaults tofingerprintinstead ofappVersion, so OTA updates require matching native fingerprints.Macroscope summarized ab86724.