summaryrefslogtreecommitdiffhomepage
path: root/packages/app
diff options
context:
space:
mode:
authoradamelmore <[email protected]>2026-01-27 11:34:30 -0600
committeradamelmore <[email protected]>2026-01-27 11:40:39 -0600
commit7de42ca242af2045fdfc4bbbe1ada33069d2769e (patch)
tree2df894cba88a9fb6c098853b8f0e9d0c7fa64c6e /packages/app
parente2c57735b43d4bbca3b8de1ea0d2fe706f3e2799 (diff)
downloadopencode-7de42ca242af2045fdfc4bbbe1ada33069d2769e.tar.gz
opencode-7de42ca242af2045fdfc4bbbe1ada33069d2769e.zip
feat(app): improved layout
Diffstat (limited to 'packages/app')
-rw-r--r--packages/app/src/components/dialog-select-file.tsx5
-rw-r--r--packages/app/src/components/prompt-input.tsx14
-rw-r--r--packages/app/src/components/session-context-usage.tsx3
-rw-r--r--packages/app/src/components/session/session-header.tsx53
-rw-r--r--packages/app/src/context/layout.tsx104
-rw-r--r--packages/app/src/pages/session.tsx314
6 files changed, 176 insertions, 317 deletions
diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx
index 54ed48a9d..5c58725c7 100644
--- a/packages/app/src/components/dialog-select-file.tsx
+++ b/packages/app/src/components/dialog-select-file.tsx
@@ -36,7 +36,6 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
const filesOnly = () => props.mode === "files"
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey))
- const view = createMemo(() => layout.view(sessionKey))
const state = { cleanup: undefined as (() => void) | void, committed: false }
const [grouped, setGrouped] = createSignal(false)
const common = [
@@ -45,7 +44,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
"session.previous",
"session.next",
"terminal.toggle",
- "review.toggle",
+ "fileTree.toggle",
]
const limit = 5
@@ -163,7 +162,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
const value = file.tab(path)
tabs().open(value)
file.load(path)
- view().reviewPanel.open()
+ layout.fileTree.setTab("all")
props.onOpenFile?.(path)
}
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index c736ef0f1..1bd7aa4eb 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -171,7 +171,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey))
- const view = createMemo(() => layout.view(sessionKey))
const commentInReview = (path: string) => {
const sessionID = params.id
@@ -187,20 +186,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const focus = { file: item.path, id: item.commentID }
comments.setActive(focus)
- view().reviewPanel.open()
- if (item.commentOrigin === "review") {
- tabs().open("review")
- requestAnimationFrame(() => comments.setFocus(focus))
- return
- }
-
- if (item.commentOrigin !== "file" && commentInReview(item.path)) {
- tabs().open("review")
+ const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path))
+ if (wantsReview) {
+ layout.fileTree.setTab("changes")
requestAnimationFrame(() => comments.setFocus(focus))
return
}
+ layout.fileTree.setTab("all")
const tab = files.tab(item.path)
tabs().open(tab)
files.load(item.path)
diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx
index 4dbb9e048..afdb18bb0 100644
--- a/packages/app/src/components/session-context-usage.tsx
+++ b/packages/app/src/components/session-context-usage.tsx
@@ -23,7 +23,6 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const variant = createMemo(() => props.variant ?? "button")
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey))
- const view = createMemo(() => layout.view(sessionKey))
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
const usd = createMemo(
@@ -58,7 +57,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const openContext = () => {
if (!params.id) return
- view().reviewPanel.open()
+ layout.fileTree.setTab("all")
tabs().open("context")
tabs().setActive("context")
}
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index db43b5eaf..9fddb4507 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -45,7 +45,6 @@ export function SessionHeader() {
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
const showShare = createMemo(() => shareEnabled() && !!currentSession())
- const showReview = createMemo(() => !!currentSession())
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const view = createMemo(() => layout.view(sessionKey))
@@ -281,62 +280,38 @@ export function SessionHeader() {
</TooltipKeybind>
</div>
<div class="hidden md:block shrink-0">
- <TooltipKeybind title={language.t("command.review.toggle")} keybind={command.keybind("review.toggle")}>
+ <TooltipKeybind
+ title={language.t("command.fileTree.toggle")}
+ keybind={command.keybind("fileTree.toggle")}
+ >
<Button
variant="ghost"
- class="group/review-toggle size-6 p-0"
- onClick={() => view().reviewPanel.toggle()}
- aria-label={language.t("command.review.toggle")}
- aria-expanded={view().reviewPanel.opened()}
- aria-controls="review-panel"
- tabIndex={showReview() ? 0 : -1}
+ class="group/file-tree-toggle size-6 p-0"
+ onClick={() => layout.fileTree.toggle()}
+ aria-label={language.t("command.fileTree.toggle")}
+ aria-expanded={layout.fileTree.opened()}
+ aria-controls="file-tree-panel"
>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
- name={view().reviewPanel.opened() ? "layout-right-full" : "layout-right"}
- class="group-hover/review-toggle:hidden"
+ name={layout.fileTree.opened() ? "layout-right-full" : "layout-right"}
+ class="group-hover/file-tree-toggle:hidden"
/>
<Icon
size="small"
name="layout-right-partial"
- class="hidden group-hover/review-toggle:inline-block"
+ class="hidden group-hover/file-tree-toggle:inline-block"
/>
<Icon
size="small"
- name={view().reviewPanel.opened() ? "layout-right" : "layout-right-full"}
- class="hidden group-active/review-toggle:inline-block"
+ name={layout.fileTree.opened() ? "layout-right" : "layout-right-full"}
+ class="hidden group-active/file-tree-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
</div>
- <div class="hidden md:block shrink-0">
- <Tooltip value="Toggle file tree" placement="bottom">
- <Button
- variant="ghost"
- class="group/file-tree-toggle size-6 p-0"
- onClick={() => {
- const opening = !layout.fileTree.opened()
- if (opening && !view().reviewPanel.opened()) view().reviewPanel.open()
- layout.fileTree.toggle()
- }}
- aria-label="Toggle file tree"
- aria-expanded={layout.fileTree.opened()}
- >
- <div class="relative flex items-center justify-center size-4">
- <Icon
- size="small"
- name="bullet-list"
- classList={{
- "text-icon-strong": layout.fileTree.opened(),
- "text-icon-weak": !layout.fileTree.opened(),
- }}
- />
- </div>
- </Button>
- </Tooltip>
- </div>
</div>
</Portal>
)}
diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx
index 414c3d6f1..d30fd11cf 100644
--- a/packages/app/src/context/layout.tsx
+++ b/packages/app/src/context/layout.tsx
@@ -51,16 +51,37 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const migrate = (value: unknown) => {
if (!isRecord(value)) return value
+
const sidebar = value.sidebar
- if (!isRecord(sidebar)) return value
- if (typeof sidebar.workspaces !== "boolean") return value
- return {
- ...value,
- sidebar: {
+ const migratedSidebar = (() => {
+ if (!isRecord(sidebar)) return sidebar
+ if (typeof sidebar.workspaces !== "boolean") return sidebar
+ return {
...sidebar,
workspaces: {},
workspacesDefault: sidebar.workspaces,
- },
+ }
+ })()
+
+ const fileTree = value.fileTree
+ const migratedFileTree = (() => {
+ if (!isRecord(fileTree)) return fileTree
+ if (fileTree.tab === "changes" || fileTree.tab === "all") return fileTree
+
+ const width = typeof fileTree.width === "number" ? fileTree.width : 344
+ return {
+ ...fileTree,
+ opened: true,
+ width: width === 260 ? 344 : width,
+ tab: "changes",
+ }
+ })()
+
+ if (migratedSidebar === sidebar && migratedFileTree === fileTree) return value
+ return {
+ ...value,
+ sidebar: migratedSidebar,
+ fileTree: migratedFileTree,
}
}
@@ -80,11 +101,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
review: {
diffStyle: "split" as ReviewDiffStyle,
- panelOpened: true,
},
fileTree: {
- opened: false,
- width: 260,
+ opened: true,
+ width: 344,
+ tab: "changes" as "changes" | "all",
},
session: {
width: 600,
@@ -454,32 +475,40 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
},
fileTree: {
- opened: createMemo(() => store.fileTree?.opened ?? false),
- width: createMemo(() => store.fileTree?.width ?? 260),
+ opened: createMemo(() => store.fileTree?.opened ?? true),
+ width: createMemo(() => store.fileTree?.width ?? 344),
+ tab: createMemo(() => store.fileTree?.tab ?? "changes"),
+ setTab(tab: "changes" | "all") {
+ if (!store.fileTree) {
+ setStore("fileTree", { opened: true, width: 344, tab })
+ return
+ }
+ setStore("fileTree", "tab", tab)
+ },
open() {
if (!store.fileTree) {
- setStore("fileTree", { opened: true, width: 260 })
+ setStore("fileTree", { opened: true, width: 344, tab: "changes" })
return
}
setStore("fileTree", "opened", true)
},
close() {
if (!store.fileTree) {
- setStore("fileTree", { opened: false, width: 260 })
+ setStore("fileTree", { opened: false, width: 344, tab: "changes" })
return
}
setStore("fileTree", "opened", false)
},
toggle() {
if (!store.fileTree) {
- setStore("fileTree", { opened: true, width: 260 })
+ setStore("fileTree", { opened: true, width: 344, tab: "changes" })
return
}
setStore("fileTree", "opened", (x) => !x)
},
resize(width: number) {
if (!store.fileTree) {
- setStore("fileTree", { opened: true, width })
+ setStore("fileTree", { opened: true, width, tab: "changes" })
return
}
setStore("fileTree", "width", width)
@@ -526,7 +555,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
- const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? true)
function setTerminalOpened(next: boolean) {
const current = store.terminal
@@ -540,18 +568,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setStore("terminal", "opened", next)
}
- function setReviewPanelOpened(next: boolean) {
- const current = store.review
- if (!current) {
- setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
- return
- }
-
- const value = current.panelOpened ?? true
- if (value === next) return
- setStore("review", "panelOpened", next)
- }
-
return {
scroll(tab: string) {
return scroll.scroll(key(), tab)
@@ -571,18 +587,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setTerminalOpened(!terminalOpened())
},
},
- reviewPanel: {
- opened: reviewPanelOpened,
- open() {
- setReviewPanelOpened(true)
- },
- close() {
- setReviewPanelOpened(false)
- },
- toggle() {
- setReviewPanelOpened(!reviewPanelOpened())
- },
- },
review: {
open: createMemo(() => s().reviewOpen),
setOpen(open: string[]) {
@@ -620,10 +624,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
return {
tabs,
- active: createMemo(() => tabs().active),
- all: createMemo(() => tabs().all),
+ active: createMemo(() => (tabs().active === "review" ? undefined : tabs().active)),
+ all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
setActive(tab: string | undefined) {
const session = key()
+ if (tab === "review") return
if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all: [], active: tab })
} else {
@@ -632,25 +637,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
setAll(all: string[]) {
const session = key()
+ const next = all.filter((tab) => tab !== "review")
if (!store.sessionTabs[session]) {
- setStore("sessionTabs", session, { all, active: undefined })
+ setStore("sessionTabs", session, { all: next, active: undefined })
} else {
- setStore("sessionTabs", session, "all", all)
+ setStore("sessionTabs", session, "all", next)
}
},
async open(tab: string) {
+ if (tab === "review") return
const session = key()
const current = store.sessionTabs[session] ?? { all: [] }
- if (tab === "review") {
- if (!store.sessionTabs[session]) {
- setStore("sessionTabs", session, { all: [], active: tab })
- return
- }
- setStore("sessionTabs", session, "active", tab)
- return
- }
-
if (tab === "context") {
const all = [tab, ...current.all.filter((x) => x !== tab)]
if (!store.sessionTabs[session]) {
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index a75b1169a..146450293 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -23,7 +23,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
-import { DiffChanges } from "@opencode-ai/ui/diff-changes"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Tabs } from "@opencode-ai/ui/tabs"
import { useCodeComponent } from "@opencode-ai/ui/context/code"
@@ -433,7 +432,7 @@ export default function Page() {
expanded: {} as Record<string, boolean>,
messageId: undefined as string | undefined,
turnStart: 0,
- mobileTab: "session" as "session" | "review",
+ mobileTab: "session" as "session" | "changes",
newSessionWorktree: "main",
promptHeight: 0,
})
@@ -693,12 +692,12 @@ export default function Page() {
onSelect: () => view().terminal.toggle(),
},
{
- id: "review.toggle",
- title: language.t("command.review.toggle"),
+ id: "fileTree.toggle",
+ title: language.t("command.fileTree.toggle"),
description: "",
category: language.t("command.category.view"),
keybind: "mod+shift+r",
- onSelect: () => view().reviewPanel.toggle(),
+ onSelect: () => layout.fileTree.toggle(),
},
{
id: "terminal.new",
@@ -822,7 +821,7 @@ export default function Page() {
const sessionID = params.id
if (!sessionID) return
if (status()?.type !== "idle") {
- await sdk.client.session.abort({ sessionID }).catch(() => {})
+ await sdk.client.session.abort({ sessionID }).catch(() => { })
}
const revert = info()?.revert?.messageID
// Find the last user message that's not already reverted
@@ -905,69 +904,69 @@ export default function Page() {
},
...(sync.data.config.share !== "disabled"
? [
- {
- id: "session.share",
- title: language.t("command.session.share"),
- description: language.t("command.session.share.description"),
- category: language.t("command.category.session"),
- slash: "share",
- disabled: !params.id || !!info()?.share?.url,
- onSelect: async () => {
- if (!params.id) return
- await sdk.client.session
- .share({ sessionID: params.id })
- .then((res) => {
- navigator.clipboard.writeText(res.data!.share!.url).catch(() =>
- showToast({
- title: language.t("toast.session.share.copyFailed.title"),
- variant: "error",
- }),
- )
- })
- .then(() =>
+ {
+ id: "session.share",
+ title: language.t("command.session.share"),
+ description: language.t("command.session.share.description"),
+ category: language.t("command.category.session"),
+ slash: "share",
+ disabled: !params.id || !!info()?.share?.url,
+ onSelect: async () => {
+ if (!params.id) return
+ await sdk.client.session
+ .share({ sessionID: params.id })
+ .then((res) => {
+ navigator.clipboard.writeText(res.data!.share!.url).catch(() =>
showToast({
- title: language.t("toast.session.share.success.title"),
- description: language.t("toast.session.share.success.description"),
- variant: "success",
- }),
- )
- .catch(() =>
- showToast({
- title: language.t("toast.session.share.failed.title"),
- description: language.t("toast.session.share.failed.description"),
+ title: language.t("toast.session.share.copyFailed.title"),
variant: "error",
}),
)
- },
+ })
+ .then(() =>
+ showToast({
+ title: language.t("toast.session.share.success.title"),
+ description: language.t("toast.session.share.success.description"),
+ variant: "success",
+ }),
+ )
+ .catch(() =>
+ showToast({
+ title: language.t("toast.session.share.failed.title"),
+ description: language.t("toast.session.share.failed.description"),
+ variant: "error",
+ }),
+ )
},
- {
- id: "session.unshare",
- title: language.t("command.session.unshare"),
- description: language.t("command.session.unshare.description"),
- category: language.t("command.category.session"),
- slash: "unshare",
- disabled: !params.id || !info()?.share?.url,
- onSelect: async () => {
- if (!params.id) return
- await sdk.client.session
- .unshare({ sessionID: params.id })
- .then(() =>
- showToast({
- title: language.t("toast.session.unshare.success.title"),
- description: language.t("toast.session.unshare.success.description"),
- variant: "success",
- }),
- )
- .catch(() =>
- showToast({
- title: language.t("toast.session.unshare.failed.title"),
- description: language.t("toast.session.unshare.failed.description"),
- variant: "error",
- }),
- )
- },
+ },
+ {
+ id: "session.unshare",
+ title: language.t("command.session.unshare"),
+ description: language.t("command.session.unshare.description"),
+ category: language.t("command.category.session"),
+ slash: "unshare",
+ disabled: !params.id || !info()?.share?.url,
+ onSelect: async () => {
+ if (!params.id) return
+ await sdk.client.session
+ .unshare({ sessionID: params.id })
+ .then(() =>
+ showToast({
+ title: language.t("toast.session.unshare.success.title"),
+ description: language.t("toast.session.unshare.success.description"),
+ variant: "success",
+ }),
+ )
+ .catch(() =>
+ showToast({
+ title: language.t("toast.session.unshare.failed.title"),
+ description: language.t("toast.session.unshare.failed.description"),
+ variant: "error",
+ }),
+ )
},
- ]
+ },
+ ]
: []),
])
@@ -1067,40 +1066,31 @@ export default function Page() {
.filter((tab) => tab !== "context"),
)
- const mobileReview = createMemo(() => !isDesktop() && view().reviewPanel.opened() && store.mobileTab === "review")
+ const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
- const showTabs = createMemo(() => view().reviewPanel.opened())
+ const fileTreeTab = () => layout.fileTree.tab()
+ const setFileTreeTab = (value: "changes" | "all") => layout.fileTree.setTab(value)
const [tree, setTree] = createStore({
- fileTreeTab: "changes" as "changes" | "all",
reviewScroll: undefined as HTMLDivElement | undefined,
pendingDiff: undefined as string | undefined,
})
- const fileTreeTab = () => tree.fileTreeTab
- const setFileTreeTab = (value: "changes" | "all") => setTree("fileTreeTab", value)
const reviewScroll = () => tree.reviewScroll
const setReviewScroll = (value: HTMLDivElement | undefined) => setTree("reviewScroll", value)
const pendingDiff = () => tree.pendingDiff
const setPendingDiff = (value: string | undefined) => setTree("pendingDiff", value)
const showAllFiles = () => {
- if (!layout.fileTree.opened()) return
if (fileTreeTab() !== "changes") return
setFileTreeTab("all")
}
- createEffect(() => {
- if (!layout.fileTree.opened()) return
- setFileTreeTab("changes")
- })
-
createEffect(
on(
() => tabs().active(),
(active) => {
if (!active) return
- if (!layout.fileTree.opened()) return
if (fileTreeTab() !== "changes") return
if (!file.pathFromTab(active)) return
showAllFiles()
@@ -1197,49 +1187,30 @@ export default function Page() {
const activeTab = createMemo(() => {
const active = tabs().active()
- if (layout.fileTree.opened() && fileTreeTab() === "all") {
- if (active && active !== "review" && active !== "context") return normalizeTab(active)
-
- const first = openedTabs()[0]
- if (first) return first
- return "review"
- }
- if (active) return normalizeTab(active)
- if (hasReview()) return "review"
+ if (active === "context") return "context"
+ if (active && file.pathFromTab(active)) return normalizeTab(active)
const first = openedTabs()[0]
if (first) return first
if (contextOpen()) return "context"
- return "review"
+ return "empty"
})
createEffect(() => {
if (!layout.ready()) return
if (tabs().active()) return
- if (!hasReview() && openedTabs().length === 0 && !contextOpen()) return
- tabs().setActive(activeTab())
- })
-
- createEffect(() => {
- if (!layout.fileTree.opened()) return
- if (fileTreeTab() !== "all") return
-
- const first = openedTabs()[0]
- if (!first) return
+ if (openedTabs().length === 0 && !contextOpen()) return
- const active = tabs().active()
- if (active && active !== "review" && active !== "context") return
- tabs().setActive(first)
+ const next = activeTab()
+ if (next === "empty") return
+ tabs().setActive(next)
})
createEffect(() => {
const id = params.id
if (!id) return
- const wants = isDesktop()
- ? view().reviewPanel.opened() &&
- (layout.fileTree.opened() ? fileTreeTab() === "changes" : activeTab() === "review")
- : view().reviewPanel.opened() && store.mobileTab === "review"
+ const wants = isDesktop() ? fileTreeTab() === "changes" : store.mobileTab === "changes"
if (!wants) return
if (sync.data.session_diff[id] !== undefined) return
@@ -1654,8 +1625,8 @@ export default function Page() {
<div class="relative bg-background-base size-full overflow-hidden flex flex-col">
<SessionHeader />
<div class="flex-1 min-h-0 flex flex-col md:flex-row">
- {/* Mobile tab bar - only shown on mobile when user opened review */}
- <Show when={!isDesktop() && view().reviewPanel.opened()}>
+ {/* Mobile tab bar */}
+ <Show when={!isDesktop() && params.id}>
<Tabs class="h-auto">
<Tabs.List>
<Tabs.Trigger
@@ -1667,16 +1638,16 @@ export default function Page() {
{language.t("session.tab.session")}
</Tabs.Trigger>
<Tabs.Trigger
- value="review"
+ value="changes"
class="w-1/2 !border-r-0"
classes={{ button: "w-full" }}
- onClick={() => setStore("mobileTab", "review")}
+ onClick={() => setStore("mobileTab", "changes")}
>
<Switch>
<Match when={hasReview()}>
{language.t("session.review.filesChanged", { count: reviewCount() })}
</Match>
- <Match when={true}>{language.t("session.tab.review")}</Match>
+ <Match when={true}>{language.t("session.review.change.other")}</Match>
</Switch>
</Tabs.Trigger>
</Tabs.List>
@@ -1690,7 +1661,7 @@ export default function Page() {
"flex-1 md:flex-none pt-6 md:pt-3": true,
}}
style={{
- width: isDesktop() && showTabs() ? `${layout.session.width()}px` : "100%",
+ width: isDesktop() ? `${layout.session.width()}px` : "100%",
"--prompt-height": store.promptHeight ? `${store.promptHeight}px` : undefined,
}}
>
@@ -1699,7 +1670,7 @@ export default function Page() {
<Match when={params.id}>
<Show when={activeMessage()}>
<Show
- when={!mobileReview()}
+ when={!mobileChanges()}
fallback={
<div class="relative h-full overflow-hidden">
<Switch>
@@ -1789,7 +1760,6 @@ export default function Page() {
"sticky top-0 z-30 bg-background-stronger": true,
"w-full": true,
"px-4 md:px-6": true,
- "md:max-w-200 md:mx-auto": !showTabs(),
}}
>
<div class="h-10 flex items-center gap-1">
@@ -1814,13 +1784,7 @@ export default function Page() {
<div
ref={autoScroll.contentRef}
role="log"
- class="flex flex-col gap-32 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
- classList={{
- "w-full": true,
- "md:max-w-200 md:mx-auto": !showTabs(),
- "mt-0.5": !showTabs(),
- "mt-0": showTabs(),
- }}
+ class="flex flex-col gap-32 items-start justify-start w-full mt-0 pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
>
<Show when={store.turnStart > 0}>
<div class="w-full flex justify-center">
@@ -1868,10 +1832,7 @@ export default function Page() {
<div
id={anchor(message.id)}
data-message-id={message.id}
- classList={{
- "min-w-0 w-full max-w-full": true,
- "md:max-w-200": !showTabs(),
- }}
+ class="min-w-0 w-full max-w-full"
>
<SessionTurn
sessionID={params.id!}
@@ -1924,12 +1885,7 @@ export default function Page() {
ref={(el) => (promptDock = el)}
class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 px-4 md:px-0 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none"
>
- <div
- classList={{
- "w-full px-4 pointer-events-auto": true,
- "md:max-w-200": !showTabs(),
- }}
- >
+ <div class="w-full px-4 pointer-events-auto">
<Show when={request()} keyed>
{(perm) => (
<div data-component="tool-part-wrapper" data-permission="true" class="mb-3">
@@ -2000,7 +1956,7 @@ export default function Page() {
</div>
</div>
- <Show when={isDesktop() && showTabs()}>
+ <Show when={isDesktop()}>
<ResizeHandle
direction="horizontal"
size={layout.session.width()}
@@ -2011,8 +1967,8 @@ export default function Page() {
</Show>
</div>
- {/* Desktop tabs panel (Review + Context + Files) - hidden on mobile */}
- <Show when={isDesktop() && showTabs()}>
+ {/* Desktop side panel - hidden on mobile */}
+ <Show when={isDesktop()}>
<aside
id="review-panel"
aria-label={language.t("session.panel.reviewAndFiles")}
@@ -2020,7 +1976,7 @@ export default function Page() {
>
<div class="flex-1 min-w-0 h-full">
<Show
- when={layout.fileTree.opened() && fileTreeTab() === "changes"}
+ when={fileTreeTab() === "changes"}
fallback={
<DragDropProvider
onDragStart={handleDragStart}
@@ -2033,24 +1989,7 @@ export default function Page() {
<Tabs value={activeTab()} onChange={openTab}>
<div class="sticky top-0 shrink-0 flex">
<Tabs.List>
- <Show when={!layout.fileTree.opened()}>
- <Tabs.Trigger value="review" classes={{ button: "!pl-6" }}>
- <div class="flex items-center gap-3">
- <Show when={diffs()}>
- <DiffChanges changes={diffs()} variant="bars" />
- </Show>
- <div class="flex items-center gap-1.5">
- <div>{language.t("session.tab.review")}</div>
- <Show when={info()?.summary?.files}>
- <div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
- {info()?.summary?.files ?? 0}
- </div>
- </Show>
- </div>
- </div>
- </Tabs.Trigger>
- </Show>
- <Show when={!layout.fileTree.opened() && contextOpen()}>
+ <Show when={contextOpen()}>
<Tabs.Trigger
value="context"
closeButton={
@@ -2097,69 +2036,20 @@ export default function Page() {
</StickyAddButton>
</Tabs.List>
</div>
- <Show when={!layout.fileTree.opened()}>
- <Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
- <Show when={activeTab() === "review"}>
- <div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
- <Switch>
- <Match when={hasReview()}>
- <Show
- when={diffsReady()}
- fallback={
- <div class="px-6 py-4 text-text-weak">
- {language.t("session.review.loadingChanges")}
- </div>
- }
- >
- <SessionReviewTab
- diffs={diffs}
- view={view}
- diffStyle={layout.review.diffStyle()}
- onDiffStyleChange={layout.review.setDiffStyle}
- onScrollRef={setReviewScroll}
- onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })}
- comments={comments.all()}
- focusedComment={comments.focus()}
- onFocusedCommentChange={comments.setFocus}
- onViewFile={(path) => {
- showAllFiles()
- const value = file.tab(path)
- tabs().open(value)
- file.load(path)
- }}
- />
- </Show>
- </Match>
- <Match when={true}>
- <div class="h-full px-6 pb-30 flex flex-col items-center justify-center text-center gap-6">
- <Mark class="w-14 opacity-10" />
- <div class="text-14-regular text-text-weak max-w-56">
- {language.t("session.review.empty")}
- </div>
- </div>
- </Match>
- </Switch>
- </div>
- </Show>
- </Tabs.Content>
- </Show>
-
- <Show when={layout.fileTree.opened() && fileTreeTab() === "all" && openedTabs().length === 0}>
- <Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
- <Show when={activeTab() === "review"}>
- <div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
- <div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6">
- <Mark class="w-14 opacity-10" />
- <div class="text-14-regular text-text-weak max-w-56">
- {language.t("session.files.selectToOpen")}
- </div>
+ <Tabs.Content value="empty" class="flex flex-col h-full overflow-hidden contain-strict">
+ <Show when={activeTab() === "empty"}>
+ <div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
+ <div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6">
+ <Mark class="w-14 opacity-10" />
+ <div class="text-14-regular text-text-weak max-w-56">
+ {language.t("session.files.selectToOpen")}
</div>
</div>
- </Show>
- </Tabs.Content>
- </Show>
+ </div>
+ </Show>
+ </Tabs.Content>
- <Show when={!layout.fileTree.opened() && contextOpen()}>
+ <Show when={contextOpen()}>
<Tabs.Content value="context" class="flex flex-col h-full overflow-hidden contain-strict">
<Show when={activeTab() === "context"}>
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
@@ -2725,7 +2615,11 @@ export default function Page() {
</div>
<Show when={layout.fileTree.opened()}>
- <div class="relative shrink-0 h-full" style={{ width: `${layout.fileTree.width()}px` }}>
+ <div
+ id="file-tree-panel"
+ class="relative shrink-0 h-full"
+ style={{ width: `${layout.fileTree.width()}px` }}
+ >
<div class="h-full border-l border-border-weak-base flex flex-col overflow-hidden group/filetree">
<Tabs
variant="pill"