From 45923c029349c619b973c1b5301a7066d744b177 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 18 Jun 2026 02:22:59 +0200 Subject: [PATCH 1/6] fix(app): improve mobile home layout --- packages/app/src/pages/home.tsx | 64 ++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 24c7e4c3ab..e99ba37ba5 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -339,7 +339,7 @@ function HomeDesign() { return (
-
+
+ platform.openLink("https://opencode.ai/desktop-feedback")} + language={language} + />
) @@ -453,7 +459,7 @@ function HomeProjectColumn(props: { const dialog = useDialog() const controller = useServerManagementController({ navigateOnAdd: false }) return ( -
) } diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 72625a615f..9371cdd991 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1570,40 +1570,42 @@ export default function Page() { /> ) + const mobileTabs = (compact = false) => ( + + + setStore("mobileTab", "session")} + > + {language.t("session.tab.session")} + + setStore("mobileTab", "changes")} + > + {hasReview() + ? language.t("session.review.filesChanged", { count: reviewCount() }) + : language.t("session.review.change.other")} + + + + ) + return (
{sessionSync() ?? ""}
- - - - setStore("mobileTab", "session")} - > - {language.t("session.tab.session")} - - setStore("mobileTab", "changes")} - > - {hasReview() - ? language.t("session.review.filesChanged", { count: reviewCount() }) - : language.t("session.review.change.other")} - - - - + {mobileTabs()}
+ + {mobileTabs(true)} +
@@ -1629,8 +1634,8 @@ export default function Page() { {reviewContent({ diffStyle: "unified", classes: { - root: "pb-8", - header: "px-4", + root: "pb-8 [&_[data-slot=session-review-list]]:pb-0", + header: "px-4 !h-16 !pb-4", container: "px-4", }, loadingClass: "px-4 py-4 text-text-weak", @@ -1686,7 +1691,7 @@ export default function Page() {
- {composerRegion("dock")} + {composerRegion("dock")}
diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index 5a26f17816..edb618e3d4 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -1285,7 +1285,9 @@ export function MessageTimeline(props: { "sticky top-0 z-30 bg-[linear-gradient(to_bottom,var(--background-stronger)_48px,transparent)]": true, "w-full": true, "pb-4": true, - "pl-2 pr-3 md:pl-4 md:pr-3": true, + "pr-3": true, + "pl-4": settings.general.newLayoutDesigns(), + "pl-2 md:pl-4": !settings.general.newLayoutDesigns(), "md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered, }} > diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index ebdd5d939c..a7bd5e383a 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -386,7 +386,7 @@ export const SessionReview = (props: SessionReviewProps) => { >
-
+
{(file) => { From f33cc80f4839770afd1940d10dde4a30fd8af529 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 18 Jun 2026 09:40:59 +0200 Subject: [PATCH 3/6] feat(app): add mobile bottom navigation --- packages/app/index.html | 5 +++- .../src/components/settings-v2/general.tsx | 16 ++++++++++++ packages/app/src/components/titlebar.tsx | 22 +++++++++++++--- packages/app/src/context/settings.tsx | 9 +++++++ packages/app/src/i18n/en.ts | 3 +++ packages/app/src/pages/session.tsx | 25 +++++++++++++++---- 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/packages/app/index.html b/packages/app/index.html index 8c86360af3..ad49b25532 100644 --- a/packages/app/index.html +++ b/packages/app/index.html @@ -2,7 +2,10 @@ - + OpenCode diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx index 93caa64673..8a230cae68 100644 --- a/packages/app/src/components/settings-v2/general.tsx +++ b/packages/app/src/components/settings-v2/general.tsx @@ -1,4 +1,5 @@ import { Component, Show, createMemo, createResource, onMount } from "solid-js" +import { createMediaQuery } from "@solid-primitives/media" import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { SelectV2 } from "@opencode-ai/ui/v2/select-v2" import { Switch } from "@opencode-ai/ui/v2/switch-v2" @@ -89,6 +90,7 @@ export const SettingsGeneralV2: Component = () => { const dialog = useDialog() const params = useParams() const settings = useSettings() + const mobile = createMediaQuery("(max-width: 767px)") const updater = useUpdaterAction() @@ -345,6 +347,20 @@ export const SettingsGeneralV2: Component = () => { />
+ + + +
+ settings.general.setMobileTitlebarPosition(checked ? "bottom" : "top")} + /> +
+
+
) diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 8277143204..465ee83cfa 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -36,6 +36,7 @@ import { displayName, getProjectAvatarSource, projectForSession } from "@/pages/ import { useSessionTabAvatarState } from "@/pages/layout/project-avatar-state" import { makeEventListener } from "@solid-primitives/event-listener" import { createResizeObserver } from "@solid-primitives/resize-observer" +import { createMediaQuery } from "@solid-primitives/media" import { readSessionTabsRemovedDetail, SESSION_TABS_REMOVED_EVENT } from "@/components/titlebar-session-events" import { useGlobal } from "@/context/global" import { decode64 } from "@/utils/base64" @@ -87,6 +88,10 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { const location = useLocation() const params = useParams() const useV2Titlebar = createMemo(() => settings.general.newLayoutDesigns()) + const mobile = createMediaQuery("(max-width: 767px)") + const bottom = createMemo( + () => useV2Titlebar() && mobile() && settings.general.mobileTitlebarPosition() === "bottom", + ) const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos") const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows") @@ -98,6 +103,10 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { const counterZoom = () => (windows() && titlebarZoom() < 1 ? 1 / titlebarZoom() : 1) const minHeight = () => { const height = useV2Titlebar() ? v2TitlebarHeight : legacyTitlebarHeight + if (useV2Titlebar() && mobile()) { + const inset = bottom() ? "env(safe-area-inset-bottom, 0px)" : "env(safe-area-inset-top, 0px)" + return `calc(${height}px + ${inset})` + } if (mac()) return `${height / zoom()}px` if (windows()) return `${height / Math.min(titlebarZoom(), 1)}px` return undefined @@ -235,10 +244,13 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { "shrink-0 relative flex flex-row": true, "h-9 bg-v2-background-bg-deep overflow-visible": useV2Titlebar(), "h-10 bg-background-base overflow-hidden": !useV2Titlebar(), + "order-last": bottom(), }} style={{ "min-height": minHeight(), - "padding-left": mac() ? `${84 / zoom()}px` : 0, + "padding-top": useV2Titlebar() && mobile() && !bottom() ? "env(safe-area-inset-top, 0px)" : undefined, + "padding-bottom": bottom() ? "env(safe-area-inset-bottom, 0px)" : undefined, + "padding-left": mac() && !mobile() ? `${84 / zoom()}px` : 0, width: electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined, "max-width": electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` @@ -421,10 +433,12 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { return (
diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx index 21c845d0d1..2b7520da12 100644 --- a/packages/app/src/context/settings.tsx +++ b/packages/app/src/context/settings.tsx @@ -33,6 +33,7 @@ export interface Settings { editToolPartsExpanded: boolean showSessionProgressBar: boolean showCustomAgents: boolean + mobileTitlebarPosition: "top" | "bottom" newLayoutDesigns?: boolean } appearance: { @@ -118,6 +119,7 @@ const defaultSettings: Settings = { editToolPartsExpanded: false, showSessionProgressBar: true, showCustomAgents: false, + mobileTitlebarPosition: "top", }, appearance: { fontSize: 14, @@ -248,6 +250,13 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont setShowCustomAgents(value: boolean) { setStore("general", "showCustomAgents", value) }, + mobileTitlebarPosition: withFallback( + () => store.general?.mobileTitlebarPosition, + defaultSettings.general.mobileTitlebarPosition, + ), + setMobileTitlebarPosition(value: "top" | "bottom") { + setStore("general", "mobileTitlebarPosition", value) + }, newLayoutDesigns, setNewLayoutDesigns(value: boolean) { setStore("general", "newLayoutDesigns", value) diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 1ba0c3d230..152bd0d108 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -839,6 +839,9 @@ export const dict = { "settings.general.row.showTerminal.description": "Show the terminal button in the desktop title bar", "settings.general.row.showStatus.title": "Server status", "settings.general.row.showStatus.description": "Show the server status button in the title bar", + "settings.general.row.mobileTitlebarBottom.title": "Bottom navigation", + "settings.general.row.mobileTitlebarBottom.description": + "Place the title bar and session tabs at the bottom of the screen on mobile", "settings.general.row.showCustomAgents.title": "Custom agents", "settings.general.row.showCustomAgents.description": "Show the agent picker in the composer", "settings.general.row.reasoningSummaries.title": "Show reasoning summaries", diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 9371cdd991..bae32a18a0 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1570,12 +1570,20 @@ export default function Page() { /> ) - const mobileTabs = (compact = false) => ( + const mobileTabs = (compact = false, bottom = false) => ( - + setStore("mobileTab", "session")} > @@ -1583,7 +1591,10 @@ export default function Page() { setStore("mobileTab", "changes")} > @@ -1594,6 +1605,9 @@ export default function Page() { ) + const mobileTabsBottom = createMemo( + () => !isDesktop() && settings.general.newLayoutDesigns() && settings.general.mobileTitlebarPosition() === "bottom", + ) return (
@@ -1624,7 +1638,7 @@ export default function Page() { "shadow-[var(--v2-elevation-raised)]": settings.general.newLayoutDesigns() && !!params.id, }} > - + {mobileTabs(true)}
@@ -1692,6 +1706,7 @@ export default function Page() {
{composerRegion("dock")} + {mobileTabs(true, true)}
From 27371cfb2ba27cd0af15ed880b7c06ba8285e16b Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 18 Jun 2026 09:47:53 +0200 Subject: [PATCH 4/6] fix(app): improve iOS PWA shell --- packages/app/index.html | 11 +++++++---- packages/app/public/oc-theme-preload.js | 3 ++- packages/ui/src/assets/favicon/site.webmanifest | 9 ++++++--- packages/ui/src/theme/context.tsx | 3 ++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/app/index.html b/packages/app/index.html index ad49b25532..d71535182c 100644 --- a/packages/app/index.html +++ b/packages/app/index.html @@ -1,5 +1,5 @@ - + - + + + + - + -
+
diff --git a/packages/app/public/oc-theme-preload.js b/packages/app/public/oc-theme-preload.js index 18846fceb6..11c9b39ec4 100644 --- a/packages/app/public/oc-theme-preload.js +++ b/packages/app/public/oc-theme-preload.js @@ -15,10 +15,11 @@ document.documentElement.dataset.theme = themeId document.documentElement.dataset.colorScheme = mode + document.documentElement.style.backgroundColor = isDark ? "#080808" : "#fafafa" // Update theme-color meta tag to match app color scheme var metas = document.querySelectorAll("meta[name='theme-color']") - if (metas.length > 0) metas[0].setAttribute("content", isDark ? "#131010" : "#F8F7F7") + if (metas.length > 0) metas[0].setAttribute("content", isDark ? "#080808" : "#fafafa") if (themeId === "oc-2") return diff --git a/packages/ui/src/assets/favicon/site.webmanifest b/packages/ui/src/assets/favicon/site.webmanifest index 41290e840c..5452d8dd97 100644 --- a/packages/ui/src/assets/favicon/site.webmanifest +++ b/packages/ui/src/assets/favicon/site.webmanifest @@ -1,6 +1,9 @@ { "name": "OpenCode", "short_name": "OpenCode", + "id": "/", + "start_url": "/", + "scope": "/", "icons": [ { "src": "/web-app-manifest-192x192.png", @@ -15,7 +18,7 @@ "purpose": "maskable" } ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" + "theme_color": "#080808", + "background_color": "#080808", + "display": "fullscreen" } diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx index a1505a0e39..1051345fbe 100644 --- a/packages/ui/src/theme/context.tsx +++ b/packages/ui/src/theme/context.tsx @@ -152,10 +152,11 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da ensureThemeStyleElement().textContent = fullCss document.documentElement.dataset.theme = themeId document.documentElement.dataset.colorScheme = mode + document.documentElement.style.backgroundColor = isDark ? "#080808" : "#fafafa" // Update theme-color meta tag to match light/dark mode const meta = document.querySelector('meta[name="theme-color"]') - if (meta) meta.setAttribute("content", isDark ? "#131010" : "#F8F7F7") + if (meta) meta.setAttribute("content", isDark ? "#080808" : "#fafafa") } function cacheThemeVariants(theme: DesktopTheme, themeId: string) { From c3993c602ca64a91d6b3205475e4647bbf8d31f4 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 18 Jun 2026 09:53:48 +0200 Subject: [PATCH 5/6] fix(app): tighten mobile utility UI --- packages/app/src/components/debug-bar.tsx | 2 +- packages/app/src/components/help-button.tsx | 2 +- packages/app/src/components/settings-v2/settings-v2.css | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx index 11f9f59e4e..eda025546a 100644 --- a/packages/app/src/components/debug-bar.tsx +++ b/packages/app/src/components/debug-bar.tsx @@ -363,7 +363,7 @@ export function DebugBar() { return (