feat(mobile): mobile UX and accessibility polish#115
Conversation
Route mobile users through a new src/mobile/pages structure and add a mobile-first home page while preserving existing desktop pages. Made-with: Cursor
Render a real mobile activity log experience with live session/task data and replace the bottom Profile tab with Settings for better navigation consistency. Made-with: Cursor
Improve mobile UX by adding a dedicated dashboard layout, closing drawer on navigation, refining activity summaries/member drilldown, and adding missing dialog metadata to remove accessibility warnings. Made-with: Cursor
There was a problem hiding this comment.
Pull request overview
This PR focuses on improving the mobile experience across routing, layout/navigation behavior, and key dashboard/activity surfaces, while also addressing accessibility metadata warnings for dialogs/sheets.
Changes:
- Add mobile-specific route entries/pages and introduce a dedicated mobile landing (
IndexMobile). - Implement new mobile dashboard + activity log views and wire them into the mobile shell/navigation.
- Polish responsiveness/visual backdrop (transparent panels + new
dashboard-backdrop) and add accessible dialog/sheet headers/descriptions.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/App.tsx | Routes conditionally render mobile vs desktop pages using useIsMobile. |
| src/mobile/pages/IndexMobile.tsx | New mobile-first landing page layout/content. |
| src/mobile/pages/LoginMobile.tsx | Mobile route wrapper for Login page. |
| src/mobile/pages/SignupMobile.tsx | Mobile route wrapper for Signup page. |
| src/mobile/pages/WelcomeToZyncMobile.tsx | Mobile route wrapper for Welcome page. |
| src/mobile/pages/DashboardMobile.tsx | Mobile route wrapper for Dashboard entry. |
| src/mobile/pages/NewProjectMobile.tsx | Mobile route wrapper for New Project flow. |
| src/mobile/pages/ProjectDetailsMobile.tsx | Mobile route wrapper for Project Details. |
| src/mobile/pages/PrivacyPolicyMobile.tsx | Mobile route wrapper for Privacy Policy. |
| src/mobile/pages/PrivacyMobile.tsx | Mobile route wrapper for Privacy page. |
| src/mobile/pages/TermsMobile.tsx | Mobile route wrapper for Terms page. |
| src/mobile/pages/NotFoundMobile.tsx | Mobile route wrapper for Not Found. |
| src/components/views/MobileView.tsx | Wire in new mobile dashboard/activity views + mobile activity data fetching/timers. |
| src/components/views/mobile/MobileDashboardView.tsx | New mobile dashboard summary (GitHub contributions + task/project counts). |
| src/components/views/mobile/MobileActivityLogView.tsx | New mobile activity summary + leader team-member activity expansion UI. |
| src/components/layout/MobileLayout.tsx | Bottom-nav updates, drawer closes on selection, sheet accessibility header/description. |
| src/components/views/SettingsView.tsx | Make settings tabs horizontally scrollable on small screens. |
| src/components/views/DesktopView.tsx | Transparent main shell + fixed backdrop layer for full-viewport gradient canvas. |
| src/components/views/DesignView.tsx | Make design view background transparent to show backdrop. |
| src/index.css | Add dashboard-backdrop gradient styles (dark + light). |
| src/components/views/ActivityLogView.tsx | Centralize time formatting helpers and use them in summary UI. |
| src/components/ui/command.tsx | Add sr-only dialog title/description to satisfy accessibility tooling. |
| src/components/ProfilePhotoCropper.tsx | Add dialog description (sr-only) for accessibility metadata completeness. |
| src/lib/firebase.ts | Remove DEV-only console info about missing reCAPTCHA App Check key. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| FileText, | ||
| Video, | ||
| Settings, | ||
| LogOut, | ||
| Bell | ||
| } from "lucide-react"; |
There was a problem hiding this comment.
signOutAndClearState is still imported but no longer used after removing the Profile/sign-out UI. Please remove the unused import to avoid lint warnings and keep the file tidy.
| useEffect(() => { | ||
| const storedSession = localStorage.getItem("currentSession"); | ||
| if (!storedSession) { return; } | ||
| try { |
There was a problem hiding this comment.
Mobile sessions are only restored from localStorage here; there’s no fallback to create/start a new session when currentSession is missing or stale. Since useActivityTracker explicitly relies on an existing currentSession (and DesktopView is the only place that starts one), mobile-only users may never record sessions and elapsedTime/activity views will stay at defaults. Consider reusing the DesktopView session-start logic (or extracting it into a shared helper) so MobileView ensures a valid session exists.
| const teamMemberStats = (() => { | ||
| if (!isLeader) { | ||
| return []; | ||
| } | ||
|
|
||
| const members = new Map<string, { uid: string; displayName: string; email?: string; photoURL?: string }>(); | ||
| const teamList = [...(ownedTeams || []), ...fallbackOwnedFromMyTeams]; | ||
|
|
||
| teamList.forEach((team: any) => { |
There was a problem hiding this comment.
teamMemberStats is computed via an IIFE on every render (and it does multiple passes over teams/users/sessions). Expanding/collapsing a member will recompute all stats unnecessarily. Consider wrapping this computation in useMemo with appropriate deps (e.g., isLeader, ownedTeams, myTeams, users, teamSessions, currentUserId, elapsedSeconds) to avoid repeated work on mobile.
| <Button | ||
| size="icon" | ||
| variant="ghost" | ||
| className="h-7 w-7 text-destructive" | ||
| onClick={() => onDeleteLog(log._id)} | ||
| > | ||
| <Trash2 className="h-3.5 w-3.5" /> | ||
| </Button> |
There was a problem hiding this comment.
The delete-log icon button doesn’t have an accessible name (no aria-label, title, or visible text), which makes it hard for screen readers and can trigger accessibility tooling warnings. Add an aria-label/title or an sr-only label inside the button.
| useUserSync(); | ||
| useSyncData(); // Trigger local-first data fetch and Dexie sync on login/app load | ||
| const location = useLocation(); | ||
| const isMobile = useIsMobile(); |
There was a problem hiding this comment.
useIsMobile() initially returns false (it’s undefined until the first effect runs), so these route-level isMobile ? <…Mobile/> : <…/> conditionals can briefly render the desktop route elements on mobile and then unmount/remount once isMobile updates. That can cause visible flicker and double lazy-loading. Consider making useIsMobile return a correct initial value (e.g., initialize from window.innerWidth when window exists) or gating route rendering until the breakpoint is known.
Summary
Test plan