Skip to content

[Bug]: Duplicate top header rendered in Android split-screen view #13498

Description

@Wizhill05

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/mobile

Steps to reproduce

  1. Open the T3 Code app on an Android device.
  2. Enter Android split-screen multitasking mode (for instance, video player on one side and T3 Code on the other).
  3. Observe the top navigation / header area of the T3 Code app.

Expected behavior

A single top header bar is displayed containing the branding, search, and settings icons, maximizing usable screen real estate.

Actual behavior

The T3 Code top header appears twice stacked vertically:

  • The top header renders T3 Code ALPHA along with system status bar inset padding.
  • Immediately below it, an identical second header renders T3 Code ALPHA alongside the search and settings action buttons.

In split-screen view where vertical space is already constrained, this duplicate top bar wastes significant screen area.

Impact

Minor bug or occasional failure

Version or commit

Android app (Alpha release)

Environment

Android

Logs or stack traces

No response

Screenshots or supporting files

T3 Code Android Split Screen Duplicate Header

Workaround

No response

Activity

  1. juliusmarminge commented on Sep 24, 2026

    @juliusmarminge
    Member

    Triage: #13498 Duplicate top header in Android split-screen

    Verdict: Valid. Still present on main (f3cb2a1fee). Not a duplicate. No fix in a newer release.

    Severity: Low. The Threads screen stays usable, but split-screen (and any later resize) stacks two brand bars and burns vertical space.

    What happened

    On Android Alpha, entering split-screen shows two stacked T3 Code ALPHA bars on the Threads screen. The upper bar is only the brand, padded for the status bar. The lower bar repeats the brand and adds Search and Settings. The screenshot is a vivo PD2193F (Android 14, PD2193F_EX_A_14.2.12.0.W30.V000L1), landscape, T3 Code in the left pane, 2404×1080, taken 2026-09-25 02:51 +05:30. The Alpha badge matches the production stage label.

    Diagnosis

    Android compact Home already draws its own header. The native stack header is supposed to stay hidden, and a width change turns it back on.

    On the compact path, HomeRouteScreen re-applies the iOS brand header with headerShown: true whenever windowWidth changes. That exists so the native title can be restored after leaving the in-app split layout, and so the connection-status title gets a new max width. optionsVersion={windowWidth} was added in #8624 (053affbed26).

            {/* Restore the header after leaving split view; screen options are
                shallow-merged. The brand slot also doubles as the connection
                status surface while an environment reconnects. */}
            <NativeStackScreenOptions
              optionsVersion={windowWidth}
              options={{
                ...getConnectionAwareBrandHeaderOptions({
                  headerWidth: windowWidth,
                  onOpenEnvironments: () =>
                    navigation.navigate("SettingsSheet", {
                      screen: "SettingsContent",
                      params: { screen: "SettingsEnvironments" },
                    }),
                }),
                headerShown: true,
              }}
            />

    Android does not use that native bar. HomeHeader.android.tsx hides it and renders MaterialThreadListToolbar, which is the bar with the brand plus Search and Settings.

      return (
        <>
          <NativeStackScreenOptions options={{ headerShown: false }} />
          <MaterialThreadListToolbar
            searchQuery={props.searchQuery}
            onSearchQueryChange={props.onSearchQueryChange}
            filterActions={menuActions}
            filterCustomized={hasCustomListOptions}
            onFilterAction={handleMenuAction}
            onOpenSettings={props.onOpenSettings}
            onOpenEnvironments={props.onOpenEnvironments}
          />

    NativeStackScreenOptions skips setOptions when the options signature is unchanged. { headerShown: false } never changes, so that hide runs once at mount and then stops. The sibling that sets headerShown: true runs again on every width change and wins, because setOptions shallow-merges. The native header comes back above the Material toolbar.

    That matches the screenshot. The native title is CompactBrandTitle (wordmark, "Code", and the Alpha badge) with no trailing actions. Search and Settings exist only on the in-flow toolbar. The native bar also carries the status-bar inset. The Home screen's static options in Stack.tsx already start from headerShown: true plus the same brand title, so the first paint is only correct because the Android hide lands after it.

    A phone stays on this compact path in split-screen. Split layout needs width ≥ 720 and height ≥ 600. The screenshot is landscape (device height 1080 px), so the pane stays under the height floor. The in-app split branch is not what the user hit. That branch hides the native header and titles the detail pane "Threads".

    Leaving split-screen does not clear it. Any later width change, including returning to portrait, re-applies headerShown: true. The bad header sticks until Home remounts. A cold start in the final window size hides the native bar again, until the next resize. Rotation and freeform resize on a phone should hit the same path.

    Steps to reproduce

    1. Cold-start the Android app and open Threads. One brand bar, with Search and Settings.
    2. Enter split-screen so the window width changes while the pane stays compact (landscape side-by-side, as in the screenshot).
    3. Two T3 Code ALPHA bars are stacked. Only the lower one has Search and Settings.
    4. Exit split-screen. The duplicate remains until the app is restarted without a further resize.

    Environment

    Android Alpha (production stage label). Reporter did not pin a build number. Inspected tree is main at f3cb2a1fee, app version 1.3.1 in apps/mobile/app.config.ts. Device from the screenshot: vivo PD2193F, Android 14. This VM has no Android emulator, so this is source plus the screenshot, not a live run.

    Related issues

    No matching issue besides #13498. #10629 is the iPad Threads toolbar disappearing after resize, a different platform and failure.

    Fix

    On Android, the compact HomeRouteScreen options should keep headerShown: false across width changes. iOS should still pass headerShown: true so the native bar returns after the in-app split layout. Re-applying the hide by bumping the Android options version is weaker, because it depends on effect order.

    Workaround

    Force-quit and reopen after the window is already the size you want. Resizing again brings the second bar back.

  2. added
    bugSomething is broken or behaving incorrectly.
    acceptedfeature request accepted
    via-triageFiled through npx t3 triage
    on Sep 24, 2026
  3. coreldh commented on Sep 27, 2026

    @coreldh

    Also reproducing on a Samsung Galaxy S25 Ultra (latest Android/One UI, latest T3 Code Android Alpha from the store), in portrait, full-screen.

    As far as I can tell I did not use split-screen or rotate the phone before it appeared. I saw the same thing as the original report on the Threads screen: two stacked T3 Code ALPHA bars. The upper one is only the brand under the status bar, and the lower one has the brand plus Search and Settings.

    So the trigger may be wider than split-screen or rotation. Something else that changes the window width, or the headerShown: true re-apply at mount, might be enough. The fix proposed in the triage (keep headerShown: false on Android compact Home across width changes) should cover this case too.

    I have a screenshot. I've left it out because it shows private thread titles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions