fix(ui): reconcile git state after worktree changes
This commit is contained in:
@@ -67,6 +67,7 @@ mock.module("@/components/ui", () => ({
|
||||
import { INITIAL_STATE, type State } from "../types"
|
||||
import { ChildStoreManager, type DirectoryStore } from "../child-store"
|
||||
import { getRuntimeKey } from "@/lib/runtime-switch"
|
||||
import { sessionEvents } from "@/lib/sessionEvents"
|
||||
const {
|
||||
createEventRoutingIndex,
|
||||
handleEvent,
|
||||
@@ -340,4 +341,40 @@ describe("resyncBlockingRequestsForDirectory", () => {
|
||||
unsubscribe()
|
||||
childStores.disposeAll()
|
||||
})
|
||||
|
||||
test("refreshes Git once when a live mutating tool completes between renders", () => {
|
||||
const childStores = new ChildStoreManager()
|
||||
childStores.ensureChild("/repo", { bootstrap: false })
|
||||
const routingIndex = createEventRoutingIndex()
|
||||
const refreshes: Array<{ directory: string; paths?: string[] }> = []
|
||||
const unsubscribe = sessionEvents.onGitRefreshHint((hint) => refreshes.push(hint))
|
||||
// SAFETY: this fixture supplies the SDK event discriminator and the tool
|
||||
// part identity, tool name, and state fields consumed by the reducer.
|
||||
const toolEvent = (tool: string, status: "pending" | "completed" | "error") => ({
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
part: {
|
||||
id: "prt_tool",
|
||||
messageID: "msg_assistant",
|
||||
sessionID: "ses_a",
|
||||
type: "tool",
|
||||
tool,
|
||||
state: { status, input: {}, metadata: {} },
|
||||
},
|
||||
},
|
||||
}) as Event
|
||||
|
||||
try {
|
||||
handleEvent("/repo", toolEvent("apply_patch", "pending"), childStores, routingIndex, getRuntimeKey())
|
||||
handleEvent("/repo", toolEvent("apply_patch", "completed"), childStores, routingIndex, getRuntimeKey())
|
||||
handleEvent("/repo", toolEvent("apply_patch", "completed"), childStores, routingIndex, getRuntimeKey())
|
||||
handleEvent("/repo", toolEvent("read", "completed"), childStores, routingIndex, getRuntimeKey())
|
||||
handleEvent("/repo", toolEvent("edit", "error"), childStores, routingIndex, getRuntimeKey())
|
||||
|
||||
expect(refreshes).toEqual([{ directory: "/repo" }])
|
||||
} finally {
|
||||
unsubscribe()
|
||||
childStores.disposeAll()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -78,6 +78,7 @@ import { getRuntimeKey } from "@/lib/runtime-switch"
|
||||
import { getRegisteredRuntimeAPIs } from "@/contexts/runtimeAPIRegistry"
|
||||
import { isFilesystemError } from "@/lib/api/files-errors"
|
||||
import { formatMessage, useI18nStore } from "@/lib/i18n"
|
||||
import { sessionEvents } from "@/lib/sessionEvents"
|
||||
import { listGlobalSessionPages } from "@/stores/globalSessions"
|
||||
import { areRequestArraysReferentiallyEqual, collectScopedBlockingRequests } from "./scoped-blocking-requests"
|
||||
import { EMPTY_USER_MESSAGE_HISTORY_SNAPSHOT, buildUserMessageHistorySnapshot, type UserMessageHistorySnapshot } from "./user-message-history"
|
||||
@@ -1818,6 +1819,10 @@ export function handleEvent(
|
||||
// type will mutate. This preserves reference identity for untouched slices
|
||||
// so Zustand selectors skip re-renders for unrelated subscribers.
|
||||
const current = getDirectoryEventState(store, batch)
|
||||
const updatedPart = payload.type === "message.part.updated" ? payload.properties.part : undefined
|
||||
const previousPart = updatedPart && "messageID" in updatedPart
|
||||
? current.part[updatedPart.messageID]?.find((part) => part.id === updatedPart.id)
|
||||
: undefined
|
||||
const draft: State = { ...current }
|
||||
const clonedFields = batch?.clonedFields.get(store) ?? new Set<keyof State>()
|
||||
const newlyClonedFields: Array<keyof State> = []
|
||||
@@ -1894,6 +1899,10 @@ export function handleEvent(
|
||||
const reducerChanged = typeof reducerResult === "boolean" ? reducerResult : reducerResult.changed
|
||||
const materializationResult = typeof reducerResult === "boolean" ? undefined : reducerResult.materialization
|
||||
|
||||
if (reducerChanged && updatedPart) {
|
||||
sessionEvents.requestGitRefreshForToolTransition(resolvedDirectory, previousPart, updatedPart)
|
||||
}
|
||||
|
||||
if (reducerChanged) {
|
||||
countSyncPerformance("reducerChangedEvents")
|
||||
const eventSessionID = getSessionIdFromPayload(payload) ?? undefined
|
||||
|
||||
Reference in New Issue
Block a user