|
|
@@ -1,6 +1,6 @@
|
|
|
-import type { Project, UserMessage, VcsFileDiff } from "@opencode-ai/sdk/v2"
|
|
|
+import type { Project, UserMessage } from "@opencode-ai/sdk/v2"
|
|
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
|
-import { useMutation } from "@tanstack/solid-query"
|
|
|
+import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query"
|
|
|
import {
|
|
|
batch,
|
|
|
onCleanup,
|
|
|
@@ -324,6 +324,7 @@ export default function Page() {
|
|
|
const local = useLocal()
|
|
|
const file = useFile()
|
|
|
const sync = useSync()
|
|
|
+ const queryClient = useQueryClient()
|
|
|
const dialog = useDialog()
|
|
|
const language = useLanguage()
|
|
|
const sdk = useSDK()
|
|
|
@@ -518,26 +519,6 @@ export default function Page() {
|
|
|
deferRender: false,
|
|
|
})
|
|
|
|
|
|
- const [vcs, setVcs] = createStore<{
|
|
|
- diff: {
|
|
|
- git: VcsFileDiff[]
|
|
|
- branch: VcsFileDiff[]
|
|
|
- }
|
|
|
- ready: {
|
|
|
- git: boolean
|
|
|
- branch: boolean
|
|
|
- }
|
|
|
- }>({
|
|
|
- diff: {
|
|
|
- git: [] as VcsFileDiff[],
|
|
|
- branch: [] as VcsFileDiff[],
|
|
|
- },
|
|
|
- ready: {
|
|
|
- git: false,
|
|
|
- branch: false,
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
const [followup, setFollowup] = persisted(
|
|
|
Persist.workspace(sdk.directory, "followup", ["followup.v1"]),
|
|
|
createStore<{
|
|
|
@@ -571,68 +552,6 @@ 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, list(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()
|
|
|
@@ -663,21 +582,52 @@ export default function Page() {
|
|
|
list.push("turn")
|
|
|
return list
|
|
|
})
|
|
|
+ const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
|
|
|
+ const wantsReview = createMemo(() =>
|
|
|
+ isDesktop()
|
|
|
+ ? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review")
|
|
|
+ : store.mobileTab === "changes",
|
|
|
+ )
|
|
|
const vcsMode = createMemo<VcsMode | undefined>(() => {
|
|
|
if (store.changes === "git" || store.changes === "branch") return store.changes
|
|
|
})
|
|
|
- const reviewDiffs = createMemo(() => {
|
|
|
- if (store.changes === "git") return list(vcs.diff.git)
|
|
|
- if (store.changes === "branch") return list(vcs.diff.branch)
|
|
|
- return turnDiffs()
|
|
|
+ const vcsKey = createMemo(
|
|
|
+ () => ["session-vcs", sdk.directory, sync.data.vcs?.branch ?? "", sync.data.vcs?.default_branch ?? ""] as const,
|
|
|
+ )
|
|
|
+ const vcsQuery = createQuery(() => {
|
|
|
+ const mode = vcsMode()
|
|
|
+ const enabled = wantsReview() && sync.project?.vcs === "git"
|
|
|
+
|
|
|
+ return {
|
|
|
+ queryKey: [...vcsKey(), mode] as const,
|
|
|
+ enabled,
|
|
|
+ staleTime: Number.POSITIVE_INFINITY,
|
|
|
+ gcTime: 60 * 1000,
|
|
|
+ queryFn: mode
|
|
|
+ ? () =>
|
|
|
+ sdk.client.vcs
|
|
|
+ .diff({ mode })
|
|
|
+ .then((result) => list(result.data))
|
|
|
+ .catch((error) => {
|
|
|
+ console.debug("[session-review] failed to load vcs diff", { mode, error })
|
|
|
+ return []
|
|
|
+ })
|
|
|
+ : skipToken,
|
|
|
+ }
|
|
|
})
|
|
|
- const reviewCount = createMemo(() => reviewDiffs().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
|
|
|
+ const refreshVcs = () => void queryClient.invalidateQueries({ queryKey: vcsKey() })
|
|
|
+ const reviewDiffs = () => {
|
|
|
+ if (store.changes === "git" || store.changes === "branch")
|
|
|
+ // avoids suspense
|
|
|
+ return vcsQuery.isFetched ? (vcsQuery.data ?? []) : []
|
|
|
+ return turnDiffs()
|
|
|
+ }
|
|
|
+ const reviewCount = () => reviewDiffs().length
|
|
|
+ const hasReview = () => reviewCount() > 0
|
|
|
+ const reviewReady = () => {
|
|
|
+ if (store.changes === "git" || store.changes === "branch") return !vcsQuery.isPending
|
|
|
return true
|
|
|
- })
|
|
|
+ }
|
|
|
|
|
|
const newSessionWorktree = createMemo(() => {
|
|
|
if (store.newSessionWorktree === "create") return "create"
|
|
|
@@ -897,27 +847,6 @@ 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 =
|
|
|
@@ -1051,13 +980,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
|
|
|
@@ -1066,22 +988,12 @@ export default function Page() {
|
|
|
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)
|
|
|
+ refreshVcs()
|
|
|
},
|
|
|
{ defer: true },
|
|
|
),
|