summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authoradamelmore <[email protected]>2026-01-27 20:24:23 -0600
committeradamelmore <[email protected]>2026-01-27 20:24:27 -0600
commitd9741866c52472469c8858b83f44ad9c5a5f4925 (patch)
treeb9d168939286d44ae382880fa6601ff03a9a1b82 /packages
parent898118bafbf8a12c0ef9d6673d36305472991906 (diff)
downloadopencode-d9741866c52472469c8858b83f44ad9c5a5f4925.tar.gz
opencode-d9741866c52472469c8858b83f44ad9c5a5f4925.zip
fix(app): reintroduce review tab
Diffstat (limited to 'packages')
-rw-r--r--packages/app/src/components/prompt-input.tsx1
-rw-r--r--packages/app/src/context/layout.tsx13
-rw-r--r--packages/app/src/pages/session.tsx142
3 files changed, 108 insertions, 48 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index 9f038b6e8..d46ce095a 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -190,6 +190,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path))
if (wantsReview) {
layout.fileTree.setTab("changes")
+ if (!layout.fileTree.opened()) tabs().open("review")
requestAnimationFrame(() => comments.setFocus(focus))
return
}
diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx
index d30fd11cf..2ea5f0435 100644
--- a/packages/app/src/context/layout.tsx
+++ b/packages/app/src/context/layout.tsx
@@ -624,11 +624,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
return {
tabs,
- active: createMemo(() => (tabs().active === "review" ? undefined : tabs().active)),
+ active: createMemo(() => 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 {
@@ -645,10 +644,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
}
},
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: current.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 a845d3a65..7b4f31c50 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -23,6 +23,7 @@ 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"
@@ -1096,11 +1097,12 @@ export default function Page() {
}, 0)
}
+ const reviewTab = createMemo(() => isDesktop() && !layout.fileTree.opened())
const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context"))
const openedTabs = createMemo(() =>
tabs()
.all()
- .filter((tab) => tab !== "context"),
+ .filter((tab) => tab !== "context" && tab !== "review"),
)
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
@@ -1126,6 +1128,46 @@ export default function Page() {
setFileTreeTab("all")
}
+ const reviewPanel = () => (
+ <div class="flex flex-col h-full overflow-hidden bg-background-stronger contain-strict">
+ <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}
+ focusedFile={activeDiff()}
+ 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>
+ </div>
+ )
+
createEffect(
on(
() => tabs().active(),
@@ -1229,29 +1271,56 @@ export default function Page() {
const activeTab = createMemo(() => {
const active = tabs().active()
if (active === "context") return "context"
+ if (active === "review" && reviewTab()) return "review"
if (active && file.pathFromTab(active)) return normalizeTab(active)
const first = openedTabs()[0]
if (first) return first
if (contextOpen()) return "context"
+ if (reviewTab() && hasReview()) return "review"
return "empty"
})
createEffect(() => {
if (!layout.ready()) return
if (tabs().active()) return
- if (openedTabs().length === 0 && !contextOpen()) return
+ if (openedTabs().length === 0 && !contextOpen() && !(reviewTab() && hasReview())) return
const next = activeTab()
if (next === "empty") return
tabs().setActive(next)
})
+ createEffect(
+ on(
+ () => layout.fileTree.opened(),
+ (opened, prev) => {
+ if (prev === undefined) return
+ if (!isDesktop()) return
+
+ if (opened) {
+ const active = tabs().active()
+ const tab = active === "review" || (!active && hasReview()) ? "changes" : "all"
+ layout.fileTree.setTab(tab)
+ return
+ }
+
+ if (fileTreeTab() !== "changes") return
+ tabs().setActive("review")
+ },
+ { defer: true },
+ ),
+ )
+
createEffect(() => {
const id = params.id
if (!id) return
- const wants = isDesktop() ? fileTreeTab() === "changes" : store.mobileTab === "changes"
+ const wants = isDesktop()
+ ? layout.fileTree.opened()
+ ? fileTreeTab() === "changes"
+ : activeTab() === "review"
+ : store.mobileTab === "changes"
if (!wants) return
if (sync.data.session_diff[id] !== undefined) return
if (sync.status === "loading") return
@@ -2114,7 +2183,7 @@ export default function Page() {
>
<div class="flex-1 min-w-0 h-full">
<Show
- when={fileTreeTab() === "changes"}
+ when={layout.fileTree.opened() && fileTreeTab() === "changes"}
fallback={
<DragDropProvider
onDragStart={handleDragStart}
@@ -2127,6 +2196,23 @@ export default function Page() {
<Tabs value={activeTab()} onChange={openTab}>
<div class="sticky top-0 shrink-0 flex">
<Tabs.List>
+ <Show when={reviewTab()}>
+ <Tabs.Trigger value="review" classes={{ button: "!pl-6" }}>
+ <div class="flex items-center gap-3">
+ <Show when={hasReview() && diffsReady()}>
+ <DiffChanges changes={diffs()} variant="bars" />
+ </Show>
+ <div class="flex items-center gap-1.5">
+ <div>{language.t("session.tab.review")}</div>
+ <Show when={hasReview()}>
+ <div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
+ {reviewCount()}
+ </div>
+ </Show>
+ </div>
+ </div>
+ </Tabs.Trigger>
+ </Show>
<Show when={contextOpen()}>
<Tabs.Trigger
value="context"
@@ -2175,6 +2261,12 @@ export default function Page() {
</Tabs.List>
</div>
+ <Show when={reviewTab()}>
+ <Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
+ <Show when={activeTab() === "review"}>{reviewPanel()}</Show>
+ </Tabs.Content>
+ </Show>
+
<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">
@@ -2710,47 +2802,7 @@ export default function Page() {
</DragDropProvider>
}
>
- <div class="flex flex-col h-full overflow-hidden bg-background-stronger contain-strict">
- <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}
- focusedFile={activeDiff()}
- 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>
- </div>
+ {reviewPanel()}
</Show>
</div>