diff options
| author | Shoubhit Dash <[email protected]> | 2026-04-03 20:24:57 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-03 09:54:57 -0500 |
| commit | 35350b1d25a56665cf065eba68929fc00617fdd2 (patch) | |
| tree | 91bf53b5d87ff9532ebf0a779c4dbe7bc99148f3 /packages/app/src | |
| parent | 263dcf75b548810a149f08ea5e32e0f6754128d5 (diff) | |
| download | opencode-35350b1d25a56665cf065eba68929fc00617fdd2.tar.gz opencode-35350b1d25a56665cf065eba68929fc00617fdd2.zip | |
feat: restore git-backed review modes (#20845)
Diffstat (limited to 'packages/app/src')
| -rw-r--r-- | packages/app/src/context/global-sync/bootstrap.ts | 2 | ||||
| -rw-r--r-- | packages/app/src/context/global-sync/event-reducer.test.ts | 10 | ||||
| -rw-r--r-- | packages/app/src/context/global-sync/event-reducer.ts | 4 | ||||
| -rw-r--r-- | packages/app/src/i18n/en.ts | 2 | ||||
| -rw-r--r-- | packages/app/src/pages/session.tsx | 272 | ||||
| -rw-r--r-- | packages/app/src/pages/session/session-side-panel.tsx | 58 | ||||
| -rw-r--r-- | packages/app/src/pages/session/use-session-commands.tsx | 6 |
7 files changed, 266 insertions, 88 deletions
diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index cf104ad97..7edd5a1ce 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -248,7 +248,7 @@ export async function bootstrapDirectory(input: { input.sdk.vcs.get().then((x) => { const next = x.data ?? input.store.vcs input.setStore("vcs", next) - if (next?.branch) input.vcsCache.setStore("value", next) + if (next) input.vcsCache.setStore("value", next) }), ), () => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))), diff --git a/packages/app/src/context/global-sync/event-reducer.test.ts b/packages/app/src/context/global-sync/event-reducer.test.ts index cf2da135c..892129788 100644 --- a/packages/app/src/context/global-sync/event-reducer.test.ts +++ b/packages/app/src/context/global-sync/event-reducer.test.ts @@ -494,8 +494,10 @@ describe("applyDirectoryEvent", () => { }) test("updates vcs branch in store and cache", () => { - const [store, setStore] = createStore(baseState()) - const [cacheStore, setCacheStore] = createStore({ value: undefined as State["vcs"] }) + const [store, setStore] = createStore(baseState({ vcs: { branch: "main", default_branch: "main" } })) + const [cacheStore, setCacheStore] = createStore({ + value: { branch: "main", default_branch: "main" } as State["vcs"], + }) applyDirectoryEvent({ event: { type: "vcs.branch.updated", properties: { branch: "feature/test" } }, @@ -511,8 +513,8 @@ describe("applyDirectoryEvent", () => { }, }) - expect(store.vcs).toEqual({ branch: "feature/test" }) - expect(cacheStore.value).toEqual({ branch: "feature/test" }) + expect(store.vcs).toEqual({ branch: "feature/test", default_branch: "main" }) + expect(cacheStore.value).toEqual({ branch: "feature/test", default_branch: "main" }) }) test("routes disposal and lsp events to side-effect handlers", () => { diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index 5d8b7c4e3..4af636553 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -271,9 +271,9 @@ export function applyDirectoryEvent(input: { break } case "vcs.branch.updated": { - const props = event.properties as { branch: string } + const props = event.properties as { branch?: string } if (input.store.vcs?.branch === props.branch) break - const next = { branch: props.branch } + const next = { ...input.store.vcs, branch: props.branch } input.setStore("vcs", next) if (input.vcsCache) input.vcsCache.setStore("value", next) break diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 39317b8d6..ace0efeb8 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -535,6 +535,8 @@ export const dict = { "session.review.noVcs.createGit.action": "Create Git repository", "session.review.noSnapshot": "Snapshot tracking is disabled in config, so session changes are unavailable", "session.review.noChanges": "No changes", + "session.review.noUncommittedChanges": "No uncommitted changes yet", + "session.review.noBranchChanges": "No branch changes yet", "session.files.selectToOpen": "Select a file to open", "session.files.all": "All files", diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index ae895adbe..a81df9dd2 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,4 +1,4 @@ -import type { Project, UserMessage } from "@opencode-ai/sdk/v2" +import type { FileDiff, Project, UserMessage } from "@opencode-ai/sdk/v2" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useMutation } from "@tanstack/solid-query" import { @@ -68,6 +68,9 @@ type FollowupItem = FollowupDraft & { id: string } type FollowupEdit = Pick<FollowupItem, "id" | "prompt" | "context"> const emptyFollowups: FollowupItem[] = [] +type ChangeMode = "git" | "branch" | "session" | "turn" +type VcsMode = "git" | "branch" + type SessionHistoryWindowInput = { sessionID: () => string | undefined messagesReady: () => boolean @@ -427,15 +430,16 @@ export default function Page() { const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : [])) - const reviewCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length)) - const hasReview = createMemo(() => reviewCount() > 0) + const sessionCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length)) + const hasSessionReview = createMemo(() => sessionCount() > 0) + const canReview = createMemo(() => !!sync.project) const reviewTab = createMemo(() => isDesktop()) const tabState = createSessionTabs({ tabs, pathFromTab: file.pathFromTab, normalizeTab, review: reviewTab, - hasReview, + hasReview: canReview, }) const contextOpen = tabState.contextOpen const openedTabs = tabState.openedTabs @@ -458,6 +462,12 @@ export default function Page() { if (!id) return false return sync.session.history.loading(id) }) + const diffsReady = createMemo(() => { + const id = params.id + if (!id) return true + if (!hasSessionReview()) return true + return sync.data.session_diff[id] !== undefined + }) const userMessages = createMemo( () => messages().filter((m) => m.role === "user") as UserMessage[], @@ -511,11 +521,22 @@ export default function Page() { const [store, setStore] = createStore({ messageId: undefined as string | undefined, mobileTab: "session" as "session" | "changes", - changes: "session" as "session" | "turn", + changes: "git" as ChangeMode, newSessionWorktree: "main", deferRender: false, }) + const [vcs, setVcs] = createStore({ + diff: { + git: [] as FileDiff[], + branch: [] as FileDiff[], + }, + ready: { + git: false, + branch: false, + }, + }) + const [followup, setFollowup] = persisted( Persist.workspace(sdk.directory, "followup", ["followup.v1"]), createStore<{ @@ -549,6 +570,68 @@ export default function Page() { let todoTimer: number | undefined let diffFrame: number | undefined let diffTimer: number | undefined + const vcsTask = new Map<VcsMode, Promise<void>>() + const vcsRun = new Map<VcsMode, number>() + + const bumpVcs = (mode: VcsMode) => { + const next = (vcsRun.get(mode) ?? 0) + 1 + vcsRun.set(mode, next) + return next + } + + const resetVcs = (mode?: VcsMode) => { + const list = mode ? [mode] : (["git", "branch"] as const) + list.forEach((item) => { + bumpVcs(item) + vcsTask.delete(item) + setVcs("diff", item, []) + setVcs("ready", item, false) + }) + } + + const loadVcs = (mode: VcsMode, force = false) => { + if (sync.project?.vcs !== "git") return Promise.resolve() + if (!force && vcs.ready[mode]) return Promise.resolve() + + if (force) { + if (vcsTask.has(mode)) bumpVcs(mode) + vcsTask.delete(mode) + setVcs("ready", mode, false) + } + + const current = vcsTask.get(mode) + if (current) return current + + const run = bumpVcs(mode) + + const task = sdk.client.vcs + .diff({ mode }) + .then((result) => { + if (vcsRun.get(mode) !== run) return + setVcs("diff", mode, result.data ?? []) + setVcs("ready", mode, true) + }) + .catch((error) => { + if (vcsRun.get(mode) !== run) return + console.debug("[session-review] failed to load vcs diff", { mode, error }) + setVcs("diff", mode, []) + setVcs("ready", mode, true) + }) + .finally(() => { + if (vcsTask.get(mode) === task) vcsTask.delete(mode) + }) + + vcsTask.set(mode, task) + return task + } + + const refreshVcs = () => { + resetVcs() + const mode = untrack(vcsMode) + if (!mode) return + if (!untrack(wantsReview)) return + void loadVcs(mode, true) + } createComputed((prev) => { const open = desktopReviewOpen() @@ -564,7 +647,42 @@ export default function Page() { }, desktopReviewOpen()) const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? []) - const reviewDiffs = createMemo(() => (store.changes === "session" ? diffs() : turnDiffs())) + const changesOptions = createMemo<ChangeMode[]>(() => { + const list: ChangeMode[] = [] + if (sync.project?.vcs === "git") list.push("git") + if ( + sync.project?.vcs === "git" && + sync.data.vcs?.branch && + sync.data.vcs?.default_branch && + sync.data.vcs.branch !== sync.data.vcs.default_branch + ) { + list.push("branch") + } + list.push("session", "turn") + return list + }) + const vcsMode = createMemo<VcsMode | undefined>(() => { + if (store.changes === "git" || store.changes === "branch") return store.changes + }) + const reviewDiffs = createMemo(() => { + if (store.changes === "git") return vcs.diff.git + if (store.changes === "branch") return vcs.diff.branch + if (store.changes === "session") return diffs() + return turnDiffs() + }) + const reviewCount = createMemo(() => { + if (store.changes === "git") return vcs.diff.git.length + if (store.changes === "branch") return vcs.diff.branch.length + if (store.changes === "session") return sessionCount() + return turnDiffs().length + }) + const hasReview = createMemo(() => reviewCount() > 0) + const reviewReady = createMemo(() => { + if (store.changes === "git") return vcs.ready.git + if (store.changes === "branch") return vcs.ready.branch + if (store.changes === "session") return !hasSessionReview() || diffsReady() + return true + }) const newSessionWorktree = createMemo(() => { if (store.newSessionWorktree === "create") return "create" @@ -630,13 +748,7 @@ export default function Page() { scrollToMessage(msgs[targetIndex], "auto") } - const diffsReady = createMemo(() => { - const id = params.id - if (!id) return true - if (!hasReview()) return true - return sync.data.session_diff[id] !== undefined - }) - const reviewEmptyKey = createMemo(() => { + const sessionEmptyKey = createMemo(() => { const project = sync.project if (project && !project.vcs) return "session.review.noVcs" if (sync.data.config.snapshot === false) return "session.review.noSnapshot" @@ -790,7 +902,7 @@ export default function Page() { sessionKey, () => { setStore("messageId", undefined) - setStore("changes", "session") + setStore("changes", "git") setUi("pendingMessage", undefined) }, { defer: true }, @@ -799,6 +911,39 @@ export default function Page() { createEffect( on( + () => sdk.directory, + () => { + resetVcs() + }, + { defer: true }, + ), + ) + + createEffect( + on( + () => [sync.data.vcs?.branch, sync.data.vcs?.default_branch] as const, + (next, prev) => { + if (prev === undefined || same(next, prev)) return + refreshVcs() + }, + { defer: true }, + ), + ) + + const stopVcs = sdk.event.listen((evt) => { + if (evt.details.type !== "file.watcher.updated") return + const props = + typeof evt.details.properties === "object" && evt.details.properties + ? (evt.details.properties as Record<string, unknown>) + : undefined + const file = typeof props?.file === "string" ? props.file : undefined + if (!file || file.startsWith(".git/")) return + refreshVcs() + }) + onCleanup(stopVcs) + + createEffect( + on( () => params.dir, (dir) => { if (!dir) return @@ -919,6 +1064,40 @@ export default function Page() { } const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes") + const wantsReview = createMemo(() => + isDesktop() + ? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review") + : store.mobileTab === "changes", + ) + + createEffect(() => { + const list = changesOptions() + if (list.includes(store.changes)) return + const next = list[0] + if (!next) return + setStore("changes", next) + }) + + createEffect(() => { + const mode = vcsMode() + if (!mode) return + if (!wantsReview()) return + void loadVcs(mode) + }) + + createEffect( + on( + () => sync.data.session_status[params.id ?? ""]?.type, + (next, prev) => { + const mode = vcsMode() + if (!mode) return + if (!wantsReview()) return + if (next !== "idle" || prev === undefined || prev === "idle") return + void loadVcs(mode, true) + }, + { defer: true }, + ), + ) const fileTreeTab = () => layout.fileTree.tab() const setFileTreeTab = (value: "changes" | "all") => layout.fileTree.setTab(value) @@ -965,21 +1144,23 @@ export default function Page() { loadFile: file.load, }) - const changesOptions = ["session", "turn"] as const - const changesOptionsList = [...changesOptions] - const changesTitle = () => { - if (!hasReview()) { + if (!canReview()) { return null } + const label = (option: ChangeMode) => { + if (option === "git") return language.t("ui.sessionReview.title.git") + if (option === "branch") return language.t("ui.sessionReview.title.branch") + if (option === "session") return language.t("ui.sessionReview.title") + return language.t("ui.sessionReview.title.lastTurn") + } + return ( <Select - options={changesOptionsList} + options={changesOptions()} current={store.changes} - label={(option) => - option === "session" ? language.t("ui.sessionReview.title") : language.t("ui.sessionReview.title.lastTurn") - } + label={label} onSelect={(option) => option && setStore("changes", option)} variant="ghost" size="small" @@ -988,20 +1169,34 @@ export default function Page() { ) } - const emptyTurn = () => ( + const empty = (text: string) => ( <div class="h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6"> - <div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.noChanges")}</div> + <div class="text-14-regular text-text-weak max-w-56">{text}</div> </div> ) + const reviewEmptyText = createMemo(() => { + if (store.changes === "git") return language.t("session.review.noUncommittedChanges") + if (store.changes === "branch") return language.t("session.review.noBranchChanges") + if (store.changes === "turn") return language.t("session.review.noChanges") + return language.t(sessionEmptyKey()) + }) + const reviewEmpty = (input: { loadingClass: string; emptyClass: string }) => { - if (store.changes === "turn") return emptyTurn() + if (store.changes === "git" || store.changes === "branch") { + if (!reviewReady()) return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div> + return empty(reviewEmptyText()) + } - if (hasReview() && !diffsReady()) { + if (store.changes === "turn") { + return empty(reviewEmptyText()) + } + + if (hasSessionReview() && !diffsReady()) { return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div> } - if (reviewEmptyKey() === "session.review.noVcs") { + if (sessionEmptyKey() === "session.review.noVcs") { return ( <div class={input.emptyClass}> <div class="flex flex-col gap-3"> @@ -1021,7 +1216,7 @@ export default function Page() { return ( <div class={input.emptyClass}> - <div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div> + <div class="text-14-regular text-text-weak max-w-56">{reviewEmptyText()}</div> </div> ) } @@ -1128,7 +1323,7 @@ export default function Page() { const pending = tree.pendingDiff if (!pending) return if (!tree.reviewScroll) return - if (!diffsReady()) return + if (!reviewReady()) return const attempt = (count: number) => { if (tree.pendingDiff !== pending) return @@ -1169,10 +1364,7 @@ export default function Page() { const id = params.id if (!id) return - const wants = isDesktop() - ? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review") - : store.mobileTab === "changes" - if (!wants) return + if (!wantsReview()) return if (sync.data.session_diff[id] !== undefined) return if (sync.status === "loading") return @@ -1181,13 +1373,7 @@ export default function Page() { createEffect( on( - () => - [ - sessionKey(), - isDesktop() - ? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review") - : store.mobileTab === "changes", - ] as const, + () => [sessionKey(), wantsReview()] as const, ([key, wants]) => { if (diffFrame !== undefined) cancelAnimationFrame(diffFrame) if (diffTimer !== undefined) window.clearTimeout(diffTimer) @@ -1867,6 +2053,12 @@ export default function Page() { </div> <SessionSidePanel + canReview={canReview} + diffs={reviewDiffs} + diffsReady={reviewReady} + empty={reviewEmptyText} + hasReview={hasReview} + reviewCount={reviewCount} reviewPanel={reviewPanel} activeDiff={tree.activeDiff} focusReviewDiff={focusReviewDiff} diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index c07942627..86f932ea2 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -8,6 +8,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Mark } from "@opencode-ai/ui/logo" import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd" import type { DragEvent } from "@thisbeyond/solid-dnd" +import type { FileDiff } from "@opencode-ai/sdk/v2" import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" import { useDialog } from "@opencode-ai/ui/context/dialog" @@ -18,7 +19,6 @@ import { useCommand } from "@/context/command" import { useFile, type SelectedLineRange } from "@/context/file" import { useLanguage } from "@/context/language" import { useLayout } from "@/context/layout" -import { useSync } from "@/context/sync" import { createFileTabListSync } from "@/pages/session/file-tab-scroll" import { FileTabContent } from "@/pages/session/file-tabs" import { createOpenSessionFileTab, createSessionTabs, getTabReorderIndex, type Sizing } from "@/pages/session/helpers" @@ -26,6 +26,12 @@ import { setSessionHandoff } from "@/pages/session/handoff" import { useSessionLayout } from "@/pages/session/session-layout" export function SessionSidePanel(props: { + canReview: () => boolean + diffs: () => FileDiff[] + diffsReady: () => boolean + empty: () => string + hasReview: () => boolean + reviewCount: () => number reviewPanel: () => JSX.Element activeDiff?: string focusReviewDiff: (path: string) => void @@ -33,12 +39,11 @@ export function SessionSidePanel(props: { size: Sizing }) { const layout = useLayout() - const sync = useSync() const file = useFile() const language = useLanguage() const command = useCommand() const dialog = useDialog() - const { params, sessionKey, tabs, view } = useSessionLayout() + const { sessionKey, tabs, view } = useSessionLayout() const isDesktop = createMediaQuery("(min-width: 768px)") @@ -53,24 +58,7 @@ export function SessionSidePanel(props: { }) const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px")) - const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) - const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : [])) - const reviewCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length)) - const hasReview = createMemo(() => reviewCount() > 0) - const diffsReady = createMemo(() => { - const id = params.id - if (!id) return true - if (!hasReview()) return true - return sync.data.session_diff[id] !== undefined - }) - - const reviewEmptyKey = createMemo(() => { - if (sync.project && !sync.project.vcs) return "session.review.noVcs" - if (sync.data.config.snapshot === false) return "session.review.noSnapshot" - return "session.review.noChanges" - }) - - const diffFiles = createMemo(() => diffs().map((d) => d.file)) + const diffFiles = createMemo(() => props.diffs().map((d) => d.file)) const kinds = createMemo(() => { const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => { if (!a) return b @@ -81,7 +69,7 @@ export function SessionSidePanel(props: { const normalize = (p: string) => p.replaceAll("\\\\", "/").replace(/\/+$/, "") const out = new Map<string, "add" | "del" | "mix">() - for (const diff of diffs()) { + for (const diff of props.diffs()) { const file = normalize(diff.file) const kind = diff.status === "added" ? "add" : diff.status === "deleted" ? "del" : "mix" @@ -135,7 +123,7 @@ export function SessionSidePanel(props: { pathFromTab: file.pathFromTab, normalizeTab, review: reviewTab, - hasReview, + hasReview: props.canReview, }) const contextOpen = tabState.contextOpen const openedTabs = tabState.openedTabs @@ -240,12 +228,12 @@ export function SessionSidePanel(props: { onCleanup(stop) }} > - <Show when={reviewTab()}> + <Show when={reviewTab() && props.canReview()}> <Tabs.Trigger value="review"> <div class="flex items-center gap-1.5"> <div>{language.t("session.tab.review")}</div> - <Show when={hasReview()}> - <div>{reviewCount()}</div> + <Show when={props.hasReview()}> + <div>{props.reviewCount()}</div> </Show> </div> </Tabs.Trigger> @@ -304,7 +292,7 @@ export function SessionSidePanel(props: { </Tabs.List> </div> - <Show when={reviewTab()}> + <Show when={reviewTab() && props.canReview()}> <Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict"> <Show when={activeTab() === "review"}>{props.reviewPanel()}</Show> </Tabs.Content> @@ -378,8 +366,10 @@ export function SessionSidePanel(props: { > <Tabs.List> <Tabs.Trigger value="changes" class="flex-1" classes={{ button: "w-full" }}> - {reviewCount()}{" "} - {language.t(reviewCount() === 1 ? "session.review.change.one" : "session.review.change.other")} + {props.reviewCount()}{" "} + {language.t( + props.reviewCount() === 1 ? "session.review.change.one" : "session.review.change.other", + )} </Tabs.Trigger> <Tabs.Trigger value="all" class="flex-1" classes={{ button: "w-full" }}> {language.t("session.files.all")} @@ -387,9 +377,9 @@ export function SessionSidePanel(props: { </Tabs.List> <Tabs.Content value="changes" class="bg-background-stronger px-3 py-0"> <Switch> - <Match when={hasReview()}> + <Match when={props.hasReview() || !props.diffsReady()}> <Show - when={diffsReady()} + when={props.diffsReady()} fallback={ <div class="px-2 py-2 text-12-regular text-text-weak"> {language.t("common.loading")} @@ -408,11 +398,7 @@ export function SessionSidePanel(props: { /> </Show> </Match> - <Match when={true}> - {empty( - language.t(sync.project && !sync.project.vcs ? "session.review.noChanges" : reviewEmptyKey()), - )} - </Match> + <Match when={true}>{empty(props.empty())}</Match> </Switch> </Tabs.Content> <Tabs.Content value="all" class="bg-background-stronger px-3 py-0"> diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 430fd46f8..239795373 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -52,11 +52,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { if (!id) return return sync.session.get(id) } - const hasReview = () => { - const id = params.id - if (!id) return false - return Math.max(info()?.summary?.files ?? 0, (sync.data.session_diff[id] ?? []).length) > 0 - } + const hasReview = () => !!params.id const normalizeTab = (tab: string) => { if (!tab.startsWith("file://")) return tab return file.tab(tab) |
