Conversation
- Header banner: smaller padding, text, and buttons on mobile; hide subtitle and "Demos" label on narrow screens - Demo gallery: full-screen bottom sheet on mobile, side drawer on sm+ - Brand shell: tighter padding and smaller border-radius on small screens - Add viewport meta tag for proper mobile scaling
|
Works well for most cases but the output might not be mobile friendly need to have a modal warning instead best view on desktop |
Shows a dismissible glassmorphic modal on viewports <= 768px warning that interactive visualizations work best on larger screens. Dismissed state persists for the session via sessionStorage.
…p modal Replace useEffect+useState with useSyncExternalStore for sessionStorage subscription, and use CSS md:hidden instead of JS matchMedia for the mobile viewport gate.
GeneralJerel
left a comment
There was a problem hiding this comment.
PR Review: Mobile Responsive Layout
Overall: Solid mobile responsive work. The drawer-to-bottom-sheet pattern and responsive header are well-executed. A few issues worth addressing:
Issues
1. maximum-scale=1 blocks user zoom (accessibility) — apps/app/src/app/layout.tsx:13
maximum-scale=1 prevents pinch-to-zoom, violating WCAG 2.1 SC 1.4.4. This was historically used to prevent iOS double-tap zoom glitches, but those are fixed in modern Safari. Remove it:
-<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />2. sessionStorage access can throw — apps/app/src/components/desktop-tip-modal.tsx:20
In privacy-restricted contexts (e.g., Safari private browsing on older versions, some embedded webviews), sessionStorage.getItem() and .setItem() can throw. The useSyncExternalStore client snapshot and dismiss function should wrap these in try/catch:
() => {
try { return !sessionStorage.getItem(DISMISSED_KEY); }
catch { return false; }
},3. Desktop tip modal has no Escape-to-dismiss — apps/app/src/components/desktop-tip-modal.tsx
The DemoGallery already supports Escape key (demo-gallery/index.tsx:27-29). The desktop tip modal should be consistent — users expect Escape to dismiss overlays. A simple useEffect with a keydown listener would do it.
4. Breakpoint mismatch between responsive layout and modal visibility — apps/app/src/components/desktop-tip-modal.tsx:34
The responsive layout uses sm: (640px) as its breakpoint, but the modal uses md:hidden (768px). This means users between 640–768px get the desktop layout but still see the "best viewed on desktop" warning. Either:
- Change the modal to
sm:hiddento match the layout breakpoint, or - Accept this intentionally (tablets may still benefit from the tip)
If intentional, a comment would help clarify.
Minor Nits
textAlign: "center" as const(desktop-tip-modal.tsx:53) — Theas constcast shouldn't be needed with React 19's types. Can likely be removed.- Heavy inline styles on the modal — The modal uses ~40 lines of inline styles while the rest of the PR moves styles into CSS (the drawer refactor). For consistency, these could live in
globals.cssalongside the other glassmorphic patterns. Not blocking, but would improve maintainability.
What looks good
- Moving drawer positioning from inline styles to CSS classes (
demo-gallery-drawer) is a clean improvement - Mobile-first CSS for the drawer (default: bottom sheet,
@media (min-width: 640px): side panel) is the right approach overscroll-containon the scrollable card list prevents scroll chaining — nice touch- Larger touch target on the close button (
p-2 -mr-1) is a good mobile UX detail useSyncExternalStoreis the correct React pattern for subscribing tosessionStoragewithout the lint error- Responsive header typography/spacing degrades gracefully
- Remove maximum-scale=1 from viewport meta to allow pinch-to-zoom (WCAG 2.1) - Wrap sessionStorage access in try/catch for privacy-restricted contexts - Add Escape key to dismiss desktop tip modal (consistent with demo gallery) - Change tip modal breakpoint from md:hidden to sm:hidden to match layout
Self-Review FindingsVerdict: No merge blockers (P0=0, P1=0) Findings
Testing Gaps
Open Questions
🤖 Generated with Claude Code |

Summary
Addresses #69 — this is the responsive approach (vs the desktop-only gate in #70)
Test plan
🤖 Generated with Claude Code