Skip to content

feat(web): adapt UI for mobile viewports#108

Merged
ZingerLittleBee merged 3 commits into
mainfrom
feat/mobile-ui-adaptation
May 16, 2026
Merged

feat(web): adapt UI for mobile viewports#108
ZingerLittleBee merged 3 commits into
mainfrom
feat/mobile-ui-adaptation

Conversation

@ZingerLittleBee
Copy link
Copy Markdown
Owner

@ZingerLittleBee ZingerLittleBee commented May 16, 2026

Summary

  • Responsive pass across settings, dashboard, data-table, and detail pages: header/action rows stack on small screens (flex-col … sm:flex-row), widths constrained (min-w-0, max-w-[calc(100vw-1.5rem)]) to eliminate horizontal page overflow, and shared ScrollArea/Table primitives now allow horizontal scrolling.
  • Servers list now defaults to grid view on mobile (<768px); extracted view-mode resolution into mobile-view.ts and server cell renderers into components/index-cells.tsx.
  • Fixed a real mobile bug found during acceptance testing: the servers table view and traffic ranking table were missing the page-root width clamp other table pages already use, so table-fixed + min-w-max propagated a runaway intrinsic width up through main and columns were clipped unreachably at 375px instead of scrolling. Added the established max-w-[calc(100vw-1.5rem)] clamp.

Test plan

  • tsc --noEmit passes
  • 493 vitest tests pass (incl. new mobile-view and data-table mobile-layout tests)
  • Biome clean on changed files
  • Browser acceptance test at 375×812 against real production data: 4 parallel agents across layout shell, servers list, 11 settings pages, 6 data tables, and full-height detail pages — all pass after the table-width fix
  • Verified horizontal scroll engages on /servers?view=table and /traffic (viewport clamped to 349px, scrollLeft advances, no page overflow)
  • Quick manual swipe check on a real device for table views before merge

Summary by CodeRabbit

  • New Features

    • Added horizontal scrollbar visibility for tables
    • Added mobile view mode selection (grid/table toggle) for servers list
  • Style

    • Improved responsive layouts across settings, pages, and dialogs for better mobile experience
    • Enhanced flex sizing and grid responsiveness to prevent horizontal overflow on smaller screens
    • Better spacing and wrapping behavior for dialogs and form sections
  • Tests

    • Added test coverage for view mode selection logic
    • Added test coverage for table mobile layout behavior

Review Change Stack

Make settings, dashboard, data-table, and detail pages responsive:
stack header/action rows on small screens, allow horizontal table
scrolling via ScrollArea, and constrain widths to prevent overflow.
Default the servers list to grid view on mobile and split server
cell renderers and view-mode resolution into dedicated modules.
Move mobile-view.test.ts into the components/ directory so it sits
beside mobile-view.ts, matching the index-cells test layout.
The servers table view and traffic ranking table were missing the
max-w-[calc(100vw-1.5rem)] page-root constraint that other table
pages already use. Without a definite width ceiling the table-fixed
+ min-w-max intrinsic width propagated up through main, so the
ScrollArea viewport equalled the table width and columns were
clipped unreachably at 375px instead of scrolling. Add the
established page-root clamp and mirror the ui/data-table max-w-full
wrapper on the shared DataTable.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
server-bee-docs Ready Ready Preview, Comment May 16, 2026 11:41am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

📝 Walkthrough

Walkthrough

This PR transforms the web app into a mobile-responsive layout system by adding width constraints, horizontal scrolling, responsive grid breakpoints, and column-stacking flex layouts. It centralizes mobile view detection for the servers list and updates 40+ UI components and pages with consistent responsive patterns.

Changes

Mobile-First Responsive Layout System

Layer / File(s) Summary
UI Component Foundation & Scrolling
apps/web/src/components/ui/scroll-area.tsx, apps/web/src/components/ui/table.tsx, apps/web/src/components/ui/data-table.tsx
ScrollArea now renders an additional horizontal scrollbar. Table wrapper adds min-w-0 and flex-1 sizing, while the <table> element gains min-w-max to preserve minimum width. DataTable container enforces min-w-0 max-w-full overflow bounds.
Data Table Responsive Layout System
apps/web/src/components/data-table/data-table.tsx, apps/web/src/components/data-table/data-table-pagination.tsx, apps/web/src/components/data-table/data-table-toolbar.tsx, apps/web/src/components/data-table/data-table-skeleton.tsx, apps/web/src/components/data-table/data-table.test.tsx
DataTable wrapper switches to min-w-0 flex-col overflow-hidden with testid'd scroll container. Pagination removes overflow-auto and changes spacing to gap-2. Toolbar becomes flex-col on mobile with responsive filter input sizing. Skeleton updates to min-w-0 overflow-hidden with wrapping/gap classes. New test verifies mobile layout enforces min-w-0/overflow-hidden constraints.
Dashboard & Servers Mobile View Resolution
apps/web/src/components/dashboard/dashboard-editor-view.tsx, apps/web/src/components/dashboard/dashboard-grid.tsx, apps/web/src/components/dashboard/widget-picker.tsx, apps/web/src/routes/_authed/servers/components/mobile-view.ts, apps/web/src/routes/_authed/servers/components/mobile-view.test.ts, apps/web/src/routes/_authed/servers/index.tsx
Dashboard editor layout adds responsive width constraints (max-w-[calc(100vw-1.5rem)]). EditOverlay accepts optional forceVisible prop to show on mobile. Widget picker grid uses responsive sm:grid-cols-2. New mobile-view.ts module exports ServersViewMode type, resolveInitialServersView for precedence-based view selection, and getInitialServersView for runtime localStorage/viewport detection. Servers index integrates view resolver and validates search param to only accept grid/table.
Settings Pages Responsive Layout
apps/web/src/routes/_authed/settings/alerts.tsx, apps/web/src/routes/_authed/settings/api-keys.tsx, apps/web/src/routes/_authed/settings/appearance.tsx, apps/web/src/routes/_authed/settings/audit-logs.tsx, apps/web/src/routes/_authed/settings/capabilities.tsx, apps/web/src/routes/_authed/settings/index.tsx, apps/web/src/routes/_authed/settings/mobile-devices.tsx, apps/web/src/routes/_authed/settings/network-probes.tsx, apps/web/src/routes/_authed/settings/notifications.tsx, apps/web/src/routes/_authed/settings/ping-tasks.tsx, apps/web/src/routes/_authed/settings/security.tsx, apps/web/src/routes/_authed/settings/service-monitors.tsx, apps/web/src/routes/_authed/settings/status-pages.tsx, apps/web/src/routes/_authed/settings/users.tsx
All 14 settings pages apply consistent responsive pattern: header rows, list item rows, and form control rows switch from single-row flex to flex-col with gap-3 on mobile, sm:flex-row on larger screens. Appearance theme grids use gap-3 sm:grid-cols-2 lg:grid-cols-4. Service monitors and audit logs add full-width responsive containers with min-w-0 max-w-[calc(100vw-1.5rem)]. Capabilities and index pages add flex-wrap and min-w-0 for wrapping on constrained widths.
Route Layouts & Dialogs Responsive Design
apps/web/src/routes/_authed.tsx, apps/web/src/routes/_authed/files.$serverId.tsx, apps/web/src/routes/_authed/network/$serverId.tsx, apps/web/src/routes/_authed/terminal.$serverId.tsx, apps/web/src/routes/_authed/traffic/index.tsx, apps/web/src/routes/login.tsx, apps/web/src/components/server/server-edit-dialog.tsx, apps/web/src/components/task/scheduled-task-dialog.tsx
Authenticated layout header uses responsive padding/height and relocates language/theme controls to right-side flex container. Breadcrumb item adds min-w-0 truncation. Files page removes overflow-y-auto, uses min-height flex. Network page grid changes from fixed grid-cols-3 to sm:grid-cols-3. Terminal page adds min-h-0 flex constraints. Traffic page wraps table in min-w-0 max-w-[calc(100vw-1.5rem)] container. Login OAuth buttons use sm:grid-cols-2 instead of always 2-column. Server edit and task dialogs change from fixed-column grids to grid with sm:grid-cols-* breakpoints.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ZingerLittleBee/ServerBee#98: Both PRs update data-table-pagination.tsx layout and spacing classes, with this PR adjusting wrapper overflow and flex spacing on top of the component's existing structure.
  • ZingerLittleBee/ServerBee#103: Both PRs refactor appearance.tsx theme grids and headers; this PR adds responsive breakpoints to the custom theme system introduced in that PR.
  • ZingerLittleBee/ServerBee#91: Both PRs modify scroll-area.tsx component, with this PR adding horizontal scrollbar rendering that complements or builds on scroll improvements from PR #91.

Poem

🐰 Mobile screens now stack with grace,
Flex constraints find their place,
From min-w-0 to responsive grids,
Each layout flows like a rabbit's bids!
Breadcrumbs truncate, tables scroll wide,
Mobile-first changes swiftly guide. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(web): adapt UI for mobile viewports' accurately and concisely summarizes the main objective of the pull request, which comprehensively updates the web UI for mobile/small-screen responsiveness across multiple components and pages.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mobile-ui-adaptation

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/src/routes/_authed/traffic/index.tsx (1)

268-268: 💤 Low value

Consider extracting the hardcoded padding value.

The max-w-[calc(100vw-1.5rem)] hardcodes the main layout's horizontal padding (p-3 = 0.75rem × 2 from _authed.tsx line 233). If the layout padding changes, this calculation will become incorrect.

♻️ Optional refactor to reduce coupling

Define a shared CSS custom property in your theme or use a Tailwind config value:

/* In your global CSS or theme */
:root {
  --main-horizontal-padding: 1.5rem;
}

`@media` (min-width: 640px) {
  :root {
    --main-horizontal-padding: 2rem; /* matches sm:p-4 */
  }
}

Then reference it:

-<div className="w-full min-w-0 max-w-[calc(100vw-1.5rem)] overflow-hidden sm:max-w-full">
+<div className="w-full min-w-0 max-w-[calc(100vw-var(--main-horizontal-padding))] overflow-hidden sm:max-w-full">

Or define the constraint pattern in a shared constant if you prefer JavaScript.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/routes/_authed/traffic/index.tsx` at line 268, The hardcoded
horizontal padding used in the div with className "w-full min-w-0
max-w-[calc(100vw-1.5rem)] overflow-hidden sm:max-w-full" should be extracted to
a shared value (either a CSS custom property like --main-horizontal-padding or a
JS/Tailwind config constant) and referenced in the max-width calc instead of the
literal 1.5rem so layout padding changes remain consistent; update the JSX to
use that shared value (or the corresponding Tailwind pattern) and adjust
responsive overrides (sm) to mirror the padding changes defined in the shared
value, ensuring the calc expression and responsive variants reference the same
single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/routes/_authed/settings/alerts.tsx`:
- Around line 483-489: The mapped list is returning a Fragment (<>...</>) while
the key is placed on an inner <div key={rule.id}>, which causes React warnings
and reconciliation issues; fix it by moving the key to the top-level returned
node—either replace the shorthand Fragment with an explicit React.Fragment and
set key={rule.id} on it, or remove the Fragment and put the key on the outermost
element that wraps the mapped content (referencing the map callback that uses
rule.id and the Fragment surrounding the <div className="flex ...">).

---

Nitpick comments:
In `@apps/web/src/routes/_authed/traffic/index.tsx`:
- Line 268: The hardcoded horizontal padding used in the div with className
"w-full min-w-0 max-w-[calc(100vw-1.5rem)] overflow-hidden sm:max-w-full" should
be extracted to a shared value (either a CSS custom property like
--main-horizontal-padding or a JS/Tailwind config constant) and referenced in
the max-width calc instead of the literal 1.5rem so layout padding changes
remain consistent; update the JSX to use that shared value (or the corresponding
Tailwind pattern) and adjust responsive overrides (sm) to mirror the padding
changes defined in the shared value, ensuring the calc expression and responsive
variants reference the same single source of truth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: abf2bb5f-59aa-4d76-9d73-9f58bf88580e

📥 Commits

Reviewing files that changed from the base of the PR and between 504bea5 and 5fc7ae1.

📒 Files selected for processing (38)
  • apps/web/src/components/dashboard/dashboard-editor-view.tsx
  • apps/web/src/components/dashboard/dashboard-grid.tsx
  • apps/web/src/components/dashboard/widget-picker.tsx
  • apps/web/src/components/data-table/data-table-pagination.tsx
  • apps/web/src/components/data-table/data-table-skeleton.tsx
  • apps/web/src/components/data-table/data-table-toolbar.tsx
  • apps/web/src/components/data-table/data-table.test.tsx
  • apps/web/src/components/data-table/data-table.tsx
  • apps/web/src/components/server/server-edit-dialog.tsx
  • apps/web/src/components/task/scheduled-task-dialog.tsx
  • apps/web/src/components/ui/data-table.tsx
  • apps/web/src/components/ui/scroll-area.tsx
  • apps/web/src/components/ui/table.tsx
  • apps/web/src/routes/_authed.tsx
  • apps/web/src/routes/_authed/files.$serverId.tsx
  • apps/web/src/routes/_authed/network/$serverId.tsx
  • apps/web/src/routes/_authed/servers/components/index-cells.tsx
  • apps/web/src/routes/_authed/servers/components/mobile-view.test.ts
  • apps/web/src/routes/_authed/servers/components/mobile-view.ts
  • apps/web/src/routes/_authed/servers/index.cells.test.tsx
  • apps/web/src/routes/_authed/servers/index.tsx
  • apps/web/src/routes/_authed/settings/alerts.tsx
  • apps/web/src/routes/_authed/settings/api-keys.tsx
  • apps/web/src/routes/_authed/settings/appearance.tsx
  • apps/web/src/routes/_authed/settings/audit-logs.tsx
  • apps/web/src/routes/_authed/settings/capabilities.tsx
  • apps/web/src/routes/_authed/settings/index.tsx
  • apps/web/src/routes/_authed/settings/mobile-devices.tsx
  • apps/web/src/routes/_authed/settings/network-probes.tsx
  • apps/web/src/routes/_authed/settings/notifications.tsx
  • apps/web/src/routes/_authed/settings/ping-tasks.tsx
  • apps/web/src/routes/_authed/settings/security.tsx
  • apps/web/src/routes/_authed/settings/service-monitors.tsx
  • apps/web/src/routes/_authed/settings/status-pages.tsx
  • apps/web/src/routes/_authed/settings/users.tsx
  • apps/web/src/routes/_authed/terminal.$serverId.tsx
  • apps/web/src/routes/_authed/traffic/index.tsx
  • apps/web/src/routes/login.tsx

Comment on lines 483 to 489
<>
<div className="flex items-center justify-between px-4 py-3" key={rule.id}>
<div className="flex items-center gap-3">
<div
className="flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
key={rule.id}
>
<div className="flex min-w-0 items-center gap-3">
<AlertTriangle
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "alerts.tsx" -path "*/_authed/settings/*" 2>/dev/null | head -5

Repository: ZingerLittleBee/ServerBee

Length of output: 51


🏁 Script executed:

# Check if file exists and get its structure
fd -p "alerts.tsx" | grep "_authed/settings"

Repository: ZingerLittleBee/ServerBee

Length of output: 117


🏁 Script executed:

# Search for the file from repo root
find . -name "alerts.tsx" -type f | grep settings | head -3

Repository: ZingerLittleBee/ServerBee

Length of output: 119


🏁 Script executed:

# Use git to find the file
git ls-files | grep "alerts.tsx" | grep settings

Repository: ZingerLittleBee/ServerBee

Length of output: 117


🏁 Script executed:

wc -l ./apps/web/src/routes/_authed/settings/alerts.tsx

Repository: ZingerLittleBee/ServerBee

Length of output: 123


🏁 Script executed:

# Read the file with context around lines 483-489
sed -n '470,510p' ./apps/web/src/routes/_authed/settings/alerts.tsx

Repository: ZingerLittleBee/ServerBee

Length of output: 2223


🏁 Script executed:

# Continue reading to see the closing of the Fragment
sed -n '483,530p' ./apps/web/src/routes/_authed/settings/alerts.tsx

Repository: ZingerLittleBee/ServerBee

Length of output: 2505


🏁 Script executed:

# Continue reading to see the closing Fragment and the expanded content
sed -n '520,560p' ./apps/web/src/routes/_authed/settings/alerts.tsx

Repository: ZingerLittleBee/ServerBee

Length of output: 2200


🏁 Script executed:

# Continue reading to see the closing of the Fragment
sed -n '555,590p' ./apps/web/src/routes/_authed/settings/alerts.tsx

Repository: ZingerLittleBee/ServerBee

Length of output: 1906


Move the key attribute to the top-level mapped element.

The key is currently on the inner <div>, but the top-level returned node from the map callback is a Fragment. React expects the key on the outermost element and will warn about a missing key, potentially causing reconciliation issues with expanded rows.

Suggested fix
-                return (
-                  <>
-                    <div
-                      className="flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
-                      key={rule.id}
-                    >
+                return (
+                  <div key={rule.id}>
+                    <div className="flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
                       <div className="flex min-w-0 items-center gap-3">
@@
-                    {expandedRuleId === rule.id && (
+                    {expandedRuleId === rule.id && (
                       <div className="border-t bg-muted/20 px-4 py-2">
@@
-                    )}
-                  </>
+                    )}
+                  </div>
                 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<>
<div className="flex items-center justify-between px-4 py-3" key={rule.id}>
<div className="flex items-center gap-3">
<div
className="flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
key={rule.id}
>
<div className="flex min-w-0 items-center gap-3">
<AlertTriangle
return (
<div key={rule.id}>
<div className="flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex min-w-0 items-center gap-3">
<AlertTriangle
{/* ... existing content ... */}
</div>
{expandedRuleId === rule.id && (
<div className="border-t bg-muted/20 px-4 py-2">
{/* ... existing content ... */}
</div>
)}
</div>
</div>
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/routes/_authed/settings/alerts.tsx` around lines 483 - 489, The
mapped list is returning a Fragment (<>...</>) while the key is placed on an
inner <div key={rule.id}>, which causes React warnings and reconciliation
issues; fix it by moving the key to the top-level returned node—either replace
the shorthand Fragment with an explicit React.Fragment and set key={rule.id} on
it, or remove the Fragment and put the key on the outermost element that wraps
the mapped content (referencing the map callback that uses rule.id and the
Fragment surrounding the <div className="flex ...">).

@ZingerLittleBee ZingerLittleBee merged commit 02ac887 into main May 16, 2026
10 checks passed
@ZingerLittleBee ZingerLittleBee deleted the feat/mobile-ui-adaptation branch May 16, 2026 11:50
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