diff --git a/interface/src/components/MobileTopBar.tsx b/interface/src/components/MobileTopBar.tsx new file mode 100644 index 000000000..ac47743e5 --- /dev/null +++ b/interface/src/components/MobileTopBar.tsx @@ -0,0 +1,43 @@ +import {List} from "@phosphor-icons/react"; +import clsx from "clsx"; +import type {ReactNode} from "react"; + +interface MobileTopBarProps { + title?: string; + onMenu: () => void; + leading?: ReactNode; + trailing?: ReactNode; + className?: string; +} + +export function MobileTopBar({ + title, + onMenu, + leading, + trailing, + className, +}: MobileTopBarProps) { + return ( +
+ {leading ?? ( + + )} +
+ {title} +
+ {trailing &&
{trailing}
} +
+ ); +} diff --git a/interface/src/components/OpenCodeEmbed.tsx b/interface/src/components/OpenCodeEmbed.tsx index 7af73e692..2419e25aa 100644 --- a/interface/src/components/OpenCodeEmbed.tsx +++ b/interface/src/components/OpenCodeEmbed.tsx @@ -218,6 +218,9 @@ export function OpenCodeEmbed({ // Pre-seed OpenCode layout preferences so it starts with a clean // chat-only view (sidebar, terminal, file tree, review all closed). + // `session.width` is intentionally omitted so OpenCode picks a width + // suitable for the current viewport — seeding a fixed 600px broke + // the native mobile layout at narrow widths. const layoutKey = "opencode.global.dat:layout"; if (!localStorage.getItem(layoutKey)) { localStorage.setItem( @@ -232,7 +235,6 @@ export function OpenCodeEmbed({ terminal: {height: 280, opened: false}, review: {diffStyle: "split", panelOpened: false}, fileTree: {opened: false, width: 344, tab: "changes"}, - session: {width: 600}, mobileSidebar: {opened: false}, sessionTabs: {}, sessionView: {}, @@ -261,12 +263,14 @@ export function OpenCodeEmbed({ style.textContent = cssText; shadow.appendChild(style); - // Hide the sidebar, mobile sidebar, and top bar in embedded - // mode — we only want the session/chat view. + // Hide OpenCode's desktop nav + title chrome — spacebot provides + // its own. The mobile nav (`sidebar-nav-mobile`) is intentionally + // NOT hidden: when the viewport drops below md, OpenCode's + // internal responsive layout surfaces it, and we want users to + // have a way to switch sessions inside the embed on phones. const overrides = document.createElement("style"); overrides.textContent = ` [data-component="sidebar-nav-desktop"], - [data-component="sidebar-nav-mobile"], header:has(#opencode-titlebar-center), [data-session-title] { display: none !important; } main { border: none !important; border-radius: 0 !important; } diff --git a/interface/src/components/Sidebar.tsx b/interface/src/components/Sidebar.tsx index 0f84a4712..09b7a055c 100644 --- a/interface/src/components/Sidebar.tsx +++ b/interface/src/components/Sidebar.tsx @@ -62,6 +62,8 @@ import {IS_DESKTOP, IS_MACOS} from "@/platform"; interface SidebarProps { liveStates: Record; + /** When true, drop the fixed 220px width — sidebar fills its drawer container. */ + fillWidth?: boolean; } const agentSubItems = [ @@ -185,7 +187,10 @@ function readProjectId(search: unknown): string | null { return typeof id === "string" ? id : null; } -export function Sidebar({liveStates: _liveStates}: SidebarProps) { +export function Sidebar({ + liveStates: _liveStates, + fillWidth = false, +}: SidebarProps) { const navigate = useNavigate(); const [createOpen, setCreateOpen] = useState(false); const [userExpandedAgent, setUserExpandedAgent] = useState( @@ -273,7 +278,13 @@ export function Sidebar({liveStates: _liveStates}: SidebarProps) { }; return ( -