Skip to content

fix(ui): mobile composer layout#1381

Closed
starship-s wants to merge 5 commits intonesquena:masterfrom
starship-s:fix/mobile-titlebar-composer-layout
Closed

fix(ui): mobile composer layout#1381
starship-s wants to merge 5 commits intonesquena:masterfrom
starship-s:fix/mobile-titlebar-composer-layout

Conversation

@starship-s
Copy link
Copy Markdown
Contributor

Thinking Path

  • The mobile layout had two separate pressure points:
    • normal browser/webview shells could end up with extra titlebar spacing from top safe-area padding
    • the composer had more always-visible controls than narrow phone widths can comfortably support, leading to visible control overlap and uneven icon spacing
  • The titlebar fix is to only apply top safe-area padding in installed/fullscreen display modes.
  • For the composer, shrinking controls would make the UI harder to tap, and wrapping the footer into multiple rows would make the composer feel heavier and consume more vertical space.
  • This keeps the mobile composer as a single primary row and uses progressive disclosure for lower-priority configuration controls.
  • The primary typing path stays visible inline, while workspace/model/reasoning/context controls remain available through a phone-only config panel.
  • This should not affect the normal desktop composer layout. The new config button and overflow panel are hidden by default and only become active under the existing compact/mobile breakpoints. Desktop-width views keep the current inline controls.

What Changed

  • Scoped titlebar top safe-area padding to standalone / fullscreen display modes.
  • Added a phone-only composer config button.
  • Moved secondary mobile controls into the config panel:
    • workspace
    • model
    • reasoning
    • context details
  • Kept primary composer actions visible inline on mobile:
    • attach
    • microphone
    • persona/profile
    • workspace files
    • config
    • send
  • Added a compact context usage badge to the config button and moved the full context details into the config panel.
  • Updated mobile composer styles so visible controls preserve touch-friendly sizing and do not overlap.
  • Added mobile layout regression coverage for the new structure.
  • Kept the intermediate compact workspace switch chip from becoming a blank pill segment by preserving its dropdown affordance until the tighter mobile config behavior takes over.
  • Added mobile layout regression coverage for the new structure and compact workspace switch affordance.

Why It Matters

  • Reduces excess titlebar whitespace in mobile browser/webview layouts.
  • Preserves safe-area handling for installed/fullscreen app modes.
  • Prevents mobile composer controls from visually overlapping or bunching unevenly as more controls are added.
  • Preserves 44px touch targets instead of solving crowding by making buttons smaller.
  • Avoids a bulky multi-row composer while still keeping secondary controls reachable.
  • Keeps context usage visible through a compact badge, with full context details available in the config panel.
  • Makes the mobile hierarchy clearer: frequent actions stay inline, configuration lives behind an explicit control.

Screenshots

Before: crowded composer controls on mobile, with overlapping touch highlights.

Old Mobile UI

After: mobile config panel with workspace/model/reasoning/context controls available.

New Mobile UI

After: normal mobile composer state.

New UI 2

Verification

Targeted mobile layout coverage:

python -m pytest tests/test_mobile_layout.py -q

Result: 42 passed

Syntax / hygiene checks:

node --check static/ui.js

node --check static/panels.js

git diff --check

Risks / Follow-ups

  • This is a responsive layout change, so a quick mobile browser/webview smoke test is still useful before merge.
  • The config panel follows the existing inline-SVG pattern used in the file; icon consolidation can be handled separately if desired.

Model Used

  • GPT-5.5 xhigh for planning / implementation
  • Claude Opus 4.7 for review

@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Nice mobile composer rework. Two genuine pressure points (titlebar safe-area in non-installed shells, composer crowding on phone widths) handled separately rather than mashed into one fix — that's the right shape.

A few observations from skimming the diff against current static/index.html, static/style.css, and static/panels.js:

  • Titlebar fix is correctly scoped. Limiting top safe-area padding to (display-mode: standalone) / (display-mode: fullscreen) is the right call. Mobile Safari and Chrome on iOS report safe-area insets even in regular browser tabs where the browser chrome already accounts for the notch — the previous unconditional padding was double-counting. This should be invisible on desktop and shave the extra whitespace in plain mobile browser views.

  • Progressive disclosure preserves touch targets. Folding workspace/model/reasoning/context behind an explicit config button instead of shrinking inline controls is the correct trade-off for narrow widths. 44px touch targets are a real iOS HIG floor; squeezing inline controls to fit would have created the worse class of bug (mis-taps).

  • Dropdown anchoring is the subtle part. _positionComposerWsDropdown() now anchors to composerMobileWorkspaceAction when the mobile config panel is open, falling back to the desktop chip otherwise. That looks correct, but it's exactly the kind of code that drifts when someone adds another dropdown surface — worth calling out in a comment in the function so the next person doesn't accidentally re-anchor everything to the desktop chip.

  • Compact context badge on the config button is a nice touch. Keeps the "am I about to blow my context window" signal visible on mobile without needing to expand the panel. Glanceable cost/usage matters more on phones because the user is more likely to be away from the keyboard when it suddenly matters.

A few things worth checking in review:

  1. Backdrop / outside-tap to close the config panel. The diff adds aria-expanded and aria-controls (good), but I didn't see an outside-click or escape-key handler in the snippet I looked at. If the panel only closes via re-tapping the config button, mobile users will hit it as a papercut — they expect overlay-style panels to dismiss on outside tap.

  2. Reasoning row is display:none by default. That's fine because not every model exposes reasoning, but make sure the show/hide logic checks the same condition as the desktop reasoning chip — otherwise the mobile panel will be silently inconsistent with the desktop composer for the same session.

  3. Workspace label sync. syncWorkspaceDisplays() now writes to both composerLabel and mobileLabel. Both are gated on S._bootReady to avoid the "No workspace" flash. Good — that's the same pattern the desktop chip uses, so the mobile path won't regress the boot-flicker fix from a few releases ago.

  4. i18n strings for the new copy. Strings like "Workspace", "Model", "Reasoning", "Context" inside the new composer-mobile-config-kicker spans look hardcoded in HTML. The rest of the composer goes through data-i18n attributes. Worth migrating these to keys eventually so the mobile panel translates with the rest of the UI.

  5. Test file added 474 lines. That's a meaningful chunk of regression coverage (tests/test_mobile_layout.py going from 42 passed to a larger count). Worth confirming the new asserts cover the dropdown re-anchor, the backdrop dismiss (if added), and the reasoning visibility condition — those are the bits most likely to break under future refactors.

Self-contained frontend change, no API or CLI surface touched, no hermes-agent dependency. Will defer merge to maintainer review.

@starship-s
Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — pushed a small follow-up in e500305 addressing the items you called out:

  • added an inline comment on the mobile workspace dropdown anchoring so future edits don't accidentally re-anchor it to the desktop chip only
  • added Escape-to-close support for the mobile config panel, including cleanup of workspace/model/reasoning dropdown state
  • confirmed the reasoning chip sync path updates both desktop and mobile controls, and added regression coverage for that
  • moved the new mobile panel kicker copy onto data-i18n keys with English fallback text preserved
  • added targeted mobile layout tests for the anchoring, Escape behavior, reasoning sync, and i18n labels

Local verification passed:

python -m pytest tests/test_mobile_layout.py -q
# 46 passed

python -m pytest tests/test_reasoning_chip_btw_fixes.py tests/test_reasoning_chip_js_behaviour.py -q
# 28 passed

node --check static/ui.js
node --check static/panels.js
node --check static/i18n.js
git diff --check
# all passed

@nesquena-hermes nesquena-hermes added hold ux User experience / visual polish labels May 1, 2026
@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Thanks for this — the structure here is solid (scoped to @container composer-footer (max-width: 520px) + @media (max-width: 640px), tests added, CI green, desktop layout looks unaffected on inspection). Marking hold + ux because mobile-composer changes at this size (+918/-57 across 6 files including index.html/panels.js/ui.js/style.css) need a visual sign-off on real devices before we merge — that's a release-process rule we apply to all mobile UI refreshes, not a comment on the PR itself.

What we'll need before flipping to merge-queue:

  1. Screenshots / short clip on a real phone (or 390×844 DevTools mobile preset) showing:
    • Composer at rest with the new config button visible
    • Config panel open with workspace / model / reasoning / context all reachable
    • The compact context badge in its 3 states (default / mid / high) — even synthetic
    • One full streaming send with a queued message, to confirm queue card insertion still respects pinning under the new layout
  2. Confirmation that the new composer-mobile-config-panel doesn't appear on desktop at any width that the previous layout supported. I'd specifically like a 1024×768 and a 320×568 screenshot side-by-side so we can see the desktop chrome is identical to before and the panel only activates at narrow widths.
  3. Behavior of _positionComposerWsDropdown() when both anchors exist — the new code anchors to #composerMobileWorkspaceAction when the panel is open, otherwise to the desktop chip. If the panel is open at a desktop width via debug toggling, the anchor switch could position the dropdown weirdly. Quick note that the panel .open class is only ever applied at narrow widths would resolve this.

Code-level notes (none blocking, but worth addressing in the same push):

  • closeWsDropdown() now also clears mobileAction.classList.remove('active') — good. Same pattern is missing in any Esc-key dismissal path; if there isn't one, no action needed.
  • The new :root{--app-titlebar-safe-top:0px;} + @supports (padding-top: env(safe-area-inset-top)) { @media (display-mode: standalone), (display-mode: fullscreen) { :root{--app-titlebar-safe-top:env(safe-area-inset-top,0px);} } } is exactly the right scoping. Nice.
  • The .composer-workspace-chip{display:none!important;} hides the chip but keeps the wrapper for layout — confirm the wrapper has no rendered border/background at compact widths (otherwise you'll see a blank pill segment).
  • When the config panel is open and the user opens the system file picker (workspace files) or sends a message, does the panel auto-close? Worth pinning that behavior in _renderQueueChips.

Once the screenshots are posted I'll re-review and flip to the merge queue. Thank you for the careful scoping with @container — that's the right way to do this.

@starship-s
Copy link
Copy Markdown
Contributor Author

Added visual validation for the mobile composer update.

The summary image covers the requested states:

  • 390×844 mobile composer at rest with the config button visible
  • 390×844 mobile config panel open with workspace / model / reasoning / context controls reachable
  • compact context badge states: default / mid / high
  • queued message state with the queue card pinned above the composer
  • 320×568 narrow viewport showing the config/tools button remains visible beside Send
  • 1024×768 desktop sanity check showing the desktop composer layout remains unchanged

Also confirmed:

  • the mobile config panel is scoped to narrow/mobile layouts
  • 44px touch targets are preserved
  • the panel auto-closes on send / workspace open / file attach

Mobile composer visual validation

@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Released as part of v0.50.253 — thanks @starship-s!

This PR was merged into the v0.50.253 release batch via #1391 alongside two other contributor fixes (#1342 by @bergeouss). Full CHANGELOG entry: https://github.com/nesquena/hermes-webui/blob/master/CHANGELOG.md.

Pre-release verification:

  • pytest: 3558 passing (up from 3507 baseline)
  • run-browser-tests.sh + webui_qa_agent.sh: all green
  • Comprehensive E2E browser walk (desktop + mobile) — every interactive surface verified
  • Telegram screenshot approval gate: passed
  • Opus Advisor pre-release review: APPROVED with 1 NEEDS-FIX (resolved) and 2 follow-ups (both applied in same release)
  • Independent review by @nesquena: APPROVED with end-to-end behavioral harness verification

Closing this PR — the change is live on master and tagged.

GeoffBao pushed a commit to GeoffBao/hermes-webui that referenced this pull request May 1, 2026
GeoffBao pushed a commit to GeoffBao/hermes-webui that referenced this pull request May 1, 2026
…ktop view (CSS specificity)

The .composer-mobile-config-btn{display:none} base rule was at line 896 but
.icon-btn{display:flex} (the button's other class) was at line 941 — equal
specificity, but later in source wins. Result: the button was visible at
desktop widths, sandwiched between the workspace and model chips.

Bumping the base rule's selector to .icon-btn.composer-mobile-config-btn
gives it specificity 0,0,2,0 (vs .icon-btn at 0,0,1,0), so it always wins
the cascade. The two narrow-viewport rules already use !important and remain
unaffected — desktop hides cleanly, mobile shows correctly.

Verified via Agent Browser CDP: 1440x900 desktop now shows the standard
chips only (no extra config button); iPhone 14 mobile shows the new compact
config btn at 44x44 with the panel toggling correctly. Screenshots:
/tmp/may2-shots/desktop-final.png, mobile-{closed,open}-final.png
GeoffBao pushed a commit to GeoffBao/hermes-webui that referenced this pull request May 1, 2026
…egacy phones

Pulls in the extra commit pushed to PR nesquena#1381 after our initial absorb. Adds a
@media (max-width: 340px) block that compacts gutters (composer-wrap padding,
composer-footer gap, composer-left gap) without shrinking the 44px touch
targets. Plus its regression test.

Verified with apply --check failed but actual apply succeeded — the failure
was due to context drift from our earlier CSS specificity fix; the new lines
landed at the correct location. test_mobile_layout.py: 47 tests passing.
GeoffBao pushed a commit to GeoffBao/hermes-webui that referenced this pull request May 1, 2026
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request May 1, 2026
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request May 1, 2026
…ktop view (CSS specificity)

The .composer-mobile-config-btn{display:none} base rule was at line 896 but
.icon-btn{display:flex} (the button's other class) was at line 941 — equal
specificity, but later in source wins. Result: the button was visible at
desktop widths, sandwiched between the workspace and model chips.

Bumping the base rule's selector to .icon-btn.composer-mobile-config-btn
gives it specificity 0,0,2,0 (vs .icon-btn at 0,0,1,0), so it always wins
the cascade. The two narrow-viewport rules already use !important and remain
unaffected — desktop hides cleanly, mobile shows correctly.

Verified via Agent Browser CDP: 1440x900 desktop now shows the standard
chips only (no extra config button); iPhone 14 mobile shows the new compact
config btn at 44x44 with the panel toggling correctly. Screenshots:
/tmp/may2-shots/desktop-final.png, mobile-{closed,open}-final.png
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request May 1, 2026
…egacy phones

Pulls in the extra commit pushed to PR nesquena#1381 after our initial absorb. Adds a
@media (max-width: 340px) block that compacts gutters (composer-wrap padding,
composer-footer gap, composer-left gap) without shrinking the 44px touch
targets. Plus its regression test.

Verified with apply --check failed but actual apply succeeded — the failure
was due to context drift from our earlier CSS specificity fix; the new lines
landed at the correct location. test_mobile_layout.py: 47 tests passing.
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ux User experience / visual polish

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants