fix(aiui)+docs: keyboard-resize parity for standalone AIUI; companion handover note

Operator-reported: keyboard opening in chat pads the tab bar and scrolls the
page instead of scaling the chat window. Triage: neode-ui already ships
interactive-widget=resizes-content + the --visual-viewport-height var, so
mobile-web Chrome resizes correctly — but an Android WebView ignores that meta
entirely, and the described pan-plus-padding is the adjustPan/edge-to-edge-
without-IME-insets signature. Companion-side fix documented for handover in
docs/companion-keyboard-viewport.md (manifest adjustResize, or IME insets when
edge-to-edge, plus a chrome://inspect verification recipe). Web side gets the
one real parity gap: AIUI's standalone index.html lacked the meta neode-ui has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-04 03:21:53 -04:00
co-authored by Claude Opus 5
parent 86129d5da2
commit 57628a2aa4
2 changed files with 108 additions and 1 deletions
+9 -1
View File
@@ -2,7 +2,15 @@
<html lang="en" class="h-full overflow-hidden">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<!-- interactive-widget=resizes-content: when the soft keyboard opens,
Chrome (108+) resizes the LAYOUT viewport instead of only the visual
one, so window.innerHeight shrinks and full-height layouts scale to
the space above the keyboard rather than scrolling under it.
neode-ui's index.html already carries this; this adds parity for
AIUI's standalone mode. Ignored by iOS Safari and by Android WebView —
the companion app controls keyboard resize itself via
windowSoftInputMode/IME insets (see docs/companion-keyboard-viewport.md). -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
<meta name="theme-color" content="#0a0a0a" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#faf9f6" media="(prefers-color-scheme: light)" />
<meta name="mobile-web-app-capable" content="yes" />
+99
View File
@@ -0,0 +1,99 @@
# Companion app — soft-keyboard viewport handover
**Audience:** the companion (Android WebView wrapper) developer.
**Reported:** 2026-08-04 by the operator — in chat, when the soft keyboard opens,
padding is added to the bottom tab bar and the page scrolls; the chat window
should instead scale to the height that remains above the keyboard.
## Why this is (most likely) companion-side
The symptom described — content keeps its full height, the browser *pans/scrolls*
the focused input into view, and the fixed bottom bar picks up a visual gap — is
the classic Android `adjustPan` (or edge-to-edge-without-IME-insets) signature.
The web side already implements the correct contract, verified in-repo:
1. **`neode-ui/index.html`** carries
`interactive-widget=resizes-content` in its viewport meta. In Chrome 108+
this makes the keyboard resize the **layout** viewport, so
`window.innerHeight` shrinks.
2. **`neode-ui/src/main.ts``syncViewportHeightVar()`** mirrors
`window.innerHeight` into the CSS var `--visual-viewport-height` on
`resize`, `orientationchange`, and `visualViewport.resize`. It deliberately
uses `innerHeight` (not `visualViewport.height`) so the value shares a
reference frame with `position: fixed` elements like the mobile tab bar.
3. **`neode-ui/src/style.css`** sizes the mobile layout (including the chat
iframe container) from
`var(--visual-viewport-height, 100dvh)` minus the tab-bar/safe-area vars.
So on mobile web Chrome, the keyboard shrinks `innerHeight`, the var updates,
and the chat scales. **An Android WebView ignores the `interactive-widget`
meta entirely** — keyboard resize there is governed by the host app. If the
host pans instead of resizing, no amount of web CSS can fix it: the WebView's
`innerHeight` never changes and the system scrolls the page instead.
## What the companion app should do
Pick the branch that matches how the Activity is configured:
### A. Not edge-to-edge (no `WindowCompat.setDecorFitsSystemWindows(window, false)`)
Set the soft-input mode so the WebView is *resized*, not panned:
```xml
<!-- AndroidManifest.xml, on the Activity hosting the WebView -->
<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustResize" />
```
`adjustPan` (and on some OEM builds the historical default `adjustUnspecified`)
produces exactly the reported behaviour.
### B. Edge-to-edge (decorFitsSystemWindows = false)
`adjustResize` alone stops working in edge-to-edge; you must consume the IME
inset yourself and resize the WebView:
```kotlin
ViewCompat.setOnApplyWindowInsetsListener(webViewContainer) { view, insets ->
val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
// Resize the container to end above the keyboard (or the nav bar when closed)
view.updatePadding(bottom = maxOf(ime.bottom, bars.bottom))
WindowInsetsCompat.CONSUMED
}
```
(If targeting SDK 35+, edge-to-edge is enforced, so branch B is the one that
applies.)
### Do NOT compensate on the web side
Please don't inject extra bottom padding, margins, or scroll offsets into the
page from the wrapper — the web layout already subtracts the tab bar and safe
areas from `--visual-viewport-height`, and wrapper-side compensation double
counts (that is the "padding added to the tabs" half of the symptom).
## How to verify the fix
In the WebView's remote-debug console (`chrome://inspect`), focus the chat
input and check:
- `window.innerHeight` **shrinks** by roughly the keyboard height → correct
(branch A/B working). The chat window will scale; no page scroll.
- `window.innerHeight` **unchanged** while `window.visualViewport.height`
shrinks → the WebView is still panning; the manifest/insets change hasn't
taken effect.
## Web-side status (for completeness)
- neode-ui (the page the companion actually loads): already correct, no change
needed.
- AIUI standalone (`aiui/packages/app/index.html`): was missing the
`interactive-widget` token; added 2026-08-04 for parity. Only affects AIUI
used outside a node, not the embedded/companion path.
- iOS Safari / iOS WKWebView: `interactive-widget` is not supported there. If
an iOS wrapper appears later, the equivalent is
`KeyboardLayoutGuide`/`keyboardWillChangeFrame` driving the WKWebView frame —
same principle: resize the web content, never pan it.