Description
On the Android shell, the floating server-picker pill is a native TextView stacked on top of the WebView. At certain window widths it lands directly over the conversations sidebar's header controls — Search / Settings / Collapse sidebar — and, being a native clickable view above the WebView, it swallows every touch in its rect. Those three buttons become un-tappable.
Root cause. The pill is centred over the entire window, with no knowledge of the web layout beneath it:
web/android/app/src/main/java/ai/omnigent/android/MainActivity.kt:176-186
FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT,
Gravity.TOP or Gravity.CENTER_HORIZONTAL)
It is isClickable = true (:172), added to the FrameLayout after the WebView (:161, :187) so it is always on top, and its label is host[:port] (hostLabelOf, :460-474) with no width cap — so its width varies with the hostname.
Meanwhile the sidebar's action cluster is pinned to that card's top-right edge (web/src/shell/Sidebar.tsx:674-728, data-testid="sidebar-header-actions"): three size-6 buttons with gap-1, ≈80px total, inset 16px by px-4. The card is md:m-2 and user-resizable — default 320px, min 220, max 0.5 × window width (web/src/hooks/useResizableSidebar.ts:19-28).
With sidebar width S, window width W and half-pill-width p, the cluster spans x ∈ [S-88, S-8] and the pill spans x ∈ [W/2-p, W/2+p]. They collide iff:
W/2 - p + 8 < S < W/2 + p + 88
- Short host
10.0.2.2:8000 (p ≈ 50dp) at W = 768dp → collides for 342 < S < 906. The 320px default just misses, but any user-widened sidebar lands in it.
- Long host
omnigent.joyful.house (p ≈ 77dp) → collides for 315 < S < 933, so the default 320px sidebar collides immediately.
- At the maximum sidebar width
S = W/2, the cluster's right edge is at W/2 - 8, inside the pill for any p > 8 — it always collides there.
Vertically they always overlap (pill at y ∈ [safeTop+8, safeTop+~36]; the sidebar's 48px header row starts at safeTop thanks to the injected padding-top, putting the 24px buttons at y ∈ [safeTop+14, safeTop+38]). Horizontal position is the only variable — which is why this only bites when the screen width, sidebar width and hostname length line up.
A hide contract already exists on the web side, unimplemented on Android. web/src/lib/nativeBridge.ts:519-529 setNativeServerSwitcherHidden() calls native.setServerSwitcherHidden ?? native.setSidebarOpen, guarded by if (!setter) return;. The Android facade (NativeBridgeScript.kt:204-253) exposes only kind / setColorScheme / setBadgeCount / notify / onNotificationActivated / onNativeInsets, and the native dispatch table (OmnigentBridgeListener.kt:48) handles only the first three — so the call is a silent no-op on Android. Every web call site is isIOSShell()-gated (ChatPage.tsx:1585,1589; useNativeServerSwitcher.ts:17,86,90).
Note the existing iOS mechanism would not fix this case even if enabled on Android: it hides via an elementFromPoint hit-test at the chat surface's centre (useNativeServerSwitcher.ts:95-129), and at tablet widths the sidebar is a docked column that never covers that centre, so it would report "frontmost" and keep the pill shown.
Constraint worth preserving: MainActivity.kt:65-67 documents the pill as deliberately "Always visible so it's always available as a recovery path". Tapping it is the only entry point to showServerSwitcherMenu, so any fix that hides it must not strand a user whose server is unreachable — the sidebar is open by default at ≥768px (AppShell.tsx:1548-1551), so "hide whenever the rail is expanded" would hide the recovery control by default on every tablet.
iOS parity: web/ios/Omnigent/WebShellView.swift:32-45 centres the switcher the same way, but does cap its width at min(172, max(120, width*0.38)) (:197-201). Android has no cap.
Steps to reproduce
- Install the Android app on a tablet or unfolded foldable (or any device whose WebView viewport is ≥768 CSS px, so the sidebar docks instead of overlaying).
- Connect to a server with a reasonably long hostname (e.g.
omnigent.joyful.house) — or use any host and drag the sidebar's right edge wider.
- With the conversations sidebar expanded, try to tap Search, Settings or Collapse sidebar in the sidebar's top-right corner.
Expected: the buttons respond.
Actual: taps landing under the server-picker pill are swallowed by the native view; the buttons are dead. Collapsing the sidebar (if you can still reach the control) or resizing it moves the collision away.
Version
4d23ed78 (0.8.0.dev0); Android shell versionName 0.1.0
OS
Android (tablet / unfolded foldable, e.g. Pixel 10 Pro Fold). The iOS shell shares the centred-overlay shape, mitigated there by a width cap.
Description
On the Android shell, the floating server-picker pill is a native
TextViewstacked on top of the WebView. At certain window widths it lands directly over the conversations sidebar's header controls — Search / Settings / Collapse sidebar — and, being a native clickable view above the WebView, it swallows every touch in its rect. Those three buttons become un-tappable.Root cause. The pill is centred over the entire window, with no knowledge of the web layout beneath it:
It is
isClickable = true(:172), added to theFrameLayoutafter the WebView (:161,:187) so it is always on top, and its label ishost[:port](hostLabelOf,:460-474) with no width cap — so its width varies with the hostname.Meanwhile the sidebar's action cluster is pinned to that card's top-right edge (
web/src/shell/Sidebar.tsx:674-728,data-testid="sidebar-header-actions"): threesize-6buttons withgap-1, ≈80px total, inset 16px bypx-4. The card ismd:m-2and user-resizable — default 320px, min 220, max 0.5 × window width (web/src/hooks/useResizableSidebar.ts:19-28).With sidebar width
S, window widthWand half-pill-widthp, the cluster spansx ∈ [S-88, S-8]and the pill spansx ∈ [W/2-p, W/2+p]. They collide iff:10.0.2.2:8000(p ≈ 50dp) at W = 768dp → collides for342 < S < 906. The 320px default just misses, but any user-widened sidebar lands in it.omnigent.joyful.house(p ≈ 77dp) → collides for315 < S < 933, so the default 320px sidebar collides immediately.S = W/2, the cluster's right edge is atW/2 - 8, inside the pill for anyp > 8— it always collides there.Vertically they always overlap (pill at
y ∈ [safeTop+8, safeTop+~36]; the sidebar's 48px header row starts atsafeTopthanks to the injectedpadding-top, putting the 24px buttons aty ∈ [safeTop+14, safeTop+38]). Horizontal position is the only variable — which is why this only bites when the screen width, sidebar width and hostname length line up.A hide contract already exists on the web side, unimplemented on Android.
web/src/lib/nativeBridge.ts:519-529setNativeServerSwitcherHidden()callsnative.setServerSwitcherHidden ?? native.setSidebarOpen, guarded byif (!setter) return;. The Android facade (NativeBridgeScript.kt:204-253) exposes onlykind/setColorScheme/setBadgeCount/notify/onNotificationActivated/onNativeInsets, and the native dispatch table (OmnigentBridgeListener.kt:48) handles only the first three — so the call is a silent no-op on Android. Every web call site isisIOSShell()-gated (ChatPage.tsx:1585,1589;useNativeServerSwitcher.ts:17,86,90).Note the existing iOS mechanism would not fix this case even if enabled on Android: it hides via an
elementFromPointhit-test at the chat surface's centre (useNativeServerSwitcher.ts:95-129), and at tablet widths the sidebar is a docked column that never covers that centre, so it would report "frontmost" and keep the pill shown.Constraint worth preserving:
MainActivity.kt:65-67documents the pill as deliberately "Always visible so it's always available as a recovery path". Tapping it is the only entry point toshowServerSwitcherMenu, so any fix that hides it must not strand a user whose server is unreachable — the sidebar is open by default at ≥768px (AppShell.tsx:1548-1551), so "hide whenever the rail is expanded" would hide the recovery control by default on every tablet.iOS parity:
web/ios/Omnigent/WebShellView.swift:32-45centres the switcher the same way, but does cap its width atmin(172, max(120, width*0.38))(:197-201). Android has no cap.Steps to reproduce
omnigent.joyful.house) — or use any host and drag the sidebar's right edge wider.Expected: the buttons respond.
Actual: taps landing under the server-picker pill are swallowed by the native view; the buttons are dead. Collapsing the sidebar (if you can still reach the control) or resizing it moves the collision away.
Version
4d23ed78(0.8.0.dev0); Android shell versionName 0.1.0OS
Android (tablet / unfolded foldable, e.g. Pixel 10 Pro Fold). The iOS shell shares the centred-overlay shape, mitigated there by a width cap.