Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion packages/app/src/components/settings-v2/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { useUpdaterAction } from "../updater-action"
import { useSettings } from "@/context/settings"
import { useSettings, type NavigationMode } from "@/context/settings"
import { ExternalLink } from "../external-link"
import { SettingsListV2 } from "./parts/list"
import { SettingsRowV2 } from "./parts/row"
Expand Down Expand Up @@ -119,6 +119,59 @@ const ShellSetting: Component<{ controller: ShellSettingsController }> = (props)
)
}

const navigationModeOptions: NavigationMode[] = ["tabs", "sidebar"]
const sidebarSessionDayOptions = [0, 1, 3, 7, 30]

const NavigationModeSetting: Component = () => {
const language = useLanguage()
const settings = useSettings()
return (
<SettingsRowV2
title={language.t("settings.shortcuts.group.navigation")}
description={language.t("settings.general.row.navigation.description")}
>
<SelectV2
appearance="inline"
data-action="settings-navigation-mode"
options={navigationModeOptions}
current={navigationModeOptions.find((option) => option === settings.general.navigationMode())}
placement="bottom-end"
gutter={6}
label={(option) =>
option === "tabs"
? language.t("settings.general.row.navigation.tabs")
: language.t("settings.general.row.navigation.sidebar")
}
onSelect={(option) => option && settings.general.setNavigationMode(option)}
/>
</SettingsRowV2>
)
}

const SidebarSessionDaysSetting: Component = () => {
const language = useLanguage()
const settings = useSettings()
return (
<SettingsRowV2
title={language.t("settings.general.row.sidebarSessions.title")}
description={language.t("settings.general.row.sidebarSessions.description")}
>
<SelectV2
appearance="inline"
data-action="settings-sidebar-session-days"
options={sidebarSessionDayOptions}
current={sidebarSessionDayOptions.find((option) => option === settings.general.sidebarSessionDays())}
placement="bottom-end"
gutter={6}
label={(option) =>
option ? String(option) : language.t("settings.general.row.sidebarSessions.always")
}
onSelect={(option) => option !== null && settings.general.setSidebarSessionDays(option)}
/>
</SettingsRowV2>
)
}

const AppearanceSection: Component<{ controller: AppearanceSettingsController }> = (props) => {
const language = useLanguage()
return (
Expand Down Expand Up @@ -333,6 +386,13 @@ export const SettingsGeneralV2: Component<{

<ShellSetting controller={shell} />

<Show when={settings.general.newLayoutDesigns()}>
<NavigationModeSetting />
<Show when={settings.general.navigationMode() === "sidebar"}>
<SidebarSessionDaysSetting />
</Show>
</Show>

<SettingsRowV2
title={language.t("settings.general.row.reasoningSummaries.title")}
description={language.t("settings.general.row.reasoningSummaries.description")}
Expand Down
72 changes: 39 additions & 33 deletions packages/app/src/components/titlebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
const params = useParams()
const useV2Titlebar = createMemo(() => settings.general.newLayoutDesigns())
const mobile = createMediaQuery("(max-width: 767px)")
// Sidebar navigation mode replaces the tab strip on desktop widths; mobile keeps tabs.
const sidebarNavigation = createMemo(
() => useV2Titlebar() && !mobile() && settings.general.navigationMode() === "sidebar",
)
const bottom = createMemo(() => useV2Titlebar() && mobile() && settings.general.mobileTitlebarPosition() === "bottom")

const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
Expand Down Expand Up @@ -395,40 +399,42 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
/>
</TooltipV2>

<TitlebarTabStrip
tabs={tabsStore}
currentTab={currentTab}
forceTruncate={tabsAreOverflowing()}
onOverflowChange={setTabsAreOverflowing}
onNavigate={(tab, el) => {
tabs.select(tab)
el?.scrollIntoView({ behavior: "instant" })
}}
onClose={(tab) => {
const index = tabsStore.findIndex((item) => tabKey(item) === tabKey(tab))
if (index !== -1) tabsStoreActions.closeTab(index)
}}
onReorder={(keys) => tabsStoreActions.reorder(keys)}
/>
<TooltipV2
placement="bottom"
value={
<>
{language.t("command.session.new")}
<KeybindV2 keys={newTabTooltipKeybind(command)} variant="neutral" />
</>
}
>
<IconButtonV2
type="button"
variant="ghost-muted"
size="large"
class="shrink-0"
icon={<IconV2 name="plus" />}
onClick={openNewTab}
aria-label={language.t("command.session.new")}
<Show when={!sidebarNavigation()}>
<TitlebarTabStrip
tabs={tabsStore}
currentTab={currentTab}
forceTruncate={tabsAreOverflowing()}
onOverflowChange={setTabsAreOverflowing}
onNavigate={(tab, el) => {
tabs.select(tab)
el?.scrollIntoView({ behavior: "instant" })
}}
onClose={(tab) => {
const index = tabsStore.findIndex((item) => tabKey(item) === tabKey(tab))
if (index !== -1) tabsStoreActions.closeTab(index)
}}
onReorder={(keys) => tabsStoreActions.reorder(keys)}
/>
</TooltipV2>
<TooltipV2
placement="bottom"
value={
<>
{language.t("command.session.new")}
<KeybindV2 keys={newTabTooltipKeybind(command)} variant="neutral" />
</>
}
>
<IconButtonV2
type="button"
variant="ghost-muted"
size="large"
class="shrink-0"
icon={<IconV2 name="plus" />}
onClick={openNewTab}
aria-label={language.t("command.session.new")}
/>
</TooltipV2>
</Show>
<div class="flex-1" />
<TitlebarV2Right state={v2RightState()} />
</div>
Expand Down
17 changes: 17 additions & 0 deletions packages/app/src/context/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface SoundSettings {
errors: string
}

export type NavigationMode = "tabs" | "sidebar"

export interface Settings {
general: {
autoSave: boolean
Expand All @@ -34,6 +36,8 @@ export interface Settings {
editToolPartsExpanded: boolean
showCustomAgents: boolean
mobileTitlebarPosition: "top" | "bottom"
navigationMode?: NavigationMode
sidebarSessionDays?: number
newLayoutDesigns?: boolean
layoutTransitionEligible?: boolean
agentVisibilityInitialized?: boolean
Expand Down Expand Up @@ -195,6 +199,8 @@ const defaultSettings: Settings = {
editToolPartsExpanded: false,
showCustomAgents: false,
mobileTitlebarPosition: "top",
navigationMode: "tabs",
sidebarSessionDays: 3,
},
appearance: {
fontSize: 14,
Expand Down Expand Up @@ -428,6 +434,17 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setMobileTitlebarPosition(value: "top" | "bottom") {
setStore("general", "mobileTitlebarPosition", value)
},
navigationMode: withFallback(() => store.general?.navigationMode, defaultSettings.general.navigationMode),
setNavigationMode(value: NavigationMode) {
setStore("general", "navigationMode", value)
},
sidebarSessionDays: withFallback(
() => store.general?.sidebarSessionDays,
defaultSettings.general.sidebarSessionDays,
),
setSidebarSessionDays(value: number) {
setStore("general", "sidebarSessionDays", value)
},
newLayoutDesigns,
setNewLayoutDesigns(value: boolean) {
const next = oldInterfaceRetired() ? true : value
Expand Down
22 changes: 22 additions & 0 deletions packages/app/src/context/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
import { createStore, produce } from "solid-js/store"
import { Persist, persisted, removePersisted, draftPersistedKeys } from "@/utils/persist"
import { ServerConnection, useServer } from "./server"
import { useSettings } from "./settings"
import { createMediaQuery } from "@solid-primitives/media"
import { createEffect, getOwner, onCleanup, startTransition } from "solid-js"
import { useLocation, useNavigate, useParams } from "@solidjs/router"
import { usePlatform } from "./platform"
Expand Down Expand Up @@ -73,6 +75,26 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const location = useLocation()
const memory = createTabMemory(getOwner())

const settings = useSettings()
const mobile = createMediaQuery("(max-width: 767px)")
let pruned = false
createEffect(() => {
if (pruned || !ready()) return
pruned = true
// The tab-strip paradigm reopens and prefetches every persisted tab at startup; sidebar
// navigation only needs the tab you were actually on, so drop the rest once on launch.
if (settings.general.navigationMode() !== "sidebar" || mobile()) return
const sessionID = location.pathname.match(/\/session\/([^/]+)$/)?.[1]
const draftID = new URLSearchParams(location.search).get("draftId")
setStore((tabs) =>
tabs.filter(
(tab) =>
(tab.type === "session" && tab.sessionId === sessionID) ||
(tab.type === "draft" && tab.draftID === draftID),
),
)
})

const closing = new Set<string>()
let recentWrite = 0
let recentValue: string | undefined
Expand Down
15 changes: 15 additions & 0 deletions packages/app/src/i18n/am.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,15 @@ export const dict = {
"sidebar.project.clearNotifications": "ማሳወቂያዎችን አጽዳ",
"sidebar.empty.title": "ምንም ክፍት ፕሮጀክቶች የሉም",
"sidebar.empty.description": "ለመጀመር ፕሮጀክት ይክፈቱ",
"sidebar.settled": "የተረጉ",
"sidebar.settle": "ርን አጋ",
"sidebar.unsettle": "ርን መልስ",
"sidebar.thread.new": "አዲስ ክር",
"sidebar.thread.pin": "ክርን ሰካ",
"sidebar.thread.unpin": "ኪውን አውልቅ",
"sidebar.status.working": "በስራ ላይ",
"sidebar.status.done": "ተጠናቀቀ",
"sidebar.status.attention": "ግቤት ያስልጋል",
"debugBar.ariaLabel": "የልማት አፈጻጸም ምርመራዎች",
"debugBar.na": "n/a",
"debugBar.nav.label": "NAV",
Expand Down Expand Up @@ -906,6 +915,12 @@ export const dict = {
"settings.general.section.display": "ማሳያ",
"settings.general.row.language.title": "ቋንቋ",
"settings.general.row.language.description": "የማሳያ ቋንቋውን ለOpenCode",
"settings.general.row.navigation.description": "በርዕስ አሞሌ ውስጥ ትሮችን ወይም ቋሚ የጎን አሞሌ ይጠቀሙ",
"settings.general.row.navigation.tabs": "ትሮች",
"settings.general.row.navigation.sidebar": "የጎን አሞሌ",
"settings.general.row.sidebarSessions.title": "ንቁ ያልሆኑ ክሮችን የማረጋት ጊዜ",
"settings.general.row.sidebarSessions.description": "ንቁ ያልሆኑ ክሮች ወደ የተረጉ ሄዳ። በስራ ላይ ያሉ ክሮች ይታያሉ።",
"settings.general.row.sidebarSessions.always": "ሁልጊዜ አሳይ",
"settings.general.row.shell.title": "ተርሚናል ሼል",
"settings.general.row.shell.description": "ሼል በተርሚናል እና ወኪል መሳሪያዎች ጥቅም ላይ የዋለ",
"settings.general.row.shell.autoDefault": "ራስ-ሰር (ነባሪ)",
Expand Down
15 changes: 15 additions & 0 deletions packages/app/src/i18n/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,15 @@ export const dict = {
"sidebar.project.clearNotifications": "مسح الإشعارات",
"sidebar.empty.title": "لا توجد مشاريع مفتوحة",
"sidebar.empty.description": "افتح مشروعًا للبدء",
"sidebar.settled": "المنتهية",
"sidebar.settle": "إنهاء المحادثة",
"sidebar.unsettle": "إعادة المحادثة",
"sidebar.thread.new": "محادثة جديدة",
"sidebar.thread.pin": "تثبيت المحادثة",
"sidebar.thread.unpin": "إلغاء تثبيت المحادثة",
"sidebar.status.working": "يعمل",
"sidebar.status.done": "تم",
"sidebar.status.attention": "يحتاج إدخال",
"app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "سطح المكتب",
"settings.section.server": "الخادم",
Expand All @@ -844,6 +853,12 @@ export const dict = {
"settings.general.section.display": "العرض",
"settings.general.row.language.title": "اللغة",
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
"settings.general.row.navigation.description": "استخدم علامات التبويب في شريط العنوان أو الشريط الجانبي الثابت",
"settings.general.row.navigation.tabs": "علامات التبويب",
"settings.general.row.navigation.sidebar": "الشريط الجانبي",
"settings.general.row.sidebarSessions.title": "إنهاء المحادثات الخاملة بعد",
"settings.general.row.sidebarSessions.description": "تنتقل المحادثات الخاملة إلى المنتهية. المحادثات العاملة تبقى ظاهرة.",
"settings.general.row.sidebarSessions.always": "الإظهار دائمًا",
"settings.general.row.shell.title": "Shell المحطة الطرفية",
"settings.general.row.shell.description":
"اختر shell المستخدم في المحطة الطرفية. تُستخدم واجهات shell المتوافقة أيضًا في استدعاءات أدوات الوكيل.",
Expand Down
15 changes: 15 additions & 0 deletions packages/app/src/i18n/az.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,15 @@ export const dict = {
"sidebar.project.clearNotifications": "Bildirişləri təmizlə",
"sidebar.empty.title": "Heç bir layihə açılmayıb",
"sidebar.empty.description": "Başlamaq üçün bir layihə açın",
"sidebar.settled": "Bağlanan",
"sidebar.settle": "Mövzunu bağla",
"sidebar.unsettle": "Mövzunu yenidən aç",
"sidebar.thread.new": "Yeni mövzu",
"sidebar.thread.pin": "Mövzunu bərkid",
"sidebar.thread.unpin": "Bərkitməni ləğv et",
"sidebar.status.working": "İşləyir",
"sidebar.status.done": "Hazır",
"sidebar.status.attention": "Giriş lazımdır",
"debugBar.ariaLabel": "Tərtibatçı performans diaqnostikası",
"debugBar.na": "yox",
"debugBar.nav.label": "NAV",
Expand Down Expand Up @@ -934,6 +943,12 @@ export const dict = {
"settings.general.section.display": "Ekran",
"settings.general.row.language.title": "Dil",
"settings.general.row.language.description": "OpenCode üçün ekran dilini dəyişdirin",
"settings.general.row.navigation.description": "Başlıq panelində tablar və ya daimi yan panel istifadə et",
"settings.general.row.navigation.tabs": "Tablar",
"settings.general.row.navigation.sidebar": "Yan panel",
"settings.general.row.sidebarSessions.title": "Boş mövzuları bağlama müddəti",
"settings.general.row.sidebarSessions.description": "Boş mövzular bağlananlara daşınır. İşləyən mövzular görünür qalır.",
"settings.general.row.sidebarSessions.always": "Həmişə göstər",
"settings.general.row.shell.title": "Terminal qabığı",
"settings.general.row.shell.description": "Terminal və agent alətləri tərəfindən istifadə edilən qabıq",
"settings.general.row.shell.autoDefault": "Avtomatik (Standart)",
Expand Down
15 changes: 15 additions & 0 deletions packages/app/src/i18n/bg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,15 @@ export const dict = {
"sidebar.project.clearNotifications": "Изчистване на известията",
"sidebar.empty.title": "Няма отворени проекти",
"sidebar.empty.description": "Отворете проект, за да започнете",
"sidebar.settled": "Отложени",
"sidebar.settle": "Отложи нишка",
"sidebar.unsettle": "Върни нишка",
"sidebar.thread.new": "Нова нишка",
"sidebar.thread.pin": "Закачи нишка",
"sidebar.thread.unpin": "Откачи нишка",
"sidebar.status.working": "В процес",
"sidebar.status.done": "Готово",
"sidebar.status.attention": "Нужен е вход",
"debugBar.ariaLabel": "Диагностика на ефективността на разработката",
"debugBar.na": "няма",
"debugBar.nav.label": "NAV",
Expand Down Expand Up @@ -931,6 +940,12 @@ export const dict = {
"settings.general.section.display": "Дисплей",
"settings.general.row.language.title": "език",
"settings.general.row.language.description": "Променете езика на дисплея за OpenCode",
"settings.general.row.navigation.description": "Използвайте раздели в заглавната лента или постоянна странична лента",
"settings.general.row.navigation.tabs": "Раздели",
"settings.general.row.navigation.sidebar": "Странична лента",
"settings.general.row.sidebarSessions.title": "Отложи неактивни нишки след",
"settings.general.row.sidebarSessions.description": "Неактивните нишки отиват в отложените. Активните нишки остават видими.",
"settings.general.row.sidebarSessions.always": "Винаги показвай",
"settings.general.row.shell.title": "Терминал shell",
"settings.general.row.shell.description": "Shell използвани от инструментите на терминала и агента",
"settings.general.row.shell.autoDefault": "Автоматично (по подразбиране)",
Expand Down
Loading
Loading