Skip to content

feat(web): mobile shell — route depth model, stacked settings, touch affordances - #958

Merged
qqqqqf-q merged 7 commits into
felinics:mainfrom
qqqqqf-q:feat/web-mobile-shell
Aug 5, 2026
Merged

feat(web): mobile shell — route depth model, stacked settings, touch affordances#958
qqqqqf-q merged 7 commits into
felinics:mainfrom
qqqqqf-q:feat/web-mobile-shell

Conversation

@qqqqqf-q

@qqqqqf-q qqqqqf-q commented Aug 5, 2026

Copy link
Copy Markdown
Member

Turns the desktop-only web app into a usable phone experience, modeled as a route-based depth tree (Chat=0 → /settings=1 → /settings/x=2 → /settings/bots/:name=3 → ?tab=x=4). The system back button and the shell's ← walk the same history, so navigation is consistent and every level is addressable by URL.

What's in here

Mobile shell chrome — chat stack chrome (mobile top bar with ≡/← left action and a + panel menu), settings & bot-detail mobile stacks, and a nav sheet. The + menu mirrors the desktop tab-strip add actions with the same permission gates.

Keyboard-lift composeruseVirtualKeyboard() measures the iOS/Android on-screen keyboard via the VisualViewport API and lifts the composer above it. Robust against URL-bar collapse, guards against pinch-zoom (scale !== 1), and dispatches a synthetic resize so floating-ui re-anchors popovers. Android is handled natively via interactive-widget=resizes-content. The lift is suppressed while the model picker is open to avoid a double-shift.

Touch affordances — 44px touch targets on composer controls (max-md:size-11), :active press feedback on session rows (touch has no hover), and stacked settings rows below md so phone-width rows don't crush label + control onto one line.

Page gutters — settings/bot-detail pages step down to 16px gutters below md (desktop ≥md pixel-identical).

Submodule dependency

This PR bumps packages/ui to commits that live in felinics/ui#14 (mobile gutter, wrapping page header, 16px menu search input, hover-chip gating behind @media (hover: hover)). ui#14 must land first, or the gitlink here dangles. If ui#14 is squash-merged, the gitlink needs a one-line bump to the squashed SHA afterward.

Test plan

  • Phone (real device): open Chat → send a message with the keyboard up → composer stays visible above the keyboard
  • Top-bar + opens the panel menu; items respect permission gates
  • Settings → drill into a bot → General tab rows stack cleanly below md; ≥md desktop unchanged
  • Long-press a session row → context menu opens with visible press feedback
  • System back and ← stay in sync through the depth tree
  • Desktop regression: no visual change at ≥md

Note: onboarding/real-device keyboard behavior is exercised in Playwright (fake VisualViewport) but the true iOS keyboard animation/caret-scroll needs a human on hardware.

…bile stacks

Phase 1 (chat): useIsMobile single JS breakpoint (<768px), mobile top bar
(≡/←/title/+), nav Sheet reusing the rail panels, and dockview runtime
constraints keyed on it — split veto, programmatic splits collapsed into the
single group, group merge on entry, hidden tab strip, layout persistence
sealed so the desktop snapshot is never read or written while mobile.

Phase 2 (settings): mobile list/content stack for the settings section
(full-width SettingsSidebar as the nav homepage) and the same stack for
master-detail, deleting the hamburger+Sheet mobile mode outright.

docs: PLAN.md — architecture ruling and phase plan.

Checkpoint before the depth-model rework: the list states are currently
shell state rather than routes, which the review showed cannot be patched
into consistency — the route-based depth tree lands next.
…t composer

Depth model: every mobile navigation level is now a real addressable route,
so the system back button and the shell's ← walk the same history.
- /settings is the settings LIST (redirect removed; a desktop guard
  re-applies the old /settings/bots default). The list renders as an overlay
  above a persistent, keep-alive content section, so the breakpoint never
  unmounts the page tree.
- Bot detail's tab list is driven by the ?tab= query: tab content is a push
  from the list (system back returns), close falls back to replace.
- master-detail-sidebar-layout drops the hamburger + left Sheet entirely;
  below the breakpoint it is a two-pane stack keyed on the detailOpen prop.
- Back affordances stay history-following (useBackAffordance), so labels
  always name where back actually leads.

Page-level adaptation: PageShell gains a <md gutter step (px-4/pt-6/pb-8)
matching SettingsShell/DetailPane; PageHeader's framed row wraps instead of
crushing the title into truncation; the settings list's first row and the
bot-detail container pick up the same 16px mobile margin. (ui submodule:
069430e, 9178910.)

Composer: useVirtualKeyboard tracks the software keyboard via the Visual
Viewport API and lifts the whole dock (plus the message list's bottom pad)
above it on iOS; Android lifts natively via interactive-widget=
resizes-content. The lift is suppressed while the model picker is open so it
can't yank the trigger from under the popover. Composer controls grow to the
44px touch floor below md, the model picker clamps to the viewport, and menu
search inputs render at 16px on phones to stop iOS focus zoom.
…rows

- Top-bar "+" menu was dead: the rework had wrapped the reka as-child
  trigger in MobileBarIconButton, whose declared click emit swallows the
  trigger's injected onClick and strands the anchor ref (menu never opened).
  The trigger is a plain ui Button again; the shared bar chrome class moved
  to icon-button-class.ts, with a warning on the component never to use it
  as an as-child child.
- Session rows give no feedback on touch: the hover fill never fires and the
  long-press context menu felt dead until it appeared. The row now paints
  the same fill on :active.
- Bot settings General tab rows no longer force the desktop side-by-side
  layout onto a phone: label + wide-control rows (URL name, language,
  timezone, default agent, chat model, search/fetch/memory, TTS,
  transcription, image, video) use SettingsRow's stack="sm" owner axis and
  their fixed w-52/w-56 wrappers go full width when stacked. Switch rows
  stay side-by-side; >=sm is pixel-identical.
@qqqqqf-q
qqqqqf-q requested review from a team as code owners August 5, 2026 17:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bb43c71de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/pages/main-section/components/mobile-top-bar.vue
qqqqqf-q and others added 4 commits August 5, 2026 10:55
The single-row pill is a desktop affordance: it seats the inline textarea,
the model capsule, and send on one line and only reflows when text wraps or
the pane is too narrow. On a phone that reflow is width luck — a slightly
wider screen or a shorter model name lands the composer back on the cramped
pill, which is a poor touch target. Wire the shell's mobile breakpoint
(useIsMobile) into the layout engine as a third expansion trigger so the
composer is ALWAYS multiline below md (textarea row, controls row below).
Desktop >= md is untouched: the pill remains the compact default.
@qqqqqf-q
qqqqqf-q merged commit ca004d1 into felinics:main Aug 5, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant