fix(app): restore the invisible settings/model/effort controls on Android - #1598
fix(app): restore the invisible settings/model/effort controls on Android#1598chphch wants to merge 1 commit into
Conversation
…roid Upstream's mobile UI refresh routes the session composer's permission, model and effort triggers through a Jetpack Compose DropdownMenu. On Android nothing painted: the row showed the photo, mic and send buttons with an empty gap where the three controls belong, so the model and effort pickers were unreachable on a phone. The trigger content is a React Native subtree hosted inside a Compose view. expo-modules-core wraps such a child in ExpoComposeAndroidView, which renders it with `Modifier.size(view.width, view.height)` sampled once at composition and never re-read — there is no layout listener. Composed before React Native has measured the child, the trigger is pinned at 0x0 for the rest of its life: laid out, tappable, invisible. The composer already ships its own popup pickers for the non-native path, and they render the same controls, so Android now uses those and iOS keeps its native menus. Two smaller fixes in the same area: - `DropdownMenuItem.Text` is a native slot view, so the bare string child threw "Text strings must be rendered within a <Text> component" once per option on every menu render. Compose's own Text turns children into the native `text` prop. - The composer handed `NativeSettingsMenu` the full button style for both the wrapper and the child inside it, which insets the content twice. The wrapper now carries only the slot's size. Verified on an Android emulator (Pixel 5, API 30 arm64) against `happy://dev/rig-preview`: the gear and the "GPT Shared · high" model/effort control render, and tapping the latter opens the picker. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
|
Device capture — Android emulator (Pixel 5, API 30, arm64, 393dp wide so Before — the action row paints only the send/stop control; the gear and the model/effort control occupy their slots but render nothing: After — the gear and the After, tapped — the model picker opens: One caveat on the "before" frame so it is not overstated: it was captured on the same debug build with an earlier, ineffective attempt at the fix in place (wrapping the trigger in For what it is worth, |



On Android the session composer's permission, model and effort controls do not paint: the action row shows the photo, mic and send buttons with an empty gap where the three controls belong, so the model and effort pickers are unreachable on a phone. The controls are still laid out and tappable, which makes it look like the row is simply missing options.
The cause is the Compose → React Native boundary. Each trigger is a React Native subtree handed to a Jetpack Compose
DropdownMenu.Trigger, and expo-modules-core wraps such a child inExpoComposeAndroidView, which renders it withModifier.size(view.width, view.height)sampled once at composition:There is no layout listener, so a trigger composed before React Native has measured it stays pinned at 0x0 for the rest of its life.
AgentInputalready ships popup pickers for the non-native path that render the same three controls, so Android now uses those and iOS keeps its native menus.Two smaller fixes in the same area:
DropdownMenuItem.Textis a native slot view (SlotView), so the bare string child threwText strings must be rendered within a <Text> componentonce per option on every menu render — nine per render on the rig preview. Compose's ownTextturns children into the nativetextprop instead. Same fix inNativeOptionsPicker.android, which has the identical slot usage.NativeSettingsMenuthe full button style for both the wrapper and the child inside it, so padding applied twice once the controls became visible. The wrapper now carries only the slot's size, matching howHomeDockalready splits it.Verified on an Android emulator (Pixel 5, API 30, arm64) against
happy://dev/rig-preview: before the change the row renders only the send control; after it, the gear and theGPT Shared · highmodel/effort control render and tapping the latter opens the picker.