From 555c358fe27b308ff958860c157940330f4ed46c Mon Sep 17 00:00:00 2001 From: jerelvelarde Date: Thu, 26 Mar 2026 11:50:41 -0700 Subject: [PATCH 1/4] feat: make app mobile responsive - 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 --- apps/app/src/app/globals.css | 35 +++++++++++++++++++ apps/app/src/app/layout.tsx | 1 + apps/app/src/app/page.tsx | 20 +++++------ .../app/src/components/demo-gallery/index.tsx | 21 ++++------- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/apps/app/src/app/globals.css b/apps/app/src/app/globals.css index b0e8d06..caa5763 100644 --- a/apps/app/src/app/globals.css +++ b/apps/app/src/app/globals.css @@ -308,6 +308,12 @@ body, html { @media (max-width: 768px) { .brand-shell { padding: 8px; } + .brand-glass-container { border-radius: var(--radius-lg); } +} + +@media (max-width: 480px) { + .brand-shell { padding: 4px; } + .brand-glass-container { border-radius: var(--radius-md); } } /* === Gradient Utilities === */ @@ -602,6 +608,35 @@ body, html { flex-shrink: 0; } +/* === Demo Gallery Drawer === */ +/* Mobile: full-screen, slides up from bottom */ +.demo-gallery-drawer { + inset: 0; + border-radius: 0; + transform: translateY(100%); +} +.demo-gallery-drawer--open { + transform: translateY(0); +} + +/* Desktop (sm+): right side panel, slides in from right */ +@media (min-width: 640px) { + .demo-gallery-drawer { + inset: auto; + top: 0; + right: 0; + height: 100%; + width: 480px; + max-width: 90vw; + border-left: 1px solid var(--color-border-glass, rgba(0,0,0,0.1)); + transform: translateX(100%); + } + .demo-gallery-drawer--open { + transform: translateX(0); + box-shadow: -8px 0 30px rgba(0,0,0,0.1); + } +} + /* === Flash Animation === */ .content-flash { animation: content-flash 700ms ease-out; diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx index 112234f..db486cb 100644 --- a/apps/app/src/app/layout.tsx +++ b/apps/app/src/app/layout.tsx @@ -10,6 +10,7 @@ export default function RootLayout({children}: Readonly<{ children: React.ReactN return ( + Open Generative UI diff --git a/apps/app/src/app/page.tsx b/apps/app/src/app/page.tsx index 1f1fa66..9020879 100644 --- a/apps/app/src/app/page.tsx +++ b/apps/app/src/app/page.tsx @@ -51,25 +51,25 @@ export default function HomePage() { background: "linear-gradient(135deg, rgba(190,194,255,0.08) 0%, rgba(133,224,206,0.06) 100%)", }} > -
-
+
+
- 🪁 + 🪁
-

+

Open Generative UI - — powered by CopilotKit + — powered by CopilotKit

-
+
)} - {/* Drawer panel */} + {/* Drawer: full-screen bottom sheet on mobile, right side panel on sm+ */}
{/* Header */}
+
+
+ ); +} From fd16303b7a7e4121a5196e1905a59b1512d03d0e Mon Sep 17 00:00:00 2001 From: jerelvelarde Date: Thu, 26 Mar 2026 12:47:24 -0700 Subject: [PATCH 3/4] fix: resolve react-hooks/set-state-in-effect lint error in desktop tip modal Replace useEffect+useState with useSyncExternalStore for sessionStorage subscription, and use CSS md:hidden instead of JS matchMedia for the mobile viewport gate. --- apps/app/src/components/desktop-tip-modal.tsx | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/app/src/components/desktop-tip-modal.tsx b/apps/app/src/components/desktop-tip-modal.tsx index a032ef6..b44d86b 100644 --- a/apps/app/src/components/desktop-tip-modal.tsx +++ b/apps/app/src/components/desktop-tip-modal.tsx @@ -1,41 +1,41 @@ "use client"; -import { useEffect, useState } from "react"; +import { useSyncExternalStore } from "react"; const DISMISSED_KEY = "desktop-tip-dismissed"; -export function DesktopTipModal() { - const [visible, setVisible] = useState(false); +let listeners: Array<() => void> = []; +function emitChange() { + listeners.forEach((l) => l()); +} - useEffect(() => { - // Only show on narrow viewports and if not previously dismissed - const mq = window.matchMedia("(max-width: 768px)"); - if (mq.matches && !sessionStorage.getItem(DISMISSED_KEY)) { - setVisible(true); - } - }, []); +export function DesktopTipModal() { + const notDismissed = useSyncExternalStore( + (listener) => { + listeners.push(listener); + return () => { + listeners = listeners.filter((l) => l !== listener); + }; + }, + () => !sessionStorage.getItem(DISMISSED_KEY), + () => false, + ); - if (!visible) return null; + if (!notDismissed) return null; const dismiss = () => { sessionStorage.setItem(DISMISSED_KEY, "1"); - setVisible(false); + emitChange(); }; return (
Date: Thu, 26 Mar 2026 13:59:26 -0700 Subject: [PATCH 4/4] fix: address PR review findings on mobile responsive layout - 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 --- apps/app/src/app/layout.tsx | 2 +- apps/app/src/components/desktop-tip-modal.tsx | 27 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx index db486cb..0a06223 100644 --- a/apps/app/src/app/layout.tsx +++ b/apps/app/src/app/layout.tsx @@ -10,7 +10,7 @@ export default function RootLayout({children}: Readonly<{ children: React.ReactN return ( - + Open Generative UI diff --git a/apps/app/src/components/desktop-tip-modal.tsx b/apps/app/src/components/desktop-tip-modal.tsx index b44d86b..582ea04 100644 --- a/apps/app/src/components/desktop-tip-modal.tsx +++ b/apps/app/src/components/desktop-tip-modal.tsx @@ -1,6 +1,6 @@ "use client"; -import { useSyncExternalStore } from "react"; +import { useEffect, useSyncExternalStore } from "react"; const DISMISSED_KEY = "desktop-tip-dismissed"; @@ -17,21 +17,34 @@ export function DesktopTipModal() { listeners = listeners.filter((l) => l !== listener); }; }, - () => !sessionStorage.getItem(DISMISSED_KEY), + () => { + try { return !sessionStorage.getItem(DISMISSED_KEY); } + catch { return false; } + }, () => false, ); - if (!notDismissed) return null; - const dismiss = () => { - sessionStorage.setItem(DISMISSED_KEY, "1"); + try { sessionStorage.setItem(DISMISSED_KEY, "1"); } + catch { /* privacy-restricted context */ } emitChange(); }; + useEffect(() => { + if (!notDismissed) return; + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") dismiss(); + }; + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }); + + if (!notDismissed) return null; + return (