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/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index db1990dfaa..a65bc6491f 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -10,6 +10,7 @@ import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" import { getFilename } from "@opencode-ai/core/util/path" import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js" import { createStore } from "solid-js/store" +import { createMediaQuery } from "@solid-primitives/media" import { Portal } from "solid-js/web" import { useCommand } from "@/context/command" import { useLanguage } from "@/context/language" @@ -158,6 +159,7 @@ export function SessionHeader() { const isV2 = settings.general.newLayoutDesigns const search = settings.visibility.search const status = settings.visibility.status + const isDesktop = createMediaQuery("(min-width: 768px)") const [exists, setExists] = createStore>>({ finder: true, @@ -236,6 +238,7 @@ export function SessionHeader() { statusLabel: language.t("status.popover.trigger"), reviewLabel: language.t("command.review.toggle"), reviewKeybind: command.keybind("review.toggle"), + reviewVisible: isDesktop(), reviewOpened: view().reviewPanel.opened(), onReviewToggle: () => view().reviewPanel.toggle(), })) @@ -518,6 +521,7 @@ type SessionHeaderV2ActionsState = { statusLabel: string reviewLabel: string reviewKeybind: string + reviewVisible: boolean reviewOpened: boolean onReviewToggle: () => void } @@ -530,20 +534,22 @@ function SessionHeaderV2Actions(props: { state: SessionHeaderV2ActionsState }) { - - } - /> - + + + } + /> + + ) } 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 3312856391..f350ba183e 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -35,6 +35,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 { ServerConnection, useServer } from "@/context/server" @@ -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()}))` @@ -426,10 +438,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/home.tsx b/packages/app/src/pages/home.tsx index e9f036993e..cbb9c71ba4 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -329,7 +329,7 @@ export function NewHome() { return (
-
+
+ platform.openLink("https://opencode.ai/desktop-feedback")} + language={language} + />
) @@ -443,7 +449,7 @@ function HomeProjectColumn(props: { const dialog = useDialog() const controller = useServerManagementController({ navigateOnAdd: false }) return ( -