From e8cd8616671643c6611d05f28fa28b11949abf15 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Mon, 2 Feb 2026 13:05:41 +0100 Subject: [PATCH 1/4] feat(app): add dynamic sidebar sorting for active projects and sessions Automatically moves projects and sessions to the top of the sidebar when Claude starts working on them. This makes it easier to track and switch between active work when managing multiple projects. - Add dynamicSort setting to server context with persistence - Track session activity via timestamps to sort active sessions first - Move project to top when any session becomes busy - Add toggle in Settings > General > Desktop section - Include translations for all 15 supported languages --- .../app/src/components/settings-general.tsx | 25 ++++++++++++++++++- packages/app/src/context/global-sync.tsx | 7 ++++++ .../src/context/global-sync/child-store.ts | 1 + .../src/context/global-sync/event-reducer.ts | 2 ++ packages/app/src/context/global-sync/types.ts | 3 +++ packages/app/src/context/server.tsx | 20 ++++++++++++++- packages/app/src/i18n/ar.ts | 6 ++++- packages/app/src/i18n/br.ts | 6 ++++- packages/app/src/i18n/da.ts | 6 ++++- packages/app/src/i18n/de.ts | 5 ++++ packages/app/src/i18n/en.ts | 5 ++++ packages/app/src/i18n/es.ts | 6 ++++- packages/app/src/i18n/fr.ts | 6 ++++- packages/app/src/i18n/ja.ts | 6 ++++- packages/app/src/i18n/ko.ts | 6 ++++- packages/app/src/i18n/no.ts | 6 ++++- packages/app/src/i18n/pl.ts | 6 ++++- packages/app/src/i18n/ru.ts | 6 ++++- packages/app/src/i18n/th.ts | 6 ++++- packages/app/src/i18n/zh.ts | 6 ++++- packages/app/src/i18n/zht.ts | 6 ++++- packages/app/src/pages/layout.tsx | 5 ++-- packages/app/src/pages/layout/helpers.ts | 16 ++++++++++-- .../app/src/pages/layout/sidebar-project.tsx | 8 ++++-- .../src/pages/layout/sidebar-workspace.tsx | 7 ++++-- 25 files changed, 159 insertions(+), 23 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index b31cfb6cc79..9b97fd754b8 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -1,4 +1,4 @@ -import { Component, createMemo, type JSX } from "solid-js" +import { Component, createMemo, Show, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { Button } from "@opencode-ai/ui/button" import { Select } from "@opencode-ai/ui/select" @@ -7,6 +7,7 @@ import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme" import { showToast } from "@opencode-ai/ui/toast" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" +import { useServer } from "@/context/server" import { useSettings, monoFontFamily } from "@/context/settings" import { playSound, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "./link" @@ -35,6 +36,7 @@ export const SettingsGeneral: Component = () => { const language = useLanguage() const platform = usePlatform() const settings = useSettings() + const server = useServer() const [store, setStore] = createStore({ checking: false, @@ -410,6 +412,27 @@ export const SettingsGeneral: Component = () => { + + {/* Desktop Section - Desktop only */} + +
+

{language.t("settings.general.section.desktop")}

+ +
+ +
+ server.dynamicSort.set(checked)} + /> +
+
+
+
+
) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index e2bf4498074..30871c0e2ac 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -35,6 +35,7 @@ import { bootstrapDirectory, bootstrapGlobal } from "./global-sync/bootstrap" import { sanitizeProject } from "./global-sync/utils" import type { ProjectMeta } from "./global-sync/types" import { SESSION_RECENT_LIMIT } from "./global-sync/types" +import { useServer } from "./server" type GlobalStore = { ready: boolean @@ -51,6 +52,7 @@ function createGlobalSync() { const globalSDK = useGlobalSDK() const platform = usePlatform() const language = useLanguage() + const server = useServer() const owner = getOwner() if (!owner) throw new Error("GlobalSync must be created within owner") @@ -280,6 +282,11 @@ function createGlobalSync() { .lsp.status() .then((x) => setStore("lsp", x.data ?? [])) }, + onSessionBusy: (sessionID) => { + if (!server.dynamicSort.enabled()) return + server.projects.bringToTop(directory) + setStore("session_active", sessionID, Date.now()) + }, }) }) diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index 2feb7fe0884..d0091d3050a 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -171,6 +171,7 @@ export function createChildStoreManager(input: { session: [], sessionTotal: 0, session_status: {}, + session_active: {}, session_diff: {}, todo: {}, permission: {}, diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index c658d82c8b7..d536120c9a9 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -77,6 +77,7 @@ export function applyDirectoryEvent(input: { directory: string loadLsp: () => void vcsCache?: VcsCache + onSessionBusy?: (sessionID: string) => void }) { const event = input.event switch (event.type) { @@ -154,6 +155,7 @@ export function applyDirectoryEvent(input: { case "session.status": { const props = event.properties as { sessionID: string; status: SessionStatus } input.setStore("session_status", props.sessionID, reconcile(props.status)) + if (props.status.type === "busy") input.onSessionBusy?.(props.sessionID) break } case "message.updated": { diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index ade0b973a2a..0e90c4b6cfc 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -46,6 +46,9 @@ export type State = { session_status: { [sessionID: string]: SessionStatus } + session_active: { + [sessionID: string]: number + } session_diff: { [sessionID: string]: FileDiff[] } diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index 72693e6ef64..4c9be6abde8 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -5,7 +5,7 @@ import { usePlatform } from "@/context/platform" import { Persist, persisted } from "@/utils/persist" import { checkServerHealth } from "@/utils/server-health" -type StoredProject = { worktree: string; expanded: boolean } +type StoredProject = { worktree: string; expanded: boolean; lastUsed?: number } export function normalizeServerUrl(input: string) { const trimmed = input.trim() @@ -37,6 +37,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( list: [] as string[], projects: {} as Record, lastProject: {} as Record, + dynamicSort: true, }), ) @@ -202,6 +203,23 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( if (!key) return setStore("lastProject", key, directory) }, + bringToTop(directory: string) { + if (!store.dynamicSort) return + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const index = current.findIndex((x) => x.worktree === directory) + if (index <= 0) return + const project = { ...current[index], lastUsed: Date.now() } + const rest = current.filter((_, i) => i !== index) + setStore("projects", key, [project, ...rest]) + }, + }, + dynamicSort: { + enabled: createMemo(() => store.dynamicSort ?? true), + set(value: boolean) { + setStore("dynamicSort", value) + }, }, } }, diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 3778adcd679..b71bf668f73 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -511,7 +511,11 @@ export const dict = { "settings.general.section.appearance": "المظهر", "settings.general.section.notifications": "إشعارات النظام", "settings.general.section.updates": "التحديثات", - "settings.general.section.sounds": "المؤثرات الصوتية", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "اللغة", "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 74bfd8707c8..31f03e7fae9 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -515,7 +515,11 @@ export const dict = { "settings.general.section.appearance": "Aparência", "settings.general.section.notifications": "Notificações do sistema", "settings.general.section.updates": "Atualizações", - "settings.general.section.sounds": "Efeitos sonoros", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 7242fb5849f..ea8cffc6cb8 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -515,7 +515,11 @@ export const dict = { "settings.general.section.appearance": "Udseende", "settings.general.section.notifications": "Systemmeddelelser", "settings.general.section.updates": "Opdateringer", - "settings.general.section.sounds": "Lydeffekter", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Sprog", "settings.general.row.language.description": "Ændr visningssproget for OpenCode", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index bd8acae5e8f..b6c3980f957 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -560,6 +560,11 @@ export const dict = { "settings.general.section.notifications": "Systembenachrichtigungen", "settings.general.section.updates": "Updates", "settings.general.section.sounds": "Soundeffekte", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamische Seitenleisten-Sortierung", + "settings.general.desktop.dynamicSort.description": + "Aktive Projekte und Sitzungen automatisch nach oben verschieben, wenn Claude daran arbeitet", "settings.general.row.language.title": "Sprache", "settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 8fba6861b0b..c5f24767bd4 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -588,6 +588,11 @@ export const dict = { "settings.general.section.notifications": "System notifications", "settings.general.section.updates": "Updates", "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Language", "settings.general.row.language.description": "Change the display language for OpenCode", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index f9b11ade870..23662d7e000 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -518,7 +518,11 @@ export const dict = { "settings.general.section.appearance": "Apariencia", "settings.general.section.notifications": "Notificaciones del sistema", "settings.general.section.updates": "Actualizaciones", - "settings.general.section.sounds": "Efectos de sonido", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 0cc81e5ea7a..e8aab2fa242 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -525,7 +525,11 @@ export const dict = { "settings.general.section.appearance": "Apparence", "settings.general.section.notifications": "Notifications système", "settings.general.section.updates": "Mises à jour", - "settings.general.section.sounds": "Effets sonores", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Langue", "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 337e1b0d349..1a2196e91e0 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -510,7 +510,11 @@ export const dict = { "settings.general.section.appearance": "外観", "settings.general.section.notifications": "システム通知", "settings.general.section.updates": "アップデート", - "settings.general.section.sounds": "効果音", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "言語", "settings.general.row.language.description": "OpenCodeの表示言語を変更します", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 283bb6f3bdc..7a17615d1a3 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -516,7 +516,11 @@ export const dict = { "settings.general.section.appearance": "모양", "settings.general.section.notifications": "시스템 알림", "settings.general.section.updates": "업데이트", - "settings.general.section.sounds": "효과음", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "언어", "settings.general.row.language.description": "OpenCode 표시 언어 변경", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index bbffd0083d1..88787330252 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -518,7 +518,11 @@ export const dict = { "settings.general.section.appearance": "Utseende", "settings.general.section.notifications": "Systemvarsler", "settings.general.section.updates": "Oppdateringer", - "settings.general.section.sounds": "Lydeffekter", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Språk", "settings.general.row.language.description": "Endre visningsspråket for OpenCode", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 2d36ca8c180..16eb1b89fb6 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -517,7 +517,11 @@ export const dict = { "settings.general.section.appearance": "Wygląd", "settings.general.section.notifications": "Powiadomienia systemowe", "settings.general.section.updates": "Aktualizacje", - "settings.general.section.sounds": "Efekty dźwiękowe", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Język", "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 18b0ba5f47d..626d57ce71f 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -520,7 +520,11 @@ export const dict = { "settings.general.section.appearance": "Внешний вид", "settings.general.section.notifications": "Системные уведомления", "settings.general.section.updates": "Обновления", - "settings.general.section.sounds": "Звуковые эффекты", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Язык", "settings.general.row.language.description": "Изменить язык отображения OpenCode", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index d48a7cea665..1103b67a691 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -519,7 +519,11 @@ export const dict = { "settings.general.section.appearance": "รูปลักษณ์", "settings.general.section.notifications": "การแจ้งเตือนระบบ", "settings.general.section.updates": "การอัปเดต", - "settings.general.section.sounds": "เสียงเอฟเฟกต์", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "ภาษา", "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 070064d1c41..903bb54ec4e 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -551,7 +551,11 @@ export const dict = { "settings.general.section.appearance": "外观", "settings.general.section.notifications": "系统通知", "settings.general.section.updates": "更新", - "settings.general.section.sounds": "音效", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "语言", "settings.general.row.language.description": "更改 OpenCode 的显示语言", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 39dcd92e276..e64d6779e5b 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -548,7 +548,11 @@ export const dict = { "settings.general.section.appearance": "外觀", "settings.general.section.notifications": "系統通知", "settings.general.section.updates": "更新", - "settings.general.section.sounds": "音效", + "settings.general.section.sounds": "Sound effects", + "settings.general.section.desktop": "Desktop", + + "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", + "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "語言", "settings.general.row.language.description": "變更 OpenCode 的顯示語言", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 59adef4694a..8133c0fcb61 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -576,6 +576,7 @@ export default function Layout(props: ParentProps) { const project = currentProject() if (!project) return [] as Session[] const now = Date.now() + const dynamic = server.dynamicSort.enabled() if (workspaceSetting()) { const dirs = workspaceIds(project) const activeDir = currentDir() @@ -585,13 +586,13 @@ export default function Layout(props: ParentProps) { const active = dir === activeDir if (!expanded && !active) continue const [dirStore] = globalSync.child(dir, { bootstrap: true }) - const dirSessions = sortedRootSessions(dirStore, now) + const dirSessions = sortedRootSessions(dirStore, now, dynamic) result.push(...dirSessions) } return result } const [projectStore] = globalSync.child(project.worktree) - return sortedRootSessions(projectStore, now) + return sortedRootSessions(projectStore, now, dynamic) }) type PrefetchQueue = { diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index 6ecccb95cf8..4feea935a8c 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -25,8 +25,20 @@ export function sortSessions(now: number) { export const isRootVisibleSession = (session: Session, directory: string) => workspaceKey(session.directory) === workspaceKey(directory) && !session.parentID && !session.time?.archived -export const sortedRootSessions = (store: { session: Session[]; path: { directory: string } }, now: number) => - store.session.filter((session) => isRootVisibleSession(session, store.path.directory)).toSorted(sortSessions(now)) +export const sortedRootSessions = ( + store: { session: Session[]; path: { directory: string }; session_active?: { [sessionID: string]: number } }, + now: number, + dynamicSort?: boolean, +) => { + const filtered = store.session.filter((session) => isRootVisibleSession(session, store.path.directory)) + if (!dynamicSort) return filtered.toSorted(sortSessions(now)) + return filtered.toSorted((a, b) => { + const aActive = store.session_active?.[a.id] ?? 0 + const bActive = store.session_active?.[b.id] ?? 0 + if (aActive !== bActive) return bActive - aActive + return (b.time.updated ?? b.time.created) - (a.time.updated ?? a.time.created) + }) +} export const childMapByParent = (sessions: Session[]) => { const map = new Map() diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index c91dc987d80..9dda3054b74 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -10,6 +10,7 @@ import { createSortable } from "@thisbeyond/solid-dnd" import { type LocalProject } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" +import { useServer } from "@/context/server" import { ProjectIcon, SessionItem, type SessionItemProps } from "./sidebar-items" import { childMapByParent, displayName, sortedRootSessions } from "./helpers" import { projectSelected, projectTileActive } from "./sidebar-project-helpers" @@ -58,6 +59,7 @@ export const SortableProject = (props: { }): JSX.Element => { const globalSync = useGlobalSync() const language = useLanguage() + const server = useServer() const sortable = createSortable(props.project.worktree) const selected = createMemo(() => projectSelected(props.ctx.currentDir(), props.project.worktree, props.project.sandboxes), @@ -95,11 +97,13 @@ export const SortableProject = (props: { } const projectStore = createMemo(() => globalSync.child(props.project.worktree, { bootstrap: false })[0]) - const projectSessions = createMemo(() => sortedRootSessions(projectStore(), Date.now()).slice(0, 2)) + const projectSessions = createMemo(() => + sortedRootSessions(projectStore(), Date.now(), server.dynamicSort.enabled()).slice(0, 2), + ) const projectChildren = createMemo(() => childMapByParent(projectStore().session)) const workspaceSessions = (directory: string) => { const [data] = globalSync.child(directory, { bootstrap: false }) - return sortedRootSessions(data, Date.now()).slice(0, 2) + return sortedRootSessions(data, Date.now(), server.dynamicSort.enabled()).slice(0, 2) } const workspaceChildren = (directory: string) => { const [data] = globalSync.child(directory, { bootstrap: false }) diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index 44f4b35307b..26290ca3b24 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -15,6 +15,7 @@ import { type Session } from "@opencode-ai/sdk/v2/client" import { type LocalProject } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" +import { useServer } from "@/context/server" import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items" import { childMapByParent, sortedRootSessions } from "./helpers" @@ -91,6 +92,7 @@ export const SortableWorkspace = (props: { const params = useParams() const globalSync = useGlobalSync() const language = useLanguage() + const server = useServer() const sortable = createSortable(props.directory) const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory, { bootstrap: false }) const [menu, setMenu] = createStore({ @@ -98,7 +100,7 @@ export const SortableWorkspace = (props: { pendingRename: false, }) const slug = createMemo(() => base64Encode(props.directory)) - const sessions = createMemo(() => sortedRootSessions(workspaceStore, Date.now())) + const sessions = createMemo(() => sortedRootSessions(workspaceStore, Date.now(), server.dynamicSort.enabled())) const children = createMemo(() => childMapByParent(workspaceStore.session)) const local = createMemo(() => props.directory === props.project.worktree) const active = createMemo(() => props.ctx.currentDir() === props.directory) @@ -347,12 +349,13 @@ export const LocalWorkspace = (props: { }): JSX.Element => { const globalSync = useGlobalSync() const language = useLanguage() + const server = useServer() const workspace = createMemo(() => { const [store, setStore] = globalSync.child(props.project.worktree) return { store, setStore } }) const slug = createMemo(() => base64Encode(props.project.worktree)) - const sessions = createMemo(() => sortedRootSessions(workspace().store, Date.now())) + const sessions = createMemo(() => sortedRootSessions(workspace().store, Date.now(), server.dynamicSort.enabled())) const children = createMemo(() => childMapByParent(workspace().store.session)) const booted = createMemo((prev) => prev || workspace().store.status === "complete", false) const loading = createMemo(() => !booted() && sessions().length === 0) From 2e7bdaef47190baf187e92b8b9fe9b5b0c4d7206 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Mon, 2 Feb 2026 17:31:02 +0100 Subject: [PATCH 2/4] feat(app): add pin/unpin projects to sidebar Pinned projects stay at the top of the sidebar and are not displaced by the dynamic sorting when Claude becomes active. Adds pin/unpin toggle to the project dropdown menu with i18n for all 15 languages. --- packages/app/src/context/server.tsx | 33 +++++++++++++++++++++++++++-- packages/app/src/i18n/ar.ts | 5 ++++- packages/app/src/i18n/br.ts | 5 ++++- packages/app/src/i18n/da.ts | 5 ++++- packages/app/src/i18n/de.ts | 2 ++ packages/app/src/i18n/en.ts | 2 ++ packages/app/src/i18n/es.ts | 5 ++++- packages/app/src/i18n/fr.ts | 5 ++++- packages/app/src/i18n/ja.ts | 5 ++++- packages/app/src/i18n/ko.ts | 5 ++++- packages/app/src/i18n/no.ts | 5 ++++- packages/app/src/i18n/pl.ts | 5 ++++- packages/app/src/i18n/ru.ts | 5 ++++- packages/app/src/i18n/th.ts | 5 ++++- packages/app/src/i18n/zh.ts | 5 ++++- packages/app/src/i18n/zht.ts | 5 ++++- packages/app/src/pages/layout.tsx | 15 +++++++++++++ 17 files changed, 102 insertions(+), 15 deletions(-) diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index 4c9be6abde8..096deea90fe 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -5,7 +5,7 @@ import { usePlatform } from "@/context/platform" import { Persist, persisted } from "@/utils/persist" import { checkServerHealth } from "@/utils/server-health" -type StoredProject = { worktree: string; expanded: boolean; lastUsed?: number } +type StoredProject = { worktree: string; expanded: boolean; lastUsed?: number; pinned?: boolean } export function normalizeServerUrl(input: string) { const trimmed = input.trim() @@ -210,9 +210,38 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( const current = store.projects[key] ?? [] const index = current.findIndex((x) => x.worktree === directory) if (index <= 0) return + if (current[index].pinned) return const project = { ...current[index], lastUsed: Date.now() } const rest = current.filter((_, i) => i !== index) - setStore("projects", key, [project, ...rest]) + const insertAt = rest.findIndex((x) => !x.pinned) + const result = [...rest] + result.splice(insertAt === -1 ? rest.length : insertAt, 0, project) + setStore("projects", key, result) + }, + pin(directory: string) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const index = current.findIndex((x) => x.worktree === directory) + if (index === -1) return + const project = { ...current[index], pinned: true } + const rest = current.filter((_, i) => i !== index) + const lastPinned = rest.findLastIndex((x) => x.pinned) + const result = [...rest] + result.splice(lastPinned + 1, 0, project) + setStore("projects", key, result) + }, + unpin(directory: string) { + const key = origin() + if (!key) return + const current = store.projects[key] ?? [] + const index = current.findIndex((x) => x.worktree === directory) + if (index === -1) return + setStore("projects", key, index, "pinned", false) + }, + isPinned(directory: string) { + const current = store.projects[origin()] ?? [] + return current.find((x) => x.worktree === directory)?.pinned ?? false }, }, dynamicSort: { diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index b71bf668f73..3f5fd936131 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -494,6 +494,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "المشاريع والجلسات", "sidebar.settings": "الإعدادات", "sidebar.help": "مساعدة", + "sidebar.project.pin": "تثبيت في الأعلى", + "sidebar.project.unpin": "إلغاء التثبيت", "sidebar.workspaces.enable": "تمكين مساحات العمل", "sidebar.workspaces.disable": "تعطيل مساحات العمل", "sidebar.gettingStarted.title": "البدء", @@ -515,7 +517,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "اللغة", "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 31f03e7fae9..1f9c00a8f52 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -498,6 +498,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projetos e sessões", "sidebar.settings": "Configurações", "sidebar.help": "Ajuda", + "sidebar.project.pin": "Fixar no topo", + "sidebar.project.unpin": "Desafixar", "sidebar.workspaces.enable": "Habilitar espaços de trabalho", "sidebar.workspaces.disable": "Desabilitar espaços de trabalho", "sidebar.gettingStarted.title": "Começando", @@ -519,7 +521,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index ea8cffc6cb8..015fad59b80 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -498,6 +498,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projekter og sessioner", "sidebar.settings": "Indstillinger", "sidebar.help": "Hjælp", + "sidebar.project.pin": "Fastgør øverst", + "sidebar.project.unpin": "Frigør", "sidebar.workspaces.enable": "Aktiver arbejdsområder", "sidebar.workspaces.disable": "Deaktiver arbejdsområder", "sidebar.gettingStarted.title": "Kom i gang", @@ -519,7 +521,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Sprog", "settings.general.row.language.description": "Ændr visningssproget for OpenCode", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index b6c3980f957..63f2d205d3f 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -541,6 +541,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projekte und Sitzungen", "sidebar.settings": "Einstellungen", "sidebar.help": "Hilfe", + "sidebar.project.pin": "Oben anheften", + "sidebar.project.unpin": "Loslösen", "sidebar.workspaces.enable": "Arbeitsbereiche aktivieren", "sidebar.workspaces.disable": "Arbeitsbereiche deaktivieren", "sidebar.gettingStarted.title": "Erste Schritte", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index c5f24767bd4..343bee84d0d 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -569,6 +569,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projects and sessions", "sidebar.settings": "Settings", "sidebar.help": "Help", + "sidebar.project.pin": "Pin to top", + "sidebar.project.unpin": "Unpin", "sidebar.workspaces.enable": "Enable workspaces", "sidebar.workspaces.disable": "Disable workspaces", "sidebar.gettingStarted.title": "Getting started", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 23662d7e000..2c8ddaec9bf 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -501,6 +501,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Proyectos y sesiones", "sidebar.settings": "Ajustes", "sidebar.help": "Ayuda", + "sidebar.project.pin": "Fijar arriba", + "sidebar.project.unpin": "Desfijar", "sidebar.workspaces.enable": "Habilitar espacios de trabajo", "sidebar.workspaces.disable": "Deshabilitar espacios de trabajo", "sidebar.gettingStarted.title": "Empezando", @@ -522,7 +524,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index e8aab2fa242..59bd9a3fda4 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -506,6 +506,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projets et sessions", "sidebar.settings": "Paramètres", "sidebar.help": "Aide", + "sidebar.project.pin": "Épingler en haut", + "sidebar.project.unpin": "Désépingler", "sidebar.workspaces.enable": "Activer les espaces de travail", "sidebar.workspaces.disable": "Désactiver les espaces de travail", "sidebar.gettingStarted.title": "Commencer", @@ -529,7 +531,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Langue", "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 1a2196e91e0..caa6cd4d3a5 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -493,6 +493,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "プロジェクトとセッション", "sidebar.settings": "設定", "sidebar.help": "ヘルプ", + "sidebar.project.pin": "上部に固定", + "sidebar.project.unpin": "固定を解除", "sidebar.workspaces.enable": "ワークスペースを有効化", "sidebar.workspaces.disable": "ワークスペースを無効化", "sidebar.gettingStarted.title": "はじめに", @@ -514,7 +516,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "言語", "settings.general.row.language.description": "OpenCodeの表示言語を変更します", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 7a17615d1a3..390ec77e522 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -499,6 +499,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "프로젝트 및 세션", "sidebar.settings": "설정", "sidebar.help": "도움말", + "sidebar.project.pin": "상단에 고정", + "sidebar.project.unpin": "고정 해제", "sidebar.workspaces.enable": "작업 공간 활성화", "sidebar.workspaces.disable": "작업 공간 비활성화", "sidebar.gettingStarted.title": "시작하기", @@ -520,7 +522,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "언어", "settings.general.row.language.description": "OpenCode 표시 언어 변경", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 88787330252..b1b8fc5f0c1 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -501,6 +501,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Prosjekter og sesjoner", "sidebar.settings": "Innstillinger", "sidebar.help": "Hjelp", + "sidebar.project.pin": "Fest øverst", + "sidebar.project.unpin": "Løsne", "sidebar.workspaces.enable": "Aktiver arbeidsområder", "sidebar.workspaces.disable": "Deaktiver arbeidsområder", "sidebar.gettingStarted.title": "Kom i gang", @@ -522,7 +524,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Språk", "settings.general.row.language.description": "Endre visningsspråket for OpenCode", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 16eb1b89fb6..694dc264455 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -500,6 +500,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Projekty i sesje", "sidebar.settings": "Ustawienia", "sidebar.help": "Pomoc", + "sidebar.project.pin": "Przypnij na górze", + "sidebar.project.unpin": "Odepnij", "sidebar.workspaces.enable": "Włącz przestrzenie robocze", "sidebar.workspaces.disable": "Wyłącz przestrzenie robocze", "sidebar.gettingStarted.title": "Pierwsze kroki", @@ -521,7 +523,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Język", "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 626d57ce71f..7f33482e1a9 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -502,6 +502,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "Проекты и сессии", "sidebar.settings": "Настройки", "sidebar.help": "Помощь", + "sidebar.project.pin": "Закрепить наверху", + "sidebar.project.unpin": "Открепить", "sidebar.workspaces.enable": "Включить рабочие пространства", "sidebar.workspaces.disable": "Отключить рабочие пространства", "sidebar.gettingStarted.title": "Начало работы", @@ -524,7 +526,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "Язык", "settings.general.row.language.description": "Изменить язык отображения OpenCode", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 1103b67a691..357ac976f55 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -501,6 +501,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "โปรเจกต์และเซสชัน", "sidebar.settings": "การตั้งค่า", "sidebar.help": "ช่วยเหลือ", + "sidebar.project.pin": "ปักหมุดไว้ด้านบน", + "sidebar.project.unpin": "เลิกปักหมุด", "sidebar.workspaces.enable": "เปิดใช้งานพื้นที่ทำงาน", "sidebar.workspaces.disable": "ปิดใช้งานพื้นที่ทำงาน", "sidebar.gettingStarted.title": "เริ่มต้นใช้งาน", @@ -523,7 +525,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "ภาษา", "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 903bb54ec4e..8a1d2338bea 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -534,6 +534,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "项目和会话", "sidebar.settings": "设置", "sidebar.help": "帮助", + "sidebar.project.pin": "置顶", + "sidebar.project.unpin": "取消置顶", "sidebar.workspaces.enable": "启用工作区", "sidebar.workspaces.disable": "禁用工作区", "sidebar.gettingStarted.title": "入门", @@ -555,7 +557,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "语言", "settings.general.row.language.description": "更改 OpenCode 的显示语言", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index e64d6779e5b..2958ef1f595 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -531,6 +531,8 @@ export const dict = { "sidebar.nav.projectsAndSessions": "專案與工作階段", "sidebar.settings": "設定", "sidebar.help": "說明", + "sidebar.project.pin": "置頂", + "sidebar.project.unpin": "取消置頂", "sidebar.workspaces.enable": "啟用工作區", "sidebar.workspaces.disable": "停用工作區", "sidebar.gettingStarted.title": "開始使用", @@ -552,7 +554,8 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": + "Automatically move active projects and sessions to the top when Claude starts working on them", "settings.general.row.language.title": "語言", "settings.general.row.language.description": "變更 OpenCode 的顯示語言", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 8133c0fcb61..44cc24c214d 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1730,6 +1730,21 @@ export default function Layout(props: ParentProps) { showEditProjectDialog(p())}> {language.t("common.edit")} + { + server.projects.isPinned(p.worktree) + ? server.projects.unpin(p.worktree) + : server.projects.pin(p.worktree) + }} + > + + {server.projects.isPinned(p.worktree) + ? language.t("sidebar.project.unpin") + : language.t("sidebar.project.pin")} + + Date: Mon, 2 Feb 2026 17:47:21 +0100 Subject: [PATCH 3/4] fix(i18n): translate all dynamic sort strings to native languages Previously title and description were left as English in all non-DE languages. Now properly translated for all 15 languages. Also updates the description wording to be shorter and provider-agnostic per community feedback. --- packages/app/src/i18n/ar.ts | 7 +++---- packages/app/src/i18n/br.ts | 7 +++---- packages/app/src/i18n/da.ts | 7 +++---- packages/app/src/i18n/de.ts | 3 +-- packages/app/src/i18n/en.ts | 3 +-- packages/app/src/i18n/es.ts | 7 +++---- packages/app/src/i18n/fr.ts | 7 +++---- packages/app/src/i18n/ja.ts | 7 +++---- packages/app/src/i18n/ko.ts | 7 +++---- packages/app/src/i18n/no.ts | 7 +++---- packages/app/src/i18n/pl.ts | 7 +++---- packages/app/src/i18n/ru.ts | 7 +++---- packages/app/src/i18n/th.ts | 7 +++---- packages/app/src/i18n/zh.ts | 7 +++---- packages/app/src/i18n/zht.ts | 7 +++---- packages/app/src/pages/layout.tsx | 10 +++++----- 16 files changed, 46 insertions(+), 61 deletions(-) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 3f5fd936131..30b99afa7da 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -513,12 +513,11 @@ export const dict = { "settings.general.section.appearance": "المظهر", "settings.general.section.notifications": "إشعارات النظام", "settings.general.section.updates": "التحديثات", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "المؤثرات الصوتية", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "ترتيب ديناميكي للشريط الجانبي", + "settings.general.desktop.dynamicSort.description": "نقل المشاريع النشطة مؤخراً إلى الأعلى", "settings.general.row.language.title": "اللغة", "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 1f9c00a8f52..a924c5a25b6 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -517,12 +517,11 @@ export const dict = { "settings.general.section.appearance": "Aparência", "settings.general.section.notifications": "Notificações do sistema", "settings.general.section.updates": "Atualizações", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "Efeitos sonoros", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "Ordenação dinâmica da barra lateral", + "settings.general.desktop.dynamicSort.description": "Mover projetos recentemente ativos para o topo", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 015fad59b80..f556e58d44d 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -517,12 +517,11 @@ export const dict = { "settings.general.section.appearance": "Udseende", "settings.general.section.notifications": "Systemmeddelelser", "settings.general.section.updates": "Opdateringer", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "Lydeffekter", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "Dynamisk sidebjælkesortering", + "settings.general.desktop.dynamicSort.description": "Flyt nyligt aktive projekter til toppen", "settings.general.row.language.title": "Sprog", "settings.general.row.language.description": "Ændr visningssproget for OpenCode", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 63f2d205d3f..3b153534ce2 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -565,8 +565,7 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamische Seitenleisten-Sortierung", - "settings.general.desktop.dynamicSort.description": - "Aktive Projekte und Sitzungen automatisch nach oben verschieben, wenn Claude daran arbeitet", + "settings.general.desktop.dynamicSort.description": "Kürzlich aktive Projekte nach oben verschieben", "settings.general.row.language.title": "Sprache", "settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 343bee84d0d..0f609797444 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -593,8 +593,7 @@ export const dict = { "settings.general.section.desktop": "Desktop", "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.description": "Move recently active projects to the top", "settings.general.row.language.title": "Language", "settings.general.row.language.description": "Change the display language for OpenCode", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 2c8ddaec9bf..747322bb172 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -520,12 +520,11 @@ export const dict = { "settings.general.section.appearance": "Apariencia", "settings.general.section.notifications": "Notificaciones del sistema", "settings.general.section.updates": "Actualizaciones", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "Efectos de sonido", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "Ordenación dinámica de la barra lateral", + "settings.general.desktop.dynamicSort.description": "Mover proyectos recientemente activos a la parte superior", "settings.general.row.language.title": "Idioma", "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 59bd9a3fda4..599a8babd22 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -527,12 +527,11 @@ export const dict = { "settings.general.section.appearance": "Apparence", "settings.general.section.notifications": "Notifications système", "settings.general.section.updates": "Mises à jour", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "Effets sonores", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "Tri dynamique de la barre latérale", + "settings.general.desktop.dynamicSort.description": "Déplacer les projets récemment actifs vers le haut", "settings.general.row.language.title": "Langue", "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index caa6cd4d3a5..b4970c2a376 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -512,12 +512,11 @@ export const dict = { "settings.general.section.appearance": "外観", "settings.general.section.notifications": "システム通知", "settings.general.section.updates": "アップデート", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "効果音", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "サイドバーの動的ソート", + "settings.general.desktop.dynamicSort.description": "最近アクティブなプロジェクトを上部に移動", "settings.general.row.language.title": "言語", "settings.general.row.language.description": "OpenCodeの表示言語を変更します", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 390ec77e522..8c23f234f08 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -518,12 +518,11 @@ export const dict = { "settings.general.section.appearance": "모양", "settings.general.section.notifications": "시스템 알림", "settings.general.section.updates": "업데이트", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "효과음", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "사이드바 동적 정렬", + "settings.general.desktop.dynamicSort.description": "최근 활성 프로젝트를 상단으로 이동", "settings.general.row.language.title": "언어", "settings.general.row.language.description": "OpenCode 표시 언어 변경", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index b1b8fc5f0c1..96e1309cad6 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -520,12 +520,11 @@ export const dict = { "settings.general.section.appearance": "Utseende", "settings.general.section.notifications": "Systemvarsler", "settings.general.section.updates": "Oppdateringer", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "Lydeffekter", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "Dynamisk sidefeltssortering", + "settings.general.desktop.dynamicSort.description": "Flytt nylig aktive prosjekter til toppen", "settings.general.row.language.title": "Språk", "settings.general.row.language.description": "Endre visningsspråket for OpenCode", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 694dc264455..6202733b006 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -519,12 +519,11 @@ export const dict = { "settings.general.section.appearance": "Wygląd", "settings.general.section.notifications": "Powiadomienia systemowe", "settings.general.section.updates": "Aktualizacje", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "Efekty dźwiękowe", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "Dynamiczne sortowanie paska bocznego", + "settings.general.desktop.dynamicSort.description": "Przenieś ostatnio aktywne projekty na górę", "settings.general.row.language.title": "Język", "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 7f33482e1a9..f0d12b22fce 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -522,12 +522,11 @@ export const dict = { "settings.general.section.appearance": "Внешний вид", "settings.general.section.notifications": "Системные уведомления", "settings.general.section.updates": "Обновления", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "Звуковые эффекты", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "Динамическая сортировка боковой панели", + "settings.general.desktop.dynamicSort.description": "Перемещать недавно активные проекты наверх", "settings.general.row.language.title": "Язык", "settings.general.row.language.description": "Изменить язык отображения OpenCode", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 357ac976f55..8b9a3d0c442 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -521,12 +521,11 @@ export const dict = { "settings.general.section.appearance": "รูปลักษณ์", "settings.general.section.notifications": "การแจ้งเตือนระบบ", "settings.general.section.updates": "การอัปเดต", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "เสียงเอฟเฟกต์", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "การจัดเรียงแถบด้านข้างแบบไดนามิก", + "settings.general.desktop.dynamicSort.description": "ย้ายโปรเจกต์ที่ใช้งานล่าสุดไปด้านบน", "settings.general.row.language.title": "ภาษา", "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 8a1d2338bea..66565ceb48b 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -553,12 +553,11 @@ export const dict = { "settings.general.section.appearance": "外观", "settings.general.section.notifications": "系统通知", "settings.general.section.updates": "更新", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "音效", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "动态侧边栏排序", + "settings.general.desktop.dynamicSort.description": "将最近活跃的项目移至顶部", "settings.general.row.language.title": "语言", "settings.general.row.language.description": "更改 OpenCode 的显示语言", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 2958ef1f595..97a6f9287b1 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -550,12 +550,11 @@ export const dict = { "settings.general.section.appearance": "外觀", "settings.general.section.notifications": "系統通知", "settings.general.section.updates": "更新", - "settings.general.section.sounds": "Sound effects", + "settings.general.section.sounds": "音效", "settings.general.section.desktop": "Desktop", - "settings.general.desktop.dynamicSort.title": "Dynamic sidebar sorting", - "settings.general.desktop.dynamicSort.description": - "Automatically move active projects and sessions to the top when Claude starts working on them", + "settings.general.desktop.dynamicSort.title": "動態側邊欄排序", + "settings.general.desktop.dynamicSort.description": "將最近活躍的專案移至頂部", "settings.general.row.language.title": "語言", "settings.general.row.language.description": "變更 OpenCode 的顯示語言", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 44cc24c214d..dcb3adc4df9 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1732,15 +1732,15 @@ export default function Layout(props: ParentProps) { { - server.projects.isPinned(p.worktree) - ? server.projects.unpin(p.worktree) - : server.projects.pin(p.worktree) + server.projects.isPinned(p().worktree) + ? server.projects.unpin(p().worktree) + : server.projects.pin(p().worktree) }} > - {server.projects.isPinned(p.worktree) + {server.projects.isPinned(p().worktree) ? language.t("sidebar.project.unpin") : language.t("sidebar.project.pin")} From 788210561d9525143c5964a18d1a66e5770cda40 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Fri, 6 Feb 2026 21:59:12 +0100 Subject: [PATCH 4/4] feat: add pin/unpin to project icon right-click menu --- packages/app/src/pages/layout/sidebar-project.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index 9dda3054b74..3248e66be32 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -153,6 +153,21 @@ export const SortableProject = (props: { props.ctx.showEditProjectDialog(props.project)}> {language.t("common.edit")} + { + server.projects.isPinned(props.project.worktree) + ? server.projects.unpin(props.project.worktree) + : server.projects.pin(props.project.worktree) + }} + > + + {server.projects.isPinned(props.project.worktree) + ? language.t("sidebar.project.unpin") + : language.t("sidebar.project.pin")} + +