From c01d121748f35016b1a0e57d600c134db9ed56c3 Mon Sep 17 00:00:00 2001 From: glass beadian Date: Tue, 13 Oct 2026 17:41:05 +0000 Subject: [PATCH] mobile: gateway handoff for Chat and Drive below 1024px; pin Hub theme in the SSO bootstrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Panel consensus (the branding panel): don't mount full-app iframes on mobile — nested scrolling, keyboard collapse and double chrome break both Element and oCIS under the bottom tab bar. Chat and Drive render a gateway card (same-tab handoff, matrix: deep link, signed-in-as) below 1024px; the iframe stays on desktop. hub-sso.html pins mx_local_settings.theme to custom-OccupyHub Dark once (version-flagged) — a device's earlier picker choice would otherwise beat config.json's default_theme. --- apps/web/src/app/(main)/chat/page.tsx | 35 ++++++++++++++++++++ apps/web/src/app/(main)/files/drive/page.tsx | 22 ++++++++++++ infra/occupymedia/edge/hub-sso/chat-sso.html | 15 +++++++++ 3 files changed, 72 insertions(+) diff --git a/apps/web/src/app/(main)/chat/page.tsx b/apps/web/src/app/(main)/chat/page.tsx index 8373bbb..0431e9e 100644 --- a/apps/web/src/app/(main)/chat/page.tsx +++ b/apps/web/src/app/(main)/chat/page.tsx @@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { EmbeddedApp } from "../../../components/embed/embedded-app"; import { api, errorMessage } from "../../../lib/api-client"; import { useApiResource } from "../../../lib/use-api"; +import { useIsMobile } from "../../../hooks/use-is-mobile"; /** * In-app Chat: Element Web, full height inside the Hub shell, signed in as the @@ -59,6 +60,7 @@ function originOf(url: string): string | null { export default function ChatPage(): React.ReactElement { const identity = useApiResource("/matrix/identity", identityView, null); + const isMobile = useIsMobile(); const frameRef = useRef(null); const mints = useRef(0); const tokenSent = useRef(false); @@ -127,6 +129,39 @@ export default function ChatPage(): React.ReactElement { ? ssoUser : undefined; + if (isMobile) { + // Mobile: a framed three-pane Element under the bottom tab bar breaks + // (nested scrolling, keyboard collapse, double chrome) — hand off to the + // standalone app instead (panel consensus: gateway pattern). + return ( +
+

Newsroom chat

+

+ Chat runs best as its own app on mobile — the full three-pane interface gets cramped inside the Hub frame. +

+
+ + Open chat ↗ + + {ssoUser !== null && ( + + Open in a Matrix app + + )} +
+

+ {ssoUser !== null ? `Signed in as ${ssoUser}` : "Sign in on desktop to link your account."} +

+
+ ); + } + return ( session; export default function DrivePage(): React.ReactElement { const { t } = useFilesLang(); const drive = useApiResource("/drive/session", driveView, null); + const isMobile = useIsMobile(); const driveUrl = drive.source === "sample" ? FALLBACK_DRIVE_URL : drive.data?.driveUrl ?? null; const notConfigured = drive.source === "live" && drive.error === null && driveUrl === null; @@ -43,6 +45,26 @@ export default function DrivePage(): React.ReactElement { es: "Inicia sesión una vez dentro de la unidad y quedará abierta. Los archivos de los colectivos están en la pestaña Biblioteca.", }); + if (isMobile) { + // Mobile: the oCIS file browser under the bottom tab bar breaks (nested + // scrolling, double chrome) — hand off to the standalone drive instead + // (panel consensus: gateway pattern). The Library tab is fully native. + return ( +
+

{t({ en: "Drive", es: "Unidad" })}

+

{error ?? notice}

+ +
+ ); + } + return (