Skip to content

Commit fa645aa

Browse files
authored
Merge pull request #488 from objectstack-ai/copilot/optimize-mobile-user-experience
2 parents dbc860b + de659d0 commit fa645aa

10 files changed

Lines changed: 235 additions & 30 deletions

File tree

ROADMAP.md

Lines changed: 200 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
> **Current Version:** v0.5.x
55
> **Spec Version:** @objectstack/spec v3.0.0
66
> **Client Version:** @objectstack/client v3.0.0
7-
> **Current Priority:** 🎯 Developer Experience · User Experience · Component Excellence · Documentation
7+
> **Current Priority:** 🎯 Developer Experience · User Experience · Component Excellence · Documentation · Mobile UX
88
99
---
1010

@@ -20,6 +20,7 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
2020
2. **🎨 User Experience (UX)** — Console polish, i18n completeness, accessibility, performance at scale
2121
3. **🧩 Component Excellence** — Every component polished, well-tested, and delightful to use
2222
4. **📖 Documentation** — Complete API docs, rich Storybook stories, tutorials, and guides
23+
5. **📱 Mobile User Experience** — Responsive layouts, touch-friendly interactions, PWA support
2324

2425
> 📄 Companion documents:
2526
> - [SPEC_COMPLIANCE_EVALUATION.md](./SPEC_COMPLIANCE_EVALUATION.md) — Per-package spec compliance (98% current)
@@ -283,6 +284,193 @@ All 4 phases complete across 5 designers (Page, View, DataModel, Process, Report
283284

284285
---
285286

287+
### P5. Mobile User Experience 📱
288+
289+
**Goal:** Every component and page delivers a native-quality experience on phones and tablets — responsive layout, touch-friendly interactions, and fast performance on mobile networks.
290+
291+
> **Existing Infrastructure:** `@object-ui/mobile` provides `useBreakpoint` (isMobile/isTablet/isDesktop), `useResponsive`, `useGesture`, `useTouchTarget`, `MobileProvider`, and `ResponsiveContainer`. `@object-ui/layout` provides `ResponsiveGrid` with Tailwind-aligned breakpoints. These building blocks are production-ready but under-adopted across plugin views and the Console app.
292+
293+
#### P5.1 Plugin Views — Mobile-First Layouts
294+
295+
Each plugin view must work seamlessly from 320px (small phone) to 2560px (ultrawide).
296+
297+
##### ObjectGrid (`plugin-grid`)
298+
- [ ] Wrap data table in `overflow-x-auto` container for horizontal scroll on mobile
299+
- [ ] Add responsive toolbar: stack filter/sort/search controls vertically below `sm:` breakpoint
300+
- [ ] Collapse non-essential columns on mobile via `hidden sm:table-cell` pattern
301+
- [ ] Scale row padding: `px-2 py-1.5 sm:px-3 sm:py-2 md:px-4 md:py-2.5`
302+
- [ ] Add mobile card-view fallback for screens below 480px (toggle between table and card layout)
303+
- [ ] Ensure touch targets ≥ 44px for all interactive row elements
304+
305+
##### ObjectKanban (`plugin-kanban`)
306+
- [ ] Stack columns vertically on mobile with horizontal swipe navigation between columns
307+
- [ ] Scale card sizing: `p-2 sm:p-3 md:p-4` with responsive typography
308+
- [ ] Add touch-friendly drag-and-drop via `useGesture` (long-press to initiate, haptic feedback)
309+
- [ ] Column headers: `text-sm sm:text-base` with truncation on mobile
310+
- [ ] Add column count badge and swipe indicator on mobile
311+
- [ ] Limit visible card fields on mobile (show title + status only, expand on tap)
312+
313+
##### ObjectForm (`plugin-form`)
314+
- [ ] Ensure mobile-first column stacking: 1 column on `xs`, 2 on `sm:`, 3+ on `md:`
315+
- [ ] Scale field labels: `text-xs sm:text-sm` with proper spacing
316+
- [ ] Make action buttons full-width on mobile: `w-full sm:w-auto`
317+
- [ ] Increase touch targets for all form controls (min 44×44px)
318+
- [ ] Optimize select/dropdown fields for mobile (bottom sheet pattern on phones)
319+
- [ ] Ensure date pickers and multi-select fields are mobile-friendly
320+
321+
##### ObjectDashboard (`plugin-dashboard`)
322+
- [ ] Implement responsive grid: `grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4`
323+
- [ ] Scale widget padding: `p-3 sm:p-4 md:p-6` per widget card
324+
- [ ] Stack dashboard header controls on mobile (title above actions)
325+
- [ ] Add swipeable widget carousel option for mobile
326+
- [ ] Chart widgets: reduce axis label density on mobile
327+
328+
##### ObjectCalendar (`plugin-calendar`)
329+
- [ ] Replace fixed `h-[calc(100vh-200px)]` with responsive height: `h-[calc(100vh-120px)] sm:h-[calc(100vh-160px)] md:h-[calc(100vh-200px)]`
330+
- [ ] Default to day or agenda view on mobile (month view unreadable on phones)
331+
- [ ] Add swipe-to-navigate between days/weeks on mobile via `useGesture`
332+
- [ ] Scale event text: `text-xs sm:text-sm` with single-line truncation
333+
- [ ] Make event creation touch-friendly (long-press on timeslot)
334+
335+
##### ObjectTimeline (`plugin-timeline`)
336+
- [ ] Switch from side-by-side to single-column layout on mobile
337+
- [ ] Scale item padding: `p-2 sm:p-3 md:p-4` with responsive typography
338+
- [ ] Truncate event descriptions on mobile with "Show more" expand
339+
- [ ] Add pull-to-refresh via `usePullToRefresh` for timeline data
340+
341+
##### ObjectList (`plugin-list`)
342+
- [ ] Stack toolbar controls vertically on mobile with collapsible filter panel
343+
- [ ] Scale search bar: `w-full sm:w-48 lg:w-64` (full-width on mobile)
344+
- [ ] Responsive list item padding: `px-2 sm:px-3 md:px-4`
345+
- [ ] Ensure action menus use bottom sheet on mobile instead of popovers
346+
- [ ] Touch-friendly row selection (checkbox size ≥ 44px)
347+
348+
##### DetailView (`plugin-detail`)
349+
- [ ] Stack header actions vertically on narrow screens: `flex-col sm:flex-row`
350+
- [ ] Full-width action buttons on mobile: `w-full sm:w-auto`
351+
- [ ] Scale section padding: `p-3 sm:p-4 md:p-6`
352+
- [ ] Convert metadata panel to bottom drawer on mobile
353+
- [ ] Increase field value touch targets for copy-to-clipboard
354+
355+
##### Charts (`plugin-charts`)
356+
- [ ] Set responsive chart heights: `h-48 sm:h-64 md:h-80 lg:h-96`
357+
- [ ] Reduce axis label count on mobile to prevent overlap
358+
- [ ] Enable touch-to-inspect data points (tooltip on tap)
359+
- [ ] Stack legend below chart on mobile: `flex-col sm:flex-row`
360+
361+
##### Map (`plugin-map`)
362+
- [ ] Enable pinch-to-zoom and two-finger pan on mobile
363+
- [ ] Scale info popup sizing for mobile screens
364+
- [ ] Add mobile-friendly location search with bottom sheet
365+
- [ ] Ensure map controls (zoom, layer toggle) are touch-accessible
366+
367+
##### Gantt (`plugin-gantt`)
368+
- [ ] Add horizontal scroll container with touch momentum
369+
- [ ] Scale bar heights: `h-6 sm:h-8 md:h-10`
370+
- [ ] Collapse task details on mobile (show name only, expand on tap)
371+
- [ ] Add responsive zoom levels (day view on mobile, week on tablet, month on desktop)
372+
373+
#### P5.2 Console Pages — Responsive Layouts
374+
375+
##### AppHeader
376+
- [ ] Ensure all header actions are accessible on mobile (overflow menu for hidden actions)
377+
- [ ] Add mobile-specific command palette trigger (prominent search icon)
378+
- [ ] Scale header height: `h-12 sm:h-14 md:h-16`
379+
- [ ] Responsive breadcrumb: show only current page on mobile, full path on desktop
380+
381+
##### AppSidebar
382+
- [ ] Auto-collapse to icon-only mode on tablet, full overlay on mobile
383+
- [ ] Add swipe-from-edge gesture to open sidebar on mobile via `useGesture`
384+
- [ ] Scale menu item padding for touch: `py-2.5 sm:py-2` (larger on mobile)
385+
- [ ] Bottom navigation bar option for mobile (5-item tab bar)
386+
387+
##### DashboardView (Console)
388+
- [ ] Stack title and metadata toggle vertically on mobile
389+
- [ ] Scale heading: `text-lg sm:text-xl md:text-2xl`
390+
- [ ] Add responsive padding: `p-3 sm:p-4 md:p-6 lg:p-8`
391+
- [ ] Optimize metadata panel as collapsible accordion on mobile
392+
393+
##### RecordDetailView
394+
- [ ] Scale dialog width: `w-[calc(100vw-1rem)] sm:w-[calc(100vw-2rem)] md:max-w-2xl lg:max-w-4xl`
395+
- [ ] Stack field label and value vertically on mobile
396+
- [ ] Add responsive padding: `p-3 sm:p-4 md:p-6`
397+
- [ ] Ensure inline editing works with on-screen keyboard
398+
399+
##### Create/Edit Dialogs
400+
- [ ] Full-screen modal on mobile: `h-[100dvh] sm:h-auto sm:max-h-[90vh]`
401+
- [ ] Sticky action buttons at bottom on mobile
402+
- [ ] Add swipe-to-dismiss for mobile dialogs
403+
404+
#### P5.3 Core Component Primitives
405+
406+
##### DataTable (`components/renderers/complex`)
407+
- [ ] Add outer `overflow-x-auto` wrapper with `-webkit-overflow-scrolling: touch`
408+
- [ ] Scale pagination controls: full-width on mobile with larger touch targets
409+
- [ ] Responsive column visibility system (priority-based column hiding)
410+
- [ ] Mobile-optimized search/filter bar: full-width, collapsible
411+
- [ ] Horizontal scroll indicator (shadow/gradient) on mobile
412+
413+
##### Form Renderer (`components/renderers/form`)
414+
- [ ] Verify responsive column mapping works correctly for all column counts (1–4)
415+
- [ ] Ensure wizard/stepper variant shows 1 step at a time on mobile
416+
- [ ] Scale step indicators for mobile: `text-xs sm:text-sm`, smaller circles
417+
- [ ] Full-width submit/cancel buttons on mobile
418+
419+
##### Navigation Components
420+
- [ ] Breadcrumb: truncate to current + parent on mobile with "..." overflow
421+
- [ ] Tab navigation: horizontal scroll with `overflow-x-auto` for many tabs
422+
- [ ] DropdownMenu: use bottom sheet pattern on mobile via `useIsMobile()` detection
423+
424+
#### P5.4 Mobile Infrastructure Enhancements
425+
426+
##### Touch & Gesture System
427+
- [ ] Integrate `usePullToRefresh` into all data-fetching views (Grid, List, Timeline, Calendar)
428+
- [ ] Add swipe-to-go-back gesture for Console navigation
429+
- [ ] Integrate `useTouchTarget` hook to enforce minimum 44px touch targets across all interactive elements
430+
- [ ] Add haptic feedback triggers for drag-and-drop completion
431+
432+
##### Performance on Mobile Networks
433+
- [ ] Implement progressive image loading for all image fields and avatars
434+
- [ ] Add connection-aware data fetching (reduce page size on slow connections)
435+
- [ ] Enable service worker caching for static assets via `registerServiceWorker`
436+
- [ ] Add `loading="lazy"` to all below-fold images and iframes
437+
438+
##### PWA Support
439+
- [ ] Integrate `MobileProvider` into Console app root with PWA configuration
440+
- [ ] Generate app manifest via `generatePWAManifest()` utility
441+
- [ ] Add app install prompt for mobile browsers
442+
- [ ] Implement offline indicator and cached-data fallback
443+
444+
##### Viewport & Input Handling
445+
- [ ] Handle virtual keyboard resize (100dvh instead of 100vh for mobile)
446+
- [ ] Prevent zoom on input focus (`font-size: 16px` minimum for inputs)
447+
- [ ] Add viewport meta tag validation in Console HTML template
448+
- [ ] Support safe-area-inset for notched devices (`env(safe-area-inset-*)`)
449+
450+
#### P5.5 Testing & Quality Assurance
451+
452+
- [ ] Add Playwright mobile viewport tests (iPhone SE 375px, iPhone 14 390px, iPad 768px)
453+
- [ ] Add visual regression tests for all views at mobile breakpoints
454+
- [ ] Add touch interaction tests (swipe, pinch, long-press) via Playwright touch emulation
455+
- [ ] Verify all views pass axe-core at mobile viewport sizes
456+
- [ ] Add Storybook mobile viewport decorator for all component stories
457+
- [ ] Test on-screen keyboard interaction for all form fields
458+
- [ ] Performance benchmark on simulated mobile CPU (4× slowdown) and 3G network
459+
460+
#### P5.6 Mobile-Specific Success Metrics
461+
462+
| Metric | Target | How Measured |
463+
|--------|--------|--------------|
464+
| **Touch Target Compliance** | 100% ≥ 44px | Automated scan via `useTouchTarget` |
465+
| **Mobile Lighthouse Score** | ≥ 90 (Performance, Accessibility) | Lighthouse CI on mobile preset |
466+
| **First Contentful Paint (Mobile 3G)** | < 1.5s | Lighthouse / Web Vitals |
467+
| **Time to Interactive (Mobile 3G)** | < 3.5s | Lighthouse / Web Vitals |
468+
| **Responsive Breakpoint Coverage** | All 13+ views × 3 breakpoints | Playwright viewport tests |
469+
| **Mobile Gesture Coverage** | Swipe, pinch, long-press in all applicable views | E2E gesture tests |
470+
| **PWA Install Success** | Manifest + service worker valid | Lighthouse PWA audit |
471+
472+
---
473+
286474
## 🔮 Future Vision (Deferred)
287475

288476
> The following items are **not** in the current sprint. They will be re-evaluated once P1–P4 are substantially complete.
@@ -326,6 +514,9 @@ All 4 phases complete across 5 designers (Page, View, DataModel, Process, Report
326514
| **WCAG AA Compliance** | Full Console pages | Full Console pages | axe-core audit |
327515
| **CLI Commands Working** | 11 | 11 (all verified) | `objectui doctor` |
328516
| **TODO/FIXME Count** | 0 files | 0 | Grep `TODO\|FIXME\|HACK` |
517+
| **Mobile Responsive Views** | Partial (2/13) | 13/13 (100%) | Playwright mobile tests |
518+
| **Mobile Lighthouse Score** | Not measured | ≥ 90 | Lighthouse CI mobile preset |
519+
| **Touch Target Compliance** | Not measured | 100% ≥ 44px | Automated touch target scan |
329520

330521
### DX Success Criteria
331522
- [x] New developer can `git clone``pnpm install``pnpm dev` → see Console in < 5 minutes
@@ -339,6 +530,13 @@ All 4 phases complete across 5 designers (Page, View, DataModel, Process, Report
339530
- [x] Grid view handles 10,000+ records without jank (< 100ms interaction latency)
340531
- [x] Full keyboard navigation for all Console workflows (no mouse required)
341532

533+
### Mobile UX Success Criteria
534+
- [ ] All 13+ views render correctly at 375px viewport (iPhone SE)
535+
- [ ] Touch targets ≥ 44px for all interactive elements
536+
- [ ] Console Lighthouse mobile score ≥ 90 (Performance + Accessibility)
537+
- [ ] All views support horizontal scroll or responsive stacking on mobile
538+
- [ ] Swipe gestures work for navigation, Kanban columns, and Calendar days
539+
342540
---
343541

344542
## ⚠️ Risk Management
@@ -352,6 +550,7 @@ All 4 phases complete across 5 designers (Page, View, DataModel, Process, Report
352550
| **Component API inconsistency** | Audit checklist, automated prop-type validation, Storybook as source of truth |
353551
| **Documentation drift** | TSDoc generation from source, Storybook stories alongside code, link checker CI |
354552
| **Accessibility regression** | axe-core tests on full Console pages (not just primitives), WCAG AA CI check |
553+
| **Mobile responsiveness regression** | Playwright mobile viewport tests, Lighthouse CI mobile preset, touch target audit |
355554

356555
---
357556

packages/components/src/renderers/complex/data-table.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -510,13 +510,13 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
510510
const showToolbar = searchable || exportable || (selectable && selectedRowIds.size > 0) || hasPendingChanges;
511511

512512
return (
513-
<div className={`flex flex-col h-full gap-4 ${className || ''}`}>
513+
<div className={`flex flex-col h-full gap-2 sm:gap-4 ${className || ''}`}>
514514
{/* Toolbar */}
515515
{showToolbar && (
516-
<div className="flex items-center justify-between gap-4 flex-none">
516+
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-2 sm:gap-4 flex-none">
517517
<div className="flex items-center gap-2 flex-1">
518518
{searchable && (
519-
<div className="relative max-w-sm flex-1">
519+
<div className="relative w-full sm:max-w-sm flex-1">
520520
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
521521
<Input
522522
placeholder="Search..."
@@ -531,7 +531,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
531531
)}
532532
</div>
533533

534-
<div className="flex items-center gap-2">
534+
<div className="flex flex-wrap items-center gap-2">
535535
{hasPendingChanges && (
536536
<>
537537
<div className="text-sm text-muted-foreground">
@@ -580,7 +580,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
580580
)}
581581

582582
{/* Table */}
583-
<div className="rounded-md border flex-1 min-h-0 overflow-auto relative bg-background">
583+
<div className="rounded-md border flex-1 min-h-0 overflow-auto relative bg-background [-webkit-overflow-scrolling:touch]">
584584
<Table>
585585
{caption && <TableCaption>{caption}</TableCaption>}
586586
<TableHeader className="sticky top-0 bg-background z-10 shadow-sm">
@@ -804,7 +804,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
804804

805805
{/* Pagination */}
806806
{pagination && sortedData.length > 0 && (
807-
<div className="flex items-center justify-between">
807+
<div className="flex flex-col sm:flex-row items-center justify-between gap-2">
808808
<div className="flex items-center gap-2">
809809
<span className="text-sm text-muted-foreground">Rows per page:</span>
810810
<Select

packages/plugin-calendar/src/ObjectCalendar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ export const ObjectCalendar: React.FC<ObjectCalendarProps> = ({
326326

327327
return (
328328
<div className={className}>
329-
<div className="border rounded-lg bg-background h-[calc(100vh-200px)] min-h-[600px]">
329+
<div className="border rounded-lg bg-background h-[calc(100vh-120px)] sm:h-[calc(100vh-160px)] md:h-[calc(100vh-200px)] min-h-[400px] sm:min-h-[600px]">
330330
<CalendarView
331331
events={events}
332332
currentDate={currentDate}

packages/plugin-dashboard/src/DashboardRenderer.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,15 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
5656
return (
5757
<div
5858
ref={ref}
59-
className={cn("grid auto-rows-min", className)}
59+
className={cn(
60+
"grid auto-rows-min",
61+
// Responsive grid: 1 column on mobile, 2 on sm, 3 on lg, 4 on xl
62+
// When columns > 4, inline gridTemplateColumns style overrides these classes
63+
"grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
64+
className
65+
)}
6066
style={{
61-
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
67+
...(columns > 4 && { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }),
6268
gap: `${gap * 0.25}rem`
6369
}}
6470
{...props}
@@ -166,7 +172,7 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
166172
</CardHeader>
167173
)}
168174
<CardContent className="p-0">
169-
<div className={cn("h-full w-full", !widget.title ? "p-4" : "p-4")}>
175+
<div className={cn("h-full w-full", "p-3 sm:p-4 md:p-6")}>
170176
<SchemaRenderer schema={componentSchema} />
171177
</div>
172178
</CardContent>

packages/plugin-detail/src/DetailView.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ export const DetailView: React.FC<DetailViewProps> = ({
182182
<TooltipProvider>
183183
<div className={cn('space-y-6', className)}>
184184
{/* Header - Airtable-inspired layout */}
185-
<div className="flex items-start justify-between gap-4 pb-4 border-b">
186-
<div className="flex items-start gap-3 flex-1 min-w-0">
185+
<div className="flex flex-col sm:flex-row items-start justify-between gap-3 sm:gap-4 pb-4 border-b">
186+
<div className="flex items-start gap-2 sm:gap-3 flex-1 min-w-0">
187187
{(schema.showBack ?? true) && (
188188
<Tooltip>
189189
<TooltipTrigger asChild>
@@ -196,7 +196,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
196196
)}
197197
<div className="flex-1 min-w-0">
198198
<div className="flex items-center gap-2">
199-
<h1 className="text-2xl font-bold truncate">{schema.title || 'Details'}</h1>
199+
<h1 className="text-xl sm:text-2xl font-bold truncate">{schema.title || 'Details'}</h1>
200200
<Tooltip>
201201
<TooltipTrigger asChild>
202202
<Button
@@ -227,7 +227,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
227227
</div>
228228
</div>
229229

230-
<div className="flex items-center gap-1.5 shrink-0">
230+
<div className="flex flex-wrap items-center gap-1.5 shrink-0">
231231
{schema.actions?.map((action, index) => (
232232
<SchemaRenderer key={index} schema={action} data={data} />
233233
))}

packages/plugin-form/src/ObjectForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ const SimpleObjectForm: React.FC<ObjectFormProps> = ({
511511
// Render error state
512512
if (error) {
513513
return (
514-
<div className="p-4 border border-red-300 bg-red-50 rounded-md">
514+
<div className="p-3 sm:p-4 border border-red-300 bg-red-50 rounded-md">
515515
<h3 className="text-red-800 font-semibold">Error loading form</h3>
516516
<p className="text-red-600 text-sm mt-1">{error.message}</p>
517517
</div>
@@ -521,7 +521,7 @@ const SimpleObjectForm: React.FC<ObjectFormProps> = ({
521521
// Render loading state
522522
if (loading) {
523523
return (
524-
<div className="p-8 text-center">
524+
<div className="p-4 sm:p-8 text-center">
525525
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
526526
<p className="mt-2 text-sm text-gray-600">Loading form...</p>
527527
</div>

0 commit comments

Comments
 (0)