Skip to content

Commit b7fab49

Browse files
thdxradamdotdevin
andauthored
refactor(snapshot): store unified patches in file diffs (#21244)
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
1 parent 4633184 commit b7fab49

File tree

30 files changed

+343
-183
lines changed

30 files changed

+343
-183
lines changed

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/app/src/context/global-sync/event-reducer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { Binary } from "@opencode-ai/util/binary"
22
import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store"
33
import type {
4-
FileDiff,
54
Message,
65
Part,
76
PermissionRequest,
87
Project,
98
QuestionRequest,
109
Session,
1110
SessionStatus,
11+
SnapshotFileDiff,
1212
Todo,
1313
} from "@opencode-ai/sdk/v2/client"
1414
import type { State, VcsCache } from "./types"
@@ -161,7 +161,7 @@ export function applyDirectoryEvent(input: {
161161
break
162162
}
163163
case "session.diff": {
164-
const props = event.properties as { sessionID: string; diff: FileDiff[] }
164+
const props = event.properties as { sessionID: string; diff: SnapshotFileDiff[] }
165165
input.setStore("session_diff", props.sessionID, reconcile(props.diff, { key: "file" }))
166166
break
167167
}

packages/app/src/context/global-sync/session-cache.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { describe, expect, test } from "bun:test"
22
import type {
3-
FileDiff,
43
Message,
54
Part,
65
PermissionRequest,
76
QuestionRequest,
87
SessionStatus,
8+
SnapshotFileDiff,
99
Todo,
1010
} from "@opencode-ai/sdk/v2/client"
1111
import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache"
@@ -33,7 +33,7 @@ describe("app session cache", () => {
3333
test("dropSessionCaches clears orphaned parts without message rows", () => {
3434
const store: {
3535
session_status: Record<string, SessionStatus | undefined>
36-
session_diff: Record<string, FileDiff[] | undefined>
36+
session_diff: Record<string, SnapshotFileDiff[] | undefined>
3737
todo: Record<string, Todo[] | undefined>
3838
message: Record<string, Message[] | undefined>
3939
part: Record<string, Part[] | undefined>
@@ -64,7 +64,7 @@ describe("app session cache", () => {
6464
const m = msg("msg_1", "ses_1")
6565
const store: {
6666
session_status: Record<string, SessionStatus | undefined>
67-
session_diff: Record<string, FileDiff[] | undefined>
67+
session_diff: Record<string, SnapshotFileDiff[] | undefined>
6868
todo: Record<string, Todo[] | undefined>
6969
message: Record<string, Message[] | undefined>
7070
part: Record<string, Part[] | undefined>

packages/app/src/context/global-sync/session-cache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import type {
2-
FileDiff,
32
Message,
43
Part,
54
PermissionRequest,
65
QuestionRequest,
76
SessionStatus,
7+
SnapshotFileDiff,
88
Todo,
99
} from "@opencode-ai/sdk/v2/client"
1010

1111
export const SESSION_CACHE_LIMIT = 40
1212

1313
type SessionCache = {
1414
session_status: Record<string, SessionStatus | undefined>
15-
session_diff: Record<string, FileDiff[] | undefined>
15+
session_diff: Record<string, SnapshotFileDiff[] | undefined>
1616
todo: Record<string, Todo[] | undefined>
1717
message: Record<string, Message[] | undefined>
1818
part: Record<string, Part[] | undefined>

packages/app/src/context/global-sync/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type {
22
Agent,
33
Command,
44
Config,
5-
FileDiff,
65
LspStatus,
76
McpStatus,
87
Message,
@@ -14,6 +13,7 @@ import type {
1413
QuestionRequest,
1514
Session,
1615
SessionStatus,
16+
SnapshotFileDiff,
1717
Todo,
1818
VcsInfo,
1919
} from "@opencode-ai/sdk/v2/client"
@@ -48,7 +48,7 @@ export type State = {
4848
[sessionID: string]: SessionStatus
4949
}
5050
session_diff: {
51-
[sessionID: string]: FileDiff[]
51+
[sessionID: string]: SnapshotFileDiff[]
5252
}
5353
todo: {
5454
[sessionID: string]: Todo[]

packages/app/src/pages/session.tsx

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FileDiff, Project, UserMessage } from "@opencode-ai/sdk/v2"
1+
import type { Project, UserMessage, VcsFileDiff } from "@opencode-ai/sdk/v2"
22
import { useDialog } from "@opencode-ai/ui/context/dialog"
33
import { useMutation } from "@tanstack/solid-query"
44
import {
@@ -68,7 +68,7 @@ type FollowupItem = FollowupDraft & { id: string }
6868
type FollowupEdit = Pick<FollowupItem, "id" | "prompt" | "context">
6969
const emptyFollowups: FollowupItem[] = []
7070

71-
type ChangeMode = "git" | "branch" | "session" | "turn"
71+
type ChangeMode = "git" | "branch" | "turn"
7272
type VcsMode = "git" | "branch"
7373

7474
type SessionHistoryWindowInput = {
@@ -463,13 +463,6 @@ export default function Page() {
463463
if (!id) return false
464464
return sync.session.history.loading(id)
465465
})
466-
const diffsReady = createMemo(() => {
467-
const id = params.id
468-
if (!id) return true
469-
if (!hasSessionReview()) return true
470-
return sync.data.session_diff[id] !== undefined
471-
})
472-
473466
const userMessages = createMemo(
474467
() => messages().filter((m) => m.role === "user") as UserMessage[],
475468
emptyUserMessages,
@@ -527,10 +520,19 @@ export default function Page() {
527520
deferRender: false,
528521
})
529522

530-
const [vcs, setVcs] = createStore({
523+
const [vcs, setVcs] = createStore<{
524+
diff: {
525+
git: VcsFileDiff[]
526+
branch: VcsFileDiff[]
527+
}
528+
ready: {
529+
git: boolean
530+
branch: boolean
531+
}
532+
}>({
531533
diff: {
532-
git: [] as FileDiff[],
533-
branch: [] as FileDiff[],
534+
git: [] as VcsFileDiff[],
535+
branch: [] as VcsFileDiff[],
534536
},
535537
ready: {
536538
git: false,
@@ -648,6 +650,7 @@ export default function Page() {
648650
}, desktopReviewOpen())
649651

650652
const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? [])
653+
const nogit = createMemo(() => !!sync.project && sync.project.vcs !== "git")
651654
const changesOptions = createMemo<ChangeMode[]>(() => {
652655
const list: ChangeMode[] = []
653656
if (sync.project?.vcs === "git") list.push("git")
@@ -659,7 +662,7 @@ export default function Page() {
659662
) {
660663
list.push("branch")
661664
}
662-
list.push("session", "turn")
665+
list.push("turn")
663666
return list
664667
})
665668
const vcsMode = createMemo<VcsMode | undefined>(() => {
@@ -668,20 +671,17 @@ export default function Page() {
668671
const reviewDiffs = createMemo(() => {
669672
if (store.changes === "git") return vcs.diff.git
670673
if (store.changes === "branch") return vcs.diff.branch
671-
if (store.changes === "session") return diffs()
672674
return turnDiffs()
673675
})
674676
const reviewCount = createMemo(() => {
675677
if (store.changes === "git") return vcs.diff.git.length
676678
if (store.changes === "branch") return vcs.diff.branch.length
677-
if (store.changes === "session") return sessionCount()
678679
return turnDiffs().length
679680
})
680681
const hasReview = createMemo(() => reviewCount() > 0)
681682
const reviewReady = createMemo(() => {
682683
if (store.changes === "git") return vcs.ready.git
683684
if (store.changes === "branch") return vcs.ready.branch
684-
if (store.changes === "session") return !hasSessionReview() || diffsReady()
685685
return true
686686
})
687687

@@ -749,13 +749,6 @@ export default function Page() {
749749
scrollToMessage(msgs[targetIndex], "auto")
750750
}
751751

752-
const sessionEmptyKey = createMemo(() => {
753-
const project = sync.project
754-
if (project && !project.vcs) return "session.review.noVcs"
755-
if (sync.data.config.snapshot === false) return "session.review.noSnapshot"
756-
return "session.review.empty"
757-
})
758-
759752
function upsert(next: Project) {
760753
const list = globalSync.data.project
761754
sync.set("project", next.id)
@@ -1156,7 +1149,6 @@ export default function Page() {
11561149
const label = (option: ChangeMode) => {
11571150
if (option === "git") return language.t("ui.sessionReview.title.git")
11581151
if (option === "branch") return language.t("ui.sessionReview.title.branch")
1159-
if (option === "session") return language.t("ui.sessionReview.title")
11601152
return language.t("ui.sessionReview.title.lastTurn")
11611153
}
11621154

@@ -1179,11 +1171,26 @@ export default function Page() {
11791171
</div>
11801172
)
11811173

1174+
const createGit = (input: { emptyClass: string }) => (
1175+
<div class={input.emptyClass}>
1176+
<div class="flex flex-col gap-3">
1177+
<div class="text-14-medium text-text-strong">{language.t("session.review.noVcs.createGit.title")}</div>
1178+
<div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}>
1179+
{language.t("session.review.noVcs.createGit.description")}
1180+
</div>
1181+
</div>
1182+
<Button size="large" disabled={gitMutation.isPending} onClick={initGit}>
1183+
{gitMutation.isPending
1184+
? language.t("session.review.noVcs.createGit.actionLoading")
1185+
: language.t("session.review.noVcs.createGit.action")}
1186+
</Button>
1187+
</div>
1188+
)
1189+
11821190
const reviewEmptyText = createMemo(() => {
11831191
if (store.changes === "git") return language.t("session.review.noUncommittedChanges")
11841192
if (store.changes === "branch") return language.t("session.review.noBranchChanges")
1185-
if (store.changes === "turn") return language.t("session.review.noChanges")
1186-
return language.t(sessionEmptyKey())
1193+
return language.t("session.review.noChanges")
11871194
})
11881195

11891196
const reviewEmpty = (input: { loadingClass: string; emptyClass: string }) => {
@@ -1193,31 +1200,10 @@ export default function Page() {
11931200
}
11941201

11951202
if (store.changes === "turn") {
1203+
if (nogit()) return createGit(input)
11961204
return empty(reviewEmptyText())
11971205
}
11981206

1199-
if (hasSessionReview() && !diffsReady()) {
1200-
return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div>
1201-
}
1202-
1203-
if (sessionEmptyKey() === "session.review.noVcs") {
1204-
return (
1205-
<div class={input.emptyClass}>
1206-
<div class="flex flex-col gap-3">
1207-
<div class="text-14-medium text-text-strong">{language.t("session.review.noVcs.createGit.title")}</div>
1208-
<div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}>
1209-
{language.t("session.review.noVcs.createGit.description")}
1210-
</div>
1211-
</div>
1212-
<Button size="large" disabled={gitMutation.isPending} onClick={initGit}>
1213-
{gitMutation.isPending
1214-
? language.t("session.review.noVcs.createGit.actionLoading")
1215-
: language.t("session.review.noVcs.createGit.action")}
1216-
</Button>
1217-
</div>
1218-
)
1219-
}
1220-
12211207
return (
12221208
<div class={input.emptyClass}>
12231209
<div class="text-14-regular text-text-weak max-w-56">{reviewEmptyText()}</div>

packages/app/src/pages/session/review-tab.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createEffect, createSignal, onCleanup, type JSX } from "solid-js"
22
import { makeEventListener } from "@solid-primitives/event-listener"
3-
import type { FileDiff } from "@opencode-ai/sdk/v2"
3+
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
44
import { SessionReview } from "@opencode-ai/ui/session-review"
55
import type {
66
SessionReviewCommentActions,
@@ -14,10 +14,12 @@ import type { LineComment } from "@/context/comments"
1414

1515
export type DiffStyle = "unified" | "split"
1616

17+
type ReviewDiff = SnapshotFileDiff | VcsFileDiff
18+
1719
export interface SessionReviewTabProps {
1820
title?: JSX.Element
1921
empty?: JSX.Element
20-
diffs: () => FileDiff[]
22+
diffs: () => ReviewDiff[]
2123
view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
2224
diffStyle: DiffStyle
2325
onDiffStyleChange?: (style: DiffStyle) => void

packages/app/src/pages/session/session-side-panel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
88
import { Mark } from "@opencode-ai/ui/logo"
99
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
1010
import type { DragEvent } from "@thisbeyond/solid-dnd"
11-
import type { FileDiff } from "@opencode-ai/sdk/v2"
11+
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
1212
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
1313
import { useDialog } from "@opencode-ai/ui/context/dialog"
1414

@@ -27,7 +27,7 @@ import { useSessionLayout } from "@/pages/session/session-layout"
2727

2828
export function SessionSidePanel(props: {
2929
canReview: () => boolean
30-
diffs: () => FileDiff[]
30+
diffs: () => (SnapshotFileDiff | VcsFileDiff)[]
3131
diffsReady: () => boolean
3232
empty: () => string
3333
hasReview: () => boolean

packages/enterprise/src/core/share.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FileDiff, Message, Model, Part, Session } from "@opencode-ai/sdk/v2"
1+
import { Message, Model, Part, Session, SnapshotFileDiff } from "@opencode-ai/sdk/v2"
22
import { fn } from "@opencode-ai/util/fn"
33
import { iife } from "@opencode-ai/util/iife"
44
import z from "zod"
@@ -27,7 +27,7 @@ export namespace Share {
2727
}),
2828
z.object({
2929
type: z.literal("session_diff"),
30-
data: z.custom<FileDiff[]>(),
30+
data: z.custom<SnapshotFileDiff[]>(),
3131
}),
3232
z.object({
3333
type: z.literal("model"),

packages/enterprise/src/routes/share/[shareID].tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } from "@opencode-ai/sdk/v2"
1+
import { Message, Model, Part, Session, SessionStatus, SnapshotFileDiff, UserMessage } from "@opencode-ai/sdk/v2"
22
import { SessionTurn } from "@opencode-ai/ui/session-turn"
33
import { SessionReview } from "@opencode-ai/ui/session-review"
44
import { DataProvider } from "@opencode-ai/ui/context"
@@ -51,7 +51,7 @@ const getData = query(async (shareID) => {
5151
shareID: string
5252
session: Session[]
5353
session_diff: {
54-
[sessionID: string]: FileDiff[]
54+
[sessionID: string]: SnapshotFileDiff[]
5555
}
5656
session_status: {
5757
[sessionID: string]: SessionStatus

0 commit comments

Comments
 (0)