Skip to content

fix(mobile): reconnects no longer shift the thread list - #5372

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/mobile-reconnect-status
Aug 7, 2026
Merged

fix(mobile): reconnects no longer shift the thread list#5372
t3dotgg merged 1 commit into
mainfrom
t3code/mobile-reconnect-status

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 5, 2026

Copy link
Copy Markdown
Member

When an environment reconnected on mobile, the connection status appeared as a row above the thread list. Every reconnect pushed all the rows down, then popped them back when it resolved. Jarring, and it happens constantly.

Now the header's brand slot doubles as the status surface. While an environment is unavailable, the T3 Code lockup ("Threads" on the iPad sidebar) fades to a spinner plus a status label in the same place, then fades back on reconnect. Nothing is inserted into the list, so its geometry never moves.

An 800ms debounce gates the swap, so the sub-second blips that make up most reconnects now show nothing at all. Offline and error states get a wifi-slash icon instead of the spinner, and tapping the status still opens environment settings the way the old pill did.

Covers every surface the old pill lived on: iPhone Home (both the liquid-glass and pre-glass header paths), Android Home, and the iPad sidebar in both native and custom chrome. The pill component is deleted; its display logic stays in workspace-connection-status.ts with tests extended for the new presentation helper.

Testing

  • vp test run in apps/mobile: 611 passing
  • Typecheck and lint clean for the changed files (one pre-existing packages/contracts/src/orchestration.ts error reproduces on clean main, unrelated to this branch)
  • iOS 26.5 Simulator (iPhone 17e): connected to an isolated environment, stopped its backend, confirmed the debounced "Reconnecting to leftbook" header while the seeded thread row stayed fixed, then restarted the backend and confirmed the brand returned

Simulator verification

Connected Reconnecting
Connected header Reconnect status in the header

Reconnect transition

📹 Full-quality 9-second simulator recording


Written by Claude Opus 5 (1M context) in T3 Code.


Note

Low Risk
UI-only mobile header/list layout change with no auth, data, or API changes; main risk is header animation/native chrome edge cases; the iPhone path is now verified in an iOS 26.5 Simulator.

Overview
Reconnects used to insert a connection status pill above the thread list, which pushed rows down and back on every blip. That pill is removed; status now lives in the header brand/title slot via new WorkspaceConnectionTitle, so list geometry stays fixed.

While disconnected (after an 800ms debounce), the T3 Code lockup or “Threads” title is replaced in place by a tappable label (spinner for reconnect/sync, wifi-slash for offline/errors) that still opens environment settings. getConnectionAwareBrandHeaderOptions wires this into iOS nav headers; Android home header and iPad sidebar (native + custom chrome) use the same component. Shared brandTitleOffset keeps status aligned with the brand.

workspace-connection-status.ts gains workspaceConnectionStatusPresentation (label + whether to show progress); tests cover it. onOpenEnvironments moves from HomeScreen to HomeHeader / route header options.

Reviewed by Cursor Bugbot for commit d61499f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Move connection status display from thread list into the header title area

  • Replaces the inline WorkspaceConnectionStatus component (rendered inside HomeScreen and ThreadNavigationSidebarPane) with a new WorkspaceConnectionTitle component that swaps the header brand area to show connection status during reconnects.
  • Adds getConnectionAwareBrandHeaderOptions to wire WorkspaceConnectionTitle into native stack headers, with platform-specific placement (iOS native glass vs. Android).
  • Introduces useDelayedConnectionStatus, which suppresses status changes shorter than 800ms so brief reconnects don't flash the UI.
  • On Android, the header brand area becomes pressable to open environment settings; iOS uses unstable_headerLeftItems for alignment.
  • Behavioral Change: connection status no longer shifts the thread list layout; it is shown in-place within the header title slot.

Macroscope summarized d61499f.

The environment connection status rendered as a row above the thread
list, so every reconnect pushed the rows down and popped them back.

The brand/title slot in the header now doubles as the status surface:
while an environment is unavailable the lockup ("Threads" on the iPad
sidebar) fades to a spinner plus status label in place, and fades back
on reconnect. Nothing is inserted into the list, so its geometry never
moves. An 800ms debounce keeps sub-second blips invisible entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f1f323bb-e050-401a-8d61-682cf1d3d01f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 5, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d61499f. Configure here.

{status.label}
</Text>
</Pressable>
</StatusFadeIn>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status labels overflow header actions

Medium Severity

When WorkspaceConnectionTitle displays a connection status with grow, the inner Pressable and its label don't properly constrain their width. This allows long status messages to overflow and overlap adjacent header controls, as flex: 1 and minWidth: 0 are not effectively applied to the content.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d61499f. Configure here.

@macroscopeapp

macroscopeapp Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

PR is marked WIP and introduces a new component with meaningful UI behavior changes (relocating connection status display). An unresolved comment identifies a potential layout overflow issue in the new component.

You can customize Macroscope's approvability policy. Learn more.

@t3dotgg t3dotgg changed the title WIP: fix(mobile): reconnects no longer shift the thread list fix(mobile): reconnects no longer shift the thread list Aug 7, 2026
@t3dotgg
t3dotgg merged commit 6d70e6d into main Aug 7, 2026
17 checks passed
@t3dotgg
t3dotgg deleted the t3code/mobile-reconnect-status branch August 7, 2026 13:21
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Adopts the two upstream commits after #378: reconnects no longer shift the
mobile thread list (pingdotgg#5372) and the v0.0.32 release prep.

pingdotgg#5372 moves connection status out of the lists into the header title slot
(WorkspaceConnectionTitle / getConnectionAwareBrandHeaderOptions) and deletes
WorkspaceConnectionStatus. The fork rendered that status in four places across
HomeScreen and ThreadNavigationSidebar — exactly the row-shifting this fixes —
so those in-list surfaces are removed and the header slots adopted. The
auto-merge left one dangling {connectionStatus} in HomeScreen whose definition
upstream had deleted; typecheck caught it.

Fork surfaces kept around the new title:
- multi-environment selection: HomeScreen keeps its selectedEnvironmentIds
  label derivation and environmentLabelById (BoardScreen consumes it) instead
  of upstream's single selectedEnvironmentId.
- board mode: ThreadNavigationSidebar keeps its board branch, board-specific
  solid header, and board-gated search bar, with upstream's connection-aware
  brand slot merged into the same header options.
- HomeRouteScreen keeps the fork's in-flow HomeHeader as title owner rather
  than also restoring upstream's native-stack title, which would surface the
  connection status twice.

Adversarial review caught two holes in the first pass, both fixed here:
getConnectionAwareBrandHeaderOptions hardcodes the brand lockup and "Threads",
so spreading it over the fork's list-mode titles silently replaced "Board" /
"Projects"; and iOS Home has its own IosHomeHeader owning the native title, so
dropping upstream's route-level options left that path with no status surface
at all. The helper now takes an optional title/brand, and both the sidebar and
IosHomeHeader pass their mode title through it.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Adopts the two upstream commits after #378: reconnects no longer shift the
mobile thread list (pingdotgg#5372) and the v0.0.32 release prep.

pingdotgg#5372 moves connection status out of the lists into the header title slot
(WorkspaceConnectionTitle / getConnectionAwareBrandHeaderOptions) and deletes
WorkspaceConnectionStatus. The fork rendered that status in four places across
HomeScreen and ThreadNavigationSidebar — exactly the row-shifting this fixes —
so those in-list surfaces are removed and the header slots adopted. The
auto-merge left one dangling {connectionStatus} in HomeScreen whose definition
upstream had deleted; typecheck caught it.

Fork surfaces kept around the new title:
- multi-environment selection: HomeScreen keeps its selectedEnvironmentIds
  label derivation and environmentLabelById (BoardScreen consumes it) instead
  of upstream's single selectedEnvironmentId.
- board mode: ThreadNavigationSidebar keeps its board branch, board-specific
  solid header, and board-gated search bar, with upstream's connection-aware
  brand slot merged into the same header options.
- HomeRouteScreen keeps the fork's in-flow HomeHeader as title owner rather
  than also restoring upstream's native-stack title, which would surface the
  connection status twice.

Adversarial review caught two holes in the first pass, both fixed here:
getConnectionAwareBrandHeaderOptions hardcodes the brand lockup and "Threads",
so spreading it over the fork's list-mode titles silently replaced "Board" /
"Projects"; and iOS Home has its own IosHomeHeader owning the native title, so
dropping upstream's route-level options left that path with no status surface
at all. The helper now takes an optional title/brand, and both the sidebar and
IosHomeHeader pass their mode title through it.

A second review pass caught one more surface — the custom (Android split)
sidebar large title still hardcoded "Threads" — plus the status offset being
applied to caller-supplied titles that lack the brand lockup's inset. Both
fixed, and mobileSurfaceExistence now asserts every adopting surface passes its
own list-mode title (that assertion fails on the pre-fix tree).

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Adopts pingdotgg#5581 (drag pinned threads into your own order) on top of the pingdotgg#5372
merge already on this branch.

Pinned rows now sort by the user's arranged pinOrderKey on every platform,
which is deliberately independent of the fork's thread-grouping preference:
that preference still orders the active rows, but a pin is an explicit
arrangement and upstream documents the sort as identical everywhere so
mixed-version fleets cannot disagree. Server capability gates dragging only,
never the sort. SidebarV2 keeps the fork's environment/ownership filters around
the new pinned block and gains upstream's reorderablePinnedKeys.

Welds the auto-merge produced, all caught by typecheck or tests:
- useThreadActions.ts: upstream's ThreadPinReorderUnsupportedError landed
  inside the fork's clearPerThreadClientState, leaving it unclosed.
- threadListV2.ts: two `for (const thread of ...)` headers stacked on one
  loop body; the pinned block takes upstream's order-key sort.
- useThreadListActions.ts: duplicate threadEnvironment import plus local
  copies of helpers this fork imports from threadActionMessages.
- HomeRouteScreen: upstream's single-environment onEnvironmentChange handler
  does not exist in this fork's multi-select screen.
- projector.pinned.test.ts indexed state.threads[0]; this fork's read model
  keys threads by id in a HashMap.
- Six migration-ledger fixtures pinned the tail at 37; upstream's 038
  ProjectionThreadsPinOrderKey extends it.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Adopts pingdotgg#5581 (drag pinned threads into your own order) on top of the pingdotgg#5372
merge already on this branch.

Pinned rows now sort by the user's arranged pinOrderKey on every platform,
which is deliberately independent of the fork's thread-grouping preference:
that preference still orders the active rows, but a pin is an explicit
arrangement and upstream documents the sort as identical everywhere so
mixed-version fleets cannot disagree. Server capability gates dragging only,
never the sort. SidebarV2 keeps the fork's environment/ownership filters around
the new pinned block and gains upstream's reorderablePinnedKeys.

Welds the auto-merge produced, all caught by typecheck or tests:
- useThreadActions.ts: upstream's ThreadPinReorderUnsupportedError landed
  inside the fork's clearPerThreadClientState, leaving it unclosed.
- threadListV2.ts: two `for (const thread of ...)` headers stacked on one
  loop body; the pinned block takes upstream's order-key sort.
- useThreadListActions.ts: duplicate threadEnvironment import plus local
  copies of helpers this fork imports from threadActionMessages.
- HomeRouteScreen: upstream's single-environment onEnvironmentChange handler
  does not exist in this fork's multi-select screen.
- projector.pinned.test.ts indexed state.threads[0]; this fork's read model
  keys threads by id in a HashMap.
- Six migration-ledger fixtures pinned the tail at 37; upstream's 038
  ProjectionThreadsPinOrderKey extends it.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Aug 7, 2026
## What's Changed
* fix(mobile): reconnects no longer shift the thread list by @t3dotgg in pingdotgg/t3code#5372
* feat(web): drag pinned threads into your own order by @t3dotgg in pingdotgg/t3code#5581


**Full Changelog**: pingdotgg/t3code@v0.0.33-nightly.20260807.1025...v0.0.33-nightly.20260807.1026

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.33-nightly.20260807.1026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant