From 039d2336791ec32a14e3399fa1ef328110d56053 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 3 Aug 2026 11:23:50 -0700 Subject: [PATCH 01/42] feat(pr-viewer): View PR with mock data in AM --- .../src/agent-manager/PRStatusPoller.ts | 112 +++++---- .../kilo-vscode/src/agent-manager/types.ts | 8 + .../agent-manager/AgentManagerApp.tsx | 31 ++- .../webview-ui/agent-manager/PRPanel.tsx | 210 +++++++++++++++++ .../webview-ui/agent-manager/TabBar.tsx | 19 +- .../agent-manager/agent-manager.css | 221 ++++++++++++++++++ .../src/types/messages/agent-manager.ts | 8 + packages/ui/src/components/icon.tsx | 1 + 8 files changed, 566 insertions(+), 44 deletions(-) create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 155bd410bba..c45cd992beb 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -1,6 +1,6 @@ import type { ExecFileOptionsWithStringEncoding } from "child_process" import type { Worktree } from "./WorktreeStateManager" -import type { PRStatus, PRCheck, PRComment, CheckStatus, AggregateCheckStatus, PRState, ReviewDecision } from "./types" +import type { PRStatus, PRCheck, PRComment, PRReviewer, CheckStatus, AggregateCheckStatus, PRState, ReviewDecision } from "./types" import { execWithShellEnv } from "./shell-env" import { classifyPRError } from "./git-import" import type { Semaphore } from "./semaphore" @@ -244,11 +244,12 @@ export class PRStatusPoller { const status: PRStatus = { number: pr.number, title: pr.title, + body: pr.body, url: pr.url, state: pr.state, review: pr.review, checks, - ...(comments && { comments }), + ...(comments && { comments, reviewers: comments.reviewers }), additions: pr.additions, deletions: pr.deletions, files: pr.files, @@ -283,7 +284,7 @@ export class PRStatusPoller { } private static readonly PR_JSON_FIELDS = - "number,title,url,state,isDraft,reviewDecision,additions,deletions,changedFiles,headRefName,headRefOid" + "number,title,body,url,state,isDraft,reviewDecision,additions,deletions,changedFiles,headRefName,headRefOid" /** Return a cached PR lookup if still fresh, otherwise fetch and cache. * Keyed by branch name so multiple worktrees on the same branch share @@ -420,10 +421,9 @@ export class PRStatusPoller { private async fetchComments( prNumber: number, cwd: string, - ): Promise<{ total: number; unresolved: number; items: PRComment[] }> { + ): Promise<{ total: number; unresolved: number; items: PRComment[]; reviewers: PRReviewer[] }> { try { const repo = await this.getRepoInfo(cwd) - const query = `query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $number) { @@ -443,52 +443,27 @@ export class PRStatusPoller { } } } + reviewRequests(first: 20) { + nodes { requestedReviewer { ... on User { login avatarUrl } } } + } + reviews(first: 20, states: [APPROVED, CHANGES_REQUESTED, COMMENTED]) { + nodes { author { login avatarUrl } state } + } } } }` - const { stdout } = await this.shell( "gh", - [ - "api", - "graphql", - "-f", - `query=${query}`, - "-F", - `owner=${repo.owner}`, - "-F", - `repo=${repo.name}`, - "-F", - `number=${prNumber}`, - ], + ["api", "graphql", "-f", `query=${query}`, "-F", `owner=${repo.owner}`, "-F", `repo=${repo.name}`, "-F", `number=${prNumber}`], { cwd, timeout: 15_000 }, ) - const result = JSON.parse(stdout) - const threads = result?.data?.repository?.pullRequest?.reviewThreads?.nodes ?? [] - - const items: PRComment[] = [] - for (const thread of threads) { - const first = thread.comments?.nodes?.[0] - if (!first) continue - items.push({ - id: first.id, - author: first.author?.login ?? "unknown", - avatar: first.author?.avatarUrl, - body: first.body ?? "", - file: first.path, - line: first.line, - url: first.url, - resolved: thread.isResolved ?? false, - createdAt: first.createdAt ? new Date(first.createdAt).getTime() : undefined, - }) - } - - const total = items.length - const unresolved = items.filter((c) => !c.resolved).length - return { total, unresolved, items } + const pr = JSON.parse(stdout)?.data?.repository?.pullRequest + const items = parseComments(pr?.reviewThreads?.nodes ?? []) + const reviewers = parseReviewers(pr?.reviewRequests?.nodes ?? [], pr?.reviews?.nodes ?? []) + return { total: items.length, unresolved: items.filter((c) => !c.resolved).length, items, reviewers } } catch (err) { this.options.log("Failed to fetch PR comments:", err) - return { total: 0, unresolved: 0, items: [] } + return { total: 0, unresolved: 0, items: [], reviewers: [] } } } } @@ -496,6 +471,7 @@ export class PRStatusPoller { interface PRResult { number: number title: string + body: string url: string state: PRState review: ReviewDecision | null @@ -510,6 +486,7 @@ function parsePRResult(json: string): PRResult | null { return { number: data.number, title: data.title ?? "", + body: data.body ?? "", url: data.url ?? "", state: parsePRState(data.isDraft, data.state), review: parseReviewDecision(data.reviewDecision), @@ -564,6 +541,57 @@ function formatCheckDuration(startedAt?: string, completedAt?: string): string | return secs < 60 ? `${secs}s` : `${Math.floor(secs / 60)}m ${secs % 60}s` } +function parseComments(threads: Record[]): PRComment[] { + const items: PRComment[] = [] + for (const thread of threads) { + const nodes = (thread.comments as { nodes?: unknown[] } | undefined)?.nodes + const first = nodes?.[0] as Record | undefined + if (!first) continue + const author = first.author as Record | undefined + items.push({ + id: first.id as string, + author: (author?.login as string) ?? "unknown", + avatar: author?.avatarUrl as string | undefined, + body: (first.body as string) ?? "", + file: first.path as string | undefined, + line: first.line as number | undefined, + url: first.url as string | undefined, + resolved: (thread.isResolved as boolean) ?? false, + createdAt: first.createdAt ? new Date(first.createdAt as string).getTime() : undefined, + }) + } + return items +} + +function parseReviewers( + requests: Record[], + reviews: Record[], +): PRReviewer[] { + const map = new Map() + for (const node of requests) { + const user = node.requestedReviewer as Record | undefined + if (!user?.login) continue + map.set(user.login as string, { login: user.login as string, avatar: user.avatarUrl as string | undefined, state: "pending" }) + } + for (const node of reviews) { + const author = node.author as Record | undefined + const login = author?.login as string | undefined + if (!login) continue + const state = REVIEWER_STATE[node.state as string] ?? "pending" + // Keep most significant: approved/changes_requested > commented > pending + if (!map.has(login) || state !== "commented") { + map.set(login, { login, avatar: author?.avatarUrl as string | undefined, state }) + } + } + return [...map.values()] +} + +const REVIEWER_STATE: Record = { + APPROVED: "approved", + CHANGES_REQUESTED: "changes_requested", + COMMENTED: "commented", +} + /** Run async thunks with bounded concurrency, returning settled results. */ async function settled(thunks: (() => Promise)[], concurrency: number): Promise[]> { const results: PromiseSettledResult[] = new Array(thunks.length) diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 3a2b555e355..44e2c3eee68 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -71,9 +71,16 @@ export interface PRComment { createdAt?: number } +export interface PRReviewer { + login: string + avatar?: string + state: "approved" | "changes_requested" | "pending" | "commented" +} + export interface PRStatus { number: number title: string + body?: string url: string state: PRState review: ReviewDecision | null @@ -90,6 +97,7 @@ export interface PRStatus { unresolved: number items: PRComment[] } + reviewers?: PRReviewer[] additions: number deletions: number files: number diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 939a8e505be..b10f83e6d6f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -141,6 +141,7 @@ import { import { focusCurrentTab, renderTab, renderTerminalLayer, renderNewTabButton } from "./tab-rendering" import { useTabScroll } from "./tab-scroll" import { DiffPanel } from "./DiffPanel" +import { PRPanel } from "./PRPanel" import { createRevertFile } from "./revert-file" import { FullScreenDiffView } from "../diff-viewer/FullScreenDiffView" import { createApplyToLocal } from "./apply-to-local" @@ -308,6 +309,7 @@ const AgentManagerContent: Component = () => { const [history, setHistory] = createSignal(false) const [sidePanel, setSidePanel] = createSignal(null) const diffOpen = () => sidePanel() === "diff" + const prOpen = () => sidePanel() === "pr" const diffs = createWorktreeDiffs(vscode) const diffDatas = diffs.diffDatas const diffLoading = diffs.diffLoading @@ -553,7 +555,13 @@ const AgentManagerContent: Component = () => { const sel = selection() if (!sel || sel === LOCAL || !prStatuses()[sel]) return metrics.track("open_pull_request", "keyboard_shortcut") - vscode.postMessage({ type: "agentManager.openPR", worktreeId: sel }) + togglePRPanel() + } + + const togglePRPanel = () => { + setHistory(false) + setReviewActive(false) + setSidePanel((prev) => (prev === "pr" ? null : "pr")) } const runWorktree = (id: string, destination: TerminalDestination) => { @@ -2448,6 +2456,13 @@ const AgentManagerContent: Component = () => { reviewActive={reviewActive} onToggleDiff={toggleDiffPanel} onToggleReview={metrics.click("fullscreen_review", "tab_toolbar", toggleReviewTab)} + prStatus={() => { + const sel = selection() + if (!sel || sel === LOCAL) return undefined + return prStatuses()[sel] + }} + prOpen={prOpen} + onTogglePR={togglePRPanel} terminalDestination={sideCtl.destination} terminalDestinationActive={() => sidePanel() === "terminal"} terminalKeybind={() => kb().showTerminal ?? ""} @@ -2686,6 +2701,20 @@ const AgentManagerContent: Component = () => { activeTerminalId={terms.activeId()} /> + + {(() => { + const sel = selection() + const pr = sel && sel !== LOCAL ? prStatuses()[sel] : undefined + if (!pr) return null + return ( + setSidePanel(null)} + onOpenExternal={() => vscode.postMessage({ type: "agentManager.openPR", worktreeId: sel as string })} + /> + ) + })()} + void + onOpenExternal: () => void +} + +const STATE_LABEL: Record = { + open: "Open", + draft: "Draft", + merged: "Merged", + closed: "Closed", +} + +const REVIEW_LABEL: Partial, string>> = { + approved: "Approved", + changes_requested: "Changes Requested", + pending: "Review Pending", +} + +const CHECK_ICON: Record = { + success: "circle-check", + failure: "circle-x", + cancelled: "circle-x", + skipped: "circle-x", + pending: "play", +} + +const CHECK_LABEL: Record = { + success: "Passed", + failure: "Failed", + pending: "Running", + skipped: "Skipped", + cancelled: "Cancelled", +} + +const REVIEWER_STATE_ICON: Record = { + approved: "circle-check", + changes_requested: "refresh", + commented: "edit", + pending: "clock", +} + +const REVIEWER_STATE_LABEL: Record = { + approved: "Approved", + changes_requested: "Changes requested", + commented: "Commented", + pending: "Awaiting", +} + +export const PRPanel: Component = (props) => { + const accent = () => prAccentColor(props.pr) + const indicator = () => prBadgeIndicator(props.pr) + const pulsing = () => prChecksRunning(props.pr) + + return ( +
+
+
+ + + #{props.pr.number} + + {props.pr.title} +
+
+ + + + + + +
+
+ +
+ {/* State + review */} +
+
+ Status + {STATE_LABEL[props.pr.state]} +
+ + {(review) => ( +
+ Review + {REVIEW_LABEL[review()]} +
+ )} +
+
+ + {/* Description */} + +
+
+
{props.pr.body}
+
+ + + {/* Reviewers */} + 0}> +
+
+
Reviewers
+
+ + {(reviewer) => ( +
+ + + {REVIEWER_STATE_LABEL[reviewer.state]} +
+ )} +
+
+
+ + + {/* Diff stats */} + 0 || props.pr.additions > 0 || props.pr.deletions > 0}> +
+
+
+ Changes + + 0}> + {props.pr.files}f + + 0}> + +{props.pr.additions} + + 0}> + −{props.pr.deletions} + + +
+
+ + + {/* Checks */} + 0}> +
+
+
+ Checks + + {props.pr.checks.passed}/{props.pr.checks.total} passed + +
+
+ + {(check) => ( +
+ + {check.name} + {CHECK_LABEL[check.status]} + + {check.duration} + + + e.preventDefault()}> + + + +
+ )} +
+
+
+ + + {/* Comments */} + 0}> +
+
+
+ Comments + + {props.pr.comments!.total} + 0}> + {" "} + ({props.pr.comments!.unresolved} unresolved) + + +
+
+ +
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx index fc1a36b462b..f3c1ddda1c3 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx @@ -13,7 +13,7 @@ import { closestCenter, type DragEvent, } from "@thisbeyond/solid-dnd" -import type { LocalGitStats, RunStatus, WorktreeGitStats } from "../src/types/messages" +import type { LocalGitStats, RunStatus, WorktreeGitStats, PRStatus } from "../src/types/messages" import type { LanguageContextValue } from "../src/context/language" import { LOCAL } from "./navigate" import { ConstrainDragYAxis } from "../src/components/chat/TabDnd" @@ -55,6 +55,9 @@ export interface TabBarProps { reviewActive: () => boolean onToggleDiff: () => void onToggleReview: () => void + prStatus: () => PRStatus | null | undefined + prOpen: () => boolean + onTogglePR: () => void terminalDestination: () => TerminalDestination terminalDestinationActive: () => boolean terminalKeybind: () => string @@ -241,6 +244,20 @@ export const TabBar: Component = (props) => ( ) })()} + + {(pr) => ( + + + + )} + `, warning: ``, reset: ``, + refresh: ``, link: ``, providers: ``, layers: ``, From 0a78fedd28eadcac51661c8c81983d8b16176ce0 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 3 Aug 2026 13:16:00 -0700 Subject: [PATCH 02/42] feat(pr-viewer): Update styles and add markdown renderer for PR description --- .../src/agent-manager/pr-status-bridge.ts | 4 +- .../kilo-vscode/src/agent-manager/types.ts | 1 + .../agent-manager/AgentManagerApp.tsx | 2 +- .../webview-ui/agent-manager/PRPanel.tsx | 85 +++++++++---- .../agent-manager/agent-manager.css | 119 ++++++++++++++++-- .../src/types/messages/webview-messages.ts | 1 + 6 files changed, 173 insertions(+), 39 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts index cbbb1bffda8..2a216c30796 100644 --- a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts +++ b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts @@ -78,8 +78,8 @@ export class PRStatusBridge { return true } if (m.type === "agentManager.openPR") { - const wt = this.host.getWorktrees().find((w: Worktree) => w.id === m.worktreeId) - if (wt?.prUrl) this.host.openExternal(wt.prUrl) + const url = (m.url as string | undefined) ?? this.host.getWorktrees().find((w: Worktree) => w.id === m.worktreeId)?.prUrl + if (url) this.host.openExternal(url) return true } return false diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 44e2c3eee68..cb2f761ed10 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -739,6 +739,7 @@ interface OpenPRIn { type: "agentManager.openPR" projectId?: string worktreeId: string + url?: string } interface OpenSessionsIn { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index b10f83e6d6f..d00471b8bd6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -2710,7 +2710,7 @@ const AgentManagerContent: Component = () => { setSidePanel(null)} - onOpenExternal={() => vscode.postMessage({ type: "agentManager.openPR", worktreeId: sel as string })} + onOpenExternal={() => vscode.postMessage({ type: "agentManager.openPR", worktreeId: sel as string, url: pr.url })} /> ) })()} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx index 8a5a011ea98..6bb89ddb585 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx @@ -3,6 +3,8 @@ import { Component, For, Show } from "solid-js" import { Icon } from "@kilocode/kilo-ui/icon" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Tooltip } from "@kilocode/kilo-ui/tooltip" +import { Markdown } from "@kilocode/kilo-ui/markdown" +import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" import type { PRStatus, CheckStatus, PRReviewer } from "../src/types/messages" import { prAccentColor, prBadgeIndicator, prChecksRunning } from "./WorktreeItem" @@ -45,7 +47,7 @@ const REVIEWER_STATE_ICON: Record = { approved: "circle-check", changes_requested: "refresh", commented: "edit", - pending: "clock", + pending: "dash", } const REVIEWER_STATE_LABEL: Record = { @@ -55,7 +57,13 @@ const REVIEWER_STATE_LABEL: Record = { pending: "Awaiting", } -export const PRPanel: Component = (props) => { +export const PRPanel: Component = (props) => ( + + + +) + +const PRPanelInner: Component = (props) => { const accent = () => prAccentColor(props.pr) const indicator = () => prBadgeIndicator(props.pr) const pulsing = () => prChecksRunning(props.pr) @@ -94,6 +102,7 @@ export const PRPanel: Component = (props) => {
{/* State + review */}
+
Overview
Status {STATE_LABEL[props.pr.state]} @@ -108,19 +117,11 @@ export const PRPanel: Component = (props) => {
- {/* Description */} - -
-
-
{props.pr.body}
-
- - {/* Reviewers */} 0}>
-
Reviewers
+
Reviewers
{(reviewer) => ( @@ -135,13 +136,24 @@ export const PRPanel: Component = (props) => {
+ {/* Description */} + +
+
+
Description
+
+ +
+
+ + {/* Diff stats */} 0 || props.pr.additions > 0 || props.pr.deletions > 0}>
-
- Changes - +
+ File Changes + 0}> {props.pr.files}f @@ -160,9 +172,9 @@ export const PRPanel: Component = (props) => { 0}>
-
- Checks - +
+ Checks + {props.pr.checks.passed}/{props.pr.checks.total} passed
@@ -192,15 +204,36 @@ export const PRPanel: Component = (props) => { 0}>
-
- Comments - - {props.pr.comments!.total} - 0}> - {" "} - ({props.pr.comments!.unresolved} unresolved) - - +
+ Comments + 0}> + + {props.pr.comments!.unresolved} unresolved + + +
+
+ + {(comment) => ( +
+
+ {comment.author} + + + {comment.file} + {`:${comment.line}`} + + + + Resolved + +
+
+ +
+
+ )} +
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 9f9403b046b..e4c19fe49d7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -4944,7 +4944,7 @@ body.vscode-high-contrast-light { .am-pr-panel-title { font-size: var(--kilo-font-size-14); - color: var(--text-base); + color: var(--vscode-foreground); font-weight: 500; overflow: hidden; text-overflow: ellipsis; @@ -4968,6 +4968,26 @@ body.vscode-high-contrast-light { padding: 4px 12px; } +.am-pr-panel-section-heading { + display: flex; + align-items: center; + justify-content: space-between; + font-size: var(--font-size-small); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-weak); + padding-bottom: 6px; +} + +.am-pr-panel-section-count { + font-size: var(--kilo-font-size-11); + font-weight: 400; + text-transform: none; + letter-spacing: 0; + color: var(--text-weak); +} + .am-pr-panel-divider { height: 1px; background: var(--vscode-panel-border); @@ -4984,14 +5004,17 @@ body.vscode-high-contrast-light { } .am-pr-panel-label { - font-size: var(--kilo-font-size-12); - color: var(--text-weak); + font-size: var(--kilo-font-size-11); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-weaker); flex-shrink: 0; } .am-pr-panel-value { font-size: var(--kilo-font-size-12); - color: var(--text-base); + color: var(--vscode-foreground); text-align: right; } @@ -5014,14 +5037,90 @@ body.vscode-high-contrast-light { color: #fbbf24; } +/* PR comment list */ +.am-pr-panel-comment-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 6px; +} + +.am-pr-panel-comment { + border: 1px solid var(--vscode-panel-border); + border-radius: 4px; + padding: 8px; + opacity: 1; +} + +.am-pr-panel-comment-resolved { + opacity: 0.5; +} + +.am-pr-panel-comment-header { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 4px; + flex-wrap: wrap; +} + +.am-pr-panel-comment-author { + font-size: var(--kilo-font-size-12); + font-weight: 600; + color: var(--vscode-foreground); +} + +.am-pr-panel-comment-file { + font-size: var(--kilo-font-size-11); + color: var(--text-weak); + font-family: var(--font-mono, monospace); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 180px; +} + +.am-pr-panel-comment-resolved-badge { + font-size: var(--kilo-font-size-10); + color: var(--text-weaker); + border: 1px solid var(--vscode-panel-border); + border-radius: 3px; + padding: 1px 4px; + margin-left: auto; +} + +.am-pr-panel-comment-body [data-component="markdown"] { + font-size: var(--kilo-font-size-12); + color: var(--vscode-foreground); +} + .am-pr-panel-description { - font-size: var(--kilo-font-size-14); - color: var(--text-base); - white-space: pre-wrap; line-height: 1.5; padding: 2px 0 4px; } +.am-pr-panel-description [data-component="markdown"], +.am-pr-panel-comment-body [data-component="markdown"] { + font-size: var(--kilo-font-size-13); + color: var(--vscode-foreground); +} + +.am-pr-panel-description [data-component="markdown"] h1, +.am-pr-panel-description [data-component="markdown"] h2, +.am-pr-panel-description [data-component="markdown"] h3, +.am-pr-panel-description [data-component="markdown"] h4, +.am-pr-panel-description [data-component="markdown"] h5, +.am-pr-panel-description [data-component="markdown"] h6 { + font-weight: 600; + color: var(--vscode-foreground); + margin-top: 0.75em; + margin-bottom: 0.25em; +} + +.am-pr-panel-description [data-component="markdown"] h1 { font-size: var(--kilo-font-size-16); } +.am-pr-panel-description [data-component="markdown"] h2 { font-size: var(--kilo-font-size-14); } +.am-pr-panel-description [data-component="markdown"] h3 { font-size: var(--kilo-font-size-13); } + /* PR Reviewers */ .am-pr-panel-reviewers { display: flex; @@ -5042,13 +5141,13 @@ body.vscode-high-contrast-light { } .am-pr-panel-reviewer[data-state="approved"] .am-pr-reviewer-icon { color: #34d399; } -.am-pr-panel-reviewer[data-state="changes_requested"] .am-pr-reviewer-icon { color: #f87171; } +.am-pr-panel-reviewer[data-state="changes_requested"] .am-pr-reviewer-icon { color: #fbbf24; } .am-pr-panel-reviewer[data-state="commented"] .am-pr-reviewer-icon { color: var(--text-weak); } .am-pr-panel-reviewer[data-state="pending"] .am-pr-reviewer-icon { color: var(--text-weaker); } .am-pr-reviewer-login { flex: 1; - color: var(--text-base); + color: var(--vscode-foreground); } .am-pr-reviewer-state { @@ -5083,7 +5182,7 @@ body.vscode-high-contrast-light { .am-pr-check-name { flex: 1; - color: var(--text-base); + color: var(--vscode-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts index 4f11b99d7c2..db195e4ad4c 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts @@ -973,6 +973,7 @@ export interface OpenPRMessage { type: "agentManager.openPR" projectId?: string worktreeId: string + url?: string } export interface ApplyWorktreeDiffMessage { From eb74496b3b55683e0ead9326c42aa74493dcee6c Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 3 Aug 2026 13:27:26 -0700 Subject: [PATCH 03/42] feat(pr-viewer): Update styles section headers --- .../agent-manager/AgentManagerApp.tsx | 24 ++- .../webview-ui/agent-manager/PRPanel.tsx | 203 +++++++++++------- .../agent-manager/agent-manager.css | 49 ++++- packages/ui/src/components/icon.tsx | 1 + 4 files changed, 186 insertions(+), 91 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index d00471b8bd6..25c22878f9b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -310,6 +310,13 @@ const AgentManagerContent: Component = () => { const [sidePanel, setSidePanel] = createSignal(null) const diffOpen = () => sidePanel() === "diff" const prOpen = () => sidePanel() === "pr" + const activePR = createMemo(() => { + const selected = selection() + if (!selected || selected === LOCAL) return undefined + const pr = prStatuses()[selected] + if (!pr) return undefined + return { pr, selected, wt: worktrees().find((w) => w.id === selected) } + }) const diffs = createWorktreeDiffs(vscode) const diffDatas = diffs.diffDatas const diffLoading = diffs.diffLoading @@ -2456,11 +2463,7 @@ const AgentManagerContent: Component = () => { reviewActive={reviewActive} onToggleDiff={toggleDiffPanel} onToggleReview={metrics.click("fullscreen_review", "tab_toolbar", toggleReviewTab)} - prStatus={() => { - const sel = selection() - if (!sel || sel === LOCAL) return undefined - return prStatuses()[sel] - }} + prStatus={() => activePR()?.pr} prOpen={prOpen} onTogglePR={togglePRPanel} terminalDestination={sideCtl.destination} @@ -2701,16 +2704,15 @@ const AgentManagerContent: Component = () => { activeTerminalId={terms.activeId()} /> - + {(() => { - const sel = selection() - const pr = sel && sel !== LOCAL ? prStatuses()[sel] : undefined - if (!pr) return null + const data = activePR()! return ( setSidePanel(null)} - onOpenExternal={() => vscode.postMessage({ type: "agentManager.openPR", worktreeId: sel as string, url: pr.url })} + onOpenExternal={() => vscode.postMessage({ type: "agentManager.openPR", worktreeId: data.selected, url: data.pr.url })} /> ) })()} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx index 6bb89ddb585..e97cfca42b7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx @@ -1,15 +1,16 @@ /** @jsxImportSource solid-js */ -import { Component, For, Show } from "solid-js" +import { Component, For, Show, createSignal } from "solid-js" import { Icon } from "@kilocode/kilo-ui/icon" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Tooltip } from "@kilocode/kilo-ui/tooltip" import { Markdown } from "@kilocode/kilo-ui/markdown" import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" -import type { PRStatus, CheckStatus, PRReviewer } from "../src/types/messages" +import type { PRStatus, CheckStatus, PRReviewer, WorktreeState } from "../src/types/messages" import { prAccentColor, prBadgeIndicator, prChecksRunning } from "./WorktreeItem" interface PRPanelProps { pr: PRStatus + worktree?: WorktreeState onClose: () => void onOpenExternal: () => void } @@ -29,9 +30,9 @@ const REVIEW_LABEL: Partial, string>> = { const CHECK_ICON: Record = { success: "circle-check", - failure: "circle-x", - cancelled: "circle-x", - skipped: "circle-x", + failure: "circle-x-outline", + cancelled: "circle-x-outline", + skipped: "circle-x-outline", pending: "play", } @@ -57,6 +58,26 @@ const REVIEWER_STATE_LABEL: Record = { pending: "Awaiting", } +function SectionHeading(props: { + title: string + open: boolean + onToggle: () => void + count?: string + countClass?: string +}) { + return ( + + ) +} + export const PRPanel: Component = (props) => ( @@ -68,6 +89,11 @@ const PRPanelInner: Component = (props) => { const indicator = () => prBadgeIndicator(props.pr) const pulsing = () => prChecksRunning(props.pr) + const [reviewersOpen, setReviewersOpen] = createSignal(true) + const [descOpen, setDescOpen] = createSignal(true) + const [checksOpen, setChecksOpen] = createSignal(true) + const [commentsOpen, setCommentsOpen] = createSignal(true) + return (
@@ -100,9 +126,20 @@ const PRPanelInner: Component = (props) => {
- {/* State + review */} + {/* Overview: branch→base + status */}
-
Overview
+ + {(wt) => ( +
+ Branch + + {wt().branch} + + {wt().parentBranch} + +
+ )} +
Status {STATE_LABEL[props.pr.state]} @@ -121,18 +158,20 @@ const PRPanelInner: Component = (props) => { 0}>
-
Reviewers
-
- - {(reviewer) => ( -
- - - {REVIEWER_STATE_LABEL[reviewer.state]} -
- )} -
-
+ setReviewersOpen((v) => !v)} /> + +
+ + {(reviewer) => ( +
+ + + {REVIEWER_STATE_LABEL[reviewer.state]} +
+ )} +
+
+
@@ -140,14 +179,16 @@ const PRPanelInner: Component = (props) => {
-
Description
-
- -
+ setDescOpen((v) => !v)} /> + +
+ +
+
- {/* Diff stats */} + {/* File Changes */} 0 || props.pr.additions > 0 || props.pr.deletions > 0}>
@@ -172,31 +213,34 @@ const PRPanelInner: Component = (props) => { 0}>
-
- Checks - - {props.pr.checks.passed}/{props.pr.checks.total} passed - -
-
- - {(check) => ( -
- - {check.name} - {CHECK_LABEL[check.status]} - - {check.duration} - - - e.preventDefault()}> - - - -
- )} -
-
+ setChecksOpen((v) => !v)} + count={`${props.pr.checks.passed}/${props.pr.checks.total} passed`} + countClass={`am-pr-checks-count-${props.pr.checks.status}`} + /> + +
+ + {(check) => ( +
+ + {check.name} + {CHECK_LABEL[check.status]} + + {check.duration} + + + e.preventDefault()}> + + + +
+ )} +
+
+
@@ -204,37 +248,38 @@ const PRPanelInner: Component = (props) => { 0}>
-
- Comments - 0}> - - {props.pr.comments!.unresolved} unresolved - - -
-
- - {(comment) => ( -
-
- {comment.author} - - - {comment.file} - {`:${comment.line}`} - - - - Resolved - + setCommentsOpen((v) => !v)} + count={props.pr.comments!.unresolved > 0 ? `${props.pr.comments!.unresolved} unresolved` : undefined} + countClass="am-pr-panel-unresolved" + /> + +
+ + {(comment) => ( +
+
+ {comment.author} + + + {comment.file} + {`:${comment.line}`} + + + + Resolved + +
+
+ +
-
- -
-
- )} - -
+ )} + +
+
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 e4c19fe49d7..21305369f5b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -4980,6 +4980,54 @@ body.vscode-high-contrast-light { padding-bottom: 6px; } +.am-pr-panel-section-toggle { + width: 100%; + background: none; + border: none; + cursor: pointer; + padding: 0; + text-align: left; + border-radius: 3px; +} + +.am-pr-panel-section-toggle:hover { + color: var(--vscode-foreground); +} + +.am-pr-panel-section-heading-left { + display: flex; + align-items: center; + gap: 4px; +} + +.am-pr-section-chevron { + flex-shrink: 0; + opacity: 0.6; +} + +/* Branch → base row */ +.am-pr-panel-branch { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.am-pr-branch-name { + font-family: var(--font-mono, monospace); + font-size: var(--kilo-font-size-11); +} + +.am-pr-branch-arrow { + opacity: 0.5; + flex-shrink: 0; +} + +/* Check count color by status */ +.am-pr-checks-count-success { color: #34d399; } +.am-pr-checks-count-failure { color: #f87171; } +.am-pr-checks-count-pending { color: #fbbf24; } + .am-pr-panel-section-count { font-size: var(--kilo-font-size-11); font-weight: 400; @@ -5032,7 +5080,6 @@ body.vscode-high-contrast-light { align-items: center; gap: 6px; } - .am-pr-panel-unresolved { color: #fbbf24; } diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index bdf68e12228..e70de73a831 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -18,6 +18,7 @@ const icons = { "chevron-grabber-vertical": ``, "chevron-double-right": ``, "circle-x": ``, + "circle-x-outline": ``, close: ``, "close-small": ``, checklist: ``, From 291ac0887e4874dd4904e582118ebb3aa22edb87 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 3 Aug 2026 13:38:21 -0700 Subject: [PATCH 04/42] fix(pr-view): Move toggle pr panel function --- .../webview-ui/agent-manager/AgentManagerApp.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 25c22878f9b..a49531d7994 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -359,7 +359,6 @@ const AgentManagerContent: Component = () => { // Per-worktree git stats (diff additions/deletions, commits missing from origin) const worktreeStats = () => registry.active().worktreeStats() - // Per-worktree PR status data const prStatuses = () => registry.active().prStatuses() const runStatuses = () => registry.active().runStatuses() @@ -558,6 +557,12 @@ const AgentManagerContent: Component = () => { } const openWindow = metrics.click("open_worktree_window", "tab_toolbar", openWorktreeDirectory) + const togglePRPanel = () => { + setHistory(false) + setReviewActive(false) + setSidePanel((prev) => (prev === "pr" ? null : "pr")) + } + const openSelectedPR = () => { const sel = selection() if (!sel || sel === LOCAL || !prStatuses()[sel]) return @@ -565,12 +570,6 @@ const AgentManagerContent: Component = () => { togglePRPanel() } - const togglePRPanel = () => { - setHistory(false) - setReviewActive(false) - setSidePanel((prev) => (prev === "pr" ? null : "pr")) - } - const runWorktree = (id: string, destination: TerminalDestination) => { const state = runStatuses()[id]?.state ?? "idle" if (state === "running" || state === "stopping") { From 8663e5baf6a7a5f5ff60ad925cae3bc5702854f7 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 3 Aug 2026 14:09:02 -0700 Subject: [PATCH 05/42] fix(pr-view): Organizing files and components, and improving types --- .../src/agent-manager/PRStatusPoller.ts | 28 +- .../kilo-vscode/src/agent-manager/types.ts | 6 +- .../agent-manager/AgentManagerApp.tsx | 2 +- .../webview-ui/agent-manager/PRPanel.tsx | 288 ------------- .../webview-ui/agent-manager/TabBar.tsx | 2 +- .../webview-ui/agent-manager/WorktreeItem.tsx | 23 +- .../agent-manager/agent-manager.css | 390 +----------------- .../webview-ui/agent-manager/pr/PRBadge.tsx | 30 ++ .../webview-ui/agent-manager/pr/PRChecks.tsx | 59 +++ .../agent-manager/pr/PRComments.tsx | 48 +++ .../agent-manager/pr/PRDescription.tsx | 21 + .../agent-manager/pr/PRFileChanges.tsx | 20 + .../agent-manager/pr/PROverview.tsx | 49 +++ .../webview-ui/agent-manager/pr/PRPanel.tsx | 61 +++ .../agent-manager/pr/PRReviewers.tsx | 44 ++ .../agent-manager/pr/SectionHeading.tsx | 23 ++ .../webview-ui/agent-manager/pr/pr-panel.css | 343 +++++++++++++++ .../webview-ui/agent-manager/pr/pr-types.ts | 32 ++ .../src/stories/agent-manager.stories.tsx | 2 +- .../src/stories/section-header.stories.tsx | 14 +- .../src/types/messages/agent-manager.ts | 39 +- 21 files changed, 793 insertions(+), 731 deletions(-) delete mode 100644 packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRDescription.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PROverview.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRReviewers.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/SectionHeading.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index c45cd992beb..01875607f00 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -249,7 +249,9 @@ export class PRStatusPoller { state: pr.state, review: pr.review, checks, - ...(comments && { comments, reviewers: comments.reviewers }), + ...(comments && { + comments: { total: comments.total, unresolved: comments.unresolved, comments: comments.comments, reviewers: comments.reviewers }, + }), additions: pr.additions, deletions: pr.deletions, files: pr.files, @@ -367,7 +369,7 @@ export class PRStatusPoller { passed: number failed: number pending: number - items: PRCheck[] + checks: PRCheck[] }> { try { const { stdout } = await this.shell( @@ -383,24 +385,24 @@ export class PRStatusPoller { completedAt?: string }> - const items: PRCheck[] = data.map((c) => ({ + const checks: PRCheck[] = data.map((c) => ({ name: c.name, status: mapCheckStatus(c.state), url: c.link, duration: formatCheckDuration(c.startedAt, c.completedAt), })) - const total = items.length - const passed = items.filter((c) => c.status === "success").length - const failed = items.filter((c) => c.status === "failure").length - const pending = items.filter((c) => c.status === "pending").length + const total = checks.length + const passed = checks.filter((c) => c.status === "success").length + const failed = checks.filter((c) => c.status === "failure").length + const pending = checks.filter((c) => c.status === "pending").length const status: AggregateCheckStatus = total === 0 ? "none" : failed > 0 ? "failure" : pending > 0 ? "pending" : "success" - return { status, total, passed, failed, pending, items } + return { status, total, passed, failed, pending, checks } } catch { - return { status: "none", total: 0, passed: 0, failed: 0, pending: 0, items: [] } + return { status: "none", total: 0, passed: 0, failed: 0, pending: 0, checks: [] } } } @@ -421,7 +423,7 @@ export class PRStatusPoller { private async fetchComments( prNumber: number, cwd: string, - ): Promise<{ total: number; unresolved: number; items: PRComment[]; reviewers: PRReviewer[] }> { + ): Promise<{ total: number; unresolved: number; comments: PRComment[]; reviewers: PRReviewer[] }> { try { const repo = await this.getRepoInfo(cwd) const query = `query($owner: String!, $repo: String!, $number: Int!) { @@ -458,12 +460,12 @@ export class PRStatusPoller { { cwd, timeout: 15_000 }, ) const pr = JSON.parse(stdout)?.data?.repository?.pullRequest - const items = parseComments(pr?.reviewThreads?.nodes ?? []) + const comments = parseComments(pr?.reviewThreads?.nodes ?? []) const reviewers = parseReviewers(pr?.reviewRequests?.nodes ?? [], pr?.reviews?.nodes ?? []) - return { total: items.length, unresolved: items.filter((c) => !c.resolved).length, items, reviewers } + return { total: comments.length, unresolved: comments.filter((c) => !c.resolved).length, comments, reviewers } } catch (err) { this.options.log("Failed to fetch PR comments:", err) - return { total: 0, unresolved: 0, items: [], reviewers: [] } + return { total: 0, unresolved: 0, comments: [], reviewers: [] } } } } diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index cb2f761ed10..38bf209d547 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -90,14 +90,14 @@ export interface PRStatus { passed: number failed: number pending: number - items: PRCheck[] + checks: PRCheck[] } comments?: { total: number unresolved: number - items: PRComment[] + comments: PRComment[] + reviewers: PRReviewer[] } - reviewers?: PRReviewer[] additions: number deletions: number files: number diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index a49531d7994..ea77a6c64b8 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -141,7 +141,7 @@ import { import { focusCurrentTab, renderTab, renderTerminalLayer, renderNewTabButton } from "./tab-rendering" import { useTabScroll } from "./tab-scroll" import { DiffPanel } from "./DiffPanel" -import { PRPanel } from "./PRPanel" +import { PRPanel } from "./pr/PRPanel" import { createRevertFile } from "./revert-file" import { FullScreenDiffView } from "../diff-viewer/FullScreenDiffView" import { createApplyToLocal } from "./apply-to-local" diff --git a/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx deleted file mode 100644 index e97cfca42b7..00000000000 --- a/packages/kilo-vscode/webview-ui/agent-manager/PRPanel.tsx +++ /dev/null @@ -1,288 +0,0 @@ -/** @jsxImportSource solid-js */ -import { Component, For, Show, createSignal } from "solid-js" -import { Icon } from "@kilocode/kilo-ui/icon" -import { IconButton } from "@kilocode/kilo-ui/icon-button" -import { Tooltip } from "@kilocode/kilo-ui/tooltip" -import { Markdown } from "@kilocode/kilo-ui/markdown" -import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" -import type { PRStatus, CheckStatus, PRReviewer, WorktreeState } from "../src/types/messages" -import { prAccentColor, prBadgeIndicator, prChecksRunning } from "./WorktreeItem" - -interface PRPanelProps { - pr: PRStatus - worktree?: WorktreeState - onClose: () => void - onOpenExternal: () => void -} - -const STATE_LABEL: Record = { - open: "Open", - draft: "Draft", - merged: "Merged", - closed: "Closed", -} - -const REVIEW_LABEL: Partial, string>> = { - approved: "Approved", - changes_requested: "Changes Requested", - pending: "Review Pending", -} - -const CHECK_ICON: Record = { - success: "circle-check", - failure: "circle-x-outline", - cancelled: "circle-x-outline", - skipped: "circle-x-outline", - pending: "play", -} - -const CHECK_LABEL: Record = { - success: "Passed", - failure: "Failed", - pending: "Running", - skipped: "Skipped", - cancelled: "Cancelled", -} - -const REVIEWER_STATE_ICON: Record = { - approved: "circle-check", - changes_requested: "refresh", - commented: "edit", - pending: "dash", -} - -const REVIEWER_STATE_LABEL: Record = { - approved: "Approved", - changes_requested: "Changes requested", - commented: "Commented", - pending: "Awaiting", -} - -function SectionHeading(props: { - title: string - open: boolean - onToggle: () => void - count?: string - countClass?: string -}) { - return ( - - ) -} - -export const PRPanel: Component = (props) => ( - - - -) - -const PRPanelInner: Component = (props) => { - const accent = () => prAccentColor(props.pr) - const indicator = () => prBadgeIndicator(props.pr) - const pulsing = () => prChecksRunning(props.pr) - - const [reviewersOpen, setReviewersOpen] = createSignal(true) - const [descOpen, setDescOpen] = createSignal(true) - const [checksOpen, setChecksOpen] = createSignal(true) - const [commentsOpen, setCommentsOpen] = createSignal(true) - - return ( -
-
-
- - - #{props.pr.number} - - {props.pr.title} -
-
- - - - - - -
-
- -
- {/* Overview: branch→base + status */} -
- - {(wt) => ( -
- Branch - - {wt().branch} - - {wt().parentBranch} - -
- )} -
-
- Status - {STATE_LABEL[props.pr.state]} -
- - {(review) => ( -
- Review - {REVIEW_LABEL[review()]} -
- )} -
-
- - {/* Reviewers */} - 0}> -
-
- setReviewersOpen((v) => !v)} /> - -
- - {(reviewer) => ( -
- - - {REVIEWER_STATE_LABEL[reviewer.state]} -
- )} -
-
-
-
- - - {/* Description */} - -
-
- setDescOpen((v) => !v)} /> - -
- -
-
-
- - - {/* File Changes */} - 0 || props.pr.additions > 0 || props.pr.deletions > 0}> -
-
-
- File Changes - - 0}> - {props.pr.files}f - - 0}> - +{props.pr.additions} - - 0}> - −{props.pr.deletions} - - -
-
- - - {/* Checks */} - 0}> -
-
- setChecksOpen((v) => !v)} - count={`${props.pr.checks.passed}/${props.pr.checks.total} passed`} - countClass={`am-pr-checks-count-${props.pr.checks.status}`} - /> - -
- - {(check) => ( -
- - {check.name} - {CHECK_LABEL[check.status]} - - {check.duration} - - - e.preventDefault()}> - - - -
- )} -
-
-
-
- - - {/* Comments */} - 0}> -
-
- setCommentsOpen((v) => !v)} - count={props.pr.comments!.unresolved > 0 ? `${props.pr.comments!.unresolved} unresolved` : undefined} - countClass="am-pr-panel-unresolved" - /> - -
- - {(comment) => ( -
-
- {comment.author} - - - {comment.file} - {`:${comment.line}`} - - - - Resolved - -
-
- -
-
- )} -
-
-
-
- -
-
- ) -} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx index f3c1ddda1c3..ab0a888f30e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx @@ -55,7 +55,7 @@ export interface TabBarProps { reviewActive: () => boolean onToggleDiff: () => void onToggleReview: () => void - prStatus: () => PRStatus | null | undefined + prStatus: () => PRStatus | undefined prOpen: () => boolean onTogglePR: () => void terminalDestination: () => TerminalDestination diff --git a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx index a7fc15a6cf4..3af617b8599 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx @@ -10,7 +10,8 @@ import { Tooltip, TooltipKeybind } from "@kilocode/kilo-ui/tooltip" import { HoverCard } from "@kilocode/kilo-ui/hover-card" import { ContextMenu } from "@kilocode/kilo-ui/context-menu" import { Button } from "@kilocode/kilo-ui/button" -import type { WorktreeState, WorktreeGitStats, PRStatus, SectionState, RunStatus } from "../src/types/messages" +import type { WorktreeState, WorktreeGitStats, SectionState, RunStatus } from "../src/types/messages" +import type { PRStatus } from "../src/types/messages" import { colorCss } from "./section-colors" import { useLanguage } from "../src/context/language" import { formatRelativeDate } from "../src/utils/date" @@ -57,7 +58,7 @@ interface WorktreeItemProps { /** Keybinding string for the open-in-vscode action. */ openKeybind: string /** PR status for this worktree's branch, or null if no PR. */ - pr?: PRStatus | null + pr?: PRStatus runStatus?: RunStatus /** Callback when the PR badge is clicked. */ onOpenPR?: () => void @@ -94,12 +95,13 @@ const hasStats = (s: WorktreeGitStats | undefined): s is WorktreeGitStats => * and review results are conveyed by a separate status icon (see prBadgeIndicator) * so a failing check is not mistaken for a closed PR. */ -export function prAccentColor(pr: PRStatus): string { - if (pr.state === "draft") return "var(--text-weaker)" - if (pr.state === "merged") return "#a78bfa" - if (pr.state === "closed") return "#f87171" - if (pr.checks.status === "pending") return "#fbbf24" - return "#34d399" +/** Derive the badge accent key used as `data-accent` for CSS color styling. */ +export function prBadgeAccent(pr: PRStatus): "open" | "pending" | "draft" | "merged" | "closed" { + if (pr.state === "draft") return "draft" + if (pr.state === "merged") return "merged" + if (pr.state === "closed") return "closed" + if (pr.checks.status === "pending") return "pending" + return "open" } /** True while an open PR's checks are still running — drives the pulsing amber badge. */ @@ -349,13 +351,12 @@ export const WorktreeItem: Component = (props) => { } > {(pr) => { - const accent = () => prAccentColor(pr()) const indicator = () => prBadgeIndicator(pr()) return ( }> 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 21305369f5b..4f2958ab18b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -846,7 +846,18 @@ button.am-section-toggle:hover .am-section-label { } /* PR pill badge — second row inside am-wt-row2. - Uses --pr-accent custom property (set inline) to derive all colors. */ + Uses data-accent attribute to derive all colors via CSS variables. */ +.am-pr-badge[data-accent="open"], +.am-pr-panel-badge[data-accent="open"] { --pr-accent: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-badge[data-accent="pending"], +.am-pr-panel-badge[data-accent="pending"] { --pr-accent: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-badge[data-accent="draft"], +.am-pr-panel-badge[data-accent="draft"] { --pr-accent: var(--text-weaker); } +.am-pr-badge[data-accent="merged"], +.am-pr-panel-badge[data-accent="merged"] { --pr-accent: var(--vscode-charts-purple, #a78bfa); } +.am-pr-badge[data-accent="closed"], +.am-pr-panel-badge[data-accent="closed"] { --pr-accent: var(--vscode-testing-iconFailed, #f87171); } + .am-pr-badge { display: inline-flex; align-items: center; @@ -881,17 +892,17 @@ button.am-section-toggle:hover .am-section-label { /* Status icon overlaid on the badge — colored by CI/review result, independent of the badge's PR-state accent color. */ .am-pr-badge-status[data-status="failure"] { - color: #ef4444; + color: var(--vscode-testing-iconFailed, #f87171); } .am-pr-badge-status[data-status="changes"] { - color: #fbbf24; + color: var(--vscode-charts-yellow, #fbbf24); } .am-pr-badge-status[data-status="approved"] { - color: #34d399; + color: var(--vscode-testing-iconPassed, #34d399); } /* Checks still running — pulse the whole amber badge. */ -.am-pr-badge[data-pending] { +.am-pr-badge.am-pr-badge-pending { animation: am-pr-pulse 1.5s ease-in-out infinite; } @keyframes am-pr-pulse { @@ -905,7 +916,7 @@ button.am-section-toggle:hover .am-section-label { } @media (prefers-reduced-motion: reduce) { - .am-pr-badge[data-pending] { + .am-pr-badge.am-pr-badge-pending { animation: none; opacity: 0.9; } @@ -4897,370 +4908,3 @@ body.vscode-high-contrast-light { .am-terminal-host [class~="xterm-viewport"] { background: transparent !important; } - -/* PR Panel */ -.am-pr-panel { - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; -} - -.am-pr-panel-header { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - padding: 10px 12px 8px; - border-bottom: 1px solid var(--vscode-panel-border); - flex-shrink: 0; -} - -.am-pr-panel-title-row { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; -} - -.am-pr-panel-badge { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 6px 2px 4px; - border-radius: 4px; - font-size: var(--kilo-font-size-10); - font-variant-numeric: tabular-nums; - background: color-mix(in srgb, var(--pr-accent, #34d399) 15%, transparent); - color: var(--pr-accent, #34d399); - border: 1px solid color-mix(in srgb, var(--pr-accent, #34d399) 40%, transparent); - white-space: nowrap; - flex-shrink: 0; -} - -.am-pr-panel-badge[data-pending] { - animation: am-pr-badge-pulse 1.5s ease-in-out infinite; -} - -.am-pr-panel-title { - font-size: var(--kilo-font-size-14); - color: var(--vscode-foreground); - font-weight: 500; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.am-pr-panel-actions { - display: flex; - align-items: center; - gap: 2px; - flex-shrink: 0; -} - -.am-pr-panel-body { - flex: 1; - overflow-y: auto; - padding: 8px 0; -} - -.am-pr-panel-section { - padding: 4px 12px; -} - -.am-pr-panel-section-heading { - display: flex; - align-items: center; - justify-content: space-between; - font-size: var(--font-size-small); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--text-weak); - padding-bottom: 6px; -} - -.am-pr-panel-section-toggle { - width: 100%; - background: none; - border: none; - cursor: pointer; - padding: 0; - text-align: left; - border-radius: 3px; -} - -.am-pr-panel-section-toggle:hover { - color: var(--vscode-foreground); -} - -.am-pr-panel-section-heading-left { - display: flex; - align-items: center; - gap: 4px; -} - -.am-pr-section-chevron { - flex-shrink: 0; - opacity: 0.6; -} - -/* Branch → base row */ -.am-pr-panel-branch { - display: flex; - align-items: center; - gap: 4px; - flex-wrap: wrap; -} - -.am-pr-branch-name { - font-family: var(--font-mono, monospace); - font-size: var(--kilo-font-size-11); -} - -.am-pr-branch-arrow { - opacity: 0.5; - flex-shrink: 0; -} - -/* Check count color by status */ -.am-pr-checks-count-success { color: #34d399; } -.am-pr-checks-count-failure { color: #f87171; } -.am-pr-checks-count-pending { color: #fbbf24; } - -.am-pr-panel-section-count { - font-size: var(--kilo-font-size-11); - font-weight: 400; - text-transform: none; - letter-spacing: 0; - color: var(--text-weak); -} - -.am-pr-panel-divider { - height: 1px; - background: var(--vscode-panel-border); - margin: 6px 0; -} - -.am-pr-panel-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 3px 0; - min-height: 22px; -} - -.am-pr-panel-label { - font-size: var(--kilo-font-size-11); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.04em; - color: var(--text-weaker); - flex-shrink: 0; -} - -.am-pr-panel-value { - font-size: var(--kilo-font-size-12); - color: var(--vscode-foreground); - text-align: right; -} - -.am-pr-panel-value[data-pr-state="open"] { color: #34d399; } -.am-pr-panel-value[data-pr-state="merged"] { color: #a78bfa; } -.am-pr-panel-value[data-pr-state="closed"] { color: #f87171; } -.am-pr-panel-value[data-pr-state="draft"] { color: var(--text-weaker); } - -.am-pr-panel-value[data-checks="failure"] { color: #f87171; } -.am-pr-panel-value[data-checks="pending"] { color: #fbbf24; } -.am-pr-panel-value[data-checks="success"] { color: #34d399; } - -.am-pr-panel-diff { - display: flex; - align-items: center; - gap: 6px; -} -.am-pr-panel-unresolved { - color: #fbbf24; -} - -/* PR comment list */ -.am-pr-panel-comment-list { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 6px; -} - -.am-pr-panel-comment { - border: 1px solid var(--vscode-panel-border); - border-radius: 4px; - padding: 8px; - opacity: 1; -} - -.am-pr-panel-comment-resolved { - opacity: 0.5; -} - -.am-pr-panel-comment-header { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 4px; - flex-wrap: wrap; -} - -.am-pr-panel-comment-author { - font-size: var(--kilo-font-size-12); - font-weight: 600; - color: var(--vscode-foreground); -} - -.am-pr-panel-comment-file { - font-size: var(--kilo-font-size-11); - color: var(--text-weak); - font-family: var(--font-mono, monospace); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 180px; -} - -.am-pr-panel-comment-resolved-badge { - font-size: var(--kilo-font-size-10); - color: var(--text-weaker); - border: 1px solid var(--vscode-panel-border); - border-radius: 3px; - padding: 1px 4px; - margin-left: auto; -} - -.am-pr-panel-comment-body [data-component="markdown"] { - font-size: var(--kilo-font-size-12); - color: var(--vscode-foreground); -} - -.am-pr-panel-description { - line-height: 1.5; - padding: 2px 0 4px; -} - -.am-pr-panel-description [data-component="markdown"], -.am-pr-panel-comment-body [data-component="markdown"] { - font-size: var(--kilo-font-size-13); - color: var(--vscode-foreground); -} - -.am-pr-panel-description [data-component="markdown"] h1, -.am-pr-panel-description [data-component="markdown"] h2, -.am-pr-panel-description [data-component="markdown"] h3, -.am-pr-panel-description [data-component="markdown"] h4, -.am-pr-panel-description [data-component="markdown"] h5, -.am-pr-panel-description [data-component="markdown"] h6 { - font-weight: 600; - color: var(--vscode-foreground); - margin-top: 0.75em; - margin-bottom: 0.25em; -} - -.am-pr-panel-description [data-component="markdown"] h1 { font-size: var(--kilo-font-size-16); } -.am-pr-panel-description [data-component="markdown"] h2 { font-size: var(--kilo-font-size-14); } -.am-pr-panel-description [data-component="markdown"] h3 { font-size: var(--kilo-font-size-13); } - -/* PR Reviewers */ -.am-pr-panel-reviewers { - display: flex; - flex-direction: column; - gap: 2px; -} - -.am-pr-panel-reviewer { - display: flex; - align-items: center; - gap: 6px; - padding: 3px 0; - font-size: var(--kilo-font-size-12); -} - -.am-pr-reviewer-icon { - flex-shrink: 0; -} - -.am-pr-panel-reviewer[data-state="approved"] .am-pr-reviewer-icon { color: #34d399; } -.am-pr-panel-reviewer[data-state="changes_requested"] .am-pr-reviewer-icon { color: #fbbf24; } -.am-pr-panel-reviewer[data-state="commented"] .am-pr-reviewer-icon { color: var(--text-weak); } -.am-pr-panel-reviewer[data-state="pending"] .am-pr-reviewer-icon { color: var(--text-weaker); } - -.am-pr-reviewer-login { - flex: 1; - color: var(--vscode-foreground); -} - -.am-pr-reviewer-state { - color: var(--text-weak); - flex-shrink: 0; -} - -.am-pr-panel-checks { - display: flex; - flex-direction: column; - gap: 2px; - margin-top: 4px; -} - -.am-pr-panel-check-item { - display: flex; - align-items: center; - gap: 6px; - padding: 3px 0; - font-size: var(--kilo-font-size-12); -} - -.am-pr-check-icon { - flex-shrink: 0; -} - -.am-pr-panel-check-item[data-status="success"] .am-pr-check-icon { color: #34d399; } -.am-pr-panel-check-item[data-status="failure"] .am-pr-check-icon { color: #f87171; } -.am-pr-panel-check-item[data-status="cancelled"] .am-pr-check-icon { color: var(--text-weaker); } -.am-pr-panel-check-item[data-status="skipped"] .am-pr-check-icon { color: var(--text-weaker); } -.am-pr-panel-check-item[data-status="pending"] .am-pr-check-icon { color: #fbbf24; } - -.am-pr-check-name { - flex: 1; - color: var(--vscode-foreground); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.am-pr-check-status { - color: var(--text-weak); - flex-shrink: 0; -} - -.am-pr-check-duration { - color: var(--text-weaker); - font-size: var(--kilo-font-size-10); - flex-shrink: 0; -} - -.am-pr-check-link { - color: var(--text-weak); - display: flex; - align-items: center; - flex-shrink: 0; - cursor: pointer; - text-decoration: none; -} - -.am-pr-check-link:hover { - color: var(--text-base); -} - -/* PR toggle button in tab bar */ -.am-pr-toggle-number { - font-size: var(--kilo-font-size-10); - font-variant-numeric: tabular-nums; -} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx new file mode 100644 index 00000000000..b80b9495152 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx @@ -0,0 +1,30 @@ +/** @jsxImportSource solid-js */ +import { Icon } from "@kilocode/kilo-ui/icon" +import type { PRStatus } from "../../src/types/messages" +import { prBadgeAccent, prBadgeIndicator, prChecksRunning } from "../WorktreeItem" + +const INDICATOR_ICON: Record = { + failure: "circle-x", + changes: "warning", + approved: "circle-check", + none: "branch", +} + +export function PRBadge(props: { pr: PRStatus }) { + const indicator = () => prBadgeIndicator(props.pr) + return ( + + + #{props.pr.number} + + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx new file mode 100644 index 00000000000..e76a760b275 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx @@ -0,0 +1,59 @@ +/** @jsxImportSource solid-js */ +import { For, Show, createSignal } from "solid-js" +import { Icon } from "@kilocode/kilo-ui/icon" +import type { PRStatus } from "../../src/types/messages" +import type { PRCheck, CheckStatus } from "./pr-types" +import { SectionHeading } from "./SectionHeading" + +const CHECK_ICON: Record = { + success: "circle-check", + failure: "circle-x-outline", + cancelled: "circle-x-outline", + skipped: "circle-x-outline", + pending: "play", +} + +const CHECK_LABEL: Record = { + success: "Passed", + failure: "Failed", + pending: "Running", + skipped: "Skipped", + cancelled: "Cancelled", +} + +export function PRChecks(props: { checks: PRStatus["checks"] }) { + const [open, setOpen] = createSignal(true) + return ( + <> +
+
+ setOpen((v) => !v)} + count={`${props.checks.passed}/${props.checks.total} passed`} + countClass={`am-pr-checks-count-${props.checks.status}`} + /> + +
+ + {(check: PRCheck) => ( +
+ + {check.name} + {CHECK_LABEL[check.status]} + {check.duration} + + e.preventDefault()}> + + + +
+ )} +
+
+
+
+ + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx new file mode 100644 index 00000000000..d3f3057e6c0 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx @@ -0,0 +1,48 @@ +/** @jsxImportSource solid-js */ +import { For, Show, createSignal } from "solid-js" +import { Markdown } from "@kilocode/kilo-ui/markdown" +import type { PRStatus } from "../../src/types/messages" +import type { PRComment } from "./pr-types" +import { SectionHeading } from "./SectionHeading" + +export function PRComments(props: { comments: NonNullable }) { + const [open, setOpen] = createSignal(true) + return ( + <> +
+
+ setOpen((v) => !v)} + count={props.comments.unresolved > 0 ? `${props.comments.unresolved} unresolved` : undefined} + countClass="am-pr-panel-unresolved" + /> + +
+ + {(comment: PRComment) => ( +
+
+ {comment.author} + + + {comment.file}{`:${comment.line}`} + + + + Resolved + +
+
+ +
+
+ )} +
+
+
+
+ + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRDescription.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRDescription.tsx new file mode 100644 index 00000000000..524be50e2ec --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRDescription.tsx @@ -0,0 +1,21 @@ +/** @jsxImportSource solid-js */ +import { Show, createSignal } from "solid-js" +import { Markdown } from "@kilocode/kilo-ui/markdown" +import { SectionHeading } from "./SectionHeading" + +export function PRDescription(props: { body: string }) { + const [open, setOpen] = createSignal(true) + return ( + <> +
+
+ setOpen((v) => !v)} /> + +
+ +
+
+
+ + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx new file mode 100644 index 00000000000..b4e558d67b0 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx @@ -0,0 +1,20 @@ +/** @jsxImportSource solid-js */ +import { Show } from "solid-js" + +export function PRFileChanges(props: { files: number; additions: number; deletions: number }) { + return ( + <> +
+
+
+ File Changes + + 0}>{props.files}f + 0}>+{props.additions} + 0}>−{props.deletions} + +
+
+ + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PROverview.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PROverview.tsx new file mode 100644 index 00000000000..6c1fc863739 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PROverview.tsx @@ -0,0 +1,49 @@ +/** @jsxImportSource solid-js */ +import { Show } from "solid-js" +import { Icon } from "@kilocode/kilo-ui/icon" +import type { WorktreeState } from "../../src/types/messages" +import type { PRStatus } from "../../src/types/messages" + +const STATE_LABEL: Record = { + open: "Open", + draft: "Draft", + merged: "Merged", + closed: "Closed", +} + +const REVIEW_LABEL: Partial, string>> = { + approved: "Approved", + changes_requested: "Changes Requested", + pending: "Review Pending", +} + +export function PROverview(props: { pr: PRStatus; worktree?: WorktreeState }) { + return ( +
+ + {(wt) => ( +
+ Branch + + {wt().branch} + + {wt().parentBranch} + +
+ )} +
+
+ Status + {STATE_LABEL[props.pr.state]} +
+ + {(review) => ( +
+ Review + {REVIEW_LABEL[review()]} +
+ )} +
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx new file mode 100644 index 00000000000..e69890b5e9f --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -0,0 +1,61 @@ +/** @jsxImportSource solid-js */ +import { Component, Show } from "solid-js" +import { IconButton } from "@kilocode/kilo-ui/icon-button" +import { Tooltip } from "@kilocode/kilo-ui/tooltip" +import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" +import type { WorktreeState } from "../../src/types/messages" +import type { PRStatus } from "../../src/types/messages" +import { PRBadge } from "./PRBadge" +import { PROverview } from "./PROverview" +import { PRReviewers } from "./PRReviewers" +import { PRDescription } from "./PRDescription" +import { PRFileChanges } from "./PRFileChanges" +import { PRChecks } from "./PRChecks" +import { PRComments } from "./PRComments" +import "./pr-panel.css" + +interface PRPanelProps { + pr: PRStatus + worktree?: WorktreeState + onClose: () => void + onOpenExternal: () => void +} + +export const PRPanel: Component = (props) => ( + +
+
+
+ + {props.pr.title} +
+
+ + + + + + +
+
+
+ + + + + + + + 0 || props.pr.additions > 0 || props.pr.deletions > 0}> + + + 0}> + + + 0}> + + +
+
+
+) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRReviewers.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRReviewers.tsx new file mode 100644 index 00000000000..85206562958 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRReviewers.tsx @@ -0,0 +1,44 @@ +/** @jsxImportSource solid-js */ +import { For, Show, createSignal } from "solid-js" +import { Icon } from "@kilocode/kilo-ui/icon" +import type { PRReviewer } from "./pr-types" +import { SectionHeading } from "./SectionHeading" + +const REVIEWER_ICON: Record = { + approved: "circle-check", + changes_requested: "refresh", + commented: "edit", + pending: "dash", +} + +const REVIEWER_LABEL: Record = { + approved: "Approved", + changes_requested: "Changes requested", + commented: "Commented", + pending: "Awaiting", +} + +export function PRReviewers(props: { reviewers: PRReviewer[] }) { + const [open, setOpen] = createSignal(true) + return ( + <> +
+
+ setOpen((v) => !v)} /> + +
+ + {(reviewer) => ( +
+ + + {REVIEWER_LABEL[reviewer.state]} +
+ )} +
+
+
+
+ + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/SectionHeading.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/SectionHeading.tsx new file mode 100644 index 00000000000..1de9bba802e --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/SectionHeading.tsx @@ -0,0 +1,23 @@ +/** @jsxImportSource solid-js */ +import { Show } from "solid-js" +import { Icon } from "@kilocode/kilo-ui/icon" + +export function SectionHeading(props: { + title: string + open: boolean + onToggle: () => void + count?: string + countClass?: string +}) { + return ( + + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css new file mode 100644 index 00000000000..ba73dd76ae8 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css @@ -0,0 +1,343 @@ +/* PR Panel */ +.am-pr-panel { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +/* Shared flex utilities scoped to the PR panel */ +.am-pr-row { display: flex; align-items: center; } +.am-pr-col { display: flex; flex-direction: column; } + +.am-pr-panel-header { + align-items: flex-start; + justify-content: space-between; + gap: 8px; + padding: 10px 12px 8px; + border-bottom: 1px solid var(--vscode-panel-border); + flex-shrink: 0; +} + +.am-pr-panel-title-row { + gap: 8px; + min-width: 0; +} + +.am-pr-panel-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 6px 2px 4px; + border-radius: 4px; + font-size: var(--kilo-font-size-10); + font-variant-numeric: tabular-nums; + background: color-mix(in srgb, var(--pr-accent) 15%, transparent); + color: var(--pr-accent); + border: 1px solid color-mix(in srgb, var(--pr-accent) 40%, transparent); + white-space: nowrap; + flex-shrink: 0; +} + +.am-pr-panel-badge.am-pr-badge-pending { + animation: am-pr-badge-pulse 1.5s ease-in-out infinite; +} + +.am-pr-panel-title { + font-size: var(--kilo-font-size-14); + color: var(--vscode-foreground); + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.am-pr-panel-actions { + gap: 2px; + flex-shrink: 0; +} + +.am-pr-panel-body { + flex: 1; + overflow-y: auto; + padding: 8px 0; +} + +.am-pr-panel-section { + padding: 4px 12px; +} + +.am-pr-panel-section-heading { + justify-content: space-between; + font-size: var(--font-size-small); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-weak); + padding-bottom: 6px; +} + +.am-pr-panel-section-toggle { + width: 100%; + background: none; + border: none; + cursor: pointer; + padding: 0; + text-align: left; + border-radius: 3px; +} + +.am-pr-panel-section-toggle:hover { + color: var(--vscode-foreground); +} + +.am-pr-panel-section-heading-left { + gap: 4px; +} + +.am-pr-section-chevron { + flex-shrink: 0; + opacity: 0.6; +} + +/* Branch → base row */ +.am-pr-panel-branch { + gap: 4px; + flex-wrap: wrap; +} + +.am-pr-branch-name { + font-family: var(--font-mono, monospace); + font-size: var(--kilo-font-size-11); +} + +.am-pr-branch-arrow { + opacity: 0.5; + flex-shrink: 0; +} + +/* Check count color by status */ +.am-pr-checks-count-success { color: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-checks-count-failure { color: var(--vscode-testing-iconFailed, #f87171); } +.am-pr-checks-count-pending { color: var(--vscode-charts-yellow, #fbbf24); } + +.am-pr-panel-section-count { + font-size: var(--kilo-font-size-11); + font-weight: 400; + text-transform: none; + letter-spacing: 0; + color: var(--text-weak); +} + +.am-pr-panel-divider { + height: 1px; + background: var(--vscode-panel-border); + margin: 6px 0; +} + +.am-pr-panel-row { + justify-content: space-between; + gap: 8px; + padding: 3px 0; + min-height: 22px; +} + +.am-pr-panel-label { + font-size: var(--kilo-font-size-11); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-weaker); + flex-shrink: 0; +} + +.am-pr-panel-value { + font-size: var(--kilo-font-size-12); + color: var(--vscode-foreground); + text-align: right; +} + +.am-pr-panel-value[data-pr-state="open"] { color: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-panel-value[data-pr-state="merged"] { color: var(--vscode-charts-purple, #a78bfa); } +.am-pr-panel-value[data-pr-state="closed"] { color: var(--vscode-testing-iconFailed, #f87171); } +.am-pr-panel-value[data-pr-state="draft"] { color: var(--text-weaker); } + +.am-pr-panel-value[data-checks="failure"] { color: var(--vscode-testing-iconFailed, #f87171); } +.am-pr-panel-value[data-checks="pending"] { color: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-panel-value[data-checks="success"] { color: var(--vscode-testing-iconPassed, #34d399); } + +.am-pr-panel-diff { + gap: 6px; +} +.am-pr-panel-unresolved { + color: var(--vscode-charts-yellow, #fbbf24); +} + +/* PR comment list */ +.am-pr-panel-comment-list { + gap: 8px; + margin-top: 6px; +} + +.am-pr-panel-comment { + border: 1px solid var(--vscode-panel-border); + border-radius: 4px; + padding: 8px; + opacity: 1; +} + +.am-pr-panel-comment-resolved { + opacity: 0.5; +} + +.am-pr-panel-comment-header { + gap: 6px; + margin-bottom: 4px; + flex-wrap: wrap; +} + +.am-pr-panel-comment-author { + font-size: var(--kilo-font-size-12); + font-weight: 600; + color: var(--vscode-foreground); +} + +.am-pr-panel-comment-file { + font-size: var(--kilo-font-size-11); + color: var(--text-weak); + font-family: var(--font-mono, monospace); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 180px; +} + +.am-pr-panel-comment-resolved-badge { + font-size: var(--kilo-font-size-10); + color: var(--text-weaker); + border: 1px solid var(--vscode-panel-border); + border-radius: 3px; + padding: 1px 4px; + margin-left: auto; +} + +.am-pr-panel-comment-body [data-component="markdown"] { + font-size: var(--kilo-font-size-12); + color: var(--vscode-foreground); +} + +.am-pr-panel-description { + line-height: 1.5; + padding: 2px 0 4px; +} + +.am-pr-panel-description [data-component="markdown"], +.am-pr-panel-comment-body [data-component="markdown"] { + font-size: var(--kilo-font-size-13); + color: var(--vscode-foreground); +} + +.am-pr-panel-description [data-component="markdown"] h1, +.am-pr-panel-description [data-component="markdown"] h2, +.am-pr-panel-description [data-component="markdown"] h3, +.am-pr-panel-description [data-component="markdown"] h4, +.am-pr-panel-description [data-component="markdown"] h5, +.am-pr-panel-description [data-component="markdown"] h6 { + font-weight: 600; + color: var(--vscode-foreground); + margin-top: 0.75em; + margin-bottom: 0.25em; +} + +.am-pr-panel-description [data-component="markdown"] h1 { font-size: var(--kilo-font-size-16); } +.am-pr-panel-description [data-component="markdown"] h2 { font-size: var(--kilo-font-size-14); } +.am-pr-panel-description [data-component="markdown"] h3 { font-size: var(--kilo-font-size-13); } + +/* PR Reviewers */ +.am-pr-panel-reviewers { + gap: 2px; +} + +.am-pr-panel-reviewer { + gap: 6px; + padding: 3px 0; + font-size: var(--kilo-font-size-12); +} + +.am-pr-reviewer-icon { + flex-shrink: 0; +} + +.am-pr-panel-reviewer[data-state="approved"] .am-pr-reviewer-icon { color: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-panel-reviewer[data-state="changes_requested"] .am-pr-reviewer-icon { color: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-panel-reviewer[data-state="commented"] .am-pr-reviewer-icon { color: var(--text-weak); } +.am-pr-panel-reviewer[data-state="pending"] .am-pr-reviewer-icon { color: var(--text-weaker); } + +.am-pr-reviewer-login { + flex: 1; + color: var(--vscode-foreground); +} + +.am-pr-reviewer-state { + color: var(--text-weak); + flex-shrink: 0; +} + +.am-pr-panel-checks { + gap: 2px; + margin-top: 4px; +} + +.am-pr-panel-check-item { + gap: 6px; + padding: 3px 0; + font-size: var(--kilo-font-size-12); +} + +.am-pr-check-icon { + flex-shrink: 0; +} + +.am-pr-panel-check-item[data-status="success"] .am-pr-check-icon { color: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-panel-check-item[data-status="failure"] .am-pr-check-icon { color: var(--vscode-testing-iconFailed, #f87171); } +.am-pr-panel-check-item[data-status="cancelled"] .am-pr-check-icon { color: var(--text-weaker); } +.am-pr-panel-check-item[data-status="skipped"] .am-pr-check-icon { color: var(--text-weaker); } +.am-pr-panel-check-item[data-status="pending"] .am-pr-check-icon { color: var(--vscode-charts-yellow, #fbbf24); } + +.am-pr-check-name { + flex: 1; + color: var(--vscode-foreground); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.am-pr-check-status { + color: var(--text-weak); + flex-shrink: 0; +} + +.am-pr-check-duration { + color: var(--text-weaker); + font-size: var(--kilo-font-size-10); + flex-shrink: 0; +} + +.am-pr-check-link { + color: var(--text-weak); + display: flex; + align-items: center; + flex-shrink: 0; + cursor: pointer; + text-decoration: none; +} + +.am-pr-check-link:hover { + color: var(--text-base); +} + +/* PR toggle button in tab bar */ +.am-pr-toggle-number { + font-size: var(--kilo-font-size-10); + font-variant-numeric: tabular-nums; +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts new file mode 100644 index 00000000000..8d88e131557 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts @@ -0,0 +1,32 @@ +// PR sub-types — source of truth for all PR-related types used in the PR panel. +// PRStatus lives in src/types/messages/agent-manager.ts for broad consumption. + +export type PRState = "open" | "draft" | "merged" | "closed" +export type ReviewDecision = "approved" | "changes_requested" | "pending" +export type CheckStatus = "success" | "failure" | "pending" | "skipped" | "cancelled" +export type AggregateCheckStatus = "success" | "failure" | "pending" | "none" + +export interface PRCheck { + name: string + status: CheckStatus + url?: string + duration?: string +} + +export interface PRComment { + id: string + author: string + avatar?: string + body: string + file?: string + line?: number + url?: string + resolved: boolean + createdAt?: number +} + +export interface PRReviewer { + login: string + avatar?: string + state: "approved" | "changes_requested" | "pending" | "commented" +} diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx index 9ebef0f8766..629818c1c25 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx @@ -604,7 +604,7 @@ const basePR: PRStatus = { url: "https://github.com/org/repo/pull/8594", state: "open", review: null, - checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, items: [] }, + checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, checks: [] }, additions: 978, deletions: 202, files: 12, diff --git a/packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx index cf348a4d339..23938282a84 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx @@ -593,7 +593,7 @@ export const WithPRBadges: Story = { additions: 120, deletions: 30, files: 5, - checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, items: [] }, + checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, checks: [] }, }} /> {/* Open + failing checks → green badge, red ✗ (no longer confusable with closed) */} @@ -611,7 +611,7 @@ export const WithPRBadges: Story = { additions: 15, deletions: 8, files: 3, - checks: { status: "failure", total: 5, passed: 3, failed: 2, pending: 0, items: [] }, + checks: { status: "failure", total: 5, passed: 3, failed: 2, pending: 0, checks: [] }, }} /> {/* Open + changes requested → green badge, amber warning */} @@ -629,7 +629,7 @@ export const WithPRBadges: Story = { additions: 60, deletions: 12, files: 4, - checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, items: [] }, + checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, checks: [] }, }} /> {/* Open + checks running → pulsing amber badge (animation disabled in snapshots) */} @@ -647,7 +647,7 @@ export const WithPRBadges: Story = { additions: 200, deletions: 0, files: 8, - checks: { status: "pending", total: 5, passed: 2, failed: 0, pending: 3, items: [] }, + checks: { status: "pending", total: 5, passed: 2, failed: 0, pending: 3, checks: [] }, }} /> {/* Draft → gray badge */} @@ -665,7 +665,7 @@ export const WithPRBadges: Story = { additions: 30, deletions: 5, files: 2, - checks: { status: "none", total: 0, passed: 0, failed: 0, pending: 0, items: [] }, + checks: { status: "none", total: 0, passed: 0, failed: 0, pending: 0, checks: [] }, }} /> {/* Merged → purple badge, no status icon */} @@ -683,7 +683,7 @@ export const WithPRBadges: Story = { additions: 90, deletions: 20, files: 6, - checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, items: [] }, + checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, checks: [] }, }} /> {/* Closed → red badge, no status icon (distinct from a failing open PR) */} @@ -701,7 +701,7 @@ export const WithPRBadges: Story = { additions: 10, deletions: 4, files: 1, - checks: { status: "failure", total: 5, passed: 1, failed: 4, pending: 0, items: [] }, + checks: { status: "failure", total: 5, passed: 1, failed: 4, pending: 0, checks: [] }, }} />
diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts index 5bdbe3cfafc..23c0395a07d 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts @@ -45,32 +45,11 @@ export interface SectionState { } // --------------------------------------------------------------------------- -// PR status types (mirrored from extension types.ts) +// PR status types — sub-types live in agent-manager/pr/pr-types.ts // --------------------------------------------------------------------------- -export type PRState = "open" | "draft" | "merged" | "closed" -export type ReviewDecision = "approved" | "changes_requested" | "pending" -export type CheckStatus = "success" | "failure" | "pending" | "skipped" | "cancelled" -export type AggregateCheckStatus = "success" | "failure" | "pending" | "none" - -export interface PRCheck { - name: string - status: CheckStatus - url?: string - duration?: string -} - -export interface PRComment { - id: string - author: string - avatar?: string - body: string - file?: string - line?: number - url?: string - resolved: boolean - createdAt?: number -} +import type { PRState, ReviewDecision, AggregateCheckStatus, PRCheck, PRComment, PRReviewer } from "../../../agent-manager/pr/pr-types" +export type { PRState, ReviewDecision, CheckStatus, AggregateCheckStatus, PRCheck, PRComment, PRReviewer } from "../../../agent-manager/pr/pr-types" export interface PRStatus { number: number @@ -85,25 +64,19 @@ export interface PRStatus { passed: number failed: number pending: number - items: PRCheck[] + checks: PRCheck[] } comments?: { total: number unresolved: number - items: PRComment[] + comments: PRComment[] + reviewers: PRReviewer[] } - reviewers?: PRReviewer[] additions: number deletions: number files: number } -export interface PRReviewer { - login: string - avatar?: string - state: "approved" | "changes_requested" | "pending" | "commented" -} - export type RunState = "idle" | "running" | "stopping" export interface RunStatus { From f01d5fdbb1120aa10881ef483cfa00729063e468 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 3 Aug 2026 14:24:03 -0700 Subject: [PATCH 06/42] feat(pr-view): Improve agent code practices --- .../src/agent-manager/pr-status-bridge.ts | 2 +- .../webview-ui/agent-manager/WorktreeItem.tsx | 19 +++++++-------- .../agent-manager/agent-manager.css | 23 +++++++++--------- .../webview-ui/agent-manager/pr/PRBadge.tsx | 12 +++++++--- .../webview-ui/agent-manager/pr/PRChecks.tsx | 24 +++++++------------ .../webview-ui/agent-manager/pr/PRPanel.tsx | 14 +++++------ 6 files changed, 43 insertions(+), 51 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts index 2a216c30796..c64da3971eb 100644 --- a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts +++ b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts @@ -78,7 +78,7 @@ export class PRStatusBridge { return true } if (m.type === "agentManager.openPR") { - const url = (m.url as string | undefined) ?? this.host.getWorktrees().find((w: Worktree) => w.id === m.worktreeId)?.prUrl + const url = (m.url as string) ?? this.host.getWorktrees().find((w: Worktree) => w.id === m.worktreeId)?.prUrl if (url) this.host.openExternal(url) return true } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx index 3af617b8599..d33b0e0a61d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx @@ -95,15 +95,6 @@ const hasStats = (s: WorktreeGitStats | undefined): s is WorktreeGitStats => * and review results are conveyed by a separate status icon (see prBadgeIndicator) * so a failing check is not mistaken for a closed PR. */ -/** Derive the badge accent key used as `data-accent` for CSS color styling. */ -export function prBadgeAccent(pr: PRStatus): "open" | "pending" | "draft" | "merged" | "closed" { - if (pr.state === "draft") return "draft" - if (pr.state === "merged") return "merged" - if (pr.state === "closed") return "closed" - if (pr.checks.status === "pending") return "pending" - return "open" -} - /** True while an open PR's checks are still running — drives the pulsing amber badge. */ export function prChecksRunning(pr: PRStatus): boolean { return pr.state === "open" && pr.checks.status === "pending" @@ -355,8 +346,14 @@ export const WorktreeItem: Component = (props) => { return ( }> 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 4f2958ab18b..79e0fee011a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -845,18 +845,17 @@ button.am-section-toggle:hover .am-section-label { color: #f87171; } -/* PR pill badge — second row inside am-wt-row2. - Uses data-accent attribute to derive all colors via CSS variables. */ -.am-pr-badge[data-accent="open"], -.am-pr-panel-badge[data-accent="open"] { --pr-accent: var(--vscode-testing-iconPassed, #34d399); } -.am-pr-badge[data-accent="pending"], -.am-pr-panel-badge[data-accent="pending"] { --pr-accent: var(--vscode-charts-yellow, #fbbf24); } -.am-pr-badge[data-accent="draft"], -.am-pr-panel-badge[data-accent="draft"] { --pr-accent: var(--text-weaker); } -.am-pr-badge[data-accent="merged"], -.am-pr-panel-badge[data-accent="merged"] { --pr-accent: var(--vscode-charts-purple, #a78bfa); } -.am-pr-badge[data-accent="closed"], -.am-pr-panel-badge[data-accent="closed"] { --pr-accent: var(--vscode-testing-iconFailed, #f87171); } +/* PR badge accent colors via classList */ +.am-pr-accent-open, +.am-pr-panel-badge.am-pr-accent-open { --pr-accent: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-accent-pending, +.am-pr-panel-badge.am-pr-accent-pending { --pr-accent: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-accent-draft, +.am-pr-panel-badge.am-pr-accent-draft { --pr-accent: var(--text-weaker); } +.am-pr-accent-merged, +.am-pr-panel-badge.am-pr-accent-merged { --pr-accent: var(--vscode-charts-purple, #a78bfa); } +.am-pr-accent-closed, +.am-pr-panel-badge.am-pr-accent-closed { --pr-accent: var(--vscode-testing-iconFailed, #f87171); } .am-pr-badge { display: inline-flex; diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx index b80b9495152..9eaec93baef 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx @@ -1,7 +1,7 @@ /** @jsxImportSource solid-js */ import { Icon } from "@kilocode/kilo-ui/icon" import type { PRStatus } from "../../src/types/messages" -import { prBadgeAccent, prBadgeIndicator, prChecksRunning } from "../WorktreeItem" +import { prBadgeIndicator, prChecksRunning } from "../WorktreeItem" const INDICATOR_ICON: Record = { failure: "circle-x", @@ -15,8 +15,14 @@ export function PRBadge(props: { pr: PRStatus }) { return ( = { - success: "circle-check", - failure: "circle-x-outline", - cancelled: "circle-x-outline", - skipped: "circle-x-outline", - pending: "play", -} - -const CHECK_LABEL: Record = { - success: "Passed", - failure: "Failed", - pending: "Running", - skipped: "Skipped", - cancelled: "Cancelled", +const CHECK: Record = { + success: { icon: "circle-check", label: "Passed" }, + failure: { icon: "circle-x-outline", label: "Failed" }, + cancelled: { icon: "circle-x-outline", label: "Cancelled" }, + skipped: { icon: "circle-x-outline", label: "Skipped" }, + pending: { icon: "play", label: "Running" }, } export function PRChecks(props: { checks: PRStatus["checks"] }) { @@ -39,9 +31,9 @@ export function PRChecks(props: { checks: PRStatus["checks"] }) { {(check: PRCheck) => (
- - + + {(comments) => } - - - 0 || props.pr.additions > 0 || props.pr.deletions > 0}> - + {(body) => } + 0}> - 0}> - + + {(comments) => }
From 6e1d7e64f3f53f6874e91ce7263cfe123d8db263 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 3 Aug 2026 14:37:31 -0700 Subject: [PATCH 07/42] feat(pr-view): Improve agent code practices, add types and styles --- .../src/agent-manager/PRStatusPoller.ts | 51 +++++++++---------- ...agent-manager-orchestration-domain.test.ts | 4 +- .../webview-ui/agent-manager/pr/PRChecks.tsx | 8 +-- .../webview-ui/agent-manager/pr/PRPanel.tsx | 2 +- .../webview-ui/agent-manager/pr/pr-panel.css | 3 -- 5 files changed, 32 insertions(+), 36 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 01875607f00..a2a6ddd51d6 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -460,8 +460,8 @@ export class PRStatusPoller { { cwd, timeout: 15_000 }, ) const pr = JSON.parse(stdout)?.data?.repository?.pullRequest - const comments = parseComments(pr?.reviewThreads?.nodes ?? []) - const reviewers = parseReviewers(pr?.reviewRequests?.nodes ?? [], pr?.reviews?.nodes ?? []) + const comments = parseComments((pr?.reviewThreads?.nodes ?? []) as GQLThread[]) + const reviewers = parseReviewers((pr?.reviewRequests?.nodes ?? []) as GQLReviewRequest[], (pr?.reviews?.nodes ?? []) as GQLReview[]) return { total: comments.length, unresolved: comments.filter((c) => !c.resolved).length, comments, reviewers } } catch (err) { this.options.log("Failed to fetch PR comments:", err) @@ -543,46 +543,45 @@ function formatCheckDuration(startedAt?: string, completedAt?: string): string | return secs < 60 ? `${secs}s` : `${Math.floor(secs / 60)}m ${secs % 60}s` } -function parseComments(threads: Record[]): PRComment[] { +interface GQLAuthor { login?: string; avatarUrl?: string } +interface GQLComment { id: string; author?: GQLAuthor; body?: string; path?: string; line?: number; url?: string; createdAt?: string } +interface GQLThread { isResolved?: boolean; comments?: { nodes?: GQLComment[] } } +interface GQLReviewRequest { requestedReviewer?: GQLAuthor } +interface GQLReview { author?: GQLAuthor; state?: string } + +function parseComments(threads: GQLThread[]): PRComment[] { const items: PRComment[] = [] for (const thread of threads) { - const nodes = (thread.comments as { nodes?: unknown[] } | undefined)?.nodes - const first = nodes?.[0] as Record | undefined + const first = thread.comments?.nodes?.[0] if (!first) continue - const author = first.author as Record | undefined items.push({ - id: first.id as string, - author: (author?.login as string) ?? "unknown", - avatar: author?.avatarUrl as string | undefined, - body: (first.body as string) ?? "", - file: first.path as string | undefined, - line: first.line as number | undefined, - url: first.url as string | undefined, - resolved: (thread.isResolved as boolean) ?? false, - createdAt: first.createdAt ? new Date(first.createdAt as string).getTime() : undefined, + id: first.id, + author: first.author?.login ?? "unknown", + avatar: first.author?.avatarUrl, + body: first.body ?? "", + file: first.path, + line: first.line, + url: first.url, + resolved: thread.isResolved ?? false, + createdAt: first.createdAt ? new Date(first.createdAt).getTime() : undefined, }) } return items } -function parseReviewers( - requests: Record[], - reviews: Record[], -): PRReviewer[] { +function parseReviewers(requests: GQLReviewRequest[], reviews: GQLReview[]): PRReviewer[] { const map = new Map() for (const node of requests) { - const user = node.requestedReviewer as Record | undefined + const user = node.requestedReviewer if (!user?.login) continue - map.set(user.login as string, { login: user.login as string, avatar: user.avatarUrl as string | undefined, state: "pending" }) + map.set(user.login, { login: user.login, avatar: user.avatarUrl, state: "pending" }) } for (const node of reviews) { - const author = node.author as Record | undefined - const login = author?.login as string | undefined + const login = node.author?.login if (!login) continue - const state = REVIEWER_STATE[node.state as string] ?? "pending" - // Keep most significant: approved/changes_requested > commented > pending + const state = REVIEWER_STATE[node.state ?? ""] ?? "pending" if (!map.has(login) || state !== "commented") { - map.set(login, { login, avatar: author?.avatarUrl as string | undefined, state }) + map.set(login, { login, avatar: node.author?.avatarUrl, state }) } } return [...map.values()] diff --git a/packages/kilo-vscode/tests/unit/agent-manager-orchestration-domain.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-orchestration-domain.test.ts index e7a6e0fb177..68173c61e3a 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-orchestration-domain.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-orchestration-domain.test.ts @@ -86,8 +86,8 @@ describe("Agent Manager orchestration domain", () => { url: "https://example.com/pr/42", state: "open", review: "approved", - checks: { status: "success", total: 1, passed: 1, failed: 0, pending: 0, items: [] }, - comments: { total: 2, unresolved: 1, items: [] }, + checks: { status: "success", total: 1, passed: 1, failed: 0, pending: 0, checks: [] }, + comments: { total: 2, unresolved: 1, comments: [], reviewers: [] }, additions: 10, deletions: 2, files: 1, diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx index 1e6b82654f0..235124dc720 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx @@ -6,11 +6,11 @@ import type { PRCheck, CheckStatus } from "./pr-types" import { SectionHeading } from "./SectionHeading" const CHECK: Record = { - success: { icon: "circle-check", label: "Passed" }, - failure: { icon: "circle-x-outline", label: "Failed" }, + success: { icon: "circle-check", label: "Passed" }, + failure: { icon: "circle-x-outline", label: "Failed" }, cancelled: { icon: "circle-x-outline", label: "Cancelled" }, - skipped: { icon: "circle-x-outline", label: "Skipped" }, - pending: { icon: "play", label: "Running" }, + skipped: { icon: "circle-x-outline", label: "Skipped" }, + pending: { icon: "play", label: "Running" }, } export function PRChecks(props: { checks: PRStatus["checks"] }) { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index a10bd6d8a3b..d3c68a71924 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -23,7 +23,7 @@ interface PRPanelProps { export const PRPanel: Component = (props) => ( -
+
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css index ba73dd76ae8..f218582cdda 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css @@ -1,12 +1,9 @@ /* PR Panel */ .am-pr-panel { - display: flex; - flex-direction: column; height: 100%; overflow: hidden; } -/* Shared flex utilities scoped to the PR panel */ .am-pr-row { display: flex; align-items: center; } .am-pr-col { display: flex; flex-direction: column; } From ffacdc60dde5e9295edfa9bf37242dac774003a3 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 3 Aug 2026 15:12:20 -0700 Subject: [PATCH 08/42] feat(pr-view): PR summary --- .../webview-ui/agent-manager/pr/PRPanel.tsx | 75 ++++++++++--------- .../webview-ui/agent-manager/pr/PRSummary.tsx | 67 +++++++++++++++++ .../webview-ui/agent-manager/pr/pr-panel.css | 49 ++++++++++++ 3 files changed, 157 insertions(+), 34 deletions(-) create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index d3c68a71924..aa92f03e8a9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource solid-js */ -import { Component, Show } from "solid-js" +import { Component, Show, createSignal } from "solid-js" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Tooltip } from "@kilocode/kilo-ui/tooltip" import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" @@ -12,6 +12,7 @@ import { PRDescription } from "./PRDescription" import { PRFileChanges } from "./PRFileChanges" import { PRChecks } from "./PRChecks" import { PRComments } from "./PRComments" +import { PRSummary } from "./PRSummary" import "./pr-panel.css" interface PRPanelProps { @@ -21,39 +22,45 @@ interface PRPanelProps { onOpenExternal: () => void } -export const PRPanel: Component = (props) => ( - -
-
-
- - {props.pr.title} +export const PRPanel: Component = (props) => { + const [summaryVisible, setSummaryVisible] = createSignal(true) + return ( + +
+
+
+ + {props.pr.title} +
+
+ + + + + + +
-
- - - - - - + + setSummaryVisible(false)} /> + +
+ + + {(comments) => } + + + {(body) => } + + + 0}> + + + + {(comments) => } +
-
- - - {(comments) => } - - - {(body) => } - - - 0}> - - - - {(comments) => } - -
-
-
-) + + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx new file mode 100644 index 00000000000..2eb405c7765 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx @@ -0,0 +1,67 @@ +/** @jsxImportSource solid-js */ +import { Component, Show, createSignal } from "solid-js" +import { Icon } from "@kilocode/kilo-ui/icon" +import { IconButton } from "@kilocode/kilo-ui/icon-button" +import type { PRStatus } from "../../src/types/messages" + +interface PRSummaryProps { + pr: PRStatus + onDismiss: () => void +} + +function summaryRows(pr: PRStatus): Array<{ icon: string; label: string; status: string }> { + const rows = [] + + if (pr.checks.total > 0) { + const { passed, total, status } = pr.checks + rows.push({ + icon: status === "success" ? "circle-check" : status === "failure" ? "circle-x-outline" : "play", + label: status === "success" ? "All checks passing" : `${passed}/${total} checks passed`, + status, + }) + } + + const reviewers = pr.comments?.reviewers + if (reviewers && reviewers.length > 0) { + const approved = reviewers.filter((r) => r.state === "approved").length + const changes = reviewers.filter((r) => r.state === "changes_requested").length + const status = approved === reviewers.length ? "success" : changes > 0 ? "failure" : "pending" + rows.push({ + icon: status === "success" ? "circle-check" : status === "failure" ? "circle-x-outline" : "play", + label: approved === reviewers.length ? "All reviewers approved" : `${approved}/${reviewers.length} approvals`, + status, + }) + } + + if (pr.comments && pr.comments.unresolved > 0) { + rows.push({ + icon: "comment", + label: `${pr.comments.unresolved} unresolved comment${pr.comments.unresolved > 1 ? "s" : ""}`, + status: "warning", + }) + } + + return rows +} + +export function PRSummary(props: PRSummaryProps) { + const rows = () => summaryRows(props.pr) + return ( + 0}> +
+
+ PR Summary + +
+
+ {rows().map((row) => ( +
+ + {row.label} +
+ ))} +
+
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css index f218582cdda..9507c8c5e0f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css @@ -338,3 +338,52 @@ font-size: var(--kilo-font-size-10); font-variant-numeric: tabular-nums; } + +/* PR Summary banner */ +.am-pr-summary { + margin: 8px 12px; + border: 1px solid var(--vscode-panel-border); + border-radius: 6px; + overflow: hidden; + flex-shrink: 0; +} + +.am-pr-summary-header { + justify-content: space-between; + padding: 6px 6px 6px 10px; + border-bottom: 1px solid var(--vscode-panel-border); + background: var(--vscode-editor-background); +} + +.am-pr-summary-title { + font-size: var(--font-size-small); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-weak); +} + +.am-pr-summary-rows { + padding: 6px 10px; + gap: 4px; + background: var(--vscode-editor-background); +} + +.am-pr-summary-row { + gap: 7px; + padding: 2px 0; + font-size: var(--kilo-font-size-12); +} + +.am-pr-summary-icon { + flex-shrink: 0; +} + +.am-pr-summary-row[data-status="success"] .am-pr-summary-icon { color: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-summary-row[data-status="failure"] .am-pr-summary-icon { color: var(--vscode-testing-iconFailed, #f87171); } +.am-pr-summary-row[data-status="pending"] .am-pr-summary-icon { color: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-summary-row[data-status="warning"] .am-pr-summary-icon { color: var(--vscode-charts-yellow, #fbbf24); } + +.am-pr-summary-label { + color: var(--vscode-foreground); +} From c7213b2a9be5dc017abef417dc24fa0899da844b Mon Sep 17 00:00:00 2001 From: cmanu Date: Tue, 4 Aug 2026 08:53:34 -0700 Subject: [PATCH 09/42] feat(pr-view): Use PR icon on badges and update summary to remove dismiss --- .../webview-ui/agent-manager/TabBar.tsx | 2 +- .../webview-ui/agent-manager/pr/PRBadge.tsx | 18 ++++++++++-------- .../webview-ui/agent-manager/pr/PRPanel.tsx | 8 +++----- .../webview-ui/agent-manager/pr/PRSummary.tsx | 5 +---- .../webview-ui/agent-manager/pr/pr-panel.css | 7 +++++++ packages/ui/src/components/icon.tsx | 1 + 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx index ab0a888f30e..c21ca2f4705 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx @@ -252,7 +252,7 @@ export const TabBar: Component = (props) => ( onClick={props.onTogglePR} title={`PR #${pr().number}`} > - + #{pr().number} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx index 9eaec93baef..cb54b3ee913 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx @@ -1,4 +1,5 @@ /** @jsxImportSource solid-js */ +import { Show } from "solid-js" import { Icon } from "@kilocode/kilo-ui/icon" import type { PRStatus } from "../../src/types/messages" import { prBadgeIndicator, prChecksRunning } from "../WorktreeItem" @@ -7,7 +8,6 @@ const INDICATOR_ICON: Record = { failure: "circle-x", changes: "warning", approved: "circle-check", - none: "branch", } export function PRBadge(props: { pr: PRStatus }) { @@ -24,13 +24,15 @@ export function PRBadge(props: { pr: PRStatus }) { "am-pr-badge-pending": prChecksRunning(props.pr), }} > - - #{props.pr.number} + + + + ) } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index aa92f03e8a9..dda904744ec 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource solid-js */ -import { Component, Show, createSignal } from "solid-js" +import { Component, Show } from "solid-js" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Tooltip } from "@kilocode/kilo-ui/tooltip" import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" @@ -23,7 +23,6 @@ interface PRPanelProps { } export const PRPanel: Component = (props) => { - const [summaryVisible, setSummaryVisible] = createSignal(true) return (
@@ -31,6 +30,7 @@ export const PRPanel: Component = (props) => {
{props.pr.title} + #{props.pr.number}
@@ -41,9 +41,7 @@ export const PRPanel: Component = (props) => {
- - setSummaryVisible(false)} /> - +
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx index 2eb405c7765..c04e89a5fe4 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx @@ -1,12 +1,10 @@ /** @jsxImportSource solid-js */ -import { Component, Show, createSignal } from "solid-js" +import { Show } from "solid-js" import { Icon } from "@kilocode/kilo-ui/icon" -import { IconButton } from "@kilocode/kilo-ui/icon-button" import type { PRStatus } from "../../src/types/messages" interface PRSummaryProps { pr: PRStatus - onDismiss: () => void } function summaryRows(pr: PRStatus): Array<{ icon: string; label: string; status: string }> { @@ -51,7 +49,6 @@ export function PRSummary(props: PRSummaryProps) {
PR Summary -
{rows().map((row) => ( diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css index 9507c8c5e0f..897f1874a6e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css @@ -49,6 +49,13 @@ white-space: nowrap; } +.am-pr-panel-number { + font-size: var(--kilo-font-size-12); + color: var(--vscode-descriptionForeground); + font-variant-numeric: tabular-nums; + flex-shrink: 0; +} + .am-pr-panel-actions { gap: 2px; flex-shrink: 0; diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index e70de73a831..e2170c0772e 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -91,6 +91,7 @@ const icons = { download: ``, menu: ``, server: ``, + "pull-request": ``, branch: ``, edit: ``, history: ``, From 93fde67c76cad60fdd021905b5e16a8d53693944 Mon Sep 17 00:00:00 2001 From: cmanu Date: Tue, 4 Aug 2026 08:56:13 -0700 Subject: [PATCH 10/42] feat(pr-view): Use PR icon on worktree item and remove pr number --- .../kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx index d33b0e0a61d..b6fa2d92898 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx @@ -356,7 +356,8 @@ export const WorktreeItem: Component = (props) => { }} onClick={handleOpenPR} > - }> + + @@ -372,7 +373,6 @@ export const WorktreeItem: Component = (props) => { /> - #{pr().number} ) }} From bd67e85902e56eb9cc8ac00045209838c04b046f Mon Sep 17 00:00:00 2001 From: cmanu Date: Tue, 4 Aug 2026 09:08:12 -0700 Subject: [PATCH 11/42] feat(pr-view): Styling and copy/paste on PR comments --- .../agent-manager/agent-manager.css | 2 +- .../agent-manager/pr/CopyButton.tsx | 22 +++++++++++++++++++ .../agent-manager/pr/PRComments.tsx | 2 ++ .../webview-ui/agent-manager/pr/pr-panel.css | 6 ++++- 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/CopyButton.tsx 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 79e0fee011a..ee101e834e9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -861,7 +861,7 @@ button.am-section-toggle:hover .am-section-label { display: inline-flex; align-items: center; gap: 2px; - padding: 2px 7px 2px 4px; + padding: 2px 4px; border-radius: 6px; border: none; background: color-mix(in srgb, var(--pr-accent) 12%, transparent); diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/CopyButton.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/CopyButton.tsx new file mode 100644 index 00000000000..4a856df8d5b --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/CopyButton.tsx @@ -0,0 +1,22 @@ +/** @jsxImportSource solid-js */ +import { createSignal } from "solid-js" +import { IconButton } from "@kilocode/kilo-ui/icon-button" + +export function CopyButton(props: { text: string; label?: string; class?: string }) { + const [copied, setCopied] = createSignal(false) + const copy = () => { + navigator.clipboard.writeText(props.text) + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } + return ( + + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx index d3f3057e6c0..6745a0522c3 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx @@ -4,6 +4,7 @@ import { Markdown } from "@kilocode/kilo-ui/markdown" import type { PRStatus } from "../../src/types/messages" import type { PRComment } from "./pr-types" import { SectionHeading } from "./SectionHeading" +import { CopyButton } from "./CopyButton" export function PRComments(props: { comments: NonNullable }) { const [open, setOpen] = createSignal(true) @@ -33,6 +34,7 @@ export function PRComments(props: { comments: NonNullable Resolved +
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css index 897f1874a6e..4a1183bb125 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css @@ -25,7 +25,7 @@ display: inline-flex; align-items: center; gap: 4px; - padding: 2px 6px 2px 4px; + padding: 2px 4px; border-radius: 4px; font-size: var(--kilo-font-size-10); font-variant-numeric: tabular-nums; @@ -225,6 +225,10 @@ margin-left: auto; } +.am-pr-copy-btn { + margin-left: auto; +} + .am-pr-panel-comment-body [data-component="markdown"] { font-size: var(--kilo-font-size-12); color: var(--vscode-foreground); From 70c37d66d8d11322ba82e7d17d783945d054f0b3 Mon Sep 17 00:00:00 2001 From: cmanu Date: Tue, 4 Aug 2026 16:05:08 -0700 Subject: [PATCH 12/42] feat(pr-view): show code -X/+Y on code diff and remove PR number --- .../webview-ui/agent-manager/TabBar.tsx | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx index c21ca2f4705..c315cc3a7a1 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx @@ -218,6 +218,19 @@ export const TabBar: Component = (props) => ( ) })()} + + {(pr) => ( + + + + )} + = (props) => ( title={props.t("agentManager.diff.toggle")} > - - - 0}> - {stats()!.files}f + + {(pr) => ( + 0 || pr().deletions > 0}> + + 0}> + +{pr().additions} + + 0}> + −{pr().deletions} + + - +{stats()!.additions} - −{stats()!.deletions} - + )} @@ -244,20 +262,6 @@ export const TabBar: Component = (props) => ( ) })()} - - {(pr) => ( - - - - )} - Date: Tue, 4 Aug 2026 16:15:47 -0700 Subject: [PATCH 13/42] feat(pr-view): fix styles to add variables and light theme fix for svg color. Make summary unfixed. --- .../agent-manager/agent-manager.css | 52 +++++++++++-------- .../webview-ui/agent-manager/pr/PRPanel.tsx | 2 +- .../webview-ui/agent-manager/pr/pr-panel.css | 1 - 3 files changed, 30 insertions(+), 25 deletions(-) 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 d27fc1dca86..88eccf3932c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -796,7 +796,7 @@ html[data-theme="kilo-vscode"] } .am-run-badge[data-run-state="stopping"] { - --am-run-accent: #fbbf24; + --am-run-accent: var(--vscode-charts-yellow, #fbbf24); } .am-run-badge[data-run-state="success"] { @@ -804,7 +804,7 @@ html[data-theme="kilo-vscode"] } .am-run-badge[data-run-state="error"] { - --am-run-accent: #f87171; + --am-run-accent: var(--vscode-testing-iconFailed, #f87171); } @keyframes am-run-pulse { @@ -854,11 +854,11 @@ html[data-theme="kilo-vscode"] } .am-stat-additions { - color: #34d399; + color: var(--vscode-testing-iconPassed, #34d399); } .am-stat-deletions { - color: #f87171; + color: var(--vscode-testing-iconFailed, #f87171); } /* PR badge accent colors via classList */ @@ -962,35 +962,35 @@ html[data-theme="kilo-vscode"] } .am-worktree-item-active .am-stat-files { - color: rgba(255, 255, 255, 0.7); + color: var(--text-weak); } .am-worktree-item-active .am-stat-additions { - color: #6ee7b7; + color: var(--vscode-testing-iconPassed, #6ee7b7); } .am-worktree-item-active .am-stat-deletions { - color: #fca5a5; + color: var(--vscode-testing-iconFailed, #fca5a5); } .am-worktree-commits { - color: #34d399; + color: var(--vscode-testing-iconPassed, #34d399); } .am-worktree-behind { - color: #f87171; + color: var(--vscode-testing-iconFailed, #f87171); } .am-worktree-item-active .am-worktree-commits { - color: #6ee7b7; + color: var(--vscode-testing-iconPassed, #6ee7b7); } .am-worktree-item-active .am-worktree-behind { - color: #fca5a5; + color: var(--vscode-testing-iconFailed, #fca5a5); } .am-local-item-active .am-worktree-behind { - color: #fca5a5; + color: var(--vscode-testing-iconFailed, #fca5a5); } .am-local-item:hover .am-worktree-stats, @@ -1664,7 +1664,7 @@ body.am-wt-dragging-active * { } .am-sidebar-search-status { - color: #fbbf24; + color: var(--vscode-charts-yellow, #fbbf24); text-transform: uppercase; } @@ -1759,9 +1759,10 @@ body.am-wt-dragging-active * { color: var(--text-base); } -.am-diff-toggle-btn.am-tab-diff-btn-active { - background: var(--surface-interactive-base) !important; - color: var(--text-base) !important; +.am-diff-toggle-btn.am-tab-diff-btn-active, +.am-diff-toggle-btn.am-tab-diff-btn-active:hover { + background: var(--surface-interactive-base); + color: var(--text-on-interactive-base); } .am-diff-toggle-btn.am-diff-toggle-has-changes { @@ -1775,20 +1776,24 @@ body.am-wt-dragging-active * { } .am-diff-toggle-btn.am-tab-diff-btn-active .am-stat-files { - color: rgba(255, 255, 255, 0.7); + color: var(--text-weak); } .am-diff-toggle-btn.am-tab-diff-btn-active .am-stat-additions { - color: #6ee7b7; + color: var(--vscode-testing-iconPassed, #6ee7b7); } .am-diff-toggle-btn.am-tab-diff-btn-active .am-stat-deletions { - color: #fca5a5; + color: var(--vscode-testing-iconFailed, #fca5a5); } .am-tab-diff-btn-active { background: var(--surface-interactive-base) !important; - color: var(--text-base) !important; + color: var(--text-on-interactive-base) !important; +} + +.am-tab-diff-btn-active [data-slot="icon-svg"] { + color: var(--text-on-interactive-base) !important; } .am-detail-content { @@ -2899,9 +2904,10 @@ body.am-wt-dragging-active * { color: var(--text-base); } -.am-nv-pill-active { - background: var(--border-focus, #007fd4) !important; - color: #fff !important; +.am-nv-pill-active, +.am-nv-pill-active:hover { + background: var(--border-focus, #007fd4); + color: var(--text-on-interactive-base); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index dda904744ec..7bf8aef9f7b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -41,8 +41,8 @@ export const PRPanel: Component = (props) => {
-
+ {(comments) => } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css index 4a1183bb125..6f1cc8cc260 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css @@ -356,7 +356,6 @@ border: 1px solid var(--vscode-panel-border); border-radius: 6px; overflow: hidden; - flex-shrink: 0; } .am-pr-summary-header { From cc900b277781b787f19d74e6b940acd6795e5470 Mon Sep 17 00:00:00 2001 From: cmanu Date: Wed, 5 Aug 2026 10:16:04 -0700 Subject: [PATCH 14/42] feat(pr-view): Organizing code; Adjust colors for themes; Improve types and icon consistency --- .../src/agent-manager/PRStatusPoller.ts | 133 +----------------- .../src/agent-manager/am-pr-utils.ts | 119 ++++++++++++++++ .../agent-manager/AgentManagerApp.tsx | 40 +++--- .../webview-ui/agent-manager/WorktreeItem.tsx | 2 +- .../agent-manager/agent-manager.css | 2 +- .../webview-ui/agent-manager/pr/PRBadge.tsx | 2 +- .../agent-manager/side-panel-layout.ts | 6 + .../agent-manager/terminal/ambient.ts | 3 +- 8 files changed, 156 insertions(+), 151 deletions(-) create mode 100644 packages/kilo-vscode/src/agent-manager/am-pr-utils.ts diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index a2a6ddd51d6..813c79f25d9 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -1,9 +1,11 @@ import type { ExecFileOptionsWithStringEncoding } from "child_process" import type { Worktree } from "./WorktreeStateManager" -import type { PRStatus, PRCheck, PRComment, PRReviewer, CheckStatus, AggregateCheckStatus, PRState, ReviewDecision } from "./types" +import type { PRStatus, PRCheck, PRComment, PRReviewer, AggregateCheckStatus } from "./types" import { execWithShellEnv } from "./shell-env" import { classifyPRError } from "./git-import" import type { Semaphore } from "./semaphore" +import { parsePRResult, checkStatus, formatCheckDuration, parseComments, parseReviewers } from "./am-pr-utils" +import type { PRResult, GhThread, GhReviewRequest, GhReview } from "./am-pr-utils" interface PRStatusPollerOptions { getWorktrees: () => Worktree[] @@ -387,7 +389,7 @@ export class PRStatusPoller { const checks: PRCheck[] = data.map((c) => ({ name: c.name, - status: mapCheckStatus(c.state), + status: checkStatus(c.state), url: c.link, duration: formatCheckDuration(c.startedAt, c.completedAt), })) @@ -460,8 +462,8 @@ export class PRStatusPoller { { cwd, timeout: 15_000 }, ) const pr = JSON.parse(stdout)?.data?.repository?.pullRequest - const comments = parseComments((pr?.reviewThreads?.nodes ?? []) as GQLThread[]) - const reviewers = parseReviewers((pr?.reviewRequests?.nodes ?? []) as GQLReviewRequest[], (pr?.reviews?.nodes ?? []) as GQLReview[]) + const comments = parseComments((pr?.reviewThreads?.nodes ?? []) as GhThread[]) + const reviewers = parseReviewers((pr?.reviewRequests?.nodes ?? []) as GhReviewRequest[], (pr?.reviews?.nodes ?? []) as GhReview[]) return { total: comments.length, unresolved: comments.filter((c) => !c.resolved).length, comments, reviewers } } catch (err) { this.options.log("Failed to fetch PR comments:", err) @@ -470,129 +472,6 @@ export class PRStatusPoller { } } -interface PRResult { - number: number - title: string - body: string - url: string - state: PRState - review: ReviewDecision | null - additions: number - deletions: number - files: number -} - -function parsePRResult(json: string): PRResult | null { - const data = JSON.parse(json) - if (!data.number) return null - return { - number: data.number, - title: data.title ?? "", - body: data.body ?? "", - url: data.url ?? "", - state: parsePRState(data.isDraft, data.state), - review: parseReviewDecision(data.reviewDecision), - additions: data.additions ?? 0, - deletions: data.deletions ?? 0, - files: data.changedFiles ?? 0, - } -} - -function parsePRState(isDraft: boolean, ghState: string): PRState { - if (isDraft) return "draft" - if (ghState === "MERGED") return "merged" - if (ghState === "CLOSED") return "closed" - return "open" -} - -function parseReviewDecision(decision: string | undefined): ReviewDecision | null { - if (decision === "APPROVED") return "approved" - if (decision === "CHANGES_REQUESTED") return "changes_requested" - if (decision === "REVIEW_REQUIRED") return "pending" - return null -} - -function mapCheckStatus(state: string): CheckStatus { - switch (state.toUpperCase()) { - case "SUCCESS": - return "success" - case "FAILURE": - case "ERROR": - return "failure" - case "PENDING": - case "QUEUED": - case "IN_PROGRESS": - case "REQUESTED": - case "WAITING": - return "pending" - case "SKIPPED": - return "skipped" - case "CANCELLED": - case "TIMED_OUT": - case "STALE": - case "STARTUP_FAILURE": - return "cancelled" - default: - return "pending" - } -} - -function formatCheckDuration(startedAt?: string, completedAt?: string): string | undefined { - if (!startedAt || !completedAt) return undefined - const secs = Math.round((new Date(completedAt).getTime() - new Date(startedAt).getTime()) / 1000) - return secs < 60 ? `${secs}s` : `${Math.floor(secs / 60)}m ${secs % 60}s` -} - -interface GQLAuthor { login?: string; avatarUrl?: string } -interface GQLComment { id: string; author?: GQLAuthor; body?: string; path?: string; line?: number; url?: string; createdAt?: string } -interface GQLThread { isResolved?: boolean; comments?: { nodes?: GQLComment[] } } -interface GQLReviewRequest { requestedReviewer?: GQLAuthor } -interface GQLReview { author?: GQLAuthor; state?: string } - -function parseComments(threads: GQLThread[]): PRComment[] { - const items: PRComment[] = [] - for (const thread of threads) { - const first = thread.comments?.nodes?.[0] - if (!first) continue - items.push({ - id: first.id, - author: first.author?.login ?? "unknown", - avatar: first.author?.avatarUrl, - body: first.body ?? "", - file: first.path, - line: first.line, - url: first.url, - resolved: thread.isResolved ?? false, - createdAt: first.createdAt ? new Date(first.createdAt).getTime() : undefined, - }) - } - return items -} - -function parseReviewers(requests: GQLReviewRequest[], reviews: GQLReview[]): PRReviewer[] { - const map = new Map() - for (const node of requests) { - const user = node.requestedReviewer - if (!user?.login) continue - map.set(user.login, { login: user.login, avatar: user.avatarUrl, state: "pending" }) - } - for (const node of reviews) { - const login = node.author?.login - if (!login) continue - const state = REVIEWER_STATE[node.state ?? ""] ?? "pending" - if (!map.has(login) || state !== "commented") { - map.set(login, { login, avatar: node.author?.avatarUrl, state }) - } - } - return [...map.values()] -} - -const REVIEWER_STATE: Record = { - APPROVED: "approved", - CHANGES_REQUESTED: "changes_requested", - COMMENTED: "commented", -} - /** Run async thunks with bounded concurrency, returning settled results. */ async function settled(thunks: (() => Promise)[], concurrency: number): Promise[]> { const results: PromiseSettledResult[] = new Array(thunks.length) diff --git a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts new file mode 100644 index 00000000000..976b486db80 --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts @@ -0,0 +1,119 @@ +import type { PRState, ReviewDecision, CheckStatus, PRComment, PRReviewer } from "./types" + +// Raw shapes returned by `gh pr view --json` + +export interface GhAuthor { login?: string; avatarUrl?: string } +export interface GhComment { id: string; author?: GhAuthor; body?: string; path?: string; line?: number; url?: string; createdAt?: string } +export interface GhThread { isResolved?: boolean; comments?: { nodes?: GhComment[] } } +export interface GhReviewRequest { requestedReviewer?: GhAuthor } +export interface GhReview { author?: GhAuthor; state?: string } + +export interface PRResult { + number: number + title: string + body: string + url: string + state: PRState + review: ReviewDecision | null + additions: number + deletions: number + files: number +} + +export function parsePRResult(json: string): PRResult | null { + const data = JSON.parse(json) + if (!data.number) return null + const state: PRState = data.isDraft ? "draft" : (data.state?.toLowerCase() as PRState) ?? "open" + const decision = data.reviewDecision as string | undefined + const review: ReviewDecision | null = + decision === "APPROVED" ? "approved" + : decision === "CHANGES_REQUESTED" ? "changes_requested" + : decision === "REVIEW_REQUIRED" ? "pending" + : null + return { + number: data.number, + title: data.title ?? "", + body: data.body ?? "", + url: data.url ?? "", + state, + review, + additions: data.additions ?? 0, + deletions: data.deletions ?? 0, + files: data.changedFiles ?? 0, + } +} + +export function checkStatus(state: string): CheckStatus { + switch (state.toUpperCase()) { + case "SUCCESS": + return "success" + case "FAILURE": + case "ERROR": + return "failure" + case "PENDING": + case "QUEUED": + case "IN_PROGRESS": + case "REQUESTED": + case "WAITING": + return "pending" + case "SKIPPED": + return "skipped" + case "CANCELLED": + case "TIMED_OUT": + case "STALE": + case "STARTUP_FAILURE": + return "cancelled" + default: + return "pending" + } +} + +export function formatCheckDuration(startedAt?: string, completedAt?: string): string | undefined { + if (!startedAt || !completedAt) return undefined + const secs = Math.round((new Date(completedAt).getTime() - new Date(startedAt).getTime()) / 1000) + return secs < 60 ? `${secs}s` : `${Math.floor(secs / 60)}m ${secs % 60}s` +} + +const REVIEWER_STATE: Record = { + APPROVED: "approved", + CHANGES_REQUESTED: "changes_requested", + COMMENTED: "commented", +} + +export function parseComments(threads: GhThread[]): PRComment[] { + const items: PRComment[] = [] + for (const thread of threads) { + const first = thread.comments?.nodes?.[0] + if (!first) continue + items.push({ + id: first.id, + author: first.author?.login ?? "unknown", + avatar: first.author?.avatarUrl, + body: first.body ?? "", + file: first.path, + line: first.line, + url: first.url, + resolved: thread.isResolved ?? false, + createdAt: first.createdAt ? new Date(first.createdAt).getTime() : undefined, + }) + } + return items +} + +export function parseReviewers(requests: GhReviewRequest[], reviews: GhReview[]): PRReviewer[] { + const map = new Map() + for (const node of requests) { + const user = node.requestedReviewer + if (!user?.login) continue + map.set(user.login, { login: user.login, avatar: user.avatarUrl, state: "pending" }) + } + for (const node of reviews) { + const login = node.author?.login + if (!login) continue + const state = REVIEWER_STATE[node.state ?? ""] ?? "pending" + if (!map.has(login) || state !== "commented") { + map.set(login, { login, avatar: node.author?.avatarUrl, state }) + } + } + return [...map.values()] +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 09b36c3439d..842a33b2540 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -175,7 +175,7 @@ import { createMarkdownRender } from "./review-preferences" import { createSidebarCollapse } from "./sidebar-collapse" import { SidebarToggleButton } from "./SidebarToggleButton" import { setTabWidths } from "./tab-widths" -import { clampPanelWidth, maxPanelWidth, minPanelWidth } from "./side-panel-layout" +import { clampPanelWidth, maxPanelWidth, minPanelWidth, SidePanel } from "./side-panel-layout" import { buildShortcutCategories } from "./shortcuts" import { tracker } from "./telemetry" import { createChatFocus, hasQuestionOption } from "./focus" @@ -195,7 +195,7 @@ interface SetupState { /** Sidebar selection: LOCAL for local repo, worktree ID for a worktree, or null for an unassigned session. */ type SidebarSelection = typeof LOCAL | string | null -type SidePanel = "diff" | "pr" | "terminal" | null +export type SidePanelState = SidePanel | null const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) // Fallback keybindings before extension sends resolved ones const MAX_JUMP_INDEX = 9 @@ -309,9 +309,9 @@ const AgentManagerContent: Component = () => { let pendingSideWidth: number | undefined const [history, setHistory] = createSignal(false) - const [sidePanel, setSidePanel] = createSignal(null) - const diffOpen = () => sidePanel() === "diff" - const prOpen = () => sidePanel() === "pr" + const [sidePanel, setSidePanel] = createSignal(null) + const diffOpen = () => sidePanel() === SidePanel.Diff + const prOpen = () => sidePanel() === SidePanel.PR const activePR = createMemo(() => { const selected = selection() if (!selected || selected === LOCAL) return undefined @@ -338,7 +338,7 @@ const AgentManagerContent: Component = () => { const showSideTerminal = () => { setHistory(false) setReviewActive(false) - setSidePanel("terminal") + setSidePanel(SidePanel.Terminal) } const [reviewOpenByContext, setReviewOpenByContext] = createSignal>({}) @@ -423,7 +423,7 @@ const AgentManagerContent: Component = () => { const rememberPromptFocus = (focused: boolean) => { if (focused) focusMemory.set(focusKey(), "prompt") } - const terminalVisible = () => sidePanel() === "terminal" && !history() && !reviewActive() + const terminalVisible = () => sidePanel() === SidePanel.Terminal && !history() && !reviewActive() const focusOnDraftChange = () => { const key = focusKey() const owner = focusMemory.get(key) @@ -551,7 +551,7 @@ const AgentManagerContent: Component = () => { const togglePRPanel = () => { setHistory(false) setReviewActive(false) - setSidePanel((prev) => (prev === "pr" ? null : "pr")) + setSidePanel((prev) => (prev === SidePanel.PR ? null : SidePanel.PR)) } const openSelectedPR = () => { @@ -1187,8 +1187,8 @@ const AgentManagerContent: Component = () => { } else if (msg.action === "toggleDiff") { if (reviewActive()) { closeReviewTab() - setSidePanel("diff") - } else setSidePanel((prev) => (prev === "diff" ? null : "diff")) + setSidePanel(SidePanel.Diff) + } else setSidePanel((prev) => (prev === SidePanel.Diff ? null : SidePanel.Diff)) } else if (msg.action === "newTab") handleNewTabForCurrentSelection() else if (msg.action === "closeTab") closeActiveTab() else if (msg.action === "newWorktree") showNewWorktreeDialog() @@ -1350,7 +1350,7 @@ const AgentManagerContent: Component = () => { onSideCreated: (contextKey, terminalId, focus) => { // Focus only when the user is still looking at this panel — // a slow create landing after a mode switch must not steal it. - if (focus && sidePanel() === "terminal" && !history() && !reviewActive() && terms.sideKey() === contextKey) { + if (focus && sidePanel() === SidePanel.Terminal && !history() && !reviewActive() && terms.sideKey() === contextKey) { terms.requestFocus(terminalId) } }, @@ -1419,7 +1419,7 @@ const AgentManagerContent: Component = () => { if (!isActivePayload(ev.projectId)) return // Close diff/review panels — nothing to show during setup. // Terminal panels keep live setup output, so they stay open. - if (sidePanel() === "diff") setSidePanel(null) + if (sidePanel() === SidePanel.Diff) setSidePanel(null) setReviewActive(false) setSetup({ active: true, message: ev.message, branch: ev.branch, worktreeId: ev.worktreeId }) } @@ -2090,7 +2090,7 @@ const AgentManagerContent: Component = () => { const sideCtl = createSideTerminal({ handlers: termHandlers, - visible: () => sidePanel() === "terminal" && !history() && !reviewActive(), + visible: () => sidePanel() === SidePanel.Terminal && !history() && !reviewActive(), focusedId: () => terms.sideFocusedId(), hide: () => { cancelAmbientSetup() @@ -2217,7 +2217,7 @@ const AgentManagerContent: Component = () => { // A focused side terminal owns Cmd+W while its panel is visible — // closing a chat tab out from under the user's cursor would be // surprising. Only that terminal dies; the panel keeps the rest. - if (sidePanel() === "terminal" && terms.sideFocusedId()) { + if (sidePanel() === SidePanel.Terminal && terms.sideFocusedId()) { if (sideCtl.close()) return } if (termHandlers.closeActive()) { @@ -2269,10 +2269,10 @@ const AgentManagerContent: Component = () => { }) if (reviewActive()) { closeReviewTab() - setSidePanel("diff") + setSidePanel(SidePanel.Diff) return } - setSidePanel((prev) => (prev === "diff" ? null : "diff")) + setSidePanel((prev) => (prev === SidePanel.Diff ? null : SidePanel.Diff)) } const renderTabById = (id: string) => @@ -2470,7 +2470,7 @@ const AgentManagerContent: Component = () => { prOpen={prOpen} onTogglePR={togglePRPanel} terminalDestination={sideCtl.destination} - terminalDestinationActive={() => sidePanel() === "terminal"} + terminalDestinationActive={() => sidePanel() === SidePanel.Terminal} terminalKeybind={() => kb().showTerminal ?? ""} onTerminalDestinationOpen={() => { cancelAmbientSetup() @@ -2658,7 +2658,7 @@ const AgentManagerContent: Component = () => { />
- + { activeTerminalId={terms.activeId()} /> - + {(() => { const data = activePR()! return ( @@ -2709,7 +2709,7 @@ const AgentManagerContent: Component = () => { sidePanel() === "terminal"} + visible={() => sidePanel() === SidePanel.Terminal} onSelect={(id) => termHandlers.selectSide(id)} onClose={(id) => { cancelAmbientSetup() diff --git a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx index b6fa2d92898..69378459144 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx @@ -359,7 +359,7 @@ export const WorktreeItem: Component = (props) => { - + 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 88eccf3932c..75953b058b5 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -869,7 +869,7 @@ html[data-theme="kilo-vscode"] .am-pr-accent-draft, .am-pr-panel-badge.am-pr-accent-draft { --pr-accent: var(--text-weaker); } .am-pr-accent-merged, -.am-pr-panel-badge.am-pr-accent-merged { --pr-accent: var(--vscode-charts-purple, #a78bfa); } +.am-pr-panel-badge.am-pr-accent-merged { --pr-accent: var(--vscode-charts-purple, var(--vscode-foreground)); } .am-pr-accent-closed, .am-pr-panel-badge.am-pr-accent-closed { --pr-accent: var(--vscode-testing-iconFailed, #f87171); } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx index cb54b3ee913..d77213de726 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx @@ -5,7 +5,7 @@ import type { PRStatus } from "../../src/types/messages" import { prBadgeIndicator, prChecksRunning } from "../WorktreeItem" const INDICATOR_ICON: Record = { - failure: "circle-x", + failure: "circle-x-outline", changes: "warning", approved: "circle-check", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/side-panel-layout.ts b/packages/kilo-vscode/webview-ui/agent-manager/side-panel-layout.ts index 8cfaafe4f9e..ed5cd0097a1 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/side-panel-layout.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/side-panel-layout.ts @@ -2,6 +2,12 @@ export const MIN_PANEL_WIDTH = 360 const DEFAULT_PANEL_WIDTH_RATIO = 0.5 const MAX_PANEL_WIDTH_RATIO = 0.8 +export enum SidePanel { + Diff = "diff", + PR = "pr", + Terminal = "terminal", +} + function viewportWidth(viewport: number): number { return Number.isFinite(viewport) && viewport > 0 ? viewport : MIN_PANEL_WIDTH } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/ambient.ts b/packages/kilo-vscode/webview-ui/agent-manager/terminal/ambient.ts index aac693f50e7..c674c80d79f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/ambient.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/ambient.ts @@ -13,11 +13,12 @@ import { createEffect, createSignal, type Accessor } from "solid-js" import type { ScriptTerminalStatus, TerminalStateControls } from "./state" import type { TerminalTabStateWithContext } from "./state" +import type { SidePanelState } from "../AgentManagerApp" interface AmbientSetupDeps { terms: TerminalStateControls selection: Accessor - sidePanel: Accessor + sidePanel: Accessor setSidePanel(panel: null): void } From 907a5f65863241e27244716c4e2197a24f4ba7bc Mon Sep 17 00:00:00 2001 From: cmanu Date: Thu, 6 Aug 2026 10:55:19 -0700 Subject: [PATCH 15/42] feat(pr-view): Handle gh auth errors in the ui --- .../src/agent-manager/PRStatusPoller.ts | 4 +++- .../src/agent-manager/pr-status-bridge.ts | 15 +++++++++++++++ packages/kilo-vscode/src/agent-manager/types.ts | 6 ++++++ .../webview-ui/agent-manager/AgentManagerApp.tsx | 6 ++++++ .../webview-ui/agent-manager/i18n/en.ts | 4 ++++ .../src/types/messages/extension-messages.ts | 6 ++++++ 6 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 813c79f25d9..a4fa6ac7aa0 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -18,6 +18,7 @@ interface PRStatusPollerOptions { } const GH_PROBE_TTL = 300_000 // 5 minutes — gh installation state rarely changes at runtime +const GH_PROBE_FAILURE_TTL = 30_000 // 30 seconds — retry faster after a failed probe const MAX_BACKOFF = 120_000 // 2 minutes — cap for exponential backoff on repeated errors const BACKOFF_MULTIPLIER = 2 const PR_LOOKUP_TTL = 10_000 // 10 seconds — short TTL; only the active worktree polls so this stays cheap @@ -164,7 +165,8 @@ export class PRStatusPoller { private async probeGh(): Promise { const now = Date.now() - if (this.ghAvailable !== undefined && now - this.ghProbeTime < GH_PROBE_TTL) { + const ttl = this.ghAvailable === false ? GH_PROBE_FAILURE_TTL : GH_PROBE_TTL + if (this.ghAvailable !== undefined && now - this.ghProbeTime < ttl) { return this.ghAvailable } try { diff --git a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts index c64da3971eb..6eb742fbd4e 100644 --- a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts +++ b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts @@ -31,6 +31,7 @@ export class PRStatusBridge { readonly poller: PRStatusPoller private readonly cache = new Map() private readonly host: PRBridgeHost + private lastErrorNotified: "gh_missing" | "gh_auth" | "fetch_failed" | undefined constructor(host: PRBridgeHost) { this.host = host @@ -61,6 +62,8 @@ export class PRStatusBridge { /** Replay cached PR statuses to a freshly-connected webview. */ replay(): void { this.cache.forEach((msg) => this.host.postToWebview(msg)) + if (this.lastErrorNotified === "gh_auth" || this.lastErrorNotified === "gh_missing") + this.host.postToWebview({ type: "agentManager.prError", error: this.lastErrorNotified } as AgentManagerOutMessage) } snapshot(): Map { @@ -93,6 +96,13 @@ export class PRStatusBridge { reset(): void { this.poller.stop() this.cache.clear() + this.lastErrorNotified = undefined + } + + notifyError(err: "gh_missing" | "gh_auth" | "fetch_failed"): void { + if (this.lastErrorNotified === err) return + this.lastErrorNotified = err + this.host.postToWebview({ type: "agentManager.prError", error: err } as AgentManagerOutMessage) } } @@ -115,6 +125,11 @@ function bridgePollerOpts(bridge: PRStatusBridge, host: PRBridgeHost) { pr: null, error: err, } as AgentManagerOutMessage) + // Always forward auth/missing errors so the webview can show a toast, + // regardless of whether prior data exists. Deduplicate per error type + // so multiple failing worktrees don't produce multiple toasts. + if (err === "gh_auth" || err === "gh_missing") + bridge.notifyError(err) return } const msg = { type: "agentManager.prStatus", worktreeId: id, pr, error: err } as AgentManagerOutMessage diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 186ac19f2ec..39f2844ab6d 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -385,6 +385,11 @@ interface PRStatusOutMessage { error?: "gh_missing" | "gh_auth" | "fetch_failed" } +interface PRErrorOutMessage { + type: "agentManager.prError" + error: "gh_missing" | "gh_auth" | "fetch_failed" +} + interface ActionOutMessage { type: "action" action: string @@ -424,6 +429,7 @@ export type AgentManagerOutMessage = | RevertWorktreeFileResultMessage | DiffBranchesMessage | PRStatusOutMessage + | PRErrorOutMessage | ActionOutMessage | RunStatusMessage | TerminalCreatedMessage diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 842a33b2540..2c822a35e38 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -37,6 +37,7 @@ import type { RunStatus, PRStatus, AgentManagerPRStatusMessage, + AgentManagerPRErrorMessage, AgentManagerProjectsMessage, AgentProjectSnapshot, ManagedSessionState, @@ -1566,6 +1567,11 @@ const AgentManagerContent: Component = () => { managedSession: focusManagedSession, }) + if (msg.type === "agentManager.prError") { + const ev = msg as AgentManagerPRErrorMessage + showToast({ variant: "error", title: t(`agentManager.pr.error.${ev.error}.title`), description: t(`agentManager.pr.error.${ev.error}.description`) }) + } + if (projectLive.apply(msg)) return }) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts index 56954f2c7ac..382bddd0451 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts @@ -214,4 +214,8 @@ export const dict = { "agentManager.import.noBranchesFound": "No branches found.", "agentManager.import.noBranchesHint": "Paste a PR URL above or create a new worktree.", "agentManager.import.failed": "Import failed", + "agentManager.pr.error.gh_auth.title": "GitHub authentication required", + "agentManager.pr.error.gh_auth.description": "Run 'gh auth login' in your terminal to restore PR status.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI not installed", + "agentManager.pr.error.gh_missing.description": "Install the GitHub CLI to enable PR status.", } diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts index d0fb0339252..a3e3b16eea8 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts @@ -1034,6 +1034,11 @@ export interface AgentManagerPRStatusMessage { error?: "gh_missing" | "gh_auth" | "fetch_failed" } +export interface AgentManagerPRErrorMessage { + type: "agentManager.prError" + error: "gh_missing" | "gh_auth" | "fetch_failed" +} + // Sidebar: Live worktree diff stats (extension → webview) export interface WorktreeStatsLoadedMessage { type: "worktreeStatsLoaded" @@ -1397,6 +1402,7 @@ export type ExtensionMessage = | AgentManagerWorktreeStatsMessage | AgentManagerLocalStatsMessage | AgentManagerPRStatusMessage + | AgentManagerPRErrorMessage | AgentManagerTerminalCreatedMessage | AgentManagerTerminalRestartedMessage | AgentManagerTerminalFontChangedMessage From 7415a47248684eb0693270c496028ddf684d7b9d Mon Sep 17 00:00:00 2001 From: cmanu Date: Thu, 6 Aug 2026 14:33:52 -0700 Subject: [PATCH 16/42] fix(pr-view): merge 'main' causes line count linter error so need to move code into new files --- .../agent-manager/AgentManagerApp.tsx | 113 ++-------------- .../agent-manager/default-base-branch.tsx | 121 ++++++++++++++++++ .../webview-ui/src/context/session-merge.ts | 41 ++++++ .../webview-ui/src/context/session.tsx | 40 +----- 4 files changed, 178 insertions(+), 137 deletions(-) create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx create mode 100644 packages/kilo-vscode/webview-ui/src/context/session-merge.ts diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 7d6aa4195be..2e6b55063d5 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -21,7 +21,6 @@ import type { AgentManagerKeybindingsMessage, AgentManagerMultiVersionProgressMessage, AgentManagerSendInitialMessage, - AgentManagerBranchesMessage, AgentManagerWorktreeDiffMessage, AgentManagerWorktreeDiffFileMessage, AgentManagerWorktreeDiffLoadingMessage, @@ -44,7 +43,6 @@ import type { SectionState, SessionInfo, SessionCreatedMessage, - BranchInfo, TerminalDestination, TerminalFont, } from "../src/types/messages" @@ -154,8 +152,10 @@ import type { ReviewComment } from "../diff-viewer/review-comments" import { clearReviewComposer, createReviewComposer } from "../diff-viewer/review-annotations" import type { SidebarSearchMenuRef } from "./SidebarSearchMenu" import { createSidebarSearch, type SidebarSearchItem } from "./sidebar-search" -import { BranchSelect } from "../src/components/shared/BranchSelect" import { randomColor } from "./section-colors" +import { createMarkdownRender } from "./review-preferences" +import { createChangeDefaultBaseBranch } from "./default-base-branch" +import { createSidebarCollapse } from "./sidebar-collapse" import { createNewTaskDrafts } from "./new-task-drafts" import { buildTopLevelItems, @@ -174,8 +174,6 @@ import { DiffScopeControls } from "../diff-viewer/DiffScopeControls" import { scopeCapabilities } from "./diff-scope-state" import { createDiffReviewScope } from "./diff-review-scope" import { initialMessage, seedInitialVariant } from "./initial-message" -import { createMarkdownRender } from "./review-preferences" -import { createSidebarCollapse } from "./sidebar-collapse" import { SidebarToggleButton } from "./SidebarToggleButton" import { setTabWidths } from "./tab-widths" import { clampPanelWidth, maxPanelWidth, minPanelWidth, SidePanel } from "./side-panel-layout" @@ -1793,101 +1791,16 @@ const AgentManagerContent: Component = () => { } const setupScript = metrics.click("configure_setup_script", "worktree_settings", handleConfigureSetupScript) - const handleChangeDefaultBaseBranch = () => { - const [search, setSearch] = createSignal("") - const [branches, setBranches] = createSignal([]) - const [loading, setLoading] = createSignal(true) - const [highlighted, setHighlighted] = createSignal(-1) - - const unsub = vscode.onMessage((msg) => { - if (msg.type === "agentManager.branches") { - const ev = msg as AgentManagerBranchesMessage - setBranches(ev.branches) - if (ev.defaultBranch) setRepoDetectedBranch(ev.defaultBranch) - setLoading(false) - } - }) - - vscode.postMessage({ type: "agentManager.requestBranches" }) - - const filtered = createMemo(() => { - const s = search().toLowerCase() - if (!s) return branches() - return branches().filter((b) => b.name.toLowerCase().includes(s)) - }) - - const selectBranch = (name: string | undefined) => { - vscode.postMessage({ type: "agentManager.setDefaultBaseBranch", branch: name }) - setDefaultBaseBranch(name) - dialog.close() - } - - const handleKeyDown = (e: KeyboardEvent) => { - const items = filtered() - // offset by 1 for auto-detect option (-1 = auto-detect) - const total = items.length + 1 - if (e.key === "ArrowDown") { - e.preventDefault() - e.stopPropagation() - setHighlighted((prev) => Math.min(prev + 1, total - 2)) - } else if (e.key === "ArrowUp") { - e.preventDefault() - e.stopPropagation() - setHighlighted((prev) => Math.max(prev - 1, -1)) - } else if (e.key === "Enter") { - e.preventDefault() - e.stopPropagation() - const idx = highlighted() - if (idx === -1) { - selectBranch(undefined) - } else { - const branch = items[idx] - if (branch) selectBranch(branch.name) - } - } else if (e.key === "Escape") { - e.preventDefault() - e.stopPropagation() - dialog.close() - } - } - - dialog.show(() => { - onCleanup(unsub) - return ( - -
- { - setSearch(v) - setHighlighted(-1) - }} - onSelect={(b) => selectBranch(b.name)} - onSearchKeyDown={handleKeyDown} - selected={defaultBaseBranch()} - highlighted={highlighted()} - onHighlight={setHighlighted} - searchPlaceholder={t("agentManager.dialog.searchBranches")} - emptyLabel={t("agentManager.import.noMatchingBranches")} - loadingLabel={t("agentManager.import.loadingBranches")} - defaultLabel={t("agentManager.dialog.branchBadge.default")} - remoteLabel={t("agentManager.dialog.branchBadge.remote")} - defaultName={defaultBaseBranch()} - autoOption={{ - label: t("agentManager.worktree.defaultBaseBranchAuto"), - hint: repoDetectedBranch(), - active: !hasConfiguredBranch(), - highlighted: highlighted() === -1, - onSelect: () => selectBranch(undefined), - }} - /> -
-
- ) - }) - } + const handleChangeDefaultBaseBranch = createChangeDefaultBaseBranch({ + vscode, + dialog, + t, + defaultBaseBranch, + setDefaultBaseBranch, + setRepoDetectedBranch, + repoDetectedBranch, + hasConfiguredBranch, + }) const handleShowKeyboardShortcuts = () => { const categories = buildShortcutCategories(kb(), t) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx b/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx new file mode 100644 index 00000000000..53c9b77443d --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx @@ -0,0 +1,121 @@ +/** @jsxImportSource solid-js */ + +import { createSignal, createMemo, onCleanup } from "solid-js" +import type { AgentManagerBranchesMessage, BranchInfo } from "../src/types/messages" +import { Dialog } from "@kilocode/kilo-ui/dialog" +import { useDialog } from "@kilocode/kilo-ui/context/dialog" +import { BranchSelect } from "../src/components/shared/BranchSelect" +import type { useVSCode } from "../src/context/vscode" +import type { useLanguage } from "../src/context/language" +import type { Accessor, Setter } from "solid-js" + +interface Deps { + vscode: ReturnType + dialog: ReturnType + t: ReturnType["t"] + defaultBaseBranch: Accessor + setDefaultBaseBranch: (v: Parameters>[0]) => void + setRepoDetectedBranch: Setter + repoDetectedBranch: Accessor + hasConfiguredBranch: Accessor +} + +export function createChangeDefaultBaseBranch(deps: Deps) { + const { vscode, dialog, t, defaultBaseBranch, setDefaultBaseBranch, setRepoDetectedBranch, repoDetectedBranch, hasConfiguredBranch } = deps + + return () => { + const [search, setSearch] = createSignal("") + const [branches, setBranches] = createSignal([]) + const [loading, setLoading] = createSignal(true) + const [highlighted, setHighlighted] = createSignal(-1) + + const unsub = vscode.onMessage((msg) => { + if (msg.type === "agentManager.branches") { + const ev = msg as AgentManagerBranchesMessage + setBranches(ev.branches) + if (ev.defaultBranch) setRepoDetectedBranch(ev.defaultBranch) + setLoading(false) + } + }) + + vscode.postMessage({ type: "agentManager.requestBranches" }) + + const filtered = createMemo(() => { + const s = search().toLowerCase() + if (!s) return branches() + return branches().filter((b) => b.name.toLowerCase().includes(s)) + }) + + const selectBranch = (name: string | undefined) => { + vscode.postMessage({ type: "agentManager.setDefaultBaseBranch", branch: name }) + setDefaultBaseBranch(name) + dialog.close() + } + + const handleKeyDown = (e: KeyboardEvent) => { + const items = filtered() + // offset by 1 for auto-detect option (-1 = auto-detect) + const total = items.length + 1 + if (e.key === "ArrowDown") { + e.preventDefault() + e.stopPropagation() + setHighlighted((prev) => Math.min(prev + 1, total - 2)) + } else if (e.key === "ArrowUp") { + e.preventDefault() + e.stopPropagation() + setHighlighted((prev) => Math.max(prev - 1, -1)) + } else if (e.key === "Enter") { + e.preventDefault() + e.stopPropagation() + const idx = highlighted() + if (idx === -1) { + selectBranch(undefined) + } else { + const branch = items[idx] + if (branch) selectBranch(branch.name) + } + } else if (e.key === "Escape") { + e.preventDefault() + e.stopPropagation() + dialog.close() + } + } + + dialog.show(() => { + onCleanup(unsub) + return ( + +
+ { + setSearch(v) + setHighlighted(-1) + }} + onSelect={(b) => selectBranch(b.name)} + onSearchKeyDown={handleKeyDown} + selected={defaultBaseBranch()} + highlighted={highlighted()} + onHighlight={setHighlighted} + searchPlaceholder={t("agentManager.dialog.searchBranches")} + emptyLabel={t("agentManager.import.noMatchingBranches")} + loadingLabel={t("agentManager.import.loadingBranches")} + defaultLabel={t("agentManager.dialog.branchBadge.default")} + remoteLabel={t("agentManager.dialog.branchBadge.remote")} + defaultName={defaultBaseBranch()} + autoOption={{ + label: t("agentManager.worktree.defaultBaseBranchAuto"), + hint: repoDetectedBranch(), + active: !hasConfiguredBranch(), + highlighted: highlighted() === -1, + onSelect: () => selectBranch(undefined), + }} + /> +
+
+ ) + }) + } +} diff --git a/packages/kilo-vscode/webview-ui/src/context/session-merge.ts b/packages/kilo-vscode/webview-ui/src/context/session-merge.ts new file mode 100644 index 00000000000..ac9264cf238 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/context/session-merge.ts @@ -0,0 +1,41 @@ +import type { Message, MessageLoadMode, Part } from "../types/messages" +import { sameParts } from "./session-parts" + +export function mergeMessages(current: Message[], incoming: Message[], mode: Exclude) { + if (mode === "reconcile") { + // Tail reconcile: incoming is the authoritative newest-N snapshot. + // Local state may already hold some of those IDs and may also hold + // newer optimistic entries created after the fetch was taken. Merge + // by id (server wins on collision) then sort by createdAt so new + // server messages land in the right position and optimistic tail + // entries stay at the end. + const byId = new Map() + for (const msg of current) byId.set(msg.id, msg) + for (const msg of incoming) byId.set(msg.id, msg) + return [...byId.values()].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) + } + const seen = new Set() + const source = mode === "prepend" ? [...incoming, ...current] : incoming + return source.filter((msg) => { + if (seen.has(msg.id)) return false + seen.add(msg.id) + return true + }) +} + +// Cheap tail check: same ids in the same order and no visible streamed-part +// correction to apply. It skips store churn when SSE already matches the +// snapshot, but lets reconcile heal part removals and finalized text. +export function sameReconcileShape( + current: Message[], + incoming: Message[], + getParts: (messageID: string) => Part[] | undefined, +): boolean { + if (current.length !== incoming.length) return false + for (const [i, n] of incoming.entries()) { + const c = current[i]! + if (c.id !== n.id) return false + if (!sameParts(getParts(c.id) ?? c.parts, n.parts)) return false + } + return true +} diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 839b0dc3f50..302d1c8727f 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -72,7 +72,8 @@ import { getAgentModel } from "./session-model-store" import { resolveMessagePrefs } from "./session-preferences" import { errorIDs } from "./session-errors" import { PartStash } from "./part-stash" -import { mergeParts, sameParts } from "./session-parts" +import { mergeParts } from "./session-parts" +import { mergeMessages, sameReconcileShape } from "./session-merge" import { state as todoState } from "./todo-revert" import { getAgentVariant, @@ -1423,28 +1424,6 @@ export const SessionProvider: ParentComponent = (props) => { setPages(sessionID, { ...(pages[sessionID] ?? emptyPageState), ...patch }) } - function mergeMessages(current: Message[], incoming: Message[], mode: Exclude) { - if (mode === "reconcile") { - // Tail reconcile: incoming is the authoritative newest-N snapshot. - // Local state may already hold some of those IDs and may also hold - // newer optimistic entries created after the fetch was taken. Merge - // by id (server wins on collision) then sort by createdAt so new - // server messages land in the right position and optimistic tail - // entries stay at the end. - const byId = new Map() - for (const msg of current) byId.set(msg.id, msg) - for (const msg of incoming) byId.set(msg.id, msg) - return [...byId.values()].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) - } - const seen = new Set() - const source = mode === "prepend" ? [...incoming, ...current] : incoming - return source.filter((msg) => { - if (seen.has(msg.id)) return false - seen.add(msg.id) - return true - }) - } - function recoverPrefs(sessionID: string, messages: Message[], names = agentNames()) { const prefs = resolveMessagePrefs(messages, names) if (prefs.agent && !store.agentSelections[sessionID]) { @@ -1469,19 +1448,6 @@ export const SessionProvider: ParentComponent = (props) => { return [...messages, ...orphans] } - // Cheap tail check: same ids in the same order and no visible streamed-part - // correction to apply. It skips store churn when SSE already matches the - // snapshot, but lets reconcile heal part removals and finalized text. - function sameReconcileShape(current: Message[], incoming: Message[]): boolean { - if (current.length !== incoming.length) return false - for (const [i, n] of incoming.entries()) { - const c = current[i]! - if (c.id !== n.id) return false - if (!sameParts(store.parts[c.id] ?? c.parts, n.parts)) return false - } - return true - } - function setTools(sessionID: string, tools: ToolPart[]) { setStore("toolParts", sessionID, reconcileSessionToolParts(tools)) } @@ -1530,7 +1496,7 @@ export const SessionProvider: ParentComponent = (props) => { // Reconcile fast-path: if the tail matches local state shape-wise, every // message+part-count already agrees with the server. Skip the reactive // store churn entirely — virtualizer and rendering stay untouched. - if (mode === "reconcile" && sameReconcileShape(store.messages[sessionID] ?? [], messages)) { + if (mode === "reconcile" && sameReconcileShape(store.messages[sessionID] ?? [], messages, (id) => store.parts[id])) { const parts = messageParts(messages) for (const msg of messages) { if (store.parts[msg.id]) delete parts[msg.id] From 0979a576e2d78d4cc2990adb2e1af42d04a04c9c Mon Sep 17 00:00:00 2001 From: cmanu Date: Thu, 6 Aug 2026 14:53:24 -0700 Subject: [PATCH 17/42] feat(pr-view): organize types --- .../src/agent-manager/PRStatusPoller.ts | 2 +- .../src/agent-manager/am-pr-types.ts | 21 +++++++++++++++ .../src/agent-manager/am-pr-utils.ts | 27 +++---------------- 3 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 packages/kilo-vscode/src/agent-manager/am-pr-types.ts diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 077552ccf38..6ce5e6b748d 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -6,7 +6,7 @@ import { execGhRead } from "./gh" import { classifyPRError } from "./git-import" import type { Semaphore } from "./semaphore" import { parsePRResult, checkStatus, formatCheckDuration, parseComments, parseReviewers } from "./am-pr-utils" -import type { PRResult, GhThread, GhReviewRequest, GhReview } from "./am-pr-utils" +import type { PRResult, GhThread, GhReviewRequest, GhReview } from "./am-pr-types" interface PRStatusPollerOptions { getWorktrees: () => Worktree[] diff --git a/packages/kilo-vscode/src/agent-manager/am-pr-types.ts b/packages/kilo-vscode/src/agent-manager/am-pr-types.ts new file mode 100644 index 00000000000..59f4604c96b --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/am-pr-types.ts @@ -0,0 +1,21 @@ +import type { PRState, ReviewDecision } from "./types" + +// Raw shapes returned by `gh pr view --json` + +export interface GhAuthor { login?: string; avatarUrl?: string } +export interface GhComment { id: string; author?: GhAuthor; body?: string; path?: string; line?: number; url?: string; createdAt?: string } +export interface GhThread { isResolved?: boolean; comments?: { nodes?: GhComment[] } } +export interface GhReviewRequest { requestedReviewer?: GhAuthor } +export interface GhReview { author?: GhAuthor; state?: string } + +export interface PRResult { + number: number + title: string + body: string + url: string + state: PRState + review: ReviewDecision | null + additions: number + deletions: number + files: number +} diff --git a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts index 976b486db80..8a45349bf61 100644 --- a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts +++ b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts @@ -1,31 +1,12 @@ -import type { PRState, ReviewDecision, CheckStatus, PRComment, PRReviewer } from "./types" - -// Raw shapes returned by `gh pr view --json` - -export interface GhAuthor { login?: string; avatarUrl?: string } -export interface GhComment { id: string; author?: GhAuthor; body?: string; path?: string; line?: number; url?: string; createdAt?: string } -export interface GhThread { isResolved?: boolean; comments?: { nodes?: GhComment[] } } -export interface GhReviewRequest { requestedReviewer?: GhAuthor } -export interface GhReview { author?: GhAuthor; state?: string } - -export interface PRResult { - number: number - title: string - body: string - url: string - state: PRState - review: ReviewDecision | null - additions: number - deletions: number - files: number -} +import type { CheckStatus, PRComment, PRReviewer } from "./types" +import type { PRResult, GhThread, GhReviewRequest, GhReview } from "./am-pr-types" export function parsePRResult(json: string): PRResult | null { const data = JSON.parse(json) if (!data.number) return null - const state: PRState = data.isDraft ? "draft" : (data.state?.toLowerCase() as PRState) ?? "open" + const state = data.isDraft ? "draft" : (data.state?.toLowerCase() ?? "open") const decision = data.reviewDecision as string | undefined - const review: ReviewDecision | null = + const review = decision === "APPROVED" ? "approved" : decision === "CHANGES_REQUESTED" ? "changes_requested" : decision === "REVIEW_REQUIRED" ? "pending" From 0c22c49d438841917f8a1829970f9e3c18338c5a Mon Sep 17 00:00:00 2001 From: cmanu Date: Thu, 6 Aug 2026 15:21:12 -0700 Subject: [PATCH 18/42] feat(pr-view): add unit tests for status bridge and pr utils --- .../tests/unit/am-pr-status-bridge.test.ts | 186 +++++++++++++++ .../tests/unit/am-pr-utils.test.ts | 214 ++++++++++++++++++ 2 files changed, 400 insertions(+) create mode 100644 packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts create mode 100644 packages/kilo-vscode/tests/unit/am-pr-utils.test.ts diff --git a/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts b/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts new file mode 100644 index 00000000000..4752844023e --- /dev/null +++ b/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts @@ -0,0 +1,186 @@ +import { describe, expect, it } from "bun:test" +import { PRStatusBridge } from "../../src/agent-manager/pr-status-bridge" +import type { AgentManagerOutMessage, PRStatus } from "../../src/agent-manager/types" + +const pr: PRStatus = { + number: 1, + title: "my PR", + url: "https://github.com/x/y/pull/1", + state: "open", + review: null, + checks: { status: "none", total: 0, passed: 0, failed: 0, pending: 0, checks: [] }, + additions: 0, + deletions: 0, + files: 0, +} + +function harness(opts: { hasPersisted?: boolean } = {}) { + const sent: AgentManagerOutMessage[] = [] + const worktrees: { id: string; prUrl?: string }[] = [] + const bridge = PRStatusBridge.create({ + getWorktrees: () => worktrees as never, + getWorkspaceRoot: () => "/repo", + postToWebview: (msg) => sent.push(msg), + updateWorktreePR: () => {}, + hasPersistedPR: () => opts.hasPersisted ?? false, + openExternal: () => {}, + log: () => {}, + }) + const onStatus = (bridge.poller as unknown as { options: { onStatus: (...a: unknown[]) => void } }).options.onStatus + return { bridge, sent, onStatus } +} + +// --- error deduplication --- + +describe("PRStatusBridge.notifyError", () => { + it("sends the first error notification", () => { + const { bridge, sent } = harness() + bridge.notifyError("gh_missing") + expect(sent).toHaveLength(1) + expect(sent[0]).toEqual(expect.objectContaining({ type: "agentManager.prError", error: "gh_missing" })) + }) + + it("deduplicates the same error type", () => { + const { bridge, sent } = harness() + bridge.notifyError("gh_auth") + bridge.notifyError("gh_auth") + expect(sent).toHaveLength(1) + }) + + it("sends again when error type changes", () => { + const { bridge, sent } = harness() + bridge.notifyError("gh_missing") + bridge.notifyError("gh_auth") + expect(sent).toHaveLength(2) + }) +}) + +// --- onStatus cache suppression --- + +describe("PRStatusBridge onStatus", () => { + it("forwards a successful status to the webview", () => { + const { sent, onStatus } = harness() + onStatus("wt1", pr) + expect(sent).toHaveLength(1) + expect(sent[0]).toEqual(expect.objectContaining({ type: "agentManager.prStatus", worktreeId: "wt1", pr })) + }) + + it("forwards pr:null error when no cache entry and no persisted PR", () => { + const { sent, onStatus } = harness() + onStatus("wt1", null, "fetch_failed") + expect(sent).toHaveLength(1) + expect(sent[0]).toEqual(expect.objectContaining({ type: "agentManager.prStatus", worktreeId: "wt1", pr: null })) + }) + + it("suppresses pr:null error when cache entry exists", () => { + const { sent, onStatus } = harness() + onStatus("wt1", pr) + sent.length = 0 + onStatus("wt1", null, "fetch_failed") + expect(sent).toHaveLength(0) + }) + + it("suppresses pr:null error when persisted PR exists", () => { + const { sent, onStatus } = harness({ hasPersisted: true }) + onStatus("wt1", null, "fetch_failed") + expect(sent).toHaveLength(0) + }) + + it("forwards gh_auth error even when cache entry exists", () => { + const { sent, onStatus } = harness() + onStatus("wt1", pr) + sent.length = 0 + onStatus("wt1", null, "gh_auth") + const errorMsg = sent.find((m) => m.type === "agentManager.prError") + expect(errorMsg).toEqual(expect.objectContaining({ error: "gh_auth" })) + }) + + it("forwards gh_missing error even when cache entry exists", () => { + const { sent, onStatus } = harness() + onStatus("wt1", pr) + sent.length = 0 + onStatus("wt1", null, "gh_missing") + const errorMsg = sent.find((m) => m.type === "agentManager.prError") + expect(errorMsg).toEqual(expect.objectContaining({ error: "gh_missing" })) + }) +}) + +// --- replay --- + +describe("PRStatusBridge.replay", () => { + it("replays cached status messages", () => { + const { bridge, sent, onStatus } = harness() + onStatus("wt1", pr) + sent.length = 0 + bridge.replay() + expect(sent).toHaveLength(1) + expect(sent[0]).toEqual(expect.objectContaining({ type: "agentManager.prStatus", worktreeId: "wt1" })) + }) + + it("replays the last auth error on reconnect", () => { + const { bridge, sent, onStatus } = harness() + onStatus("wt1", null, "gh_auth") + sent.length = 0 + bridge.replay() + expect(sent.some((m) => m.type === "agentManager.prError" && (m as never as { error: string }).error === "gh_auth")).toBe(true) + }) + + it("does not replay fetch_failed errors", () => { + const { bridge, sent, onStatus } = harness() + onStatus("wt1", null, "fetch_failed") + sent.length = 0 + bridge.replay() + expect(sent).toHaveLength(0) + }) +}) + +// --- snapshot --- + +describe("PRStatusBridge.snapshot", () => { + it("returns only entries with a non-null pr", () => { + const { bridge, onStatus } = harness() + onStatus("wt1", pr) + onStatus("wt2", pr) + expect(bridge.snapshot().size).toBe(2) + }) + + it("excludes entries where pr was null", () => { + const { bridge, onStatus } = harness({ hasPersisted: true }) + onStatus("wt1", null, "fetch_failed") + expect(bridge.snapshot().size).toBe(0) + }) +}) + +// --- remove / reset --- + +describe("PRStatusBridge.remove", () => { + it("removes a cached entry so it is no longer replayed", () => { + const { bridge, sent, onStatus } = harness() + onStatus("wt1", pr) + bridge.remove("wt1") + sent.length = 0 + bridge.replay() + expect(sent).toHaveLength(0) + }) +}) + +describe("PRStatusBridge.reset", () => { + it("clears cache and error state so replay sends nothing", () => { + const { bridge, sent, onStatus } = harness() + onStatus("wt1", pr) + bridge.notifyError("gh_auth") + bridge.reset() + sent.length = 0 + bridge.replay() + expect(sent).toHaveLength(0) + }) + + it("allows the same error to be sent again after reset", () => { + const { bridge, sent } = harness() + bridge.notifyError("gh_auth") + bridge.reset() + sent.length = 0 + bridge.notifyError("gh_auth") + expect(sent).toHaveLength(1) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts b/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts new file mode 100644 index 00000000000..e16ab9c259d --- /dev/null +++ b/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts @@ -0,0 +1,214 @@ +import { describe, expect, it } from "bun:test" +import { + parsePRResult, + checkStatus, + formatCheckDuration, + parseComments, + parseReviewers, +} from "../../src/agent-manager/am-pr-utils" +import type { GhThread, GhReviewRequest, GhReview } from "../../src/agent-manager/am-pr-types" + +// --- parsePRResult --- + +describe("parsePRResult", () => { + it("returns null when number is missing", () => { + expect(parsePRResult(JSON.stringify({ title: "foo" }))).toBeNull() + }) + + it("parses an open PR", () => { + const raw = { number: 42, title: "my PR", body: "desc", url: "https://github.com/x/y/pull/42", state: "OPEN", isDraft: false, reviewDecision: null, additions: 10, deletions: 3, changedFiles: 2 } + expect(parsePRResult(JSON.stringify(raw))).toEqual({ + number: 42, + title: "my PR", + body: "desc", + url: "https://github.com/x/y/pull/42", + state: "open", + review: null, + additions: 10, + deletions: 3, + files: 2, + }) + }) + + it("maps isDraft to draft state regardless of gh state field", () => { + const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: true, reviewDecision: null, additions: 0, deletions: 0, changedFiles: 0 } + expect(parsePRResult(JSON.stringify(raw))?.state).toBe("draft") + }) + + it("maps MERGED state", () => { + const raw = { number: 1, title: "", body: "", url: "", state: "MERGED", isDraft: false, reviewDecision: null, additions: 0, deletions: 0, changedFiles: 0 } + expect(parsePRResult(JSON.stringify(raw))?.state).toBe("merged") + }) + + it("maps CLOSED state", () => { + const raw = { number: 1, title: "", body: "", url: "", state: "CLOSED", isDraft: false, reviewDecision: null, additions: 0, deletions: 0, changedFiles: 0 } + expect(parsePRResult(JSON.stringify(raw))?.state).toBe("closed") + }) + + it("maps APPROVED review decision", () => { + const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: false, reviewDecision: "APPROVED", additions: 0, deletions: 0, changedFiles: 0 } + expect(parsePRResult(JSON.stringify(raw))?.review).toBe("approved") + }) + + it("maps CHANGES_REQUESTED review decision", () => { + const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: false, reviewDecision: "CHANGES_REQUESTED", additions: 0, deletions: 0, changedFiles: 0 } + expect(parsePRResult(JSON.stringify(raw))?.review).toBe("changes_requested") + }) + + it("maps REVIEW_REQUIRED review decision to pending", () => { + const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: false, reviewDecision: "REVIEW_REQUIRED", additions: 0, deletions: 0, changedFiles: 0 } + expect(parsePRResult(JSON.stringify(raw))?.review).toBe("pending") + }) + + it("returns null review for unknown decision", () => { + const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: false, reviewDecision: "SOMETHING_ELSE", additions: 0, deletions: 0, changedFiles: 0 } + expect(parsePRResult(JSON.stringify(raw))?.review).toBeNull() + }) + + it("defaults missing fields to empty strings and zeros", () => { + const result = parsePRResult(JSON.stringify({ number: 5 })) + expect(result).toEqual(expect.objectContaining({ title: "", body: "", url: "", additions: 0, deletions: 0, files: 0 })) + }) +}) + +// --- checkStatus --- + +describe("checkStatus", () => { + it("maps SUCCESS", () => expect(checkStatus("SUCCESS")).toBe("success")) + it("maps FAILURE", () => expect(checkStatus("FAILURE")).toBe("failure")) + it("maps ERROR to failure", () => expect(checkStatus("ERROR")).toBe("failure")) + it("maps PENDING", () => expect(checkStatus("PENDING")).toBe("pending")) + it("maps QUEUED to pending", () => expect(checkStatus("QUEUED")).toBe("pending")) + it("maps IN_PROGRESS to pending", () => expect(checkStatus("IN_PROGRESS")).toBe("pending")) + it("maps REQUESTED to pending", () => expect(checkStatus("REQUESTED")).toBe("pending")) + it("maps WAITING to pending", () => expect(checkStatus("WAITING")).toBe("pending")) + it("maps SKIPPED", () => expect(checkStatus("SKIPPED")).toBe("skipped")) + it("maps CANCELLED", () => expect(checkStatus("CANCELLED")).toBe("cancelled")) + it("maps TIMED_OUT to cancelled", () => expect(checkStatus("TIMED_OUT")).toBe("cancelled")) + it("maps STALE to cancelled", () => expect(checkStatus("STALE")).toBe("cancelled")) + it("maps STARTUP_FAILURE to cancelled", () => expect(checkStatus("STARTUP_FAILURE")).toBe("cancelled")) + it("maps unknown state to pending", () => expect(checkStatus("WHATEVER")).toBe("pending")) + it("is case-insensitive", () => expect(checkStatus("success")).toBe("success")) +}) + +// --- formatCheckDuration --- + +describe("formatCheckDuration", () => { + it("returns undefined when startedAt is missing", () => { + expect(formatCheckDuration(undefined, "2024-01-01T00:01:00Z")).toBeUndefined() + }) + + it("returns undefined when completedAt is missing", () => { + expect(formatCheckDuration("2024-01-01T00:00:00Z", undefined)).toBeUndefined() + }) + + it("formats sub-minute durations in seconds", () => { + expect(formatCheckDuration("2024-01-01T00:00:00Z", "2024-01-01T00:00:45Z")).toBe("45s") + }) + + it("formats durations over a minute as m/s", () => { + expect(formatCheckDuration("2024-01-01T00:00:00Z", "2024-01-01T00:02:30Z")).toBe("2m 30s") + }) + + it("formats exactly 60 seconds as 1m 0s", () => { + expect(formatCheckDuration("2024-01-01T00:00:00Z", "2024-01-01T00:01:00Z")).toBe("1m 0s") + }) +}) + +// --- parseComments --- + +describe("parseComments", () => { + it("returns empty array for empty threads", () => { + expect(parseComments([])).toEqual([]) + }) + + it("skips threads with no comments", () => { + const threads: GhThread[] = [{ isResolved: false, comments: { nodes: [] } }] + expect(parseComments(threads)).toHaveLength(0) + }) + + it("parses a resolved thread", () => { + const threads: GhThread[] = [{ + isResolved: true, + comments: { nodes: [{ id: "c1", author: { login: "alice", avatarUrl: "https://avatar" }, body: "looks good", path: "src/foo.ts", line: 10, url: "https://url", createdAt: "2024-01-01T00:00:00Z" }] }, + }] + expect(parseComments(threads)).toEqual([{ + id: "c1", + author: "alice", + avatar: "https://avatar", + body: "looks good", + file: "src/foo.ts", + line: 10, + url: "https://url", + resolved: true, + createdAt: new Date("2024-01-01T00:00:00Z").getTime(), + }]) + }) + + it("defaults missing author to 'unknown'", () => { + const threads: GhThread[] = [{ isResolved: false, comments: { nodes: [{ id: "c2", body: "note" }] } }] + expect(parseComments(threads)[0]?.author).toBe("unknown") + }) + + it("only uses the first comment of each thread", () => { + const threads: GhThread[] = [{ + isResolved: false, + comments: { nodes: [ + { id: "first", body: "first comment" }, + { id: "second", body: "second comment" }, + ]}, + }] + const result = parseComments(threads) + expect(result).toHaveLength(1) + expect(result[0]?.id).toBe("first") + }) +}) + +// --- parseReviewers --- + +describe("parseReviewers", () => { + it("returns empty array with no requests or reviews", () => { + expect(parseReviewers([], [])).toEqual([]) + }) + + it("adds pending reviewer from request", () => { + const requests: GhReviewRequest[] = [{ requestedReviewer: { login: "alice", avatarUrl: "https://avatar" } }] + expect(parseReviewers(requests, [])).toEqual([{ login: "alice", avatar: "https://avatar", state: "pending" }]) + }) + + it("skips review requests without a login", () => { + const requests: GhReviewRequest[] = [{ requestedReviewer: {} }] + expect(parseReviewers(requests, [])).toHaveLength(0) + }) + + it("adds reviewer from review when not in requests", () => { + const reviews: GhReview[] = [{ author: { login: "bob" }, state: "APPROVED" }] + expect(parseReviewers([], reviews)).toEqual([{ login: "bob", avatar: undefined, state: "approved" }]) + }) + + it("upgrades pending request to approved when review arrives", () => { + const requests: GhReviewRequest[] = [{ requestedReviewer: { login: "alice" } }] + const reviews: GhReview[] = [{ author: { login: "alice" }, state: "APPROVED" }] + expect(parseReviewers(requests, reviews)).toEqual([{ login: "alice", avatar: undefined, state: "approved" }]) + }) + + it("does not downgrade approved to commented", () => { + const requests: GhReviewRequest[] = [{ requestedReviewer: { login: "alice" } }] + const reviews: GhReview[] = [ + { author: { login: "alice" }, state: "APPROVED" }, + { author: { login: "alice" }, state: "COMMENTED" }, + ] + expect(parseReviewers(requests, reviews)[0]?.state).toBe("approved") + }) + + it("does upgrade pending to changes_requested", () => { + const requests: GhReviewRequest[] = [{ requestedReviewer: { login: "alice" } }] + const reviews: GhReview[] = [{ author: { login: "alice" }, state: "CHANGES_REQUESTED" }] + expect(parseReviewers(requests, reviews)[0]?.state).toBe("changes_requested") + }) + + it("skips reviews without a login", () => { + const reviews: GhReview[] = [{ author: {}, state: "APPROVED" }] + expect(parseReviewers([], reviews)).toHaveLength(0) + }) +}) From e87633400bf1b7be8a54fb48bb7af4a161468eda Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 08:53:14 -0700 Subject: [PATCH 19/42] feat(pr-view): Re-add pr number on worktree item --- packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx index 69378459144..2b07b00269c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx @@ -357,6 +357,7 @@ export const WorktreeItem: Component = (props) => { onClick={handleOpenPR} > + #{pr().number} From d0585faaeb8fc5e0a889e81f4d7f46526a441ca0 Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 09:43:52 -0700 Subject: [PATCH 20/42] fix(pr-view): unit tests --- packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index 7d0b8b1c132..0486cc70822 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -38,6 +38,7 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/MultiModelSelector.tsx"), path.join(ROOT, "webview-ui/agent-manager/ApplyDialog.tsx"), path.join(ROOT, "webview-ui/agent-manager/WorktreeItem.tsx"), + path.join(ROOT, "webview-ui/agent-manager/pr/PRBadge.tsx"), path.join(ROOT, "webview-ui/agent-manager/SectionHeader.tsx"), path.join(ROOT, "webview-ui/agent-manager/SidebarSectionHeader.tsx"), path.join(ROOT, "webview-ui/agent-manager/SidebarSearchMenu.tsx"), From d6e4182606c57a82361d427f25e36eb1de6fb13e Mon Sep 17 00:00:00 2001 From: "kilo-maintainer[bot]" Date: Fri, 7 Aug 2026 16:46:55 +0000 Subject: [PATCH 21/42] chore: update kilo-vscode visual regression baselines --- .../pr-badge-approved-checks-failing-chromium-linux.png | 4 ++-- .../agentmanager/pr-badge-checks-failing-chromium-linux.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-badge-approved-checks-failing-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-badge-approved-checks-failing-chromium-linux.png index 4720473ebb2..25035a43351 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-badge-approved-checks-failing-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-badge-approved-checks-failing-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2349f8c0d7049811702e5b8cbeb5fc2f402bdda8630d1a5210a970a13284197 -size 3694 +oid sha256:33ee6eb7070237487d1931f32ac77a2549e18e664b993895e9a9d6619527d0aa +size 4063 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-badge-checks-failing-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-badge-checks-failing-chromium-linux.png index 4720473ebb2..25035a43351 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-badge-checks-failing-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-badge-checks-failing-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2349f8c0d7049811702e5b8cbeb5fc2f402bdda8630d1a5210a970a13284197 -size 3694 +oid sha256:33ee6eb7070237487d1931f32ac77a2549e18e664b993895e9a9d6619527d0aa +size 4063 From 1a0cc860e821001d7023577971105680062ddf05 Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 10:05:58 -0700 Subject: [PATCH 22/42] fix(pr-view): update reviewers summary and types --- packages/kilo-vscode/src/agent-manager/am-pr-utils.ts | 4 ++-- packages/kilo-vscode/src/agent-manager/types.ts | 4 +++- .../webview-ui/agent-manager/pr/PRReviewers.tsx | 6 +++--- .../webview-ui/agent-manager/pr/PRSummary.tsx | 9 +++------ .../kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts | 4 +++- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts index 8a45349bf61..25291c2a5db 100644 --- a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts +++ b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts @@ -1,4 +1,4 @@ -import type { CheckStatus, PRComment, PRReviewer } from "./types" +import type { CheckStatus, PRComment, PRReviewer, ReviewerState } from "./types" import type { PRResult, GhThread, GhReviewRequest, GhReview } from "./am-pr-types" export function parsePRResult(json: string): PRResult | null { @@ -55,7 +55,7 @@ export function formatCheckDuration(startedAt?: string, completedAt?: string): s return secs < 60 ? `${secs}s` : `${Math.floor(secs / 60)}m ${secs % 60}s` } -const REVIEWER_STATE: Record = { +const REVIEWER_STATE: Record = { APPROVED: "approved", CHANGES_REQUESTED: "changes_requested", COMMENTED: "commented", diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 8e503e7db9f..836b50471a7 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -71,10 +71,12 @@ export interface PRComment { createdAt?: number } +export type ReviewerState = "approved" | "changes_requested" | "pending" | "commented" + export interface PRReviewer { login: string avatar?: string - state: "approved" | "changes_requested" | "pending" | "commented" + state: ReviewerState } export interface PRStatus { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRReviewers.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRReviewers.tsx index 85206562958..071da1f119d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRReviewers.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRReviewers.tsx @@ -1,17 +1,17 @@ /** @jsxImportSource solid-js */ import { For, Show, createSignal } from "solid-js" import { Icon } from "@kilocode/kilo-ui/icon" -import type { PRReviewer } from "./pr-types" +import type { PRReviewer, ReviewerState } from "./pr-types" import { SectionHeading } from "./SectionHeading" -const REVIEWER_ICON: Record = { +const REVIEWER_ICON: Record = { approved: "circle-check", changes_requested: "refresh", commented: "edit", pending: "dash", } -const REVIEWER_LABEL: Record = { +const REVIEWER_LABEL: Record = { approved: "Approved", changes_requested: "Changes requested", commented: "Commented", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx index c04e89a5fe4..bd7dbf36d0e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx @@ -19,14 +19,11 @@ function summaryRows(pr: PRStatus): Array<{ icon: string; label: string; status: }) } - const reviewers = pr.comments?.reviewers - if (reviewers && reviewers.length > 0) { - const approved = reviewers.filter((r) => r.state === "approved").length - const changes = reviewers.filter((r) => r.state === "changes_requested").length - const status = approved === reviewers.length ? "success" : changes > 0 ? "failure" : "pending" + if (pr.review) { + const status = pr.review === "approved" ? "success" : pr.review === "changes_requested" ? "failure" : "pending" rows.push({ icon: status === "success" ? "circle-check" : status === "failure" ? "circle-x-outline" : "play", - label: approved === reviewers.length ? "All reviewers approved" : `${approved}/${reviewers.length} approvals`, + label: status === "success" ? "Approved" : status === "failure" ? "Changes requested" : "Review pending", status, }) } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts index 8d88e131557..d5d0a89d6c6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts @@ -25,8 +25,10 @@ export interface PRComment { createdAt?: number } +export type ReviewerState = "approved" | "changes_requested" | "pending" | "commented" + export interface PRReviewer { login: string avatar?: string - state: "approved" | "changes_requested" | "pending" | "commented" + state: ReviewerState } From c875fb02be3962a8a3195da70cb4d94fd6acce34 Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 10:19:54 -0700 Subject: [PATCH 23/42] fix(pr-view): split reviewers out of comments; fix pr summary to show review decision; show reviewers for all pr statuses --- .../src/agent-manager/PRStatusPoller.ts | 47 ++++++++++++++----- .../kilo-vscode/src/agent-manager/types.ts | 2 +- .../webview-ui/agent-manager/pr/PRPanel.tsx | 4 +- .../src/stories/agent-manager.stories.tsx | 1 + .../src/stories/section-header.stories.tsx | 7 +++ .../src/types/messages/agent-manager.ts | 2 +- 6 files changed, 46 insertions(+), 17 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 6ce5e6b748d..5a2e1e51ce0 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -248,8 +248,9 @@ export class PRStatusPoller { return } - const [checks, comments] = await Promise.all([ + const [checks, reviewers, comments] = await Promise.all([ this.fetchChecks(pr.number, wt.path), + this.fetchReviewers(pr.number, wt.path), this.activeWorktreeId === worktreeId ? this.fetchComments(pr.number, wt.path) : undefined, ]) if (this.stale(generation)) return @@ -262,15 +263,16 @@ export class PRStatusPoller { state: pr.state, review: pr.review, checks, + reviewers, ...(comments && { - comments: { total: comments.total, unresolved: comments.unresolved, comments: comments.comments, reviewers: comments.reviewers }, + comments: { total: comments.total, unresolved: comments.unresolved, comments: comments.comments }, }), additions: pr.additions, deletions: pr.deletions, files: pr.files, } - const hash = `${worktreeId}:${pr.number}:${pr.state}:${pr.review}:${checks.status}:${checks.passed}/${checks.total}:${comments?.total ?? ""}:${comments?.unresolved ?? ""}` + const hash = `${worktreeId}:${pr.number}:${pr.state}:${pr.review}:${checks.status}:${checks.passed}/${checks.total}:${reviewers.length}:${comments?.total ?? ""}:${comments?.unresolved ?? ""}` if (this.lastHash.get(worktreeId) === hash) return this.lastHash.set(worktreeId, hash) @@ -431,10 +433,36 @@ export class PRStatusPoller { return info } + private async fetchReviewers(prNumber: number, cwd: string): Promise { + try { + const repo = await this.getRepoInfo(cwd) + const query = `query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + reviewRequests(first: 20) { + nodes { requestedReviewer { ... on User { login avatarUrl } } } + } + reviews(first: 20, states: [APPROVED, CHANGES_REQUESTED, COMMENTED]) { + nodes { author { login avatarUrl } state } + } + } + } + }` + const { stdout } = await this.gh( + ["api", "graphql", "-f", `query=${query}`, "-F", `owner=${repo.owner}`, "-F", `repo=${repo.name}`, "-F", `number=${prNumber}`], + { cwd, timeout: 15_000 }, + ) + const pr = JSON.parse(stdout)?.data?.repository?.pullRequest + return parseReviewers((pr?.reviewRequests?.nodes ?? []) as GhReviewRequest[], (pr?.reviews?.nodes ?? []) as GhReview[]) + } catch { + return [] + } + } + private async fetchComments( prNumber: number, cwd: string, - ): Promise<{ total: number; unresolved: number; comments: PRComment[]; reviewers: PRReviewer[] }> { + ): Promise<{ total: number; unresolved: number; comments: PRComment[] }> { try { const repo = await this.getRepoInfo(cwd) const query = `query($owner: String!, $repo: String!, $number: Int!) { @@ -456,12 +484,6 @@ export class PRStatusPoller { } } } - reviewRequests(first: 20) { - nodes { requestedReviewer { ... on User { login avatarUrl } } } - } - reviews(first: 20, states: [APPROVED, CHANGES_REQUESTED, COMMENTED]) { - nodes { author { login avatarUrl } state } - } } } }` @@ -483,11 +505,10 @@ export class PRStatusPoller { ) const pr = JSON.parse(stdout)?.data?.repository?.pullRequest const comments = parseComments((pr?.reviewThreads?.nodes ?? []) as GhThread[]) - const reviewers = parseReviewers((pr?.reviewRequests?.nodes ?? []) as GhReviewRequest[], (pr?.reviews?.nodes ?? []) as GhReview[]) - return { total: comments.length, unresolved: comments.filter((c) => !c.resolved).length, comments, reviewers } + return { total: comments.length, unresolved: comments.filter((c) => !c.resolved).length, comments } } catch (err) { this.options.log("Failed to fetch PR comments:", err) - return { total: 0, unresolved: 0, comments: [], reviewers: [] } + return { total: 0, unresolved: 0, comments: [] } } } } diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 836b50471a7..01c88ff1581 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -94,11 +94,11 @@ export interface PRStatus { pending: number checks: PRCheck[] } + reviewers: PRReviewer[] comments?: { total: number unresolved: number comments: PRComment[] - reviewers: PRReviewer[] } additions: number deletions: number diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index 7bf8aef9f7b..20382b02972 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -44,8 +44,8 @@ export const PRPanel: Component = (props) => {
- - {(comments) => } + 0}> + {(body) => } diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx index d308663f8bf..817310e1bb9 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx @@ -613,6 +613,7 @@ const basePR: PRStatus = { state: "open", review: null, checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, checks: [] }, + reviewers: [], additions: 978, deletions: 202, files: 12, diff --git a/packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx index 23938282a84..a359eefea5f 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/section-header.stories.tsx @@ -593,6 +593,7 @@ export const WithPRBadges: Story = { additions: 120, deletions: 30, files: 5, + reviewers: [], checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, checks: [] }, }} /> @@ -611,6 +612,7 @@ export const WithPRBadges: Story = { additions: 15, deletions: 8, files: 3, + reviewers: [], checks: { status: "failure", total: 5, passed: 3, failed: 2, pending: 0, checks: [] }, }} /> @@ -629,6 +631,7 @@ export const WithPRBadges: Story = { additions: 60, deletions: 12, files: 4, + reviewers: [], checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, checks: [] }, }} /> @@ -647,6 +650,7 @@ export const WithPRBadges: Story = { additions: 200, deletions: 0, files: 8, + reviewers: [], checks: { status: "pending", total: 5, passed: 2, failed: 0, pending: 3, checks: [] }, }} /> @@ -665,6 +669,7 @@ export const WithPRBadges: Story = { additions: 30, deletions: 5, files: 2, + reviewers: [], checks: { status: "none", total: 0, passed: 0, failed: 0, pending: 0, checks: [] }, }} /> @@ -683,6 +688,7 @@ export const WithPRBadges: Story = { additions: 90, deletions: 20, files: 6, + reviewers: [], checks: { status: "success", total: 5, passed: 5, failed: 0, pending: 0, checks: [] }, }} /> @@ -701,6 +707,7 @@ export const WithPRBadges: Story = { additions: 10, deletions: 4, files: 1, + reviewers: [], checks: { status: "failure", total: 5, passed: 1, failed: 4, pending: 0, checks: [] }, }} /> diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts index 23c0395a07d..f76fb185c7e 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts @@ -66,11 +66,11 @@ export interface PRStatus { pending: number checks: PRCheck[] } + reviewers: PRReviewer[] comments?: { total: number unresolved: number comments: PRComment[] - reviewers: PRReviewer[] } additions: number deletions: number From c20ccad2d7fb60ad8da9ccd943c0b4934aede1b7 Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 10:22:18 -0700 Subject: [PATCH 24/42] fix(pr-view): close pr window if wt has not pr --- .../kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 48d364feb36..234625e073b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -499,6 +499,7 @@ const AgentManagerContent: Component = () => { } createEffect(on(selection, () => cancelPendingDelete(), { defer: true })) createEffect(on(selection, () => clearReviewComposer(reviewComposer), { defer: true })) + createEffect(on(selection, () => { if (!activePR()) setSidePanel((p) => p === SidePanel.PR ? null : p) }, { defer: true })) onCleanup(() => clearTimeout(pendingDeleteTimer)) // Per-context tab memory lives in the active project's store: maps sidebar From e1f064387d8a0162524fee0e60888f3a65d2c582 Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 10:28:23 -0700 Subject: [PATCH 25/42] feat(pr-view): move file changes --- packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx | 2 -- .../kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index 20382b02972..7e4e804371c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -9,7 +9,6 @@ import { PRBadge } from "./PRBadge" import { PROverview } from "./PROverview" import { PRReviewers } from "./PRReviewers" import { PRDescription } from "./PRDescription" -import { PRFileChanges } from "./PRFileChanges" import { PRChecks } from "./PRChecks" import { PRComments } from "./PRComments" import { PRSummary } from "./PRSummary" @@ -50,7 +49,6 @@ export const PRPanel: Component = (props) => { {(body) => } - 0}> diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx index bd7dbf36d0e..5257014a3b0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx @@ -46,6 +46,11 @@ export function PRSummary(props: PRSummaryProps) {
PR Summary + + 0}>{props.pr.files}f + 0}>+{props.pr.additions} + 0}>−{props.pr.deletions} +
{rows().map((row) => ( From a357c7123045fd1b7b0739158c5710aa4758bece Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 10:36:11 -0700 Subject: [PATCH 26/42] feat(pr-view): Update pr button style --- .../kilo-vscode/webview-ui/agent-manager/TabBar.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx index ad0461db397..3f8cba105e7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx @@ -221,13 +221,14 @@ export const TabBar: Component = (props) => ( {(pr) => ( - + /> )} From 05301da2d23114b6a09caa2e3c9a1c551e685eb2 Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 13:01:24 -0700 Subject: [PATCH 27/42] fix(pr-view): Formatting and update css variable name for unit test --- .../src/agent-manager/PRStatusPoller.ts | 18 +- .../src/agent-manager/am-pr-types.ts | 29 ++- .../src/agent-manager/am-pr-utils.ts | 11 +- .../src/agent-manager/pr-status-bridge.ts | 3 +- .../tests/unit/am-pr-status-bridge.test.ts | 4 +- .../tests/unit/am-pr-utils.test.ts | 172 ++++++++++++++---- .../agent-manager/AgentManagerApp.tsx | 24 ++- .../webview-ui/agent-manager/WorktreeItem.tsx | 7 +- .../agent-manager/agent-manager.css | 34 ++-- .../agent-manager/default-base-branch.tsx | 11 +- .../webview-ui/agent-manager/pr/PRBadge.tsx | 7 +- .../webview-ui/agent-manager/pr/PRChecks.tsx | 4 +- .../agent-manager/pr/PRComments.tsx | 3 +- .../agent-manager/pr/PRFileChanges.tsx | 12 +- .../agent-manager/pr/PROverview.tsx | 4 +- .../webview-ui/agent-manager/pr/PRPanel.tsx | 12 +- .../webview-ui/agent-manager/pr/PRSummary.tsx | 12 +- .../webview-ui/agent-manager/pr/pr-panel.css | 120 ++++++++---- .../webview-ui/src/context/session.tsx | 5 +- .../src/types/messages/agent-manager.ts | 19 +- 20 files changed, 396 insertions(+), 115 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 5a2e1e51ce0..afe6a3e3854 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -449,11 +449,25 @@ export class PRStatusPoller { } }` const { stdout } = await this.gh( - ["api", "graphql", "-f", `query=${query}`, "-F", `owner=${repo.owner}`, "-F", `repo=${repo.name}`, "-F", `number=${prNumber}`], + [ + "api", + "graphql", + "-f", + `query=${query}`, + "-F", + `owner=${repo.owner}`, + "-F", + `repo=${repo.name}`, + "-F", + `number=${prNumber}`, + ], { cwd, timeout: 15_000 }, ) const pr = JSON.parse(stdout)?.data?.repository?.pullRequest - return parseReviewers((pr?.reviewRequests?.nodes ?? []) as GhReviewRequest[], (pr?.reviews?.nodes ?? []) as GhReview[]) + return parseReviewers( + (pr?.reviewRequests?.nodes ?? []) as GhReviewRequest[], + (pr?.reviews?.nodes ?? []) as GhReview[], + ) } catch { return [] } diff --git a/packages/kilo-vscode/src/agent-manager/am-pr-types.ts b/packages/kilo-vscode/src/agent-manager/am-pr-types.ts index 59f4604c96b..4cd818d7a09 100644 --- a/packages/kilo-vscode/src/agent-manager/am-pr-types.ts +++ b/packages/kilo-vscode/src/agent-manager/am-pr-types.ts @@ -2,11 +2,30 @@ import type { PRState, ReviewDecision } from "./types" // Raw shapes returned by `gh pr view --json` -export interface GhAuthor { login?: string; avatarUrl?: string } -export interface GhComment { id: string; author?: GhAuthor; body?: string; path?: string; line?: number; url?: string; createdAt?: string } -export interface GhThread { isResolved?: boolean; comments?: { nodes?: GhComment[] } } -export interface GhReviewRequest { requestedReviewer?: GhAuthor } -export interface GhReview { author?: GhAuthor; state?: string } +export interface GhAuthor { + login?: string + avatarUrl?: string +} +export interface GhComment { + id: string + author?: GhAuthor + body?: string + path?: string + line?: number + url?: string + createdAt?: string +} +export interface GhThread { + isResolved?: boolean + comments?: { nodes?: GhComment[] } +} +export interface GhReviewRequest { + requestedReviewer?: GhAuthor +} +export interface GhReview { + author?: GhAuthor + state?: string +} export interface PRResult { number: number diff --git a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts index 25291c2a5db..9cf563d0aef 100644 --- a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts +++ b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts @@ -7,10 +7,13 @@ export function parsePRResult(json: string): PRResult | null { const state = data.isDraft ? "draft" : (data.state?.toLowerCase() ?? "open") const decision = data.reviewDecision as string | undefined const review = - decision === "APPROVED" ? "approved" - : decision === "CHANGES_REQUESTED" ? "changes_requested" - : decision === "REVIEW_REQUIRED" ? "pending" - : null + decision === "APPROVED" + ? "approved" + : decision === "CHANGES_REQUESTED" + ? "changes_requested" + : decision === "REVIEW_REQUIRED" + ? "pending" + : null return { number: data.number, title: data.title ?? "", diff --git a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts index 6eb742fbd4e..af496281966 100644 --- a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts +++ b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts @@ -128,8 +128,7 @@ function bridgePollerOpts(bridge: PRStatusBridge, host: PRBridgeHost) { // Always forward auth/missing errors so the webview can show a toast, // regardless of whether prior data exists. Deduplicate per error type // so multiple failing worktrees don't produce multiple toasts. - if (err === "gh_auth" || err === "gh_missing") - bridge.notifyError(err) + if (err === "gh_auth" || err === "gh_missing") bridge.notifyError(err) return } const msg = { type: "agentManager.prStatus", worktreeId: id, pr, error: err } as AgentManagerOutMessage diff --git a/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts b/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts index 4752844023e..734b3128f6e 100644 --- a/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts +++ b/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts @@ -122,7 +122,9 @@ describe("PRStatusBridge.replay", () => { onStatus("wt1", null, "gh_auth") sent.length = 0 bridge.replay() - expect(sent.some((m) => m.type === "agentManager.prError" && (m as never as { error: string }).error === "gh_auth")).toBe(true) + expect( + sent.some((m) => m.type === "agentManager.prError" && (m as never as { error: string }).error === "gh_auth"), + ).toBe(true) }) it("does not replay fetch_failed errors", () => { diff --git a/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts b/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts index e16ab9c259d..ba2d8d80070 100644 --- a/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts @@ -16,7 +16,18 @@ describe("parsePRResult", () => { }) it("parses an open PR", () => { - const raw = { number: 42, title: "my PR", body: "desc", url: "https://github.com/x/y/pull/42", state: "OPEN", isDraft: false, reviewDecision: null, additions: 10, deletions: 3, changedFiles: 2 } + const raw = { + number: 42, + title: "my PR", + body: "desc", + url: "https://github.com/x/y/pull/42", + state: "OPEN", + isDraft: false, + reviewDecision: null, + additions: 10, + deletions: 3, + changedFiles: 2, + } expect(parsePRResult(JSON.stringify(raw))).toEqual({ number: 42, title: "my PR", @@ -31,43 +42,122 @@ describe("parsePRResult", () => { }) it("maps isDraft to draft state regardless of gh state field", () => { - const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: true, reviewDecision: null, additions: 0, deletions: 0, changedFiles: 0 } + const raw = { + number: 1, + title: "", + body: "", + url: "", + state: "OPEN", + isDraft: true, + reviewDecision: null, + additions: 0, + deletions: 0, + changedFiles: 0, + } expect(parsePRResult(JSON.stringify(raw))?.state).toBe("draft") }) it("maps MERGED state", () => { - const raw = { number: 1, title: "", body: "", url: "", state: "MERGED", isDraft: false, reviewDecision: null, additions: 0, deletions: 0, changedFiles: 0 } + const raw = { + number: 1, + title: "", + body: "", + url: "", + state: "MERGED", + isDraft: false, + reviewDecision: null, + additions: 0, + deletions: 0, + changedFiles: 0, + } expect(parsePRResult(JSON.stringify(raw))?.state).toBe("merged") }) it("maps CLOSED state", () => { - const raw = { number: 1, title: "", body: "", url: "", state: "CLOSED", isDraft: false, reviewDecision: null, additions: 0, deletions: 0, changedFiles: 0 } + const raw = { + number: 1, + title: "", + body: "", + url: "", + state: "CLOSED", + isDraft: false, + reviewDecision: null, + additions: 0, + deletions: 0, + changedFiles: 0, + } expect(parsePRResult(JSON.stringify(raw))?.state).toBe("closed") }) it("maps APPROVED review decision", () => { - const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: false, reviewDecision: "APPROVED", additions: 0, deletions: 0, changedFiles: 0 } + const raw = { + number: 1, + title: "", + body: "", + url: "", + state: "OPEN", + isDraft: false, + reviewDecision: "APPROVED", + additions: 0, + deletions: 0, + changedFiles: 0, + } expect(parsePRResult(JSON.stringify(raw))?.review).toBe("approved") }) it("maps CHANGES_REQUESTED review decision", () => { - const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: false, reviewDecision: "CHANGES_REQUESTED", additions: 0, deletions: 0, changedFiles: 0 } + const raw = { + number: 1, + title: "", + body: "", + url: "", + state: "OPEN", + isDraft: false, + reviewDecision: "CHANGES_REQUESTED", + additions: 0, + deletions: 0, + changedFiles: 0, + } expect(parsePRResult(JSON.stringify(raw))?.review).toBe("changes_requested") }) it("maps REVIEW_REQUIRED review decision to pending", () => { - const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: false, reviewDecision: "REVIEW_REQUIRED", additions: 0, deletions: 0, changedFiles: 0 } + const raw = { + number: 1, + title: "", + body: "", + url: "", + state: "OPEN", + isDraft: false, + reviewDecision: "REVIEW_REQUIRED", + additions: 0, + deletions: 0, + changedFiles: 0, + } expect(parsePRResult(JSON.stringify(raw))?.review).toBe("pending") }) it("returns null review for unknown decision", () => { - const raw = { number: 1, title: "", body: "", url: "", state: "OPEN", isDraft: false, reviewDecision: "SOMETHING_ELSE", additions: 0, deletions: 0, changedFiles: 0 } + const raw = { + number: 1, + title: "", + body: "", + url: "", + state: "OPEN", + isDraft: false, + reviewDecision: "SOMETHING_ELSE", + additions: 0, + deletions: 0, + changedFiles: 0, + } expect(parsePRResult(JSON.stringify(raw))?.review).toBeNull() }) it("defaults missing fields to empty strings and zeros", () => { const result = parsePRResult(JSON.stringify({ number: 5 })) - expect(result).toEqual(expect.objectContaining({ title: "", body: "", url: "", additions: 0, deletions: 0, files: 0 })) + expect(result).toEqual( + expect.objectContaining({ title: "", body: "", url: "", additions: 0, deletions: 0, files: 0 }), + ) }) }) @@ -128,21 +218,37 @@ describe("parseComments", () => { }) it("parses a resolved thread", () => { - const threads: GhThread[] = [{ - isResolved: true, - comments: { nodes: [{ id: "c1", author: { login: "alice", avatarUrl: "https://avatar" }, body: "looks good", path: "src/foo.ts", line: 10, url: "https://url", createdAt: "2024-01-01T00:00:00Z" }] }, - }] - expect(parseComments(threads)).toEqual([{ - id: "c1", - author: "alice", - avatar: "https://avatar", - body: "looks good", - file: "src/foo.ts", - line: 10, - url: "https://url", - resolved: true, - createdAt: new Date("2024-01-01T00:00:00Z").getTime(), - }]) + const threads: GhThread[] = [ + { + isResolved: true, + comments: { + nodes: [ + { + id: "c1", + author: { login: "alice", avatarUrl: "https://avatar" }, + body: "looks good", + path: "src/foo.ts", + line: 10, + url: "https://url", + createdAt: "2024-01-01T00:00:00Z", + }, + ], + }, + }, + ] + expect(parseComments(threads)).toEqual([ + { + id: "c1", + author: "alice", + avatar: "https://avatar", + body: "looks good", + file: "src/foo.ts", + line: 10, + url: "https://url", + resolved: true, + createdAt: new Date("2024-01-01T00:00:00Z").getTime(), + }, + ]) }) it("defaults missing author to 'unknown'", () => { @@ -151,13 +257,17 @@ describe("parseComments", () => { }) it("only uses the first comment of each thread", () => { - const threads: GhThread[] = [{ - isResolved: false, - comments: { nodes: [ - { id: "first", body: "first comment" }, - { id: "second", body: "second comment" }, - ]}, - }] + const threads: GhThread[] = [ + { + isResolved: false, + comments: { + nodes: [ + { id: "first", body: "first comment" }, + { id: "second", body: "second comment" }, + ], + }, + }, + ] const result = parseComments(threads) expect(result).toHaveLength(1) expect(result[0]?.id).toBe("first") diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 234625e073b..2dad2ea0209 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -499,7 +499,15 @@ const AgentManagerContent: Component = () => { } createEffect(on(selection, () => cancelPendingDelete(), { defer: true })) createEffect(on(selection, () => clearReviewComposer(reviewComposer), { defer: true })) - createEffect(on(selection, () => { if (!activePR()) setSidePanel((p) => p === SidePanel.PR ? null : p) }, { defer: true })) + createEffect( + on( + selection, + () => { + if (!activePR()) setSidePanel((p) => (p === SidePanel.PR ? null : p)) + }, + { defer: true }, + ), + ) onCleanup(() => clearTimeout(pendingDeleteTimer)) // Per-context tab memory lives in the active project's store: maps sidebar @@ -1581,7 +1589,11 @@ const AgentManagerContent: Component = () => { if (msg.type === "agentManager.prError") { const ev = msg as AgentManagerPRErrorMessage - showToast({ variant: "error", title: t(`agentManager.pr.error.${ev.error}.title`), description: t(`agentManager.pr.error.${ev.error}.description`) }) + showToast({ + variant: "error", + title: t(`agentManager.pr.error.${ev.error}.title`), + description: t(`agentManager.pr.error.${ev.error}.description`), + }) } if (projectLive.apply(msg)) return @@ -2656,7 +2668,13 @@ const AgentManagerContent: Component = () => { pr={data.pr} worktree={data.wt} onClose={() => setSidePanel(null)} - onOpenExternal={() => vscode.postMessage({ type: "agentManager.openPR", worktreeId: data.selected, url: data.pr.url })} + onOpenExternal={() => + vscode.postMessage({ + type: "agentManager.openPR", + worktreeId: data.selected, + url: data.pr.url, + }) + } /> ) })()} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx index 2b07b00269c..c44b767cffd 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx @@ -360,7 +360,12 @@ export const WorktreeItem: Component = (props) => { #{pr().number} - + 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 332fe65426d..5017d37a669 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -907,15 +907,25 @@ html[data-theme="kilo-vscode"] /* PR badge accent colors via classList */ .am-pr-accent-open, -.am-pr-panel-badge.am-pr-accent-open { --pr-accent: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-panel-badge.am-pr-accent-open { + --am-pr-accent: var(--vscode-testing-iconPassed, #34d399); +} .am-pr-accent-pending, -.am-pr-panel-badge.am-pr-accent-pending { --pr-accent: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-panel-badge.am-pr-accent-pending { + --am-pr-accent: var(--vscode-charts-yellow, #fbbf24); +} .am-pr-accent-draft, -.am-pr-panel-badge.am-pr-accent-draft { --pr-accent: var(--text-weaker); } +.am-pr-panel-badge.am-pr-accent-draft { + --am-pr-accent: var(--text-weaker); +} .am-pr-accent-merged, -.am-pr-panel-badge.am-pr-accent-merged { --pr-accent: var(--vscode-charts-purple, var(--vscode-foreground)); } +.am-pr-panel-badge.am-pr-accent-merged { + --am-pr-accent: var(--vscode-charts-purple, var(--vscode-foreground)); +} .am-pr-accent-closed, -.am-pr-panel-badge.am-pr-accent-closed { --pr-accent: var(--vscode-testing-iconFailed, #f87171); } +.am-pr-panel-badge.am-pr-accent-closed { + --am-pr-accent: var(--vscode-testing-iconFailed, #f87171); +} .am-pr-badge { display: inline-flex; @@ -924,8 +934,8 @@ html[data-theme="kilo-vscode"] padding: 2px 4px; border-radius: 6px; border: none; - background: color-mix(in srgb, var(--pr-accent) 12%, transparent); - color: var(--pr-accent); + background: color-mix(in srgb, var(--am-pr-accent) 12%, transparent); + color: var(--am-pr-accent); cursor: pointer; font-size: var(--kilo-font-size-11); font-weight: 500; @@ -934,19 +944,19 @@ html[data-theme="kilo-vscode"] margin-left: auto; } .am-pr-badge:hover { - background: color-mix(in srgb, var(--pr-accent) 25%, transparent); + background: color-mix(in srgb, var(--am-pr-accent) 25%, transparent); } .am-pr-badge [data-component="icon"] { width: 12px; height: 12px; - color: var(--pr-accent); + color: var(--am-pr-accent); } .am-pr-badge-number { line-height: 1; color: var(--text-muted); } .am-pr-badge:hover .am-pr-badge-number { - color: var(--pr-accent); + color: var(--am-pr-accent); } /* Status icon overlaid on the badge — colored by CI/review result, independent of the badge's PR-state accent color. */ @@ -982,10 +992,10 @@ html[data-theme="kilo-vscode"] } .am-worktree-item-active .am-pr-badge { - background: color-mix(in srgb, var(--pr-accent) 18%, transparent); + background: color-mix(in srgb, var(--am-pr-accent) 18%, transparent); } .am-worktree-item-active .am-pr-badge:hover { - background: color-mix(in srgb, var(--pr-accent) 30%, transparent); + background: color-mix(in srgb, var(--am-pr-accent) 30%, transparent); } /* Clickable link icon in hover card to open PR on GitHub */ diff --git a/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx b/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx index 53c9b77443d..421c17c8bf3 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx @@ -21,7 +21,16 @@ interface Deps { } export function createChangeDefaultBaseBranch(deps: Deps) { - const { vscode, dialog, t, defaultBaseBranch, setDefaultBaseBranch, setRepoDetectedBranch, repoDetectedBranch, hasConfiguredBranch } = deps + const { + vscode, + dialog, + t, + defaultBaseBranch, + setDefaultBaseBranch, + setRepoDetectedBranch, + repoDetectedBranch, + hasConfiguredBranch, + } = deps return () => { const [search, setSearch] = createSignal("") diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx index d77213de726..55bdea2165a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRBadge.tsx @@ -26,12 +26,7 @@ export function PRBadge(props: { pr: PRStatus }) { > - + ) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx index 235124dc720..b010d221f71 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx @@ -34,7 +34,9 @@ export function PRChecks(props: { checks: PRStatus["checks"] }) { {check.name} {CHECK[check.status].label} - {check.duration} + + {check.duration} + e.preventDefault()}> diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx index 6745a0522c3..b73c915f02d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRComments.tsx @@ -28,7 +28,8 @@ export function PRComments(props: { comments: NonNullable {comment.author} - {comment.file}{`:${comment.line}`} + {comment.file} + {`:${comment.line}`} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx index b4e558d67b0..971948d8e28 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx @@ -9,9 +9,15 @@ export function PRFileChanges(props: { files: number; additions: number; deletio
File Changes - 0}>{props.files}f - 0}>+{props.additions} - 0}>−{props.deletions} + 0}> + {props.files}f + + 0}> + +{props.additions} + + 0}> + −{props.deletions} +
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PROverview.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PROverview.tsx index 6c1fc863739..577dd4a8e7d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PROverview.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PROverview.tsx @@ -34,7 +34,9 @@ export function PROverview(props: { pr: PRStatus; worktree?: WorktreeState }) {
Status - {STATE_LABEL[props.pr.state]} + + {STATE_LABEL[props.pr.state]} +
{(review) => ( diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index 7e4e804371c..c0241f05120 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -33,7 +33,13 @@ export const PRPanel: Component = (props) => {
- + @@ -46,9 +52,7 @@ export const PRPanel: Component = (props) => { 0}> - - {(body) => } - + {(body) => } 0}> diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx index 5257014a3b0..48d9d2ee2e8 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx @@ -47,9 +47,15 @@ export function PRSummary(props: PRSummaryProps) {
PR Summary - 0}>{props.pr.files}f - 0}>+{props.pr.additions} - 0}>−{props.pr.deletions} + 0}> + {props.pr.files}f + + 0}> + +{props.pr.additions} + + 0}> + −{props.pr.deletions} +
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css index 6f1cc8cc260..e505989acee 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css @@ -4,8 +4,14 @@ overflow: hidden; } -.am-pr-row { display: flex; align-items: center; } -.am-pr-col { display: flex; flex-direction: column; } +.am-pr-row { + display: flex; + align-items: center; +} +.am-pr-col { + display: flex; + flex-direction: column; +} .am-pr-panel-header { align-items: flex-start; @@ -29,9 +35,9 @@ border-radius: 4px; font-size: var(--kilo-font-size-10); font-variant-numeric: tabular-nums; - background: color-mix(in srgb, var(--pr-accent) 15%, transparent); - color: var(--pr-accent); - border: 1px solid color-mix(in srgb, var(--pr-accent) 40%, transparent); + background: color-mix(in srgb, var(--am-pr-accent) 15%, transparent); + color: var(--am-pr-accent); + border: 1px solid color-mix(in srgb, var(--am-pr-accent) 40%, transparent); white-space: nowrap; flex-shrink: 0; } @@ -121,9 +127,15 @@ } /* Check count color by status */ -.am-pr-checks-count-success { color: var(--vscode-testing-iconPassed, #34d399); } -.am-pr-checks-count-failure { color: var(--vscode-testing-iconFailed, #f87171); } -.am-pr-checks-count-pending { color: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-checks-count-success { + color: var(--vscode-testing-iconPassed, #34d399); +} +.am-pr-checks-count-failure { + color: var(--vscode-testing-iconFailed, #f87171); +} +.am-pr-checks-count-pending { + color: var(--vscode-charts-yellow, #fbbf24); +} .am-pr-panel-section-count { font-size: var(--kilo-font-size-11); @@ -161,14 +173,28 @@ text-align: right; } -.am-pr-panel-value[data-pr-state="open"] { color: var(--vscode-testing-iconPassed, #34d399); } -.am-pr-panel-value[data-pr-state="merged"] { color: var(--vscode-charts-purple, #a78bfa); } -.am-pr-panel-value[data-pr-state="closed"] { color: var(--vscode-testing-iconFailed, #f87171); } -.am-pr-panel-value[data-pr-state="draft"] { color: var(--text-weaker); } +.am-pr-panel-value[data-pr-state="open"] { + color: var(--vscode-testing-iconPassed, #34d399); +} +.am-pr-panel-value[data-pr-state="merged"] { + color: var(--vscode-charts-purple, #a78bfa); +} +.am-pr-panel-value[data-pr-state="closed"] { + color: var(--vscode-testing-iconFailed, #f87171); +} +.am-pr-panel-value[data-pr-state="draft"] { + color: var(--text-weaker); +} -.am-pr-panel-value[data-checks="failure"] { color: var(--vscode-testing-iconFailed, #f87171); } -.am-pr-panel-value[data-checks="pending"] { color: var(--vscode-charts-yellow, #fbbf24); } -.am-pr-panel-value[data-checks="success"] { color: var(--vscode-testing-iconPassed, #34d399); } +.am-pr-panel-value[data-checks="failure"] { + color: var(--vscode-testing-iconFailed, #f87171); +} +.am-pr-panel-value[data-checks="pending"] { + color: var(--vscode-charts-yellow, #fbbf24); +} +.am-pr-panel-value[data-checks="success"] { + color: var(--vscode-testing-iconPassed, #34d399); +} .am-pr-panel-diff { gap: 6px; @@ -257,9 +283,15 @@ margin-bottom: 0.25em; } -.am-pr-panel-description [data-component="markdown"] h1 { font-size: var(--kilo-font-size-16); } -.am-pr-panel-description [data-component="markdown"] h2 { font-size: var(--kilo-font-size-14); } -.am-pr-panel-description [data-component="markdown"] h3 { font-size: var(--kilo-font-size-13); } +.am-pr-panel-description [data-component="markdown"] h1 { + font-size: var(--kilo-font-size-16); +} +.am-pr-panel-description [data-component="markdown"] h2 { + font-size: var(--kilo-font-size-14); +} +.am-pr-panel-description [data-component="markdown"] h3 { + font-size: var(--kilo-font-size-13); +} /* PR Reviewers */ .am-pr-panel-reviewers { @@ -276,10 +308,18 @@ flex-shrink: 0; } -.am-pr-panel-reviewer[data-state="approved"] .am-pr-reviewer-icon { color: var(--vscode-testing-iconPassed, #34d399); } -.am-pr-panel-reviewer[data-state="changes_requested"] .am-pr-reviewer-icon { color: var(--vscode-charts-yellow, #fbbf24); } -.am-pr-panel-reviewer[data-state="commented"] .am-pr-reviewer-icon { color: var(--text-weak); } -.am-pr-panel-reviewer[data-state="pending"] .am-pr-reviewer-icon { color: var(--text-weaker); } +.am-pr-panel-reviewer[data-state="approved"] .am-pr-reviewer-icon { + color: var(--vscode-testing-iconPassed, #34d399); +} +.am-pr-panel-reviewer[data-state="changes_requested"] .am-pr-reviewer-icon { + color: var(--vscode-charts-yellow, #fbbf24); +} +.am-pr-panel-reviewer[data-state="commented"] .am-pr-reviewer-icon { + color: var(--text-weak); +} +.am-pr-panel-reviewer[data-state="pending"] .am-pr-reviewer-icon { + color: var(--text-weaker); +} .am-pr-reviewer-login { flex: 1; @@ -306,11 +346,21 @@ flex-shrink: 0; } -.am-pr-panel-check-item[data-status="success"] .am-pr-check-icon { color: var(--vscode-testing-iconPassed, #34d399); } -.am-pr-panel-check-item[data-status="failure"] .am-pr-check-icon { color: var(--vscode-testing-iconFailed, #f87171); } -.am-pr-panel-check-item[data-status="cancelled"] .am-pr-check-icon { color: var(--text-weaker); } -.am-pr-panel-check-item[data-status="skipped"] .am-pr-check-icon { color: var(--text-weaker); } -.am-pr-panel-check-item[data-status="pending"] .am-pr-check-icon { color: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-panel-check-item[data-status="success"] .am-pr-check-icon { + color: var(--vscode-testing-iconPassed, #34d399); +} +.am-pr-panel-check-item[data-status="failure"] .am-pr-check-icon { + color: var(--vscode-testing-iconFailed, #f87171); +} +.am-pr-panel-check-item[data-status="cancelled"] .am-pr-check-icon { + color: var(--text-weaker); +} +.am-pr-panel-check-item[data-status="skipped"] .am-pr-check-icon { + color: var(--text-weaker); +} +.am-pr-panel-check-item[data-status="pending"] .am-pr-check-icon { + color: var(--vscode-charts-yellow, #fbbf24); +} .am-pr-check-name { flex: 1; @@ -389,10 +439,18 @@ flex-shrink: 0; } -.am-pr-summary-row[data-status="success"] .am-pr-summary-icon { color: var(--vscode-testing-iconPassed, #34d399); } -.am-pr-summary-row[data-status="failure"] .am-pr-summary-icon { color: var(--vscode-testing-iconFailed, #f87171); } -.am-pr-summary-row[data-status="pending"] .am-pr-summary-icon { color: var(--vscode-charts-yellow, #fbbf24); } -.am-pr-summary-row[data-status="warning"] .am-pr-summary-icon { color: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-summary-row[data-status="success"] .am-pr-summary-icon { + color: var(--vscode-testing-iconPassed, #34d399); +} +.am-pr-summary-row[data-status="failure"] .am-pr-summary-icon { + color: var(--vscode-testing-iconFailed, #f87171); +} +.am-pr-summary-row[data-status="pending"] .am-pr-summary-icon { + color: var(--vscode-charts-yellow, #fbbf24); +} +.am-pr-summary-row[data-status="warning"] .am-pr-summary-icon { + color: var(--vscode-charts-yellow, #fbbf24); +} .am-pr-summary-label { color: var(--vscode-foreground); diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index d0402d48e82..80435d4cc70 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -1453,7 +1453,10 @@ export const SessionProvider: ParentComponent = (props) => { // Reconcile fast-path: if the tail matches local state shape-wise, every // message+part-count already agrees with the server. Skip the reactive // store churn entirely — virtualizer and rendering stay untouched. - if (mode === "reconcile" && sameReconcileShape(store.messages[sessionID] ?? [], messages, (id) => store.parts[id])) { + if ( + mode === "reconcile" && + sameReconcileShape(store.messages[sessionID] ?? [], messages, (id) => store.parts[id]) + ) { const parts = messageParts(messages) for (const msg of messages) { if (store.parts[msg.id]) delete parts[msg.id] diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts index f76fb185c7e..4ccb83c0745 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts @@ -48,8 +48,23 @@ export interface SectionState { // PR status types — sub-types live in agent-manager/pr/pr-types.ts // --------------------------------------------------------------------------- -import type { PRState, ReviewDecision, AggregateCheckStatus, PRCheck, PRComment, PRReviewer } from "../../../agent-manager/pr/pr-types" -export type { PRState, ReviewDecision, CheckStatus, AggregateCheckStatus, PRCheck, PRComment, PRReviewer } from "../../../agent-manager/pr/pr-types" +import type { + PRState, + ReviewDecision, + AggregateCheckStatus, + PRCheck, + PRComment, + PRReviewer, +} from "../../../agent-manager/pr/pr-types" +export type { + PRState, + ReviewDecision, + CheckStatus, + AggregateCheckStatus, + PRCheck, + PRComment, + PRReviewer, +} from "../../../agent-manager/pr/pr-types" export interface PRStatus { number: number From db1331511a7045998aa025deeae6a77ece10157c Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 13:01:44 -0700 Subject: [PATCH 28/42] feat(pr-view): i18n for agent manager pr messaging --- .../webview-ui/agent-manager/i18n/ar.ts | 11 ++++++++--- .../webview-ui/agent-manager/i18n/br.ts | 11 ++++++++--- .../webview-ui/agent-manager/i18n/bs.ts | 13 +++++++++---- .../webview-ui/agent-manager/i18n/da.ts | 15 ++++++++++----- .../webview-ui/agent-manager/i18n/de.ts | 14 ++++++++++---- .../webview-ui/agent-manager/i18n/es.ts | 14 ++++++++++---- .../webview-ui/agent-manager/i18n/fa.ts | 11 ++++++++--- .../webview-ui/agent-manager/i18n/fr.ts | 14 ++++++++++---- .../webview-ui/agent-manager/i18n/it.ts | 8 +++++++- .../webview-ui/agent-manager/i18n/ja.ts | 15 +++++++++++---- .../webview-ui/agent-manager/i18n/ko.ts | 13 +++++++++---- .../webview-ui/agent-manager/i18n/nl.ts | 9 +++++++-- .../webview-ui/agent-manager/i18n/no.ts | 15 ++++++++++----- .../webview-ui/agent-manager/i18n/pl.ts | 11 ++++++++--- .../webview-ui/agent-manager/i18n/ru.ts | 11 ++++++++--- .../webview-ui/agent-manager/i18n/th.ts | 15 ++++++++++----- .../webview-ui/agent-manager/i18n/tr.ts | 8 +++++++- .../webview-ui/agent-manager/i18n/uk.ts | 7 ++++++- .../webview-ui/agent-manager/i18n/zh.ts | 13 +++++++++---- .../webview-ui/agent-manager/i18n/zht.ts | 13 +++++++++---- 20 files changed, 174 insertions(+), 67 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts index 726e132d709..ae7aeceab7f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Worktree جديد", "agentManager.worktree.setupScript": "سكربت إعداد Worktree", "agentManager.worktree.delete": "حذف Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "حذف؟", "agentManager.worktree.stale": "قديم", "agentManager.worktree.staleTooltip": "مفقود على القرص أو لم يعد git worktree يتتبعه", "agentManager.worktree.removeStale": "إزالة Worktree القديم", @@ -104,8 +104,8 @@ export const dict = { "agentManager.run.options": "خيارات التشغيل", "agentManager.run.configure": "تكوين سكربت التشغيل", "agentManager.shortcuts.openAgentManager": "فتح Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "وضع الوكيل التالي", + "agentManager.shortcuts.cyclePreviousAgentMode": "وضع الوكيل السابق", "agentManager.shortcuts.showShortcuts": "إظهار اختصارات لوحة المفاتيح", "agentManager.dialog.removeStaleWorktree.title": "إزالة Worktree القديم", "agentManager.dialog.removeStaleWorktree.messagePre": "إزالة Worktree القديم ", @@ -211,4 +211,9 @@ export const dict = { "agentManager.import.noBranchesFound": "لم يتم العثور على فروع.", "agentManager.import.noBranchesHint": "الصق رابط PR أعلاه أو أنشئ Worktree جديدًا.", "agentManager.import.failed": "فشل الاستيراد", + + "agentManager.pr.error.gh_auth.title": "مصادقة GitHub مطلوبة", + "agentManager.pr.error.gh_auth.description": "شغّل 'gh auth login' في الطرفية لاستعادة حالة PR.", + "agentManager.pr.error.gh_missing.title": "لم يتم تثبيت GitHub CLI", + "agentManager.pr.error.gh_missing.description": "قم بتثبيت GitHub CLI لتفعيل حالة PR.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts index 8ff91cfdff6..843a6c86e4b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Novo Worktree", "agentManager.worktree.setupScript": "Script de configuração do Worktree", "agentManager.worktree.delete": "Excluir Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "Excluir?", "agentManager.worktree.stale": "Obsoleto", "agentManager.worktree.staleTooltip": "Ausente no disco ou não é mais rastreado pelo git worktree", "agentManager.worktree.removeStale": "Remover Worktree obsoleto", @@ -106,8 +106,8 @@ export const dict = { "agentManager.run.options": "Opções de execução", "agentManager.run.configure": "Configurar script de execução", "agentManager.shortcuts.openAgentManager": "Abrir Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Próximo modo de agente", + "agentManager.shortcuts.cyclePreviousAgentMode": "Modo de agente anterior", "agentManager.shortcuts.showShortcuts": "Mostrar atalhos de teclado", "agentManager.dialog.removeStaleWorktree.title": "Remover Worktree obsoleto", "agentManager.dialog.removeStaleWorktree.messagePre": "Remover Worktree obsoleto ", @@ -215,4 +215,9 @@ export const dict = { "agentManager.import.noBranchesFound": "Nenhum branch encontrado.", "agentManager.import.noBranchesHint": "Cole uma URL de PR acima ou crie um novo Worktree.", "agentManager.import.failed": "Falha na importação", + + "agentManager.pr.error.gh_auth.title": "Autenticação do GitHub necessária", + "agentManager.pr.error.gh_auth.description": "Execute 'gh auth login' no terminal para restaurar o status do PR.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI não instalado", + "agentManager.pr.error.gh_missing.description": "Instale o GitHub CLI para habilitar o status do PR.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts index 10cd591c258..7ff68e5a77b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Novi Worktree", "agentManager.worktree.setupScript": "Skripta za postavljanje Worktree-a", "agentManager.worktree.delete": "Obriši Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "Izbrisati?", "agentManager.worktree.stale": "Zastario", "agentManager.worktree.staleTooltip": "Nedostaje na disku ili ga git worktree više ne prati", "agentManager.worktree.removeStale": "Ukloni zastarjeli Worktree", @@ -106,8 +106,8 @@ export const dict = { "agentManager.run.options": "Opcije pokretanja", "agentManager.run.configure": "Konfiguriši skriptu za pokretanje", "agentManager.shortcuts.openAgentManager": "Otvori Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Sljedeći način rada agenta", + "agentManager.shortcuts.cyclePreviousAgentMode": "Prethodni način rada agenta", "agentManager.shortcuts.showShortcuts": "Prikaži prečice na tastaturi", "agentManager.dialog.removeStaleWorktree.title": "Ukloni zastarjeli Worktree", "agentManager.dialog.removeStaleWorktree.messagePre": "Ukloni zastarjeli Worktree ", @@ -203,7 +203,7 @@ export const dict = { "agentManager.review.imageUnavailable": "Pregled slike nije dostupan za ovaj snimak sesije.", "agentManager.review.endOfLongDiff": "Došli ste do kraja!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "Zahtjev za povlačenje", "agentManager.import.pastePrUrl": "Zalijepite PR URL...", "agentManager.import.open": "Otvori", "agentManager.import.branches": "Branchevi", @@ -214,4 +214,9 @@ export const dict = { "agentManager.import.noBranchesFound": "Nisu pronađeni branchevi.", "agentManager.import.noBranchesHint": "Zalijepite PR URL iznad ili kreirajte novi Worktree.", "agentManager.import.failed": "Uvoz neuspješan", + + "agentManager.pr.error.gh_auth.title": "Potrebna GitHub autentikacija", + "agentManager.pr.error.gh_auth.description": "Pokrenite 'gh auth login' u terminalu da vratite status PR-a.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI nije instaliran", + "agentManager.pr.error.gh_missing.description": "Instalirajte GitHub CLI da omogućite status PR-a.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts index d715e408088..d89a28452a7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Nyt Worktree", "agentManager.worktree.setupScript": "Worktree-opsætningsscript", "agentManager.worktree.delete": "Slet Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "Slet?", "agentManager.worktree.stale": "Forældet", "agentManager.worktree.staleTooltip": "Mangler på disken eller spores ikke længere af git worktree", "agentManager.worktree.removeStale": "Fjern forældet Worktree", @@ -107,8 +107,8 @@ export const dict = { "agentManager.run.options": "Kørselsindstillinger", "agentManager.run.configure": "Konfigurer kørselsscript", "agentManager.shortcuts.openAgentManager": "Åbn Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Næste agenttilstand", + "agentManager.shortcuts.cyclePreviousAgentMode": "Forrige agenttilstand", "agentManager.shortcuts.showShortcuts": "Vis tastaturgenveje", "agentManager.dialog.removeStaleWorktree.title": "Fjern forældet Worktree", "agentManager.dialog.removeStaleWorktree.messagePre": "Fjern forældet Worktree ", @@ -205,10 +205,10 @@ export const dict = { "Forhåndsvisning af billedet er ikke tilgængelig for dette snapshot af sessionen.", "agentManager.review.endOfLongDiff": "Du nåede slutningen!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "Pull request", "agentManager.import.pastePrUrl": "Indsæt PR URL...", "agentManager.import.open": "Åbn", - "agentManager.import.branches": "Branches", + "agentManager.import.branches": "Grene", "agentManager.import.selectBranch": "Vælg branch...", "agentManager.import.loading": "Indlæser...", "agentManager.import.loadingBranches": "Indlæser branches...", @@ -216,4 +216,9 @@ export const dict = { "agentManager.import.noBranchesFound": "Ingen branches fundet.", "agentManager.import.noBranchesHint": "Indsæt en PR URL ovenfor eller opret et nyt Worktree.", "agentManager.import.failed": "Import mislykkedes", + + "agentManager.pr.error.gh_auth.title": "GitHub-godkendelse påkrævet", + "agentManager.pr.error.gh_auth.description": "Kør 'gh auth login' i din terminal for at gendanne PR-status.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI er ikke installeret", + "agentManager.pr.error.gh_missing.description": "Installer GitHub CLI for at aktivere PR-status.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts index f60caf8070b..0875d75f794 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Neuer Worktree", "agentManager.worktree.setupScript": "Worktree-Einrichtungsskript", "agentManager.worktree.delete": "Worktree löschen", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "Löschen?", "agentManager.worktree.stale": "Veraltet", "agentManager.worktree.staleTooltip": "Fehlt auf der Festplatte oder wird nicht mehr von git worktree verfolgt", "agentManager.worktree.removeStale": "Veralteten Worktree entfernen", @@ -108,8 +108,8 @@ export const dict = { "agentManager.run.options": "Ausführungsoptionen", "agentManager.run.configure": "Ausführungsskript konfigurieren", "agentManager.shortcuts.openAgentManager": "Agent Manager öffnen", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Nächster Agentenmodus", + "agentManager.shortcuts.cyclePreviousAgentMode": "Vorheriger Agentenmodus", "agentManager.shortcuts.showShortcuts": "Tastenkürzel anzeigen", "agentManager.dialog.removeStaleWorktree.title": "Veralteten Worktree entfernen", "agentManager.dialog.removeStaleWorktree.messagePre": "Veralteten Worktree entfernen ", @@ -208,7 +208,7 @@ export const dict = { "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "PR-URL einfügen...", "agentManager.import.open": "Öffnen", - "agentManager.import.branches": "Branches", + "agentManager.import.branches": "Zweige", "agentManager.import.selectBranch": "Branch auswählen...", "agentManager.import.loading": "Laden...", "agentManager.import.loadingBranches": "Branches werden geladen...", @@ -216,4 +216,10 @@ export const dict = { "agentManager.import.noBranchesFound": "Keine Branches gefunden.", "agentManager.import.noBranchesHint": "Fügen Sie oben eine PR-URL ein oder erstellen Sie einen neuen Worktree.", "agentManager.import.failed": "Import fehlgeschlagen", + + "agentManager.pr.error.gh_auth.title": "GitHub-Authentifizierung erforderlich", + "agentManager.pr.error.gh_auth.description": + "Führen Sie 'gh auth login' in Ihrem Terminal aus, um den PR-Status wiederherzustellen.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI nicht installiert", + "agentManager.pr.error.gh_missing.description": "Installieren Sie die GitHub CLI, um den PR-Status zu aktivieren.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts index f0173a673fd..6b17280918d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Nuevo Worktree", "agentManager.worktree.setupScript": "Script de configuración de Worktree", "agentManager.worktree.delete": "Eliminar Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "¿Eliminar?", "agentManager.worktree.stale": "Obsoleto", "agentManager.worktree.staleTooltip": "No está en disco o ya no está seguido por git worktree", "agentManager.worktree.removeStale": "Eliminar Worktree obsoleto", @@ -107,8 +107,8 @@ export const dict = { "agentManager.run.options": "Opciones de ejecución", "agentManager.run.configure": "Configurar script de ejecución", "agentManager.shortcuts.openAgentManager": "Abrir Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Siguiente modo de agente", + "agentManager.shortcuts.cyclePreviousAgentMode": "Modo de agente anterior", "agentManager.shortcuts.showShortcuts": "Mostrar atajos de teclado", "agentManager.dialog.removeStaleWorktree.title": "Eliminar Worktree obsoleto", "agentManager.dialog.removeStaleWorktree.messagePre": "¿Eliminar Worktree obsoleto ", @@ -208,7 +208,7 @@ export const dict = { "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Pegar URL del PR...", "agentManager.import.open": "Abrir", - "agentManager.import.branches": "Branches", + "agentManager.import.branches": "Ramas", "agentManager.import.selectBranch": "Seleccionar branch...", "agentManager.import.loading": "Cargando...", "agentManager.import.loadingBranches": "Cargando branches...", @@ -216,4 +216,10 @@ export const dict = { "agentManager.import.noBranchesFound": "No se encontraron branches.", "agentManager.import.noBranchesHint": "Pega una URL de PR arriba o crea un nuevo Worktree.", "agentManager.import.failed": "Error en la importación", + + "agentManager.pr.error.gh_auth.title": "Se requiere autenticación de GitHub", + "agentManager.pr.error.gh_auth.description": + "Ejecuta 'gh auth login' en tu terminal para restaurar el estado del PR.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI no está instalado", + "agentManager.pr.error.gh_missing.description": "Instala GitHub CLI para habilitar el estado del PR.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts index b5d50a2b430..01aa40d7c65 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts @@ -3,8 +3,8 @@ export const dict = { "agentManager.sidebar.collapse": "بستن نوار کناری", "agentManager.sidebar.expand": "نمایش نوار کناری", "agentManager.section.worktrees": "WORKTREES", - "agentManager.section.sessions": "SESSIONS", - "agentManager.projects": "PROJECTS", + "agentManager.section.sessions": "جلسات", + "agentManager.projects": "پروژه‌ها", "agentManager.project.add": "افزودن پروژه", "agentManager.project.remove": "حذف از Agent Manager", "agentManager.project.trust": "اعتماد", @@ -207,7 +207,7 @@ export const dict = { "agentManager.review.imageUnavailable": "پیش‌نمایش تصویر برای این نمونه جلسه در دسترس نیست.", "agentManager.review.endOfLongDiff": "به انتها رسیدید!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "درخواست ادغام", "agentManager.import.pastePrUrl": "URL درخواست PR را وارد کنید...", "agentManager.import.open": "باز کردن", "agentManager.import.branches": "شاخه‌ها", @@ -218,4 +218,9 @@ export const dict = { "agentManager.import.noBranchesFound": "هیچ شاخه‌ای یافت نشد.", "agentManager.import.noBranchesHint": "یک URL درخواست ادغام را در بالا جای‌گذاری کنید یا یک worktree جدید بسازید.", "agentManager.import.failed": "وارد کردن ناموفق بود", + + "agentManager.pr.error.gh_auth.title": "احراز هویت GitHub لازم است", + "agentManager.pr.error.gh_auth.description": "برای بازیابی وضعیت PR، دستور 'gh auth login' را در ترمینال اجرا کنید.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI نصب نشده است", + "agentManager.pr.error.gh_missing.description": "برای فعال کردن وضعیت PR، GitHub CLI را نصب کنید.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts index 544b5007a2c..3d8f7279b9f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Nouveau Worktree", "agentManager.worktree.setupScript": "Script de configuration du Worktree", "agentManager.worktree.delete": "Supprimer le Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "Supprimer ?", "agentManager.worktree.stale": "Obsolète", "agentManager.worktree.staleTooltip": "Absent du disque ou n'est plus suivi par git worktree", "agentManager.worktree.removeStale": "Supprimer le Worktree obsolète", @@ -60,7 +60,7 @@ export const dict = { "agentManager.sidebarSearch.label": "Rechercher des Worktrees et des sessions", "agentManager.sidebarSearch.scope": "Recherche dans l'espace de travail local, les sessions locales, les Worktrees et leurs sessions", - "agentManager.sidebarSearch.contexts": "LOCAL & WORKTREES", + "agentManager.sidebarSearch.contexts": "LOCAL ET WORKTREES", "agentManager.terminal.new": "Nouvel onglet de terminal", "agentManager.terminal.ended": "terminal terminé — fermez l'onglet pour ignorer", @@ -107,8 +107,8 @@ export const dict = { "agentManager.run.options": "Options d'exécution", "agentManager.run.configure": "Configurer le script d'exécution", "agentManager.shortcuts.openAgentManager": "Ouvrir Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Mode d'agent suivant", + "agentManager.shortcuts.cyclePreviousAgentMode": "Mode d'agent précédent", "agentManager.shortcuts.showShortcuts": "Afficher les raccourcis clavier", "agentManager.dialog.removeStaleWorktree.title": "Supprimer le Worktree obsolète", "agentManager.dialog.removeStaleWorktree.messagePre": "Supprimer le Worktree obsolète ", @@ -216,4 +216,10 @@ export const dict = { "agentManager.import.noBranchesFound": "Aucune branche trouvée.", "agentManager.import.noBranchesHint": "Collez une URL de PR ci-dessus ou créez un nouveau Worktree.", "agentManager.import.failed": "Échec de l'importation", + + "agentManager.pr.error.gh_auth.title": "Authentification GitHub requise", + "agentManager.pr.error.gh_auth.description": + "Exécutez 'gh auth login' dans votre terminal pour restaurer le statut de la PR.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI non installé", + "agentManager.pr.error.gh_missing.description": "Installez GitHub CLI pour activer le statut de la PR.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts index 59f6ea340c6..684ea283f4f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts @@ -36,7 +36,7 @@ export const dict = { "agentManager.section.moveUp": "Sposta su", "agentManager.section.moveDown": "Sposta giù", - "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.branch": "Ramo", "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Base", "agentManager.hoverCard.sessions": "Sessioni", @@ -222,4 +222,10 @@ export const dict = { "agentManager.import.noBranchesFound": "Nessun branch trovato.", "agentManager.import.noBranchesHint": "Incolla un URL PR sopra o crea un nuovo worktree.", "agentManager.import.failed": "Importazione non riuscita", + + "agentManager.pr.error.gh_auth.title": "Autenticazione GitHub richiesta", + "agentManager.pr.error.gh_auth.description": + "Esegui 'gh auth login' nel terminale per ripristinare lo stato della PR.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI non installato", + "agentManager.pr.error.gh_missing.description": "Installa GitHub CLI per abilitare lo stato della PR.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts index 5e4dd806ef5..c6ef13f8cb0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "新しいWorktree", "agentManager.worktree.setupScript": "Worktreeセットアップスクリプト", "agentManager.worktree.delete": "Worktreeを削除", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "削除しますか?", "agentManager.worktree.stale": "無効", "agentManager.worktree.staleTooltip": "ディスク上に存在しないか、git worktree で追跡されていません", "agentManager.worktree.removeStale": "無効な Worktree を削除", @@ -107,8 +107,8 @@ export const dict = { "agentManager.run.options": "実行オプション", "agentManager.run.configure": "実行スクリプトを設定", "agentManager.shortcuts.openAgentManager": "Agent Managerを開く", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "次のエージェントモード", + "agentManager.shortcuts.cyclePreviousAgentMode": "前のエージェントモード", "agentManager.shortcuts.showShortcuts": "キーボードショートカットを表示", "agentManager.dialog.removeStaleWorktree.title": "無効な Worktree を削除", "agentManager.dialog.removeStaleWorktree.messagePre": "無効な Worktree を削除 ", @@ -204,7 +204,7 @@ export const dict = { "agentManager.review.imageUnavailable": "このセッションスナップショットでは画像プレビューを利用できません。", "agentManager.review.endOfLongDiff": "最後まで到達しました!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "プルリクエスト", "agentManager.import.pastePrUrl": "PR URLを貼り付け...", "agentManager.import.open": "開く", "agentManager.import.branches": "ブランチ", @@ -215,4 +215,11 @@ export const dict = { "agentManager.import.noBranchesFound": "ブランチが見つかりません。", "agentManager.import.noBranchesHint": "上にPR URLを貼り付けるか、新しいWorktreeを作成してください。", "agentManager.import.failed": "インポートに失敗しました", + + "agentManager.pr.error.gh_auth.title": "GitHub認証が必要です", + "agentManager.pr.error.gh_auth.description": + "PRステータスを復元するには、ターミナルで 'gh auth login' を実行してください。", + "agentManager.pr.error.gh_missing.title": "GitHub CLIがインストールされていません", + "agentManager.pr.error.gh_missing.description": + "PRステータスを有効にするには、GitHub CLIをインストールしてください。", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts index b729493b1d3..e8fa9677984 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "새 Worktree", "agentManager.worktree.setupScript": "Worktree 설정 스크립트", "agentManager.worktree.delete": "Worktree 삭제", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "삭제하시겠습니까?", "agentManager.worktree.stale": "오래됨", "agentManager.worktree.staleTooltip": "디스크에 없거나 더 이상 git worktree에서 추적되지 않습니다", "agentManager.worktree.removeStale": "오래된 Worktree 제거", @@ -105,8 +105,8 @@ export const dict = { "agentManager.run.options": "실행 옵션", "agentManager.run.configure": "실행 스크립트 구성", "agentManager.shortcuts.openAgentManager": "Agent Manager 열기", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "다음 에이전트 모드", + "agentManager.shortcuts.cyclePreviousAgentMode": "이전 에이전트 모드", "agentManager.shortcuts.showShortcuts": "키보드 단축키 표시", "agentManager.dialog.removeStaleWorktree.title": "오래된 Worktree 제거", "agentManager.dialog.removeStaleWorktree.messagePre": "오래된 Worktree 제거 ", @@ -202,7 +202,7 @@ export const dict = { "agentManager.review.imageUnavailable": "이 세션 스냅샷에서는 이미지 미리보기를 사용할 수 없습니다.", "agentManager.review.endOfLongDiff": "끝까지 도달했습니다!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "풀 리퀘스트", "agentManager.import.pastePrUrl": "PR URL 붙여넣기...", "agentManager.import.open": "열기", "agentManager.import.branches": "브랜치", @@ -213,4 +213,9 @@ export const dict = { "agentManager.import.noBranchesFound": "브랜치를 찾을 수 없습니다.", "agentManager.import.noBranchesHint": "위에 PR URL을 붙여넣거나 새 Worktree를 만드세요.", "agentManager.import.failed": "가져오기 실패", + + "agentManager.pr.error.gh_auth.title": "GitHub 인증 필요", + "agentManager.pr.error.gh_auth.description": "PR 상태를 복원하려면 터미널에서 'gh auth login'을 실행하세요.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI가 설치되지 않음", + "agentManager.pr.error.gh_missing.description": "PR 상태를 활성화하려면 GitHub CLI를 설치하세요.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts index 3ffc6c7dce5..89171ad1b81 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts @@ -138,7 +138,7 @@ export const dict = { "agentManager.dialog.baseBranch": "Basis branch", "agentManager.dialog.searchBranches": "Zoeken naar branches...", "agentManager.dialog.branchBadge.default": "standaard", - "agentManager.dialog.branchBadge.remote": "remote", + "agentManager.dialog.branchBadge.remote": "extern", "agentManager.dialog.versions": "Versies", "agentManager.dialog.versionHint": "{{count}} worktrees zullen parallel draaien", "agentManager.dialog.compareModels": "Modellen vergelijken", @@ -209,7 +209,7 @@ export const dict = { "agentManager.review.imageUnavailable": "Afbeeldingsvoorbeeld is niet beschikbaar voor deze sessiesnapshot.", "agentManager.review.endOfLongDiff": "Je hebt het einde bereikt!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "Pull request", "agentManager.import.pastePrUrl": "Plak PR URL...", "agentManager.import.open": "Openen", "agentManager.import.branches": "Branches", @@ -220,4 +220,9 @@ export const dict = { "agentManager.import.noBranchesFound": "Geen branches gevonden.", "agentManager.import.noBranchesHint": "Plak hierboven een PR URL of maak een nieuwe worktree.", "agentManager.import.failed": "Importeren mislukt", + + "agentManager.pr.error.gh_auth.title": "GitHub-authenticatie vereist", + "agentManager.pr.error.gh_auth.description": "Voer 'gh auth login' uit in je terminal om de PR-status te herstellen.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI niet geïnstalleerd", + "agentManager.pr.error.gh_missing.description": "Installeer de GitHub CLI om de PR-status in te schakelen.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts index d5dfa02a817..5807661c9d3 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Nytt Worktree", "agentManager.worktree.setupScript": "Worktree-oppsettskript", "agentManager.worktree.delete": "Slett Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "Slette?", "agentManager.worktree.stale": "Utdatert", "agentManager.worktree.staleTooltip": "Mangler på disken eller spores ikke lenger av git worktree", "agentManager.worktree.removeStale": "Fjern utdatert Worktree", @@ -105,8 +105,8 @@ export const dict = { "agentManager.run.options": "Kjøringsalternativer", "agentManager.run.configure": "Konfigurer kjøreskript", "agentManager.shortcuts.openAgentManager": "Åpne Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Neste agentmodus", + "agentManager.shortcuts.cyclePreviousAgentMode": "Forrige agentmodus", "agentManager.shortcuts.showShortcuts": "Vis tastatursnarveier", "agentManager.dialog.removeStaleWorktree.title": "Fjern utdatert Worktree", "agentManager.dialog.removeStaleWorktree.messagePre": "Fjern utdatert Worktree ", @@ -202,10 +202,10 @@ export const dict = { "Forhåndsvisning av bildet er ikke tilgjengelig for dette øyeblikksbildet av økten.", "agentManager.review.endOfLongDiff": "Du nådde slutten!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "Pull request", "agentManager.import.pastePrUrl": "Lim inn PR URL...", "agentManager.import.open": "Åpne", - "agentManager.import.branches": "Branches", + "agentManager.import.branches": "Grener", "agentManager.import.selectBranch": "Velg branch...", "agentManager.import.loading": "Laster...", "agentManager.import.loadingBranches": "Laster branches...", @@ -213,4 +213,9 @@ export const dict = { "agentManager.import.noBranchesFound": "Ingen branches funnet.", "agentManager.import.noBranchesHint": "Lim inn en PR URL ovenfor eller opprett et nytt Worktree.", "agentManager.import.failed": "Import mislyktes", + + "agentManager.pr.error.gh_auth.title": "GitHub-autentisering kreves", + "agentManager.pr.error.gh_auth.description": "Kjør 'gh auth login' i terminalen for å gjenopprette PR-status.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI er ikke installert", + "agentManager.pr.error.gh_missing.description": "Installer GitHub CLI for å aktivere PR-status.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts index 9fb33a5c980..ddd54169991 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Nowy Worktree", "agentManager.worktree.setupScript": "Skrypt konfiguracji Worktree", "agentManager.worktree.delete": "Usuń Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "Usunąć?", "agentManager.worktree.stale": "Nieaktualny", "agentManager.worktree.staleTooltip": "Brak na dysku lub nie jest już śledzony przez git worktree", "agentManager.worktree.removeStale": "Usuń nieaktualny Worktree", @@ -107,8 +107,8 @@ export const dict = { "agentManager.run.options": "Opcje uruchamiania", "agentManager.run.configure": "Konfiguruj skrypt uruchamiania", "agentManager.shortcuts.openAgentManager": "Otwórz Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Następny tryb agenta", + "agentManager.shortcuts.cyclePreviousAgentMode": "Poprzedni tryb agenta", "agentManager.shortcuts.showShortcuts": "Pokaż skróty klawiszowe", "agentManager.dialog.removeStaleWorktree.title": "Usuń nieaktualny Worktree", "agentManager.dialog.removeStaleWorktree.messagePre": "Usunąć nieaktualny Worktree ", @@ -214,4 +214,9 @@ export const dict = { "agentManager.import.noBranchesFound": "Nie znaleziono branchy.", "agentManager.import.noBranchesHint": "Wklej URL PR powyżej lub utwórz nowy Worktree.", "agentManager.import.failed": "Import nie powiódł się", + + "agentManager.pr.error.gh_auth.title": "Wymagana autoryzacja GitHub", + "agentManager.pr.error.gh_auth.description": "Uruchom 'gh auth login' w terminalu, aby przywrócić status PR.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI nie jest zainstalowane", + "agentManager.pr.error.gh_missing.description": "Zainstaluj GitHub CLI, aby włączyć status PR.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts index 74afb2c7aa3..90ae939cbdc 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Новый Worktree", "agentManager.worktree.setupScript": "Скрипт настройки Worktree", "agentManager.worktree.delete": "Удалить Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "Удалить?", "agentManager.worktree.stale": "Устаревший", "agentManager.worktree.staleTooltip": "Отсутствует на диске или больше не отслеживается git worktree", "agentManager.worktree.removeStale": "Удалить устаревший Worktree", @@ -107,8 +107,8 @@ export const dict = { "agentManager.run.options": "Параметры запуска", "agentManager.run.configure": "Настроить скрипт запуска", "agentManager.shortcuts.openAgentManager": "Открыть Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "Следующий режим агента", + "agentManager.shortcuts.cyclePreviousAgentMode": "Предыдущий режим агента", "agentManager.shortcuts.showShortcuts": "Показать сочетания клавиш", "agentManager.dialog.removeStaleWorktree.title": "Удалить устаревший Worktree", "agentManager.dialog.removeStaleWorktree.messagePre": "Удалить устаревший Worktree ", @@ -215,4 +215,9 @@ export const dict = { "agentManager.import.noBranchesFound": "Ветки не найдены.", "agentManager.import.noBranchesHint": "Вставьте URL PR выше или создайте новый Worktree.", "agentManager.import.failed": "Ошибка импорта", + + "agentManager.pr.error.gh_auth.title": "Требуется аутентификация GitHub", + "agentManager.pr.error.gh_auth.description": "Выполните 'gh auth login' в терминале, чтобы восстановить статус PR.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI не установлен", + "agentManager.pr.error.gh_missing.description": "Установите GitHub CLI, чтобы включить статус PR.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts index b65655f053c..0a395b5ff8f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "Worktree ใหม่", "agentManager.worktree.setupScript": "สคริปต์ตั้งค่า Worktree", "agentManager.worktree.delete": "ลบ Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "ลบ?", "agentManager.worktree.stale": "ล้าสมัย", "agentManager.worktree.staleTooltip": "ไม่พบบนดิสก์หรือไม่ได้ถูกติดตามโดย git worktree อีกต่อไป", "agentManager.worktree.removeStale": "ลบ Worktree ที่ล้าสมัย", @@ -102,8 +102,8 @@ export const dict = { "agentManager.run.options": "ตัวเลือกการเรียกใช้", "agentManager.run.configure": "กำหนดค่าสคริปต์การเรียกใช้", "agentManager.shortcuts.openAgentManager": "เปิด Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "โหมดเอเจนต์ถัดไป", + "agentManager.shortcuts.cyclePreviousAgentMode": "โหมดเอเจนต์ก่อนหน้า", "agentManager.shortcuts.showShortcuts": "แสดงปุ่มลัดแป้นพิมพ์", "agentManager.dialog.removeStaleWorktree.title": "ลบ Worktree ที่ล้าสมัย", "agentManager.dialog.removeStaleWorktree.messagePre": "ลบ Worktree ที่ล้าสมัย ", @@ -198,10 +198,10 @@ export const dict = { "agentManager.review.imageUnavailable": "ไม่สามารถแสดงตัวอย่างรูปภาพสำหรับสแนปช็อตของเซสชันนี้ได้", "agentManager.review.endOfLongDiff": "คุณมาถึงท้ายสุดแล้ว!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "คำขอรวมโค้ด", "agentManager.import.pastePrUrl": "วาง URL ของ PR...", "agentManager.import.open": "เปิด", - "agentManager.import.branches": "Branches", + "agentManager.import.branches": "สาขา", "agentManager.import.selectBranch": "เลือก Branch...", "agentManager.import.loading": "กำลังโหลด...", "agentManager.import.loadingBranches": "กำลังโหลด Branches...", @@ -209,4 +209,9 @@ export const dict = { "agentManager.import.noBranchesFound": "ไม่พบ Branch", "agentManager.import.noBranchesHint": "วาง URL ของ PR ด้านบนหรือสร้าง Worktree ใหม่", "agentManager.import.failed": "นำเข้าล้มเหลว", + + "agentManager.pr.error.gh_auth.title": "ต้องยืนยันตัวตน GitHub", + "agentManager.pr.error.gh_auth.description": "รันคำสั่ง 'gh auth login' ในเทอร์มินัลเพื่อกู้คืนสถานะ PR", + "agentManager.pr.error.gh_missing.title": "ไม่ได้ติดตั้ง GitHub CLI", + "agentManager.pr.error.gh_missing.description": "ติดตั้ง GitHub CLI เพื่อเปิดใช้งานสถานะ PR", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts index 43b033d32a9..ed3c98980c7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts @@ -211,7 +211,7 @@ export const dict = { "agentManager.review.imageUnavailable": "Bu oturum anlık görüntüsü için görsel önizlemesi kullanılamıyor.", "agentManager.review.endOfLongDiff": "Sonuna geldiniz!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "Çekme İsteği", "agentManager.import.pastePrUrl": "PR URL'sini yapıştırın...", "agentManager.import.open": "Aç", "agentManager.import.branches": "Dallar", @@ -222,4 +222,10 @@ export const dict = { "agentManager.import.noBranchesFound": "Hiçbir dal bulunamadı.", "agentManager.import.noBranchesHint": "Yukarıya bir PR URL'si yapıştırın veya yeni bir worktree oluşturun.", "agentManager.import.failed": "İçe aktarma başarısız oldu", + + "agentManager.pr.error.gh_auth.title": "GitHub kimlik doğrulaması gerekli", + "agentManager.pr.error.gh_auth.description": + "PR durumunu geri yüklemek için terminalinizde 'gh auth login' komutunu çalıştırın.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI yüklü değil", + "agentManager.pr.error.gh_missing.description": "PR durumunu etkinleştirmek için GitHub CLI'yi yükleyin.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts index 0242498ddf5..a6c8f9dc53e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts @@ -212,7 +212,7 @@ export const dict = { "agentManager.review.imageUnavailable": "Попередній перегляд зображення недоступний для цього знімка сеансу.", "agentManager.review.endOfLongDiff": "Ви дійшли до кінця!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "Пул-реквест", "agentManager.import.pastePrUrl": "Вставте URL PR...", "agentManager.import.open": "Відкрити", "agentManager.import.branches": "Гілки", @@ -223,4 +223,9 @@ export const dict = { "agentManager.import.noBranchesFound": "Гілок не знайдено.", "agentManager.import.noBranchesHint": "Вставте URL PR вище або створіть нове робоче дерево.", "agentManager.import.failed": "Імпортування не вдалося", + + "agentManager.pr.error.gh_auth.title": "Потрібна автентифікація GitHub", + "agentManager.pr.error.gh_auth.description": "Виконайте 'gh auth login' у терміналі, щоб відновити статус PR.", + "agentManager.pr.error.gh_missing.title": "GitHub CLI не встановлено", + "agentManager.pr.error.gh_missing.description": "Встановіть GitHub CLI, щоб увімкнути статус PR.", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts index 8bbcfd45263..8010f67b7d7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "新建 Worktree", "agentManager.worktree.setupScript": "Worktree 设置脚本", "agentManager.worktree.delete": "删除 Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "删除?", "agentManager.worktree.stale": "已失效", "agentManager.worktree.staleTooltip": "在磁盘上缺失,或不再由 git worktree 跟踪", "agentManager.worktree.removeStale": "移除失效 Worktree", @@ -102,8 +102,8 @@ export const dict = { "agentManager.run.options": "运行选项", "agentManager.run.configure": "配置运行脚本", "agentManager.shortcuts.openAgentManager": "打开 Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "下一个代理模式", + "agentManager.shortcuts.cyclePreviousAgentMode": "上一个代理模式", "agentManager.shortcuts.showShortcuts": "显示键盘快捷键", "agentManager.dialog.removeStaleWorktree.title": "移除失效 Worktree", "agentManager.dialog.removeStaleWorktree.messagePre": "移除失效 Worktree ", @@ -196,7 +196,7 @@ export const dict = { "agentManager.review.imageUnavailable": "此会话快照无法提供图片预览。", "agentManager.review.endOfLongDiff": "你已经到末尾了!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "拉取请求", "agentManager.import.pastePrUrl": "粘贴 PR URL...", "agentManager.import.open": "打开", "agentManager.import.branches": "分支", @@ -207,4 +207,9 @@ export const dict = { "agentManager.import.noBranchesFound": "未找到分支。", "agentManager.import.noBranchesHint": "在上方粘贴 PR URL 或创建新的 Worktree。", "agentManager.import.failed": "导入失败", + + "agentManager.pr.error.gh_auth.title": "需要 GitHub 身份验证", + "agentManager.pr.error.gh_auth.description": "在终端中运行 'gh auth login' 以恢复 PR 状态。", + "agentManager.pr.error.gh_missing.title": "未安装 GitHub CLI", + "agentManager.pr.error.gh_missing.description": "安装 GitHub CLI 以启用 PR 状态。", } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts index 0acadafdbfe..ddb93a8804a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts @@ -14,7 +14,7 @@ export const dict = { "agentManager.worktree.new": "新建 Worktree", "agentManager.worktree.setupScript": "Worktree 設定指令碼", "agentManager.worktree.delete": "刪除 Worktree", - "agentManager.worktree.confirmDelete": "Delete?", + "agentManager.worktree.confirmDelete": "刪除?", "agentManager.worktree.stale": "已失效", "agentManager.worktree.staleTooltip": "磁碟上不存在,或不再由 git worktree 追蹤", "agentManager.worktree.removeStale": "移除失效 Worktree", @@ -102,8 +102,8 @@ export const dict = { "agentManager.run.options": "執行選項", "agentManager.run.configure": "設定執行指令碼", "agentManager.shortcuts.openAgentManager": "開啟 Agent Manager", - "agentManager.shortcuts.cycleAgentMode": "Next agent mode", - "agentManager.shortcuts.cyclePreviousAgentMode": "Previous agent mode", + "agentManager.shortcuts.cycleAgentMode": "下一個代理模式", + "agentManager.shortcuts.cyclePreviousAgentMode": "上一個代理模式", "agentManager.shortcuts.showShortcuts": "顯示鍵盤快捷鍵", "agentManager.dialog.removeStaleWorktree.title": "移除失效 Worktree", "agentManager.dialog.removeStaleWorktree.messagePre": "移除失效 Worktree ", @@ -196,7 +196,7 @@ export const dict = { "agentManager.review.imageUnavailable": "此工作階段快照無法預覽圖片。", "agentManager.review.endOfLongDiff": "你已經到最後了!", - "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pullRequest": "提取請求", "agentManager.import.pastePrUrl": "貼上 PR URL...", "agentManager.import.open": "開啟", "agentManager.import.branches": "分支", @@ -207,4 +207,9 @@ export const dict = { "agentManager.import.noBranchesFound": "找不到分支。", "agentManager.import.noBranchesHint": "在上方貼上 PR URL 或建立新的 Worktree。", "agentManager.import.failed": "匯入失敗", + + "agentManager.pr.error.gh_auth.title": "需要 GitHub 驗證", + "agentManager.pr.error.gh_auth.description": "在終端機中執行 'gh auth login' 以還原 PR 狀態。", + "agentManager.pr.error.gh_missing.title": "未安裝 GitHub CLI", + "agentManager.pr.error.gh_missing.description": "安裝 GitHub CLI 以啟用 PR 狀態。", } From baf568564efc1fcff9bf8057fc09e053005df508 Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 13:19:31 -0700 Subject: [PATCH 29/42] fix(pr-view): don't include skipped prs in pr summary and also remove unused files causing checks to fail --- .../src/agent-manager/PRStatusPoller.ts | 2 +- .../src/agent-manager/am-pr-utils.ts | 4 +- .../agent-manager/default-base-branch.tsx | 130 ------------------ .../agent-manager/pr/PRFileChanges.tsx | 26 ---- 4 files changed, 4 insertions(+), 158 deletions(-) delete mode 100644 packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx delete mode 100644 packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index afe6a3e3854..b66c42ba434 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -405,7 +405,7 @@ export class PRStatusPoller { duration: formatCheckDuration(c.startedAt, c.completedAt), })) - const total = checks.length + const total = checks.filter((c) => c.status !== "skipped").length const passed = checks.filter((c) => c.status === "success").length const failed = checks.filter((c) => c.status === "failure").length const pending = checks.filter((c) => c.status === "pending").length diff --git a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts index 9cf563d0aef..5877d4ab021 100644 --- a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts +++ b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts @@ -30,9 +30,11 @@ export function parsePRResult(json: string): PRResult | null { export function checkStatus(state: string): CheckStatus { switch (state.toUpperCase()) { case "SUCCESS": + case "NEUTRAL": return "success" case "FAILURE": case "ERROR": + case "ACTION_REQUIRED": return "failure" case "PENDING": case "QUEUED": @@ -48,7 +50,7 @@ export function checkStatus(state: string): CheckStatus { case "STARTUP_FAILURE": return "cancelled" default: - return "pending" + return "cancelled" } } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx b/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx deleted file mode 100644 index 421c17c8bf3..00000000000 --- a/packages/kilo-vscode/webview-ui/agent-manager/default-base-branch.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/** @jsxImportSource solid-js */ - -import { createSignal, createMemo, onCleanup } from "solid-js" -import type { AgentManagerBranchesMessage, BranchInfo } from "../src/types/messages" -import { Dialog } from "@kilocode/kilo-ui/dialog" -import { useDialog } from "@kilocode/kilo-ui/context/dialog" -import { BranchSelect } from "../src/components/shared/BranchSelect" -import type { useVSCode } from "../src/context/vscode" -import type { useLanguage } from "../src/context/language" -import type { Accessor, Setter } from "solid-js" - -interface Deps { - vscode: ReturnType - dialog: ReturnType - t: ReturnType["t"] - defaultBaseBranch: Accessor - setDefaultBaseBranch: (v: Parameters>[0]) => void - setRepoDetectedBranch: Setter - repoDetectedBranch: Accessor - hasConfiguredBranch: Accessor -} - -export function createChangeDefaultBaseBranch(deps: Deps) { - const { - vscode, - dialog, - t, - defaultBaseBranch, - setDefaultBaseBranch, - setRepoDetectedBranch, - repoDetectedBranch, - hasConfiguredBranch, - } = deps - - return () => { - const [search, setSearch] = createSignal("") - const [branches, setBranches] = createSignal([]) - const [loading, setLoading] = createSignal(true) - const [highlighted, setHighlighted] = createSignal(-1) - - const unsub = vscode.onMessage((msg) => { - if (msg.type === "agentManager.branches") { - const ev = msg as AgentManagerBranchesMessage - setBranches(ev.branches) - if (ev.defaultBranch) setRepoDetectedBranch(ev.defaultBranch) - setLoading(false) - } - }) - - vscode.postMessage({ type: "agentManager.requestBranches" }) - - const filtered = createMemo(() => { - const s = search().toLowerCase() - if (!s) return branches() - return branches().filter((b) => b.name.toLowerCase().includes(s)) - }) - - const selectBranch = (name: string | undefined) => { - vscode.postMessage({ type: "agentManager.setDefaultBaseBranch", branch: name }) - setDefaultBaseBranch(name) - dialog.close() - } - - const handleKeyDown = (e: KeyboardEvent) => { - const items = filtered() - // offset by 1 for auto-detect option (-1 = auto-detect) - const total = items.length + 1 - if (e.key === "ArrowDown") { - e.preventDefault() - e.stopPropagation() - setHighlighted((prev) => Math.min(prev + 1, total - 2)) - } else if (e.key === "ArrowUp") { - e.preventDefault() - e.stopPropagation() - setHighlighted((prev) => Math.max(prev - 1, -1)) - } else if (e.key === "Enter") { - e.preventDefault() - e.stopPropagation() - const idx = highlighted() - if (idx === -1) { - selectBranch(undefined) - } else { - const branch = items[idx] - if (branch) selectBranch(branch.name) - } - } else if (e.key === "Escape") { - e.preventDefault() - e.stopPropagation() - dialog.close() - } - } - - dialog.show(() => { - onCleanup(unsub) - return ( - -
- { - setSearch(v) - setHighlighted(-1) - }} - onSelect={(b) => selectBranch(b.name)} - onSearchKeyDown={handleKeyDown} - selected={defaultBaseBranch()} - highlighted={highlighted()} - onHighlight={setHighlighted} - searchPlaceholder={t("agentManager.dialog.searchBranches")} - emptyLabel={t("agentManager.import.noMatchingBranches")} - loadingLabel={t("agentManager.import.loadingBranches")} - defaultLabel={t("agentManager.dialog.branchBadge.default")} - remoteLabel={t("agentManager.dialog.branchBadge.remote")} - defaultName={defaultBaseBranch()} - autoOption={{ - label: t("agentManager.worktree.defaultBaseBranchAuto"), - hint: repoDetectedBranch(), - active: !hasConfiguredBranch(), - highlighted: highlighted() === -1, - onSelect: () => selectBranch(undefined), - }} - /> -
-
- ) - }) - } -} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx deleted file mode 100644 index 971948d8e28..00000000000 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRFileChanges.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/** @jsxImportSource solid-js */ -import { Show } from "solid-js" - -export function PRFileChanges(props: { files: number; additions: number; deletions: number }) { - return ( - <> -
-
-
- File Changes - - 0}> - {props.files}f - - 0}> - +{props.additions} - - 0}> - −{props.deletions} - - -
-
- - ) -} From c313dc47879218e89515765255316cf75ead61cf Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 13:44:09 -0700 Subject: [PATCH 30/42] fix(pr-view): default pr status state --- packages/kilo-vscode/src/agent-manager/am-pr-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts index 5877d4ab021..5fe6e0fad5a 100644 --- a/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts +++ b/packages/kilo-vscode/src/agent-manager/am-pr-utils.ts @@ -50,7 +50,7 @@ export function checkStatus(state: string): CheckStatus { case "STARTUP_FAILURE": return "cancelled" default: - return "cancelled" + return "pending" } } From 2dc7442ab611c1070a8c8f6d17f956c229f9c84b Mon Sep 17 00:00:00 2001 From: cmanu Date: Fri, 7 Aug 2026 14:05:18 -0700 Subject: [PATCH 31/42] fix(pr-view): add kilocode change --- packages/ui/src/components/icon.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index e2170c0772e..8bd13c7599f 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -18,7 +18,7 @@ const icons = { "chevron-grabber-vertical": ``, "chevron-double-right": ``, "circle-x": ``, - "circle-x-outline": ``, + "circle-x-outline": ``, // kilocode_change close: ``, "close-small": ``, checklist: ``, @@ -91,7 +91,7 @@ const icons = { download: ``, menu: ``, server: ``, - "pull-request": ``, + "pull-request": ``, // kilocode_change branch: ``, edit: ``, history: ``, @@ -108,7 +108,7 @@ const icons = { "star-filled": ``, warning: ``, reset: ``, - refresh: ``, + refresh: ``, // kilocode_change link: ``, providers: ``, layers: ``, From d074e0eb4b7ebea1a6c4473cf2d732b184bbcf37 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 08:11:27 -0700 Subject: [PATCH 32/42] fix(pr-view): Update mergeMessages to main --- .../kilo-vscode/webview-ui/src/context/session-merge.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/context/session-merge.ts b/packages/kilo-vscode/webview-ui/src/context/session-merge.ts index ac9264cf238..2a59bf0d0ad 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session-merge.ts +++ b/packages/kilo-vscode/webview-ui/src/context/session-merge.ts @@ -1,7 +1,9 @@ import type { Message, MessageLoadMode, Part } from "../types/messages" import { sameParts } from "./session-parts" +import { withoutResolvedSessionErrors } from "./session-errors" export function mergeMessages(current: Message[], incoming: Message[], mode: Exclude) { + const kept = withoutResolvedSessionErrors(current, incoming) if (mode === "reconcile") { // Tail reconcile: incoming is the authoritative newest-N snapshot. // Local state may already hold some of those IDs and may also hold @@ -10,12 +12,12 @@ export function mergeMessages(current: Message[], incoming: Message[], mode: Exc // server messages land in the right position and optimistic tail // entries stay at the end. const byId = new Map() - for (const msg of current) byId.set(msg.id, msg) + for (const msg of kept) byId.set(msg.id, msg) for (const msg of incoming) byId.set(msg.id, msg) return [...byId.values()].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) } const seen = new Set() - const source = mode === "prepend" ? [...incoming, ...current] : incoming + const source = mode === "prepend" ? [...incoming, ...kept] : incoming return source.filter((msg) => { if (seen.has(msg.id)) return false seen.add(msg.id) From cc397bb885c78426889e8aa781e3705bad7bc12a Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 09:20:41 -0700 Subject: [PATCH 33/42] feat(pr-view): new icons in kilo-ui for circle x, pull request, and refresh --- packages/kilo-ui/src/components/icon.tsx | 12 ++++++++++++ packages/ui/src/components/icon.tsx | 3 --- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/kilo-ui/src/components/icon.tsx b/packages/kilo-ui/src/components/icon.tsx index 96e8621531e..f58623ade7d 100644 --- a/packages/kilo-ui/src/components/icon.tsx +++ b/packages/kilo-ui/src/components/icon.tsx @@ -2,6 +2,18 @@ import { Icon as Upstream, type IconProps as Props } from "@opencode-ai/ui/icon" import { splitProps } from "solid-js" const icons: Record = { + "circle-x-outline": { + viewBox: "0 0 20 20", + path: ``, + }, + "pull-request": { + viewBox: "0 0 20 20", + path: ``, + }, + refresh: { + viewBox: "0 0 20 20", + path: ``, + }, memory: { viewBox: "0 0 24 24", path: ``, diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 8bd13c7599f..95180b8d65b 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -18,7 +18,6 @@ const icons = { "chevron-grabber-vertical": ``, "chevron-double-right": ``, "circle-x": ``, - "circle-x-outline": ``, // kilocode_change close: ``, "close-small": ``, checklist: ``, @@ -91,7 +90,6 @@ const icons = { download: ``, menu: ``, server: ``, - "pull-request": ``, // kilocode_change branch: ``, edit: ``, history: ``, @@ -108,7 +106,6 @@ const icons = { "star-filled": ``, warning: ``, reset: ``, - refresh: ``, // kilocode_change link: ``, providers: ``, layers: ``, From cf490b9eadad6ca2564e96689c9dcade1ae7e241 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 09:52:28 -0700 Subject: [PATCH 34/42] fix(pr-view): Update hash for pr status poller to include reviewers state change --- packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index b66c42ba434..4b1098777cd 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -272,7 +272,8 @@ export class PRStatusPoller { files: pr.files, } - const hash = `${worktreeId}:${pr.number}:${pr.state}:${pr.review}:${checks.status}:${checks.passed}/${checks.total}:${reviewers.length}:${comments?.total ?? ""}:${comments?.unresolved ?? ""}` + const reviewersSig = reviewers.map((r) => `${r.login}:${r.state}`).join(",") + const hash = `${worktreeId}:${pr.number}:${pr.state}:${pr.review}:${checks.status}:${checks.passed}/${checks.total}:${reviewersSig}:${pr.body?.length ?? 0}:${comments?.total ?? ""}:${comments?.unresolved ?? ""}` if (this.lastHash.get(worktreeId) === hash) return this.lastHash.set(worktreeId, hash) From 95c361e2e25897a602dad804f2cd499106574cec Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 09:54:58 -0700 Subject: [PATCH 35/42] fix(pr-view): clear error on successful status received --- packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts index af496281966..42370a5e721 100644 --- a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts +++ b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts @@ -133,6 +133,7 @@ function bridgePollerOpts(bridge: PRStatusBridge, host: PRBridgeHost) { } const msg = { type: "agentManager.prStatus", worktreeId: id, pr, error: err } as AgentManagerOutMessage bridge["cache"].set(id, msg) + bridge["lastErrorNotified"] = undefined host.postToWebview(msg) host.updateWorktreePR(id, pr?.number, pr?.url, pr?.state) }, From 1b2ab46479f71a3e4375b763f57d8abe7b48126c Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 10:00:48 -0700 Subject: [PATCH 36/42] fix(pr-view): Adjust link out pattern to use vs code post message --- .../webview-ui/agent-manager/pr/PRChecks.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx index b010d221f71..57be374882f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRChecks.tsx @@ -1,9 +1,11 @@ /** @jsxImportSource solid-js */ import { For, Show, createSignal } from "solid-js" import { Icon } from "@kilocode/kilo-ui/icon" +import { Tooltip } from "@kilocode/kilo-ui/tooltip" import type { PRStatus } from "../../src/types/messages" import type { PRCheck, CheckStatus } from "./pr-types" import { SectionHeading } from "./SectionHeading" +import { useVSCode } from "../../src/context/vscode" const CHECK: Record = { success: { icon: "circle-check", label: "Passed" }, @@ -14,6 +16,7 @@ const CHECK: Record = { } export function PRChecks(props: { checks: PRStatus["checks"] }) { + const vscode = useVSCode() const [open, setOpen] = createSignal(true) return ( <> @@ -38,9 +41,15 @@ export function PRChecks(props: { checks: PRStatus["checks"] }) { {check.duration} -
e.preventDefault()}> - - + + +
)} From a1daaadd182c4c784f362efc505dfad54f81865a Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 10:04:50 -0700 Subject: [PATCH 37/42] feat(pr-view): remove excess markedprovider on pr --- packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx | 5 +---- .../kilo-vscode/webview-ui/src/context/provider-shell.tsx | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index c0241f05120..8c3906262e0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -2,7 +2,6 @@ import { Component, Show } from "solid-js" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Tooltip } from "@kilocode/kilo-ui/tooltip" -import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" import type { WorktreeState } from "../../src/types/messages" import type { PRStatus } from "../../src/types/messages" import { PRBadge } from "./PRBadge" @@ -23,8 +22,7 @@ interface PRPanelProps { export const PRPanel: Component = (props) => { return ( - -
+
@@ -61,6 +59,5 @@ export const PRPanel: Component = (props) => {
- ) } diff --git a/packages/kilo-vscode/webview-ui/src/context/provider-shell.tsx b/packages/kilo-vscode/webview-ui/src/context/provider-shell.tsx index 48ecd641c7c..d457f5e4f00 100644 --- a/packages/kilo-vscode/webview-ui/src/context/provider-shell.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/provider-shell.tsx @@ -52,6 +52,8 @@ const Root: ParentComponent = (props) => ( + {/* MarkedProvider is required here for all markdown consumers in the tree, + including PRPanel's PRDescription and PRComments components. Do not remove. */} From 7bf1e546eb0bfe1bb03ba2028dea08ccb8373e98 Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 10:09:53 -0700 Subject: [PATCH 38/42] feat(pr-view): close review tab when pr panel opens --- .../kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 2dad2ea0209..622f0fe96f5 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -567,7 +567,7 @@ const AgentManagerContent: Component = () => { const togglePRPanel = () => { setHistory(false) - setReviewActive(false) + if (reviewActive()) closeReviewTab() setSidePanel((prev) => (prev === SidePanel.PR ? null : SidePanel.PR)) } From 1afd4e7c2a436b61e8dbbab223078d28cead5d2a Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 10:15:10 -0700 Subject: [PATCH 39/42] format(pr-view): prettier --- .../webview-ui/agent-manager/pr/PRPanel.tsx | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index 8c3906262e0..ec37c45940a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -23,41 +23,41 @@ interface PRPanelProps { export const PRPanel: Component = (props) => { return (
-
-
- - {props.pr.title} - #{props.pr.number} -
-
- - - - - - -
+
+
+ + {props.pr.title} + #{props.pr.number}
-
- - - 0}> - - - {(body) => } - 0}> - - - - {(comments) => } - +
+ + + + + +
+
+ + + 0}> + + + {(body) => } + 0}> + + + + {(comments) => } + +
+
) } From b87f4da23943194ed755ac463ff34579e1f39daa Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 10:16:44 -0700 Subject: [PATCH 40/42] feat(pr-view): add title to pr hash --- packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 4b1098777cd..d71b1200e25 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -273,7 +273,7 @@ export class PRStatusPoller { } const reviewersSig = reviewers.map((r) => `${r.login}:${r.state}`).join(",") - const hash = `${worktreeId}:${pr.number}:${pr.state}:${pr.review}:${checks.status}:${checks.passed}/${checks.total}:${reviewersSig}:${pr.body?.length ?? 0}:${comments?.total ?? ""}:${comments?.unresolved ?? ""}` + const hash = `${worktreeId}:${pr.number}:${pr.title}:${pr.state}:${pr.review}:${checks.status}:${checks.passed}/${checks.total}:${reviewersSig}:${pr.body ?? ""}:${comments?.total ?? ""}:${comments?.unresolved ?? ""}` if (this.lastHash.get(worktreeId) === hash) return this.lastHash.set(worktreeId, hash) From e62e92d368747e6b878c8a628eabcb8e5ed8ddea Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 10:19:03 -0700 Subject: [PATCH 41/42] fix(pr-view): fetch the most recent 20 reviews --- packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index d71b1200e25..743c85a4c4b 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -443,7 +443,7 @@ export class PRStatusPoller { reviewRequests(first: 20) { nodes { requestedReviewer { ... on User { login avatarUrl } } } } - reviews(first: 20, states: [APPROVED, CHANGES_REQUESTED, COMMENTED]) { + reviews(last: 20, states: [APPROVED, CHANGES_REQUESTED, COMMENTED]) { nodes { author { login avatarUrl } state } } } From e532519947bb87e1b753212442900c29895076ad Mon Sep 17 00:00:00 2001 From: cmanu Date: Mon, 10 Aug 2026 10:24:14 -0700 Subject: [PATCH 42/42] fix(pr-view): error handling for pr reviewer fetch --- packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 743c85a4c4b..d94a4511fc2 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -469,7 +469,8 @@ export class PRStatusPoller { (pr?.reviewRequests?.nodes ?? []) as GhReviewRequest[], (pr?.reviews?.nodes ?? []) as GhReview[], ) - } catch { + } catch (err) { + this.options.log("Failed to fetch PR reviewers:", err) return [] } }