diff options
| author | Dax <[email protected]> | 2026-04-07 19:48:23 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-07 19:48:23 -0400 |
| commit | b7fab49b64275b83bcec8200d7492fc5d15ffe06 (patch) | |
| tree | d9dadf65ca69eb4b8fe75654eb15666ee2b23774 /packages/app/src | |
| parent | 463318486f94fa20e8d864d77708a347fa8423e3 (diff) | |
| download | opencode-b7fab49b64275b83bcec8200d7492fc5d15ffe06.tar.gz opencode-b7fab49b64275b83bcec8200d7492fc5d15ffe06.zip | |
refactor(snapshot): store unified patches in file diffs (#21244)
Co-authored-by: Adam <[email protected]>
Diffstat (limited to 'packages/app/src')
7 files changed, 49 insertions, 61 deletions
diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index 4af636553..01248e20e 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -1,7 +1,6 @@ import { Binary } from "@opencode-ai/util/binary" import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store" import type { - FileDiff, Message, Part, PermissionRequest, @@ -9,6 +8,7 @@ import type { QuestionRequest, Session, SessionStatus, + SnapshotFileDiff, Todo, } from "@opencode-ai/sdk/v2/client" import type { State, VcsCache } from "./types" @@ -161,7 +161,7 @@ export function applyDirectoryEvent(input: { break } case "session.diff": { - const props = event.properties as { sessionID: string; diff: FileDiff[] } + const props = event.properties as { sessionID: string; diff: SnapshotFileDiff[] } input.setStore("session_diff", props.sessionID, reconcile(props.diff, { key: "file" })) break } diff --git a/packages/app/src/context/global-sync/session-cache.test.ts b/packages/app/src/context/global-sync/session-cache.test.ts index 8e11110e3..472ac219e 100644 --- a/packages/app/src/context/global-sync/session-cache.test.ts +++ b/packages/app/src/context/global-sync/session-cache.test.ts @@ -1,11 +1,11 @@ import { describe, expect, test } from "bun:test" import type { - FileDiff, Message, Part, PermissionRequest, QuestionRequest, SessionStatus, + SnapshotFileDiff, Todo, } from "@opencode-ai/sdk/v2/client" import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache" @@ -33,7 +33,7 @@ describe("app session cache", () => { test("dropSessionCaches clears orphaned parts without message rows", () => { const store: { session_status: Record<string, SessionStatus | undefined> - session_diff: Record<string, FileDiff[] | undefined> + session_diff: Record<string, SnapshotFileDiff[] | undefined> todo: Record<string, Todo[] | undefined> message: Record<string, Message[] | undefined> part: Record<string, Part[] | undefined> @@ -64,7 +64,7 @@ describe("app session cache", () => { const m = msg("msg_1", "ses_1") const store: { session_status: Record<string, SessionStatus | undefined> - session_diff: Record<string, FileDiff[] | undefined> + session_diff: Record<string, SnapshotFileDiff[] | undefined> todo: Record<string, Todo[] | undefined> message: Record<string, Message[] | undefined> part: Record<string, Part[] | undefined> diff --git a/packages/app/src/context/global-sync/session-cache.ts b/packages/app/src/context/global-sync/session-cache.ts index 0177ebbe1..6f4d81062 100644 --- a/packages/app/src/context/global-sync/session-cache.ts +++ b/packages/app/src/context/global-sync/session-cache.ts @@ -1,10 +1,10 @@ import type { - FileDiff, Message, Part, PermissionRequest, QuestionRequest, SessionStatus, + SnapshotFileDiff, Todo, } from "@opencode-ai/sdk/v2/client" @@ -12,7 +12,7 @@ export const SESSION_CACHE_LIMIT = 40 type SessionCache = { session_status: Record<string, SessionStatus | undefined> - session_diff: Record<string, FileDiff[] | undefined> + session_diff: Record<string, SnapshotFileDiff[] | undefined> todo: Record<string, Todo[] | undefined> message: Record<string, Message[] | undefined> part: Record<string, Part[] | undefined> diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index 1d6e550f8..b0f340a90 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -2,7 +2,6 @@ import type { Agent, Command, Config, - FileDiff, LspStatus, McpStatus, Message, @@ -14,6 +13,7 @@ import type { QuestionRequest, Session, SessionStatus, + SnapshotFileDiff, Todo, VcsInfo, } from "@opencode-ai/sdk/v2/client" @@ -48,7 +48,7 @@ export type State = { [sessionID: string]: SessionStatus } session_diff: { - [sessionID: string]: FileDiff[] + [sessionID: string]: SnapshotFileDiff[] } todo: { [sessionID: string]: Todo[] diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 0c6764726..cf50fbe90 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, VcsFileDiff } from "@opencode-ai/sdk/v2" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useMutation } from "@tanstack/solid-query" import { @@ -68,7 +68,7 @@ type FollowupItem = FollowupDraft & { id: string } type FollowupEdit = Pick<FollowupItem, "id" | "prompt" | "context"> const emptyFollowups: FollowupItem[] = [] -type ChangeMode = "git" | "branch" | "session" | "turn" +type ChangeMode = "git" | "branch" | "turn" type VcsMode = "git" | "branch" type SessionHistoryWindowInput = { @@ -463,13 +463,6 @@ 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[], emptyUserMessages, @@ -527,10 +520,19 @@ export default function Page() { deferRender: false, }) - const [vcs, setVcs] = createStore({ + const [vcs, setVcs] = createStore<{ + diff: { + git: VcsFileDiff[] + branch: VcsFileDiff[] + } + ready: { + git: boolean + branch: boolean + } + }>({ diff: { - git: [] as FileDiff[], - branch: [] as FileDiff[], + git: [] as VcsFileDiff[], + branch: [] as VcsFileDiff[], }, ready: { git: false, @@ -648,6 +650,7 @@ export default function Page() { }, desktopReviewOpen()) const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? []) + const nogit = createMemo(() => !!sync.project && sync.project.vcs !== "git") const changesOptions = createMemo<ChangeMode[]>(() => { const list: ChangeMode[] = [] if (sync.project?.vcs === "git") list.push("git") @@ -659,7 +662,7 @@ export default function Page() { ) { list.push("branch") } - list.push("session", "turn") + list.push("turn") return list }) const vcsMode = createMemo<VcsMode | undefined>(() => { @@ -668,20 +671,17 @@ export default function Page() { 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 }) @@ -749,13 +749,6 @@ export default function Page() { scrollToMessage(msgs[targetIndex], "auto") } - 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" - return "session.review.empty" - }) - function upsert(next: Project) { const list = globalSync.data.project sync.set("project", next.id) @@ -1156,7 +1149,6 @@ export default function Page() { 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") } @@ -1179,11 +1171,26 @@ export default function Page() { </div> ) + const createGit = (input: { emptyClass: string }) => ( + <div class={input.emptyClass}> + <div class="flex flex-col gap-3"> + <div class="text-14-medium text-text-strong">{language.t("session.review.noVcs.createGit.title")}</div> + <div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}> + {language.t("session.review.noVcs.createGit.description")} + </div> + </div> + <Button size="large" disabled={gitMutation.isPending} onClick={initGit}> + {gitMutation.isPending + ? language.t("session.review.noVcs.createGit.actionLoading") + : language.t("session.review.noVcs.createGit.action")} + </Button> + </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()) + return language.t("session.review.noChanges") }) const reviewEmpty = (input: { loadingClass: string; emptyClass: string }) => { @@ -1193,31 +1200,10 @@ export default function Page() { } if (store.changes === "turn") { + if (nogit()) return createGit(input) return empty(reviewEmptyText()) } - if (hasSessionReview() && !diffsReady()) { - return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div> - } - - if (sessionEmptyKey() === "session.review.noVcs") { - return ( - <div class={input.emptyClass}> - <div class="flex flex-col gap-3"> - <div class="text-14-medium text-text-strong">{language.t("session.review.noVcs.createGit.title")}</div> - <div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}> - {language.t("session.review.noVcs.createGit.description")} - </div> - </div> - <Button size="large" disabled={gitMutation.isPending} onClick={initGit}> - {gitMutation.isPending - ? language.t("session.review.noVcs.createGit.actionLoading") - : language.t("session.review.noVcs.createGit.action")} - </Button> - </div> - ) - } - return ( <div class={input.emptyClass}> <div class="text-14-regular text-text-weak max-w-56">{reviewEmptyText()}</div> diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx index b68128645..71dfe375e 100644 --- a/packages/app/src/pages/session/review-tab.tsx +++ b/packages/app/src/pages/session/review-tab.tsx @@ -1,6 +1,6 @@ import { createEffect, createSignal, onCleanup, type JSX } from "solid-js" import { makeEventListener } from "@solid-primitives/event-listener" -import type { FileDiff } from "@opencode-ai/sdk/v2" +import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import { SessionReview } from "@opencode-ai/ui/session-review" import type { SessionReviewCommentActions, @@ -14,10 +14,12 @@ import type { LineComment } from "@/context/comments" export type DiffStyle = "unified" | "split" +type ReviewDiff = SnapshotFileDiff | VcsFileDiff + export interface SessionReviewTabProps { title?: JSX.Element empty?: JSX.Element - diffs: () => FileDiff[] + diffs: () => ReviewDiff[] view: () => ReturnType<ReturnType<typeof useLayout>["view"]> diffStyle: DiffStyle onDiffStyleChange?: (style: DiffStyle) => void diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index 86f932ea2..cddbea84d 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -8,7 +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 type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" import { useDialog } from "@opencode-ai/ui/context/dialog" @@ -27,7 +27,7 @@ import { useSessionLayout } from "@/pages/session/session-layout" export function SessionSidePanel(props: { canReview: () => boolean - diffs: () => FileDiff[] + diffs: () => (SnapshotFileDiff | VcsFileDiff)[] diffsReady: () => boolean empty: () => string hasReview: () => boolean |
