summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages/layout
diff options
context:
space:
mode:
authorBrendan Allan <[email protected]>2026-03-19 21:59:14 +0800
committerGitHub <[email protected]>2026-03-19 19:29:14 +0530
commit84f60d97a0c37c9136dfd965a66a0c8685a19e71 (patch)
tree3257e9da5114ddf3775b310db9b5707cc48267c4 /packages/app/src/pages/layout
parentcbf4b68fee8ff45c10a484b27f94e2dbe5f5dea2 (diff)
downloadopencode-84f60d97a0c37c9136dfd965a66a0c8685a19e71.tar.gz
opencode-84f60d97a0c37c9136dfd965a66a0c8685a19e71.zip
app: fix workspace flicker when switching directories (#18207)
Co-authored-by: Shoubhit Dash <[email protected]>
Diffstat (limited to 'packages/app/src/pages/layout')
-rw-r--r--packages/app/src/pages/layout/helpers.test.ts6
-rw-r--r--packages/app/src/pages/layout/helpers.ts28
-rw-r--r--packages/app/src/pages/layout/sidebar-workspace.tsx12
3 files changed, 26 insertions, 20 deletions
diff --git a/packages/app/src/pages/layout/helpers.test.ts b/packages/app/src/pages/layout/helpers.test.ts
index 9dbc6c72d..1fe52d47a 100644
--- a/packages/app/src/pages/layout/helpers.test.ts
+++ b/packages/app/src/pages/layout/helpers.test.ts
@@ -104,14 +104,14 @@ describe("layout deep links", () => {
describe("layout workspace helpers", () => {
test("normalizes trailing slash in workspace key", () => {
expect(workspaceKey("/tmp/demo///")).toBe("/tmp/demo")
- expect(workspaceKey("C:\\tmp\\demo\\\\")).toBe("C:\\tmp\\demo")
+ expect(workspaceKey("C:\\tmp\\demo\\\\")).toBe("C:/tmp/demo")
})
test("preserves posix and drive roots in workspace key", () => {
expect(workspaceKey("/")).toBe("/")
expect(workspaceKey("///")).toBe("/")
- expect(workspaceKey("C:\\")).toBe("C:\\")
- expect(workspaceKey("C:\\\\\\")).toBe("C:\\")
+ expect(workspaceKey("C:\\")).toBe("C:/")
+ expect(workspaceKey("C://")).toBe("C:/")
expect(workspaceKey("C:///")).toBe("C:/")
})
diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts
index be4ce9f57..886ffd26a 100644
--- a/packages/app/src/pages/layout/helpers.ts
+++ b/packages/app/src/pages/layout/helpers.ts
@@ -1,11 +1,17 @@
import { getFilename } from "@opencode-ai/util/path"
import { type Session } from "@opencode-ai/sdk/v2/client"
+type SessionStore = {
+ session?: Session[]
+ path: { directory: string }
+}
+
export const workspaceKey = (directory: string) => {
- const drive = directory.match(/^([A-Za-z]:)[\\/]+$/)
- if (drive) return `${drive[1]}${directory.includes("\\") ? "\\" : "/"}`
- if (/^[\\/]+$/.test(directory)) return directory.includes("\\") ? "\\" : "/"
- return directory.replace(/[\\/]+$/, "")
+ const value = directory.replaceAll("\\", "/")
+ const drive = value.match(/^([A-Za-z]:)\/+$/)
+ if (drive) return `${drive[1]}/`
+ if (/^\/+$/i.test(value)) return "/"
+ return value.replace(/\/+$/, "")
}
function sortSessions(now: number) {
@@ -25,13 +31,11 @@ function sortSessions(now: number) {
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)).sort(sortSessions(now))
+const roots = (store: SessionStore) => (store.session ?? []).filter((session) => isRootVisibleSession(session, store.path.directory))
+
+export const sortedRootSessions = (store: SessionStore, now: number) => roots(store).sort(sortSessions(now))
-export const latestRootSession = (stores: { session: Session[]; path: { directory: string } }[], now: number) =>
- stores
- .flatMap((store) => store.session.filter((session) => isRootVisibleSession(session, store.path.directory)))
- .sort(sortSessions(now))[0]
+export const latestRootSession = (stores: SessionStore[], now: number) => stores.flatMap(roots).sort(sortSessions(now))[0]
export function hasProjectPermissions<T>(
request: Record<string, T[] | undefined>,
@@ -40,9 +44,9 @@ export function hasProjectPermissions<T>(
return Object.values(request).some((list) => list?.some(include))
}
-export const childMapByParent = (sessions: Session[]) => {
+export const childMapByParent = (sessions: Session[] | undefined) => {
const map = new Map<string, string[]>()
- for (const session of sessions) {
+ for (const session of sessions ?? []) {
if (!session.parentID) continue
const existing = map.get(session.parentID)
if (existing) {
diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx
index 86ede774e..127626feb 100644
--- a/packages/app/src/pages/layout/sidebar-workspace.tsx
+++ b/packages/app/src/pages/layout/sidebar-workspace.tsx
@@ -332,12 +332,13 @@ export const SortableWorkspace = (props: {
const open = createMemo(() => props.ctx.workspaceExpanded(props.directory, local()))
const boot = createMemo(() => open() || active())
const booted = createMemo((prev) => prev || workspaceStore.status === "complete", false)
- const hasMore = createMemo(() => workspaceStore.sessionTotal > sessions().length)
+ const count = createMemo(() => sessions()?.length ?? 0)
+ const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
const busy = createMemo(() => props.ctx.isBusy(props.directory))
const wasBusy = createMemo((prev) => prev || busy(), false)
- const loading = createMemo(() => open() && !booted() && sessions().length === 0 && !wasBusy())
+ const loading = createMemo(() => open() && !booted() && count() === 0 && !wasBusy())
const touch = createMediaQuery("(hover: none)")
- const showNew = createMemo(() => !loading() && (touch() || sessions().length === 0 || (active() && !params.id)))
+ const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
const loadMore = async () => {
setWorkspaceStore("limit", (limit) => (limit ?? 0) + 5)
await globalSync.project.loadSessions(props.directory)
@@ -472,8 +473,9 @@ export const LocalWorkspace = (props: {
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
const children = createMemo(() => childMapByParent(workspace().store.session))
const booted = createMemo((prev) => prev || workspace().store.status === "complete", false)
- const loading = createMemo(() => !booted() && sessions().length === 0)
- const hasMore = createMemo(() => workspace().store.sessionTotal > sessions().length)
+ const count = createMemo(() => sessions()?.length ?? 0)
+ const loading = createMemo(() => !booted() && count() === 0)
+ const hasMore = createMemo(() => workspace().store.sessionTotal > count())
const loadMore = async () => {
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
await globalSync.project.loadSessions(props.project.worktree)