diff options
| author | Aiden Cline <[email protected]> | 2026-03-19 11:15:07 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-19 11:15:07 -0500 |
| commit | aeece6166b7e728440f1a3c81aa7efcc32208f01 (patch) | |
| tree | c5d2666b61e5fe79fc6739e10b873add67d39b02 /packages/app/src/pages | |
| parent | 0d7e62a532ba9f2163f872414c52a213c517936a (diff) | |
| download | opencode-aeece6166b7e728440f1a3c81aa7efcc32208f01.tar.gz opencode-aeece6166b7e728440f1a3c81aa7efcc32208f01.zip | |
ignore: revert 3 commits that broke dev branch (#18260)
Diffstat (limited to 'packages/app/src/pages')
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 6 | ||||
| -rw-r--r-- | packages/app/src/pages/layout/helpers.ts | 6 | ||||
| -rw-r--r-- | packages/app/src/pages/session.tsx | 228 | ||||
| -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 |
5 files changed, 81 insertions, 223 deletions
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 52ac7c5f3..cca14fd50 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -633,8 +633,7 @@ export default function Layout(props: ParentProps) { if (!expanded) continue const key = workspaceKey(directory) const project = projects.find( - (item) => - workspaceKey(item.worktree) === key || item.sandboxes?.some((sandbox) => workspaceKey(sandbox) === key), + (item) => workspaceKey(item.worktree) === key || item.sandboxes?.some((sandbox) => workspaceKey(sandbox) === key), ) if (!project) continue if (project.vcs === "git" && layout.sidebar.workspaces(project.worktree)()) continue @@ -1164,8 +1163,7 @@ export default function Layout(props: ParentProps) { const project = layout.projects .list() .find( - (item) => - workspaceKey(item.worktree) === key || item.sandboxes?.some((sandbox) => workspaceKey(sandbox) === key), + (item) => workspaceKey(item.worktree) === key || item.sandboxes?.some((sandbox) => workspaceKey(sandbox) === key), ) if (project) return project.worktree diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index 209cff8a7..886ffd26a 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -31,13 +31,11 @@ function sortSessions(now: number) { const isRootVisibleSession = (session: Session, directory: string) => workspaceKey(session.directory) === workspaceKey(directory) && !session.parentID && !session.time?.archived -const roots = (store: SessionStore) => - (store.session ?? []).filter((session) => isRootVisibleSession(session, store.path.directory)) +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: SessionStore[], now: number) => - stores.flatMap(roots).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>, diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 970bc73b7..6d2917008 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,4 +1,4 @@ -import type { FileDiff, Project, UserMessage } from "@opencode-ai/sdk/v2" +import type { Project, UserMessage } from "@opencode-ai/sdk/v2" import { useDialog } from "@opencode-ai/ui/context/dialog" import { batch, @@ -57,9 +57,6 @@ import { formatServerError } from "@/utils/server-errors" const emptyUserMessages: UserMessage[] = [] const emptyFollowups: (FollowupDraft & { id: string })[] = [] -type ChangeMode = "git" | "branch" | "session" | "turn" -type VcsMode = "git" | "branch" - type SessionHistoryWindowInput = { sessionID: () => string | undefined messagesReady: () => boolean @@ -418,16 +415,15 @@ 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 sessionCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length)) - const hasSessionReview = createMemo(() => sessionCount() > 0) - const canReview = createMemo(() => !!params.dir) + const reviewCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length)) + const hasReview = createMemo(() => reviewCount() > 0) const reviewTab = createMemo(() => isDesktop()) const tabState = createSessionTabs({ tabs, pathFromTab: file.pathFromTab, normalizeTab, review: reviewTab, - hasReview: canReview, + hasReview, }) const contextOpen = tabState.contextOpen const openedTabs = tabState.openedTabs @@ -503,22 +499,11 @@ export default function Page() { const [store, setStore] = createStore({ messageId: undefined as string | undefined, mobileTab: "session" as "session" | "changes", - changes: "git" as ChangeMode, + changes: "session" as "session" | "turn", newSessionWorktree: "main", deferRender: false, }) - const [vcs, setVcs] = createStore({ - diff: { - git: [] as FileDiff[], - branch: [] as FileDiff[], - }, - ready: { - git: false, - branch: false, - }, - }) - const [followup, setFollowup] = createStore({ items: {} as Record<string, (FollowupDraft & { id: string })[] | undefined>, sending: {} as Record<string, string | undefined>, @@ -546,40 +531,6 @@ export default function Page() { let refreshTimer: number | undefined let diffFrame: number | undefined let diffTimer: number | undefined - const vcsTask = new Map<VcsMode, Promise<void>>() - - const resetVcs = () => { - vcsTask.clear() - setVcs({ - diff: { git: [], branch: [] }, - ready: { git: false, branch: false }, - }) - } - - const loadVcs = (mode: VcsMode, force = false) => { - if (sync.project?.vcs !== "git") return Promise.resolve() - if (vcs.ready[mode] && !force) return Promise.resolve() - const current = vcsTask.get(mode) - if (current) return current - - const task = sdk.client.vcs - .diff({ mode }) - .then((result) => { - setVcs("diff", mode, result.data ?? []) - setVcs("ready", mode, true) - }) - .catch((error) => { - console.debug("[session-review] failed to load vcs diff", { mode, error }) - setVcs("diff", mode, []) - setVcs("ready", mode, true) - }) - .finally(() => { - vcsTask.delete(mode) - }) - - vcsTask.set(mode, task) - return task - } createComputed((prev) => { const open = desktopReviewOpen() @@ -595,43 +546,7 @@ export default function Page() { }, desktopReviewOpen()) const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? []) - const changesOptions = createMemo<ChangeMode[]>(() => { - const list: ChangeMode[] = [] - const git = sync.project?.vcs === "git" - if (git) list.push("git") - if ( - 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 reviewDiffs = createMemo(() => (store.changes === "session" ? diffs() : turnDiffs())) const newSessionWorktree = createMemo(() => { if (store.newSessionWorktree === "create") return "create" @@ -700,10 +615,10 @@ export default function Page() { const diffsReady = createMemo(() => { const id = params.id if (!id) return true - if (!hasSessionReview()) return true + if (!hasReview()) return true return sync.data.session_diff[id] !== undefined }) - const sessionEmptyKey = createMemo(() => { + const reviewEmptyKey = createMemo(() => { const project = sync.project if (project && !project.vcs) return "session.review.noVcs" if (sync.data.config.snapshot === false) return "session.review.noSnapshot" @@ -826,7 +741,7 @@ export default function Page() { sessionKey, () => { setStore("messageId", undefined) - setStore("changes", "git") + setStore("changes", "session") setUi("pendingMessage", undefined) }, { defer: true }, @@ -835,16 +750,6 @@ export default function Page() { createEffect( on( - () => sdk.directory, - () => { - resetVcs() - }, - { defer: true }, - ), - ) - - createEffect( - on( () => params.dir, (dir) => { if (!dir) return @@ -965,40 +870,6 @@ 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) @@ -1045,23 +916,21 @@ export default function Page() { loadFile: file.load, }) + const changesOptions = ["session", "turn"] as const + const changesOptionsList = [...changesOptions] + const changesTitle = () => { - if (!canReview()) { + if (!hasReview()) { 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={changesOptions()} + options={changesOptionsList} current={store.changes} - label={label} + label={(option) => + option === "session" ? language.t("ui.sessionReview.title") : language.t("ui.sessionReview.title.lastTurn") + } onSelect={(option) => option && setStore("changes", option)} variant="ghost" size="small" @@ -1070,34 +939,20 @@ export default function Page() { ) } - const empty = (text: string) => ( + const emptyTurn = () => ( <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">{text}</div> + <div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.noChanges")}</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 === "git" || store.changes === "branch") { - if (!reviewReady()) return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div> - return empty(reviewEmptyText()) - } - - if (store.changes === "turn") { - return empty(reviewEmptyText()) - } + if (store.changes === "turn") return emptyTurn() - if (hasSessionReview() && !diffsReady()) { + if (hasReview() && !diffsReady()) { return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div> } - if (sessionEmptyKey() === "session.review.noVcs") { + if (reviewEmptyKey() === "session.review.noVcs") { return ( <div class={input.emptyClass}> <div class="flex flex-col gap-3"> @@ -1117,7 +972,7 @@ export default function Page() { return ( <div class={input.emptyClass}> - <div class="text-14-regular text-text-weak max-w-56">{reviewEmptyText()}</div> + <div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div> </div> ) } @@ -1165,18 +1020,6 @@ export default function Page() { </div> ) - const mobileReview = () => - reviewContent({ - diffStyle: "unified", - classes: { - root: "pb-8", - header: "px-4", - container: "px-4", - }, - loadingClass: "px-4 py-4 text-text-weak", - emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6", - }) - createEffect( on( activeFileTab, @@ -1233,7 +1076,7 @@ export default function Page() { const pending = tree.pendingDiff if (!pending) return if (!tree.reviewScroll) return - if (!reviewReady()) return + if (!diffsReady()) return const attempt = (count: number) => { if (tree.pendingDiff !== pending) return @@ -1810,7 +1653,7 @@ 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"> - <Show when={!isDesktop() && canReview()}> + <Show when={!isDesktop() && !!params.id}> <Tabs value={store.mobileTab} class="h-auto"> <Tabs.List> <Tabs.Trigger @@ -1852,7 +1695,16 @@ export default function Page() { <Show when={lastUserMessage()}> <MessageTimeline mobileChanges={mobileChanges()} - mobileFallback={mobileReview()} + mobileFallback={reviewContent({ + diffStyle: "unified", + classes: { + root: "pb-8", + header: "px-4", + container: "px-4", + }, + loadingClass: "px-4 py-4 text-text-weak", + emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6", + })} actions={actions} scroll={ui.scroll} onResumeScroll={resumeScroll} @@ -1884,9 +1736,7 @@ export default function Page() { </Show> </Match> <Match when={true}> - <Show when={mobileChanges()} fallback={<NewSessionView worktree={newSessionWorktree()} />}> - <div class="relative h-full overflow-hidden">{mobileReview()}</div> - </Show> + <NewSessionView worktree={newSessionWorktree()} /> </Match> </Switch> </div> @@ -1958,12 +1808,6 @@ 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 3ba619736..3b8b0c96b 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -8,7 +8,6 @@ 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" @@ -20,6 +19,7 @@ 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" @@ -27,12 +27,6 @@ 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 @@ -40,11 +34,12 @@ 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 { sessionKey, tabs, view } = useSessionLayout() + const { params, sessionKey, tabs, view } = useSessionLayout() const isDesktop = createMediaQuery("(min-width: 768px)") @@ -59,7 +54,24 @@ export function SessionSidePanel(props: { }) const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px")) - const diffFiles = createMemo(() => props.diffs().map((d) => d.file)) + 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 kinds = createMemo(() => { const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => { if (!a) return b @@ -70,7 +82,7 @@ export function SessionSidePanel(props: { const normalize = (p: string) => p.replaceAll("\\\\", "/").replace(/\/+$/, "") const out = new Map<string, "add" | "del" | "mix">() - for (const diff of props.diffs()) { + for (const diff of diffs()) { const file = normalize(diff.file) const kind = diff.status === "added" ? "add" : diff.status === "deleted" ? "del" : "mix" @@ -124,7 +136,7 @@ export function SessionSidePanel(props: { pathFromTab: file.pathFromTab, normalizeTab, review: reviewTab, - hasReview: props.canReview, + hasReview, }) const contextOpen = tabState.contextOpen const openedTabs = tabState.openedTabs @@ -229,12 +241,12 @@ export function SessionSidePanel(props: { onCleanup(stop) }} > - <Show when={reviewTab() && props.canReview()}> + <Show when={reviewTab()}> <Tabs.Trigger value="review"> <div class="flex items-center gap-1.5"> <div>{language.t("session.tab.review")}</div> - <Show when={props.hasReview()}> - <div>{props.reviewCount()}</div> + <Show when={hasReview()}> + <div>{reviewCount()}</div> </Show> </div> </Tabs.Trigger> @@ -291,7 +303,7 @@ export function SessionSidePanel(props: { </Tabs.List> </div> - <Show when={reviewTab() && props.canReview()}> + <Show when={reviewTab()}> <Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict"> <Show when={activeTab() === "review"}>{props.reviewPanel()}</Show> </Tabs.Content> @@ -365,10 +377,8 @@ export function SessionSidePanel(props: { > <Tabs.List> <Tabs.Trigger value="changes" class="flex-1" classes={{ button: "w-full" }}> - {props.reviewCount()}{" "} - {language.t( - props.reviewCount() === 1 ? "session.review.change.one" : "session.review.change.other", - )} + {reviewCount()}{" "} + {language.t(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")} @@ -376,9 +386,9 @@ export function SessionSidePanel(props: { </Tabs.List> <Tabs.Content value="changes" class="bg-background-stronger px-3 py-0"> <Switch> - <Match when={props.hasReview() || !props.diffsReady()}> + <Match when={hasReview()}> <Show - when={props.diffsReady()} + when={diffsReady()} fallback={ <div class="px-2 py-2 text-12-regular text-text-weak"> {language.t("common.loading")} @@ -397,7 +407,11 @@ export function SessionSidePanel(props: { /> </Show> </Match> - <Match when={true}>{empty(props.empty())}</Match> + <Match when={true}> + {empty( + language.t(sync.project && !sync.project.vcs ? "session.review.noChanges" : reviewEmptyKey()), + )} + </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 f45374359..1a2e777f5 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -56,7 +56,11 @@ export const useSessionCommands = (actions: SessionCommandContext) => { if (!id) return return sync.session.get(id) } - const hasReview = () => !!params.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 normalizeTab = (tab: string) => { if (!tab.startsWith("file://")) return tab return file.tab(tab) |
