diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index aa0d60780a7..1fd4c45216d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -1096,12 +1096,6 @@ const AgentManagerContent: Component = () => { }) } - const syncRunStatuses = (items: RunStatus[] = []) => { - const map: Record = {} - for (const item of items) map[item.worktreeId] = item - setRunStatuses(map) - } - const router = createProjectStateRouter({ catalog: projectList, apply: (state) => applyActiveState(state), @@ -1125,39 +1119,33 @@ const AgentManagerContent: Component = () => { const state = msg as AgentManagerStateMessage const pid = state.projectId if (pid) setProjectStates((prev) => ({ ...prev, [pid]: state })) - // Background payloads feed only their accordion summary; on activation the - // state push can arrive before the catalog push, so the router defers it. + const store = pid ? registry.ensure(pid) : registry.active() + // A freshly created section needs the previous list to detect the new id, + // so it is handled before the data write below replaces it. + if (pendingNewSection && isActivePayload(pid)) { + const prev = new Set(store.sections().map((s) => s.id)) + const created = (state.sections ?? []).find((s) => !prev.has(s.id)) + pendingNewSection = false + if (created) setRenamingSection(created.id) + } + // Data lands in the payload's own store unconditionally; the router + // handles only the active-transition effects (selection/tab restore). + store.applyState(state) router.routeState(state) } - /** Apply a state payload to the shared signals of the active project. */ + /** Apply the active-transition effects of a state payload (data already landed in the store). */ const applyActiveState = (state: AgentManagerStateMessage) => { const switched = applyProjectSwitch(state) - setWorktrees(state.worktrees) - setManagedSessions(state.sessions) - setStaleWorktreeIds(new Set(state.staleWorktreeIds ?? [])) if (state.isGitRepo !== undefined) setIsGitRepo(state.isGitRepo) if (!worktreesLoaded()) setWorktreesLoaded(true) // When not a git repo, also mark sessions as loaded since the Kilo // server won't connect to send the sessionsLoaded message. if (state.isGitRepo === false && !sessionsLoaded()) setSessionsLoaded(true) - const prev = new Set(sections().map((s) => s.id)), - incoming = state.sections ?? [] - setSections(incoming) - if (pendingNewSection) { - pendingNewSection = false - const c = incoming.find((s) => !prev.has(s.id)) - if (c) setRenamingSection(c.id) - } - if (state.tabOrder) setWorktreeTabOrder(state.tabOrder) - if (state.worktreeOrder) setSidebarWorktreeOrder(state.worktreeOrder) if (state.reviewDiffStyle === "split" || state.reviewDiffStyle === "unified") { setReviewDiffStyle(state.reviewDiffStyle) } markdown.setRender(state.reviewMarkdownRender === true) - if ("defaultBaseBranch" in state) setDefaultBaseBranch(state.defaultBaseBranch || undefined) - setRunScriptConfigured(state.runScriptConfigured === true) - syncRunStatuses(state.runStatuses) const current = session.currentSessionID() if (current) { const ms = state.sessions.find((s) => s.id === current) @@ -1183,17 +1171,8 @@ const AgentManagerContent: Component = () => { setActivePendingId, }) } - // Recover sessions collapsed state from extension-persisted state - if (state.sessionsCollapsed !== undefined) setSessionsCollapsed(state.sessionsCollapsed) // Recover sidebar collapsed state and mark hydrated so transitions enable sidebar.hydrate(state.sidebarCollapsed) - // Clear busy state for worktrees that have been removed - const ids = new Set(state.worktrees.map((wt) => wt.id)) - setBusyWorktrees((prev) => { - const next = new Map([...prev].filter(([id]) => ids.has(id))) - return next.size === prev.size ? prev : next - }) - setRunStatuses((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => id === "local" || ids.has(id)))) } /** Track project switches; full selection restore happens via restoreProjectTarget. */ diff --git a/packages/kilo-vscode/webview-ui/agent-manager/project/store.ts b/packages/kilo-vscode/webview-ui/agent-manager/project/store.ts index c10c22a44b9..8559b268653 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/project/store.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/project/store.ts @@ -2,6 +2,7 @@ import { createSignal, type Accessor, type Setter } from "solid-js" import type { AgentManagerApplyWorktreeDiffConflict, AgentManagerApplyWorktreeDiffStatus, + AgentManagerStateMessage, LocalGitStats, ManagedSessionState, PRStatus, @@ -75,9 +76,32 @@ export function createProjectStore(id: string, opts: { tabs?: string[] } = {}) { const [applyStates, setApplyStates] = field>({}) const [busy, setBusy] = field>(new Map()) + /** Write a project state payload into this store (data fields only). */ + const applyState = (state: AgentManagerStateMessage): void => { + setWorktrees(state.worktrees) + setManagedSessions(state.sessions) + setStaleWorktreeIds(new Set(state.staleWorktreeIds ?? [])) + setSections(state.sections ?? []) + if (state.tabOrder) setTabOrder(state.tabOrder) + if (state.worktreeOrder) setWorktreeOrder(state.worktreeOrder) + if ("defaultBaseBranch" in state) setDefaultBaseBranch(state.defaultBaseBranch || undefined) + setRunScriptConfigured(state.runScriptConfigured === true) + if (state.sessionsCollapsed !== undefined) setSessionsCollapsed(state.sessionsCollapsed) + const runs: Record = {} + for (const item of state.runStatuses ?? []) runs[item.worktreeId] = item + setRunStatuses(runs) + // Reconcile busy flags with the worktree list (deleted worktrees drop out). + const ids = new Set(state.worktrees.map((wt) => wt.id)) + setBusy((prev) => { + const next = new Map([...prev].filter(([key]) => ids.has(key))) + return next.size === prev.size ? prev : next + }) + } + return { id, tabs, + applyState, tabMemory, worktrees, setWorktrees,