Skip to content

[Bug]: Android new-thread composer toolbar (+ / model / send) invisible when "Remove animations" is on #16080

Description

@LukasOnAir

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. On Android, turn on Settings → Accessibility → Vision → Remove animations.
  2. Open the T3 Code app (fully restart it after changing the setting) and connect to an environment.
  3. Start a new thread (New thread screen: "What should we build in …?").

Expected behavior

The composer shows its toolbar: the attachment (+) button, the model / reasoning picker and the send button, the same as with animations on.

Actual behavior

The composer surface renders at full height with the prompt editor, but the toolbar row is invisible: there is no +, no model picker and no send button, so you can't start a thread from the app. Existing threads are not affected (their toolbar appears when you focus the composer). Turning "Remove animations" off and restarting the app brings the toolbar back.

Impact

Blocks work completely

Version or commit

Android app from the Play Store, installed 2026-10-05. Server 0.0.43-nightly.20260928.2375. Code read at main @ 1a3f7ad.

Environment

OnePlus Nord 3 5G (OxygenOS), Android, environment paired over Tailscale.

Logs or stack traces

No errors on the server side (server trace is clean when the new-thread screen opens).

Screenshots, recordings, or supporting files

No response

Workaround

Turn off Android's "Remove animations" and restart the app.

Likely cause (from reading the source)

ComposerDictationToolbar (apps/mobile/src/features/voice-input/ComposerDictationControl.tsx) wraps the toolbar in an Animated.View with a custom entering animation (toolbarFlip(90, 0)) whose initial values are opacity: 0 and rotateX: 90deg. On the new-thread screen (NewTaskDraftScreen.tsx) the toolbar mounts together with the screen, and with system reduced motion the entering animation seems to never move it to its final state, so it stays transparent / edge-on. In ThreadComposer.tsx the toolbar only mounts later (visible={isToolbarVisible} once the composer is focused), which may be why existing threads are fine. Possible fixes: set reduceMotion on the layout animation so it jumps to the end values, or skip the entering animation on initial mount.

Activity

  1. juliusmarminge commented on Oct 5, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks @LukasOnAir. The report matches current main (1a3f7ad). This is a mobile client bug, which is why the server trace stays clean. It is not a duplicate of #15953 or #16015. Those are programmatic scrolls that still animate under Reduce Motion. Here a control that should appear instantly never does.

    What I found

    The new-thread toolbar is mounted immediately, already inside an entering animation that starts hidden.

    • NewTaskDraftScreen renders ComposerDictationToolbar with the screen. It does not pass visible, so the animated row is created on first paint.
    • That row uses entering={toolbarFlip(90, 0)} in ComposerDictationControl.tsx. The initial pose is opacity: 0 and rotateX: 90deg, with backfaceVisibility: "hidden". The prompt editor is a sibling, so the card still looks like a composer while +, the model picker, and send are invisible.
    • withTiming is already set to reduceMotion: ReduceMotion.System. Android’s Accessibility “Remove animations” sets the animator duration scale to 0, which React Native reports as reduced motion, so this flip finishes on its first frame instead of playing out.
    • Reanimated 4.7 (what apps/mobile pins) inserts an entering view at opacity 0 and waits for a later animation frame to show it. On Android that first frame is dropped if the view is not mounted yet (software-mansion/react-native-reanimated#7493). A normal-duration flip still has later frames. A reduced-motion flip does not, and because the animation style includes opacity, the proxy’s opacity restore is cleared too. The row stays at the hidden pose.
    • An existing thread only mounts this same view once the composer is focused (visible={isToolbarVisible} in ThreadComposer.tsx), after the screen is already up. That later mount is why the toolbar can still appear there.

    Likely fix area

    ComposerDictationToolbar in apps/mobile/src/features/voice-input/ComposerDictationControl.tsx. When reduced motion is on, skip this entering animation and mount the toolbar in its resting style (opacity: 1, no rotation), instead of depending on a one-frame entering animation Android can drop. The reduceMotion flag already on withTiming does not do that. The same component is used by the scheduled-task prompt, so a fix there covers that screen too.

    A maintainer will decide on the fix direction.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 5, 2026
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

    bugSomething 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