diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 349205d9a6b..8239509744e 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -27,6 +27,7 @@ export class AgentManagerProvider implements vscode.Disposable { private state: WorktreeStateManager | undefined private setupScript: SetupScriptService | undefined private terminalManager: SessionTerminalManager + private stateReady: Promise | undefined constructor( private readonly extensionUri: vscode.Uri, @@ -74,7 +75,7 @@ export class AgentManagerProvider implements vscode.Disposable { onBeforeMessage: (msg) => this.onMessage(msg), }) - void this.initializeState() + this.stateReady = this.initializeState() void this.sendRepoInfo() this.sendKeybindings() @@ -146,6 +147,15 @@ export class AgentManagerProvider implements vscode.Disposable { void this.sendRepoInfo() return null } + if (type === "agentManager.requestState") { + void this.stateReady + ?.then(() => this.pushState()) + .catch((err) => { + this.log("initializeState failed, pushing partial state:", err) + this.pushState() + }) + return null + } if (type === "agentManager.setTabOrder" && typeof msg.key === "string" && Array.isArray(msg.order)) { this.state?.setTabOrder(msg.key as string, msg.order as string[]) return null diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 77b1fa731b7..e3a2a695ce5 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -157,6 +157,8 @@ const AgentManagerContent: Component = () => { const [selection, setSelection] = createSignal(LOCAL) const [repoBranch, setRepoBranch] = createSignal() const [deletingWorktrees, setDeletingWorktrees] = createSignal>(new Set()) + const [worktreesLoaded, setWorktreesLoaded] = createSignal(false) + const [sessionsLoaded, setSessionsLoaded] = createSignal(false) const DEFAULT_SIDEBAR_WIDTH = 260 const MIN_SIDEBAR_WIDTH = 200 @@ -443,6 +445,11 @@ const AgentManagerContent: Component = () => { } }) + // Mark sessions loaded as soon as the session context receives data (even if empty) + const unsubSessions = vscode.onMessage((msg) => { + if (msg.type === "sessionsLoaded" && !sessionsLoaded()) setSessionsLoaded(true) + }) + const unsub = vscode.onMessage((msg) => { if (msg.type === "agentManager.repoInfo") { const info = msg as AgentManagerRepoInfoMessage @@ -480,6 +487,7 @@ const AgentManagerContent: Component = () => { const state = msg as AgentManagerStateMessage setWorktrees(state.worktrees) setManagedSessions(state.sessions) + if (!worktreesLoaded()) setWorktreesLoaded(true) if (state.tabOrder) setWorktreeTabOrder(state.tabOrder) const current = session.currentSessionID() if (current) { @@ -509,6 +517,7 @@ const AgentManagerContent: Component = () => { window.removeEventListener("keydown", preventDefaults) window.removeEventListener("focus", onWindowFocus) unsubCreate() + unsubSessions() unsub() }) }) @@ -516,6 +525,9 @@ const AgentManagerContent: Component = () => { // Always select local on mount to initialize branch info and session state onMount(() => { selectLocal() + // Request worktree/session state from extension — handles race where + // initializeState() pushState fires before the webview is mounted + vscode.postMessage({ type: "agentManager.requestState" }) // Open a pending "New Session" tab if there are no persisted local sessions if (localSessionIDs().length === 0) { addPendingTab() @@ -787,100 +799,112 @@ const AgentManagerContent: Component = () => {
- {(() => { - const [hoveredWt, setHoveredWt] = createSignal(null) - const [overClose, setOverClose] = createSignal(false) - return ( - - {(wt, wtIdx) => { - const sessions = createMemo(() => managedSessions().filter((ms) => ms.worktreeId === wt.id)) - const navHint = () => { - const flat = [ - LOCAL as string, - ...worktrees().map((w) => w.id), - ...unassignedSessions().map((s) => s.id), - ] - const active = selection() ?? session.currentSessionID() ?? "" - return adjacentHint(wt.id, active, flat, kb().previousSession ?? "", kb().nextSession ?? "") - } - return ( - setHoveredWt(open ? wt.id : null)} - trigger={ -
selectWorktree(wt.id)} - > - - {worktreeLabel(wt)} - } + +
+
+
+
+
+ } + > + {(() => { + const [hoveredWt, setHoveredWt] = createSignal(null) + const [overClose, setOverClose] = createSignal(false) + return ( + + {(wt, wtIdx) => { + const sessions = createMemo(() => managedSessions().filter((ms) => ms.worktreeId === wt.id)) + const navHint = () => { + const flat = [ + LOCAL as string, + ...worktrees().map((w) => w.id), + ...unassignedSessions().map((s) => s.id), + ] + const active = selection() ?? session.currentSessionID() ?? "" + return adjacentHint(wt.id, active, flat, kb().previousSession ?? "", kb().nextSession ?? "") + } + return ( + setHoveredWt(open ? wt.id : null)} + trigger={ +
selectWorktree(wt.id)} > -
setOverClose(true)} - onMouseLeave={() => setOverClose(false)} + + {worktreeLabel(wt)} + } > - setOverClose(true)} + onMouseLeave={() => setOverClose(false)} > - handleDeleteWorktree(wt.id, e)} - /> - + + handleDeleteWorktree(wt.id, e)} + /> + +
+ +
+ } + > +
+
+
+
BRANCH
+
{wt.branch}
+
{formatRelativeDate(wt.createdAt)}
+
+ + {navHint()} + +
+ +
+
+ Base + {wt.parentBranch}
-
- } - > -
-
-
-
BRANCH
-
{wt.branch}
-
{formatRelativeDate(wt.createdAt)}
-
- - {navHint()} - -
-
- Base - {wt.parentBranch} + Sessions + {sessions().length}
- -
-
- Sessions - {sessions().length}
-
- - ) - }} - - ) - })()} - - + + ) + }} + + ) + })()} + + +
@@ -891,33 +915,53 @@ const AgentManagerContent: Component = () => {
- - {(s) => ( - - )} - +
+
+
+
+
+
+
+
+
+ } + > + + {(s) => ( + + )} + +
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css index 0f217669f0c..fec15a4f045 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -719,3 +719,86 @@ font-weight: 500; color: var(--text-base); } + +/* Skeleton loading states */ + +@keyframes am-skeleton-pulse { + 0%, + 100% { + opacity: 0.12; + } + 50% { + opacity: 0.28; + } +} + +.am-skeleton-list { + display: flex; + flex-direction: column; + gap: 2px; + animation: am-fade-in 0.2s ease; +} + +/* Worktree skeleton — matches .am-worktree-item layout */ +.am-skeleton-wt { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 10px; +} + +.am-skeleton-wt-icon { + width: 16px; + height: 16px; + border-radius: 3px; + background: var(--text-base); + animation: am-skeleton-pulse 1.5s ease-in-out infinite; + flex-shrink: 0; +} + +.am-skeleton-wt-text { + height: 13px; + border-radius: 3px; + background: var(--text-base); + animation: am-skeleton-pulse 1.5s ease-in-out infinite; +} + +.am-skeleton-wt:nth-child(2) .am-skeleton-wt-icon, +.am-skeleton-wt:nth-child(2) .am-skeleton-wt-text { + animation-delay: 0.15s; +} + +/* Session skeleton — matches .am-item layout */ +.am-skeleton-session { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 6px 10px; +} + +.am-skeleton-session-title { + height: 13px; + border-radius: 3px; + background: var(--text-base); + animation: am-skeleton-pulse 1.5s ease-in-out infinite; +} + +.am-skeleton-session-time { + height: 10px; + width: 52px; + border-radius: 3px; + background: var(--text-base); + animation: am-skeleton-pulse 1.5s ease-in-out infinite; + flex-shrink: 0; +} + +.am-skeleton-session:nth-child(2) .am-skeleton-session-title, +.am-skeleton-session:nth-child(2) .am-skeleton-session-time { + animation-delay: 0.15s; +} + +.am-skeleton-session:nth-child(3) .am-skeleton-session-title, +.am-skeleton-session:nth-child(3) .am-skeleton-session-time { + animation-delay: 0.3s; +} diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 2be7c991e86..81160ae3962 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -841,6 +841,10 @@ export interface RequestRepoInfoMessage { type: "agentManager.requestRepoInfo" } +export interface RequestStateMessage { + type: "agentManager.requestState" +} + // Configure worktree setup script export interface ConfigureSetupScriptRequest { type: "agentManager.configureSetupScript" @@ -902,6 +906,7 @@ export type WebviewMessage = | CloseSessionRequest | TelemetryRequest | RequestRepoInfoMessage + | RequestStateMessage | ConfigureSetupScriptRequest | ShowTerminalRequest | SetTabOrderRequest