Skip to content

feat(mobile):Mobile polish and use of frappe ui mobile components. - #284

Open
sadiqxansari wants to merge 5 commits into
frappe:developfrom
sadiqxansari:mobile-polish
Open

feat(mobile):Mobile polish and use of frappe ui mobile components.#284
sadiqxansari wants to merge 5 commits into
frappe:developfrom
sadiqxansari:mobile-polish

Conversation

@sadiqxansari

@sadiqxansari sadiqxansari commented Aug 13, 2026

Copy link
Copy Markdown
Contributor
Screenshot 2026-08-13 at 5 21 14 PM Screenshot 2026-08-13 at 5 22 42 PM Screenshot 2026-08-13 at 5 22 17 PM

Adopts frappe-ui's shell pattern — each page declares its own header — and fixes
the mobile surfaces that unlocks.

Patterns are taken from frappe/gameplan,
which is where frappe-ui's shell components were extracted from.

Rebased onto develop after #285 took frappe-ui from beta.24 to beta.53, which
renamed PageHeaderMobile's slots from #left/#right to #prefix/#suffix.
Vue drops an unknown named slot silently, so on the old names every back button
and header action would have rendered nothing at all — worth knowing if this
branch is ever rebased across another bump. Also picked up in that bump:
getScrollContainer()shellScrollContainer, and
onOutsideClickDirectivevOnOutsideClick.

Why

AppShell rendered one global header plus an #header-actions teleport target
that three pages reached into. Both shells already render a PageHeaderTarget
for exactly this. Because ours sat inside MobileShell's scroll area instead,
every mobile page lost its bottom 48px behind the nav bar.

What changed

  • Shell — 14 pages declare their own header; #header-actions and
    useBreadcrumbs are gone. Pages hand their scroll to the shell, so scroll
    reset moves to the router's scrollBehavior.
  • Mobile nav — the header title opens the section drawer. The bottom bar
    holds four tabs, so everything else needed a trip back to Home first.
  • Invoices — the receipt is a page on mobile (/billing/invoices/:name);
    nothing docks 24rem beside a list at 375px. Desktop keeps its panel unchanged.
  • SidePanel — a full-screen sheet below sm, with dialog semantics and focus
    handling. Fixes wallet history, role capabilities and the four feat(billing): Overview and Reports - show what the bill is made of #281 trays in
    one place.
  • Limit tiers — the four-column table reflows to name + price over
    full-width requirements; desktop keeps its columns.

Verified at 375px and desktop across every in-shell route. yarn type-check
is clean — the CollectionActionBanner.vue:80 error that was pre-existing on
develop is fixed by the beta.53 bump.

@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

The PR is not safe to merge while tall mobile SidePanels remain inaccessible by touch.

The attempted scrolling fix applies touch-pan-y below MobileShell’s acknowledged touch-none, so vertical panning remains disabled.

Files Needing Attention: dashboard/src/components/common/SidePanel.vue

Fix all with Greploop

Fix All in Claude Code Fix All in Codex

Reviews (4): Last reviewed commit: "fix(ui): mobile sizing for list filters,..." | Re-trigger Greptile

Comment thread dashboard/src/components/common/SidePanel.vue Outdated
@sadiqxansari
sadiqxansari force-pushed the mobile-polish branch 2 times, most recently from 8efe655 to 38494cc Compare August 13, 2026 11:17
sadiqxansari and others added 5 commits August 14, 2026 11:56
AppShell hand-rolled one global header for every page, plus an ad-hoc
`#header-actions` teleport target that three pages reached into. Both
MobileShell and DesktopShell already render a PageHeaderTarget above their
scroll region for exactly this: a page declares its own header and it teleports
there. We used neither.

That bypass is what cost every mobile page its last 48px. Our header sat inside
MobileShell's scroll area rather than above it, so the bottom of every page hid
behind the nav bar, and the workaround was a hand-managed flex column. Owning
the header per page removes the class of bug and turns on what the components
already do: a back button, a centred two-line title, tap-the-header to scroll
top, tap-the-active-tab to scroll top.

The pattern is taken from frappe/gameplan, which is where frappe-ui's shell
components were extracted from. Slot names drift between versions — beta.24
takes #left/#right where later releases take #prefix/#suffix — and Vue drops an
unknown named slot silently, so these are written against the installed source
rather than copied from gameplan's templates.

Two things follow from the shell owning the scroll region:

Pages hand it their own scroll boxes behind `sm:` prefixes, which is what makes
tapping the already-active bottom-nav tab scroll to top.

And scroll position is now reset by the router, not by pages remounting. The
shell's scroll div outlives the route, so without it a new page opened wherever
the last one was left. Only a real path change resets — a query-only replace is
the same page and keeps its place.

The mobile header's title also doubles as the section switcher: the bottom bar
holds four tabs, so Servers, Services, Team and the billing pages were otherwise
reachable only by going back to Home first. Home keeps a plain title, since that
page already is the list, and takes the mark on the left instead.

useBreadcrumbs loses its last consumer here and goes with them. ServerFilters comes
along because the servers page now hands it region options grouped by provider,
and a prop type has to move with the thing that passes it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ladder was a four-column table — rail, tier, requirements, price — and the
requirements column is the one that carries sentences. At 375px it got whatever
the other three left, so "≥ $300.00 paid to date" wrapped to four lines on every
rung and the check mark floated away from the text it belonged to.

Columns are the wrong primitive when one of them can't be narrowed. Each rung is
a wrapping flex row now: name and price share the first line, requirements span
the full width beneath, and at `sm` the three fall back into the same columns the
table drew. The `<style>` block goes with the table — padding and vertical
alignment are utilities once there are no cells.

DOM order is the desktop reading order, so the accessibility tree and the tab
order match what a sighted desktop user sees; only mobile reorders, and there
the sequence read aloud is the same one on screen, wrapped onto two lines.

The rail's dot sat 4px below the tier name because its offset assumed a 24px
line box. These tokens are 1.15 line-height, not Tailwind's 1.5, so a 14px name
centres at ~24px and a 16px one at ~25px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Invoices page is a list with a 24rem receipt docked beside it. Nothing docks
beside anything at 375px, so the panel stacked under the list at a fixed 384px —
and since the page auto-opens the newest invoice, a phone landed on a list with
a receipt dangling off the bottom of it.

A row pushes /billing/invoices/:name instead, and Back returns to the list. The
receipt reads the same on both, so its body moves into InvoiceReceipt and the
fetch, the payability rules and the pay wiring move into useInvoiceDetail; the
panel and the page supply only their own chrome. Desktop keeps the docked panel,
its auto-select and its URLs exactly as they were.

Auto-select is desktop-only now: opening a receipt on a phone is a navigation,
and landing on the list only to be carried off it is not what tapping Invoices
asked for. A `?invoice=` deep link did ask for one by name, so that still goes
through — straight to its page. It latches either way, so a later refetch can't
act on a query still sitting in the URL and yank you off a list you were reading.

Widening past `sm` hands the route back to the list with the same invoice open,
the way the settings pages hand back to the dialog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SidePanel is a 24rem column that slides in beside the content, hosted as the
last child of a `flex h-full` row. Once pages hand their scroll to the shell
there is no such row on mobile, so the panel stacked below the content at a
fixed 384px on a 375px viewport.

Invoices got a route because a receipt is a destination. The rest — wallet
history, role capabilities, and the four billing trays — are peeks opened from a
card, so they don't want URLs; they want the panel to stop pretending it can
dock. Below `sm` it presents as a full-screen sheet, and every consumer gets
that from one place.

A sheet is modal in a way the docked panel never was: it covers the page rather
than sitting beside it. So on mobile it announces as a dialog, takes focus when
it opens and gives it back when it closes — otherwise Close leaves focus on a
row that is no longer rendered. The Escape guard skips the panel's own root,
or the role it just gained would match the "a stacked dialog owns Escape first"
test and Escape would stop working entirely.

The wallet ledger had its own scroller nested inside the panel's — harmless
while the panel was a short column, a trap once it is the whole screen. Same
nesting frappe#281 removed from the invoice receipt.

RoleCapabilitiesPanel teleported to #team-page-aside, which existed nowhere in
the repo: it had never rendered, on any viewport, since the commit that
introduced it. Adding the target switches it on — on desktop as well as mobile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The list toolbar put each filter at a fixed width, so a 160px status filter and
a 176px date picker came to 344px against 343px of room and stacked one per row.
They grow from a 9rem floor below `sm` instead, which packs two to a row and
still lets a third wrap rather than squeezing all three thin.

The rest is what a phone reads at arm's length rather than a desk: notification
rows and their message text step up a size, the servers list panel stops hanging
off the right edge, the region and status filters stack, and the search palette
drops its keyboard hints, which are advice a phone can't take.

Also: a disabled email field that had gone invisible in dark mode, and the
appearance form's row, which was a settings-dialog layout being asked to be a
page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
:aria-modal="isMobile ? 'true' : undefined"
:aria-label="isMobile ? title : undefined"
:tabindex="isMobile ? -1 : undefined"
class="flex w-[24rem] shrink-0 flex-col border-l border-outline-gray-2 bg-surface-base focus:outline-none max-sm:fixed max-sm:inset-0 max-sm:z-20 max-sm:w-full max-sm:touch-pan-y max-sm:border-l-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Mobile sheet still blocks scrolling

When a mobile user pans a SidePanel taller than the viewport, the descendant touch-pan-y cannot override MobileShell’s ancestor touch-none, leaving off-screen wallet, receipt, and capability content unreachable.

Fix in Claude Code Fix in Codex

@sadiqxansari sadiqxansari changed the title feat(mobile): each page owns its header, the way the shells expect feat(mobile): Not ready to merge. Mobile polish and use of frappe ui mobile components. Aug 14, 2026
@sadiqxansari sadiqxansari changed the title feat(mobile): Not ready to merge. Mobile polish and use of frappe ui mobile components. feat(mobile):Mobile polish and use of frappe ui mobile components. Aug 14, 2026
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