mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
fix(agent-manager): address review findings
This commit is contained in:
@@ -395,6 +395,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
this.panelSessions.clear()
|
||||
void ctx.sessions.abortSessions(ids).catch((err) => this.log("Failed to abort sessions on panel close:", err))
|
||||
this.statsPoller.stop()
|
||||
this.projectPollers.dispose()
|
||||
this.prBridge.poller.stop()
|
||||
this.diffs.stop()
|
||||
this.activeSessionId = undefined
|
||||
@@ -450,12 +451,14 @@ export class AgentManagerProvider implements Disposable {
|
||||
|
||||
/** Initialize an expanded background project and push its state (no panel wiring). */
|
||||
private initExpanded(ctx: ProjectContext): void {
|
||||
void initContextState(ctx, (...args) => this.log(...args)).then((result) => {
|
||||
if (!result.current) return
|
||||
registerProjectSessions(ctx, this.panel?.sessions)
|
||||
this.pushState(ctx)
|
||||
this.projectPollers.sync(this.contexts)
|
||||
})
|
||||
void initContextState(ctx, (...args) => this.log(...args))
|
||||
.then((result) => {
|
||||
if (!result.current) return
|
||||
registerProjectSessions(ctx, this.panel?.sessions)
|
||||
this.pushState(ctx)
|
||||
this.projectPollers.sync(this.contexts)
|
||||
})
|
||||
.catch((err) => this.log("Failed to initialize expanded project:", err))
|
||||
}
|
||||
|
||||
// Message interceptor
|
||||
|
||||
@@ -141,8 +141,12 @@ export class ProjectContexts {
|
||||
}
|
||||
|
||||
isExpanded(id: string): boolean {
|
||||
this.rememberExpansion(id, false)
|
||||
return this.expansion.get(id) === true
|
||||
const value = this.expansion.get(id)
|
||||
if (value !== undefined) return value
|
||||
const stored = this.opts.registry.expanded?.(id)
|
||||
if (stored !== undefined) return stored
|
||||
const root = this.opts.workspaceRoot()
|
||||
return root !== undefined && projectIdFor(canonicalizePath(root)) === id
|
||||
}
|
||||
|
||||
/** Make a project the active context and expand it. Returns undefined when not allowed. */
|
||||
@@ -244,16 +248,17 @@ export class ProjectContexts {
|
||||
const id = ctx?.id ?? stored!.id
|
||||
const root = ctx?.root ?? stored!.root
|
||||
const pinned = ctx?.pinned ?? false
|
||||
const missing = ctx ? ctx.missing() : !(this.opts.deps.exists ?? fs.existsSync)(root)
|
||||
return {
|
||||
id,
|
||||
root,
|
||||
label: stored?.label || path.basename(root) || root,
|
||||
pinned,
|
||||
active: this.isActive(id),
|
||||
expanded: this.isExpanded(id),
|
||||
expanded: !missing && this.isExpanded(id),
|
||||
initialized: ctx?.loaded ?? false,
|
||||
trusted: pinned || (stored?.trusted ?? false),
|
||||
missing: ctx ? ctx.missing() : !(this.opts.deps.exists ?? fs.existsSync)(root),
|
||||
missing,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -196,8 +196,8 @@ async function setExpanded(id: string, expanded: boolean, deps: ProjectMessageDe
|
||||
}
|
||||
await deps.registry.setExpanded(id, expanded)
|
||||
if (expanded) {
|
||||
deps.contexts.expand(id)
|
||||
deps.expand(ctx)
|
||||
const next = deps.contexts.expand(id)
|
||||
if (next) deps.expand(next)
|
||||
}
|
||||
if (!expanded) deps.contexts.collapse(id)
|
||||
deps.push()
|
||||
|
||||
@@ -210,6 +210,12 @@ describe("ProjectContexts", () => {
|
||||
expect(list[1]!.initialized).toBe(false)
|
||||
})
|
||||
|
||||
it("keeps the pinned project expanded before active state is initialized", () => {
|
||||
const { contexts } = setup({ workspace: WORKSPACE })
|
||||
|
||||
expect(contexts.isExpanded(PINNED)).toBe(true)
|
||||
})
|
||||
|
||||
it("hydrates persisted project expansion without initializing the project", () => {
|
||||
const extra = stored("prj-extra", true)
|
||||
const { contexts } = setup({
|
||||
|
||||
@@ -96,7 +96,7 @@ export const ProjectsSection: Component<ProjectsSectionProps> = (props) => (
|
||||
}
|
||||
const expanded = !project().expanded
|
||||
props.onExpand(project().id, expanded)
|
||||
if (!project().active && project().trusted) props.onSelect(project().id)
|
||||
if (!project().active && project().trusted && !project().missing) props.onSelect(project().id)
|
||||
}}
|
||||
/>
|
||||
<Show when={project().expanded}>
|
||||
|
||||
@@ -31,16 +31,23 @@ const SectionHeader: Component<Props> = (props) => {
|
||||
const { t } = useLanguage()
|
||||
const [renaming, setRenaming] = createSignal(false)
|
||||
const [value, setValue] = createSignal("")
|
||||
let cancelled = false
|
||||
|
||||
const border = () => colorCss(props.section.color) ?? "var(--vscode-panel-border)"
|
||||
|
||||
const startRename = () => {
|
||||
cancelled = false
|
||||
setValue(props.section.name)
|
||||
setRenaming(true)
|
||||
}
|
||||
|
||||
const commit = () => {
|
||||
if (cancelled) {
|
||||
cancelled = false
|
||||
return
|
||||
}
|
||||
const trimmed = value().trim()
|
||||
cancelled = true
|
||||
setRenaming(false)
|
||||
props.onRenameEnd?.()
|
||||
if (trimmed && trimmed !== props.section.name) {
|
||||
@@ -49,6 +56,7 @@ const SectionHeader: Component<Props> = (props) => {
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
cancelled = true
|
||||
setRenaming(false)
|
||||
props.onRenameEnd?.()
|
||||
}
|
||||
@@ -83,6 +91,7 @@ const SectionHeader: Component<Props> = (props) => {
|
||||
if (e.key === "Enter") commit()
|
||||
if (e.key === "Escape") cancel()
|
||||
}}
|
||||
onBlur={commit}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
ref={(el) =>
|
||||
requestAnimationFrame(() =>
|
||||
|
||||
@@ -14,31 +14,30 @@ interface Props {
|
||||
|
||||
/** Shared layout for sidebar headings with a fixed leading control column. */
|
||||
export const SidebarSectionHeader: Component<Props> = (props) => {
|
||||
const keydown = (event: KeyboardEvent) => {
|
||||
if (!props.onToggle || event.target !== event.currentTarget) return
|
||||
if (event.key !== "Enter" && event.key !== " ") return
|
||||
event.preventDefault()
|
||||
props.onToggle()
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`am-sidebar-header${props.class ? ` ${props.class}` : ""}`}
|
||||
role={props.onToggle ? "button" : undefined}
|
||||
tabIndex={props.onToggle ? 0 : undefined}
|
||||
aria-expanded={props.onToggle ? props.expanded : undefined}
|
||||
aria-label={props.ariaLabel}
|
||||
class={`am-sidebar-header${props.onToggle ? " am-sidebar-header-toggleable" : ""}${props.class ? ` ${props.class}` : ""}`}
|
||||
title={props.title}
|
||||
onClick={(event) => {
|
||||
if (event.button === 0) props.onToggle?.()
|
||||
}}
|
||||
onKeyDown={keydown}
|
||||
>
|
||||
<div class="am-sidebar-header-main">
|
||||
<Show when={props.onToggle}>
|
||||
<span class="am-sidebar-header-chevron" aria-hidden="true">
|
||||
<Icon name={props.expanded ? "chevron-down" : "chevron-right"} size="small" />
|
||||
</span>
|
||||
<button
|
||||
class="am-sidebar-header-toggle"
|
||||
type="button"
|
||||
aria-expanded={props.expanded}
|
||||
aria-label={props.ariaLabel}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
props.onToggle?.()
|
||||
}}
|
||||
>
|
||||
<span class="am-sidebar-header-chevron" aria-hidden="true">
|
||||
<Icon name={props.expanded ? "chevron-down" : "chevron-right"} size="small" />
|
||||
</span>
|
||||
</button>
|
||||
</Show>
|
||||
<div class="am-sidebar-header-label">{props.label}</div>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,7 @@ export const UnassignedSessionsSection: Component<Props> = (props) => {
|
||||
<SidebarSectionHeader
|
||||
class="am-section-header am-section-toggle"
|
||||
expanded={!props.collapsed()}
|
||||
ariaLabel={t("agentManager.section.sessions")}
|
||||
label={<span class="am-section-label">{t("agentManager.section.sessions")}</span>}
|
||||
onToggle={props.onToggle}
|
||||
/>
|
||||
|
||||
@@ -190,7 +190,7 @@ html[data-theme="kilo-vscode"]
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.am-sidebar-header[role="button"] {
|
||||
.am-sidebar-header-toggleable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -213,6 +213,26 @@ html[data-theme="kilo-vscode"]
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
.am-sidebar-header-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.am-sidebar-header-toggle:hover {
|
||||
color: var(--text-base);
|
||||
background: var(--surface-inset-base-hover);
|
||||
}
|
||||
|
||||
.am-sidebar-header-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -244,6 +264,9 @@ html[data-theme="kilo-vscode"]
|
||||
}
|
||||
|
||||
.am-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 4px 8px 2px;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { SessionContext } from "../context/session"
|
||||
import { ServerContext } from "../context/server"
|
||||
import { WorktreeModeProvider } from "../context/worktree-mode"
|
||||
import { SidebarSearchMenu } from "../../agent-manager/SidebarSearchMenu"
|
||||
import { SidebarSectionHeader } from "../../agent-manager/SidebarSectionHeader"
|
||||
import { SidebarToggleButton } from "../../agent-manager/SidebarToggleButton"
|
||||
import { SideTerminalPanel, createTerminalState } from "../../agent-manager/terminal"
|
||||
import { LOCAL } from "../../agent-manager/navigate"
|
||||
@@ -1162,27 +1163,30 @@ export const SidebarSearchOpen: Story = {
|
||||
return (
|
||||
<StoryProviders noPadding>
|
||||
<div style={{ "min-height": "430px", padding: "16px", background: "var(--surface-base)" }}>
|
||||
<div class="am-section-header">
|
||||
<span class="am-section-label">WORKTREES</span>
|
||||
<div class="am-section-actions">
|
||||
<SidebarSearchMenu
|
||||
items={() => sidebarSearchItems}
|
||||
keybind="⌘F"
|
||||
current={() => sidebarSearchItems.find((item) => item.key === selected())}
|
||||
labels={{
|
||||
search: "Search worktrees and sessions",
|
||||
scope: "Searches the local workspace, local sessions, worktrees, and their sessions",
|
||||
contexts: "LOCAL & WORKTREES",
|
||||
sessions: "SESSIONS",
|
||||
waiting: "Wait",
|
||||
retry: "Retry",
|
||||
}}
|
||||
onSelect={(item) => setSelected(item.key)}
|
||||
defaultOpen
|
||||
portal={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SidebarSectionHeader
|
||||
class="am-section-header"
|
||||
label={<span class="am-section-label">WORKTREES</span>}
|
||||
actions={
|
||||
<div class="am-section-actions">
|
||||
<SidebarSearchMenu
|
||||
items={() => sidebarSearchItems}
|
||||
keybind="⌘F"
|
||||
current={() => sidebarSearchItems.find((item) => item.key === selected())}
|
||||
labels={{
|
||||
search: "Search worktrees and sessions",
|
||||
scope: "Searches the local workspace, local sessions, worktrees, and their sessions",
|
||||
contexts: "LOCAL & WORKTREES",
|
||||
sessions: "SESSIONS",
|
||||
waiting: "Wait",
|
||||
retry: "Retry",
|
||||
}}
|
||||
onSelect={(item) => setSelected(item.key)}
|
||||
defaultOpen
|
||||
portal={false}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<output class="sr-only" data-slot="sidebar-search-selection">
|
||||
{selected()}
|
||||
</output>
|
||||
|
||||
Reference in New Issue
Block a user