From d61599973706d9b1e3118545d127fce8af57effa Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 18 Jul 2026 22:14:51 -0400 Subject: [PATCH] =?UTF-8?q?fix(mobile):=20overflow=20breaks=20=E2=80=94=20?= =?UTF-8?q?tables,=20composer,=20call=20bar,=20previews,=20cards=20(M1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mobile-audit batch 1. All fixes reuse cinny's own responsive primitives and are mobile-gated so desktop is unchanged. - Message tables: wrap in an overflow-x container so a wide table scrolls instead of overflowing the message column / page body. - Composer toolbar: let the before|editable|after row and the toolbar wrap on phones (@media <=750px) instead of squeezing the editable to zero and pushing Send off-screen. - In-call control bar: collapse to the compact/stacked layout on a mobile viewport (ScreenSize.Mobile) too, not just when the bar's own container is <500px — fixes the 500-750px band where the control row overflowed. - URL-preview card: base width toRem(400) -> min(25rem, 92vw) so a single card fits a narrow phone (still exactly 400px on desktop). - Explore card grid: drop to one column at <=750px (was a fixed 3-col grid). Two review passes: desktop behavior provably unchanged (all gated by @media / ScreenSize.Mobile; the table wrapper only contains previously-overflowing tables). Co-Authored-By: Claude Opus 4.8 --- src/app/components/editor/Editor.css.ts | 14 ++++++++++++++ src/app/components/editor/Editor.tsx | 2 +- src/app/components/room-card/style.css.ts | 5 +++++ src/app/components/url-preview/UrlPreview.css.tsx | 4 +++- src/app/features/call/CallControls.tsx | 12 ++++++++++-- src/app/plugins/react-custom-html-parser.tsx | 11 +++++++++++ 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/app/components/editor/Editor.css.ts b/src/app/components/editor/Editor.css.ts index d128ed074..6ab330f71 100644 --- a/src/app/components/editor/Editor.css.ts +++ b/src/app/components/editor/Editor.css.ts @@ -16,9 +16,23 @@ export const EditorOptions = style([ DefaultReset, { padding: config.space.S200, + '@media': { + // On phones the toolbar can hold many 44px buttons; let them wrap to a + // second line instead of overflowing horizontally. + '(max-width: 750px)': { flexWrap: 'wrap' }, + }, }, ]); +// The composer's before | editable | after row. On phones, allow the toolbar +// (`after`) to wrap below the input instead of squeezing the editable to zero +// and pushing the Send button off-screen. +export const EditorInputRow = style({ + '@media': { + '(max-width: 750px)': { flexWrap: 'wrap' }, + }, +}); + export const EditorTextareaScroll = style({}); export const EditorTextarea = style([ diff --git a/src/app/components/editor/Editor.tsx b/src/app/components/editor/Editor.tsx index 06fa9265e..ddab1cd81 100644 --- a/src/app/components/editor/Editor.tsx +++ b/src/app/components/editor/Editor.tsx @@ -124,7 +124,7 @@ export const CustomEditor = forwardRef(
{top} - + {before && ( {before} diff --git a/src/app/components/room-card/style.css.ts b/src/app/components/room-card/style.css.ts index b15acfe4a..c6f68838e 100644 --- a/src/app/components/room-card/style.css.ts +++ b/src/app/components/room-card/style.css.ts @@ -6,6 +6,11 @@ export const CardGrid = style({ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: config.space.S400, + '@media': { + // Cards squish/overflow below ~360px each; drop to a single column on phones + // (mirrors the 750px breakpoint the nav uses). + '(max-width: 750px)': { gridTemplateColumns: '1fr' }, + }, }); export const RoomCardBase = style([ diff --git a/src/app/components/url-preview/UrlPreview.css.tsx b/src/app/components/url-preview/UrlPreview.css.tsx index 99e6afcc5..11cba3f74 100644 --- a/src/app/components/url-preview/UrlPreview.css.tsx +++ b/src/app/components/url-preview/UrlPreview.css.tsx @@ -4,7 +4,9 @@ import { DefaultReset, color, config, toRem } from 'folds'; export const UrlPreview = style([ DefaultReset, { - width: toRem(400), + // 25rem (=400px) on desktop, but shrink to fit narrow phones so a single + // card doesn't exceed the viewport (mirrors UrlPreviewWide's min()). + width: 'min(25rem, 92vw)', minHeight: toRem(102), backgroundColor: color.SurfaceVariant.Container, color: color.SurfaceVariant.OnContainer, diff --git a/src/app/features/call/CallControls.tsx b/src/app/features/call/CallControls.tsx index 0660fde53..912f13103 100644 --- a/src/app/features/call/CallControls.tsx +++ b/src/app/features/call/CallControls.tsx @@ -35,6 +35,7 @@ import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; import { callEmbedAtom } from '../../state/callEmbed'; import { useResizeObserver } from '../../hooks/useResizeObserver'; +import { ScreenSize, useScreenSize } from '../../hooks/useScreenSize'; import { stopPropagation } from '../../utils/keyboard'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { useCallEmbedRef } from '../../hooks/useCallEmbed'; @@ -51,18 +52,25 @@ export function CallControls({ callEmbed }: CallControlsProps) { const controlRef = useRef(null); const callEmbedRef = useCallEmbedRef(); const setCallEmbed = useSetAtom(callEmbedAtom); - const [compact, setCompact] = useState(document.body.clientWidth < 500); + const screenSize = useScreenSize(); + const [narrowContainer, setNarrowContainer] = useState(document.body.clientWidth < 500); const [isFullscreen, setIsFullscreen] = useState(false); useResizeObserver( useCallback(() => { const element = controlRef.current; if (!element) return; - setCompact(element.clientWidth < 500); + setNarrowContainer(element.clientWidth < 500); }, []), useCallback(() => controlRef.current, []), ); + // Collapse to the stacked/compact layout whenever the bar's own container is + // narrow (a small desktop call window) OR the viewport is a phone. The old + // element-only `< 500` check left the ~11-control row overflowing off-screen + // in the 500–750px band (landscape phones / small tablets). + const compact = narrowContainer || screenSize === ScreenSize.Mobile; + useEffect(() => { const onFullscreenChange = () => setIsFullscreen(!!document.fullscreenElement); document.addEventListener('fullscreenchange', onFullscreenChange); diff --git a/src/app/plugins/react-custom-html-parser.tsx b/src/app/plugins/react-custom-html-parser.tsx index 1a81680ae..6890e72e3 100644 --- a/src/app/plugins/react-custom-html-parser.tsx +++ b/src/app/plugins/react-custom-html-parser.tsx @@ -455,6 +455,17 @@ export const getReactCustomHtmlParser = ( return {children}; } + if (name === 'table') { + // Sanitize allows tables, but a wide one would otherwise overflow the + // message column and the page body on narrow screens. Wrap it in a + // horizontally-scrollable container (same idea as CodeBlock's Scroll). + return ( +
+
{domToReact(children as unknown as DOMNode[], opts)}
+ + ); + } + if (name === 'blockquote') { return (