Skip to content

feat(builder): make the app usable on mobile viewports - #970

Open
eduardocodes wants to merge 23 commits into
ChatbotXIO:mainfrom
eduardocodes:feat/responsive-mobile-ui
Open

feat(builder): make the app usable on mobile viewports#970
eduardocodes wants to merge 23 commits into
ChatbotXIO:mainfrom
eduardocodes:feat/responsive-mobile-ui

Conversation

@eduardocodes

Copy link
Copy Markdown

What

Makes the builder usable on a phone. Before this, the app was desktop-only in
practice: of 1137 .tsx files, 99 contained any responsive prefix, and of 44
layout files exactly one did. The shadcn Sidebar already collapsed into a
Sheet below md, but nothing in the app could open it — the only trigger is
absolutely positioned off the inset's inline edge — and no nav link closed it.

Scope is the app shell, the inbox, and the table/list surfaces. The flow
builder is deliberately out of scope
and untouched; it needs its own pass.

Shell and navigation

  • Explicit viewport export with viewportFit: "cover", so env(safe-area-inset-*)
    reports real values.
  • New SidebarMobileTrigger in packages/ui — a hamburger with a full touch
    target, as opposed to SidebarTrigger, which is a rail-collapse chevron and
    reads as nothing below md. Wired into a sticky md:hidden header in both
    shells (workspace and manage console).
  • NavMain now closes the mobile sheet when a link is tapped; it used to stay
    open on top of the destination page.
  • AppTab scrolls horizontally instead of overflowing. One file, and it unblocks
    the 15 surfaces that render 4–6 tabs.
  • The inbox's -m-6 negative margin — which silently depended on the shell's
    p-6 — is replaced by a documented FullBleed component, so changing the
    shell's padding can no longer break the page from a distance.

Tables and lists

  • DataTable now scrolls horizontally by default. The default was overflow-hidden
    and only 1 of its 36 consumers opted out of it, so 35 tables silently clipped
    their rightmost columns with no way to reach them.
  • New opt-in mobileCard render mode plus a generic DataTableRowCard that
    renders a row through the column's own flexRender — no duplicated cell
    logic, and field labels come from meta.label, so no new translation keys.
    Applied to contacts (bespoke card), flows and broadcasts (generic).
  • The switch between card and table is CSS, not a media-query hook, so the right
    layout is in the first paint rather than flipping after hydration.
  • Toolbar filters stack on narrow viewports; analytics dashboards collapse to one
    column; SettingRow stacks (its grid-cols-4 reached every settings form).

Inbox

Below md the inbox is a single-pane master/detail view: the conversation list,
then the thread with a back control, with the contact panel behind a button in a
Sheet. From md up the three-column ResizablePanelGroup and its layout cookie
are unchanged.

This is the one place the layout is chosen in JS rather than CSS: the three panes
are heavy and self-fetching, so rendering both arrangements would mount and fetch
everything twice. useIsMobileState was added for it — it returns undefined
until the first measurement so the layout waits instead of guessing desktop and
remounting a frame later.

Bug fixed along the way: <ChatRealtime /> lived inside the message pane. In
the mobile single-pane view that pane unmounts when returning to the list, which
would have taken the realtime socket down with it. It now sits at the layout root.

Dialogs

DialogContent's base width goes from w-full to w-[calc(100%-2rem)].
tailwind-merge resolves max-w-* by group, so the ~60 dialogs passing an
unprefixed max-w-* were replacing the max-w-[calc(100%-2rem)] guard and
rendering edge-to-edge on a phone. Width is a separate group, so the gutter now
survives. One line, no consumer changes.

Verification

  • pnpm lint clean; check-types clean for builder, ui, analytics-nextjs and
    vitest-config; pnpm build passes.
  • 1566 builder tests, 45 ui tests, 1 analytics test — all passing. 12 new tests
    across 6 new files.
  • Driven in a real browser at 390×844 across inbox, contacts, flows, broadcasts,
    settings and both analytics dashboards: every route measures
    scrollWidth == clientWidth, with no element outside the viewport. Re-checked
    at 1440×900 to confirm desktop is unchanged.
  • That sweep caught a real defect: admins-analysis.tsx carried an unprefixed
    col-span-2, which against the new one-column mobile grid created an implicit
    second column and pushed the analytics page to 471px in a 375px viewport. It
    was invisible before because the grid was always two columns. Fixed here.

packages/vitest-config gains matchMedia and ResizeObserver stubs for jsdom;
without them useIsMobile, the sidebar's mobile branch, and anything using a
resizable panel throw on mount and could not be tested at all.

Not included

max-h-screen appears on 101 dialog lines. On iOS Safari 100vh overshoots the
visible area, so a dialog's footer can sit under the browser chrome. Left alone
because the sweep would cross into the flow builder.


🤖 Generated with Claude Code

@github-actions github-actions Bot added the feature New feature or request label Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant