hermes(webui): fix mobile composer hidden behind the gesture bar
On an Android standalone PWA the composer's bottom control row was rendering about one line below the visible viewport, behind the system gesture bar, so those controls were unreachable. The theme already pads the titlebar with env(safe-area-inset-top/left/right), but the viewport meta never opted into viewport-fit=cover, so every safe-area inset collapsed to 0 and the bottom edge had no reservation. Add viewport-fit=cover to the viewport meta (base patch) so the insets carry real values, and reserve env(safe-area-inset-bottom) at the bottom of the composer (brand.css), additive with the app's existing --keyboard-bottom-inset and absorbed by the flex-1 message scroller so total height stays within the viewport. Inert on desktop (env() resolves to 0). Dockerfile verifies the meta patch landed; brand/dockerfile contracts covered by tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
This commit is contained in:
parent
b59f754f5a
commit
ffa477585d
@ -84,6 +84,7 @@ RUN /opt/hermes/.venv/bin/python -c 'import cryptography, yaml' \
|
||||
&& grep -Fq "TELEGRAM_PROJECT_NAME = 'Telegram'" /opt/hermes-webui/api/models.py \
|
||||
&& grep -Fq "'atlas/auto/maximum': 'Automatic · Maximum'" /opt/hermes-webui/static/panels.js \
|
||||
&& grep -Fq 'Atlas Jetson (private)' /opt/hermes-webui/static/index.html \
|
||||
&& grep -Fq 'user-scalable=no, viewport-fit=cover' /opt/hermes-webui/static/index.html \
|
||||
&& grep -Fq 'HERMES_WEBUI_ATLAS_TTS_URL' /opt/hermes-webui/api/routes.py \
|
||||
&& grep -Fq 'settings["webui_bundle_version"]' /opt/hermes-webui/api/routes.py \
|
||||
&& grep -Fq 'settings.webui_bundle_version||settings.webui_version' /opt/hermes-webui/static/panels.js \
|
||||
|
||||
@ -21,6 +21,26 @@ if source.count(before) != 1:
|
||||
raise SystemExit("Hermes WebUI xhigh UI patch context changed")
|
||||
index.write_text(source.replace(before, "", 1), encoding="utf-8")
|
||||
|
||||
# Enable edge-to-edge safe-area insets on mobile / standalone PWAs. The theme
|
||||
# already pads the titlebar with env(safe-area-inset-top/left/right) and the
|
||||
# composer reserves env(safe-area-inset-bottom) (hermes-brand.css), but env()
|
||||
# resolves to 0 unless the viewport opts into viewport-fit=cover. Android
|
||||
# standalone PWAs render the webview edge-to-edge regardless, so without cover
|
||||
# there is no inset to pad with and the composer's control row hides behind the
|
||||
# gesture bar. Opt in so those insets carry real values.
|
||||
source = index.read_text(encoding="utf-8")
|
||||
before = (
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1, '
|
||||
'maximum-scale=1, user-scalable=no">'
|
||||
)
|
||||
after = (
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1, '
|
||||
'maximum-scale=1, user-scalable=no, viewport-fit=cover">'
|
||||
)
|
||||
if source.count(before) != 1:
|
||||
raise SystemExit("Hermes WebUI viewport-fit patch context changed")
|
||||
index.write_text(source.replace(before, after, 1), encoding="utf-8")
|
||||
|
||||
# oauth2-proxy returns 401 for browser API and health probes when the secure
|
||||
# session expires. Re-enter OIDC with the complete return path.
|
||||
ui = Path("/opt/hermes-webui/static/ui.js")
|
||||
|
||||
@ -250,3 +250,14 @@
|
||||
border-radius: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reserve the device safe-area (home indicator / gesture bar) below the
|
||||
composer so its control row is never hidden behind it. Requires
|
||||
viewport-fit=cover on the viewport meta (set in hermes-webui-base-patch.py);
|
||||
env() resolves to 0 on devices without an inset, so this is inert on desktop.
|
||||
Additive with the keyboard inset the app already manages via
|
||||
--keyboard-bottom-inset, and absorbed by the flex-1 message scroller so the
|
||||
total column height stays within the viewport (body is height:100dvh). */
|
||||
.composer-wrap {
|
||||
padding-bottom: calc(16px + var(--keyboard-bottom-inset, 0px) + env(safe-area-inset-bottom, 0px)) !important;
|
||||
}
|
||||
|
||||
@ -379,6 +379,23 @@ def test_brand_css_is_accessible_dark_and_reduced_motion_aware() -> None:
|
||||
assert "transition: none !important" in reduced
|
||||
assert "transform: none !important" in reduced
|
||||
assert "animation:" not in css
|
||||
# The composer reserves the device bottom safe-area (home indicator / gesture
|
||||
# bar) so its control row is not hidden behind it, additive with the app's
|
||||
# keyboard inset. env() is 0 on inset-less devices, so this is desktop-inert.
|
||||
assert ".composer-wrap" in css
|
||||
composer = css.rsplit(".composer-wrap {", 1)[1].split("}", 1)[0]
|
||||
assert "env(safe-area-inset-bottom, 0px)" in composer
|
||||
assert "var(--keyboard-bottom-inset, 0px)" in composer
|
||||
|
||||
|
||||
def test_base_patch_and_dockerfile_opt_into_edge_to_edge_safe_area() -> None:
|
||||
"""viewport-fit=cover is required for env(safe-area-inset-*) to carry real
|
||||
values; the base patch adds it and the Dockerfile verifies it landed."""
|
||||
base_patch = (DOCKERFILES / "hermes-webui-base-patch.py").read_text(encoding="utf-8")
|
||||
assert "viewport-fit=cover" in base_patch
|
||||
assert "user-scalable=no, viewport-fit=cover" in base_patch
|
||||
dockerfile = (DOCKERFILES / "Dockerfile.hermes-webui").read_text(encoding="utf-8")
|
||||
assert "grep -Fq 'user-scalable=no, viewport-fit=cover'" in dockerfile
|
||||
|
||||
|
||||
def test_empty_state_uses_canonical_character_without_inline_staff(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user