Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/neat-worktree-dialogs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-console": patch
---

Replace native worktree prompts with consistent dialogs for creating, renaming, resetting, and deleting worktrees.
6 changes: 6 additions & 0 deletions .changeset/resize-console-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@kilocode/kilo-console": patch
"@kilocode/cli": patch
---

Configure the project context sidebar width and default diff layout from Global Settings.
5 changes: 5 additions & 0 deletions .changeset/rich-worktree-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-console": patch
---

Review worktree changes in a richer panel with per-file stats and syntax-highlighted diffs.
5 changes: 4 additions & 1 deletion packages/kilo-console/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export type ProjectConsoleQuery = ProjectQuery & {

export type ProjectConsoleSnapshot = {
project: ProjectItem
config: EffectiveConfig
vcs: VcsInfo
worktrees: string[]
terminals: ProjectTerminalItem[]
Expand Down Expand Up @@ -439,7 +440,8 @@ export async function loadProjectConsole(input: ProjectConsoleQuery): Promise<Pr
const project = await resolved(input)
const query = { url: input.url, dir: project.worktree }
const sdk = client(query)
const [vcs, worktrees] = await Promise.all([
const [config, vcs, worktrees] = await Promise.all([
sdk.config.overlay({ scope: "global" }),
sdk.vcs.get({ directory: query.dir }),
sdk.worktree.list({ directory: query.dir }),
])
Expand All @@ -450,6 +452,7 @@ export async function loadProjectConsole(input: ProjectConsoleQuery): Promise<Pr

return {
project,
config: demand("Config", config).effective,
vcs: demand("VCS", vcs),
worktrees: dirs,
terminals: terminals.flat(),
Expand Down
22 changes: 16 additions & 6 deletions packages/kilo-console/src/components/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Show } from "solid-js"
import { createUniqueId, Show } from "solid-js"
import { Button } from "@kilocode/kilo-web-ui/button"
import { Icon } from "@kilocode/kilo-web-ui/icon"

Expand All @@ -14,21 +14,31 @@ type Props = {
}

export function ConfirmDialog(props: Props) {
const id = createUniqueId()
const title = `${id}-title`
const message = `${id}-message`

return (
<Show when={props.open}>
<div class="confirm-scrim">
<section class="confirm-dialog" role="alertdialog" aria-modal="true" aria-labelledby="confirm-title">
<div class="confirm-scrim" onKeyDown={(event) => event.key === "Escape" && props.onCancel()}>
<section
class="confirm-dialog"
role="alertdialog"
aria-modal="true"
aria-labelledby={title}
aria-describedby={props.message ? message : undefined}
>
<div class="confirm-body">
<div class="confirm-icon" aria-hidden="true">
<Icon name="warning" />
</div>
<div>
<h2 id="confirm-title">{props.title}</h2>
<Show when={props.message}>{(text) => <p>{text()}</p>}</Show>
<h2 id={title}>{props.title}</h2>
<Show when={props.message}>{(text) => <p id={message}>{text()}</p>}</Show>
</div>
</div>
<footer class="confirm-actions">
<Button variant="ghost" disabled={props.busy} onClick={props.onCancel}>
<Button variant="ghost" disabled={props.busy} onClick={props.onCancel} autofocus>
{props.cancel ?? "Cancel"}
</Button>
<Button variant="primary" disabled={props.busy} onClick={props.onConfirm}>
Expand Down
73 changes: 73 additions & 0 deletions packages/kilo-console/src/components/PromptDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Button } from "@kilocode/kilo-web-ui/button"
import { Input } from "@kilocode/kilo-web-ui/input"
import { createUniqueId, Show } from "solid-js"

type Props = {
open: boolean
title: string
message?: string
label: string
value: string
placeholder?: string
confirm?: string
cancel?: string
busy?: boolean
onInput: (value: string) => void
onCancel: () => void
onConfirm: () => void
}

export function PromptDialog(props: Props) {
const id = createUniqueId()
const title = `${id}-title`
const message = `${id}-message`
const input = `${id}-input`

return (
<Show when={props.open}>
<div class="confirm-scrim" onKeyDown={(event) => event.key === "Escape" && props.onCancel()}>
<section
class="confirm-dialog prompt-dialog"
role="dialog"
aria-modal="true"
aria-labelledby={title}
aria-describedby={props.message ? message : undefined}
>
<form
class="prompt-form"
onSubmit={(event) => {
event.preventDefault()
props.onConfirm()
}}
>
<div class="prompt-body">
<div class="prompt-copy">
<h2 id={title}>{props.title}</h2>
<Show when={props.message}>{(text) => <p id={message}>{text()}</p>}</Show>
</div>
<label class="prompt-field" for={input}>
<span>{props.label}</span>
<Input
id={input}
value={props.value}
placeholder={props.placeholder}
disabled={props.busy}
autofocus
onInput={(event) => props.onInput(event.currentTarget.value)}
/>
</label>
</div>
<footer class="confirm-actions">
<Button type="button" variant="ghost" disabled={props.busy} onClick={props.onCancel}>
{props.cancel ?? "Cancel"}
</Button>
<Button type="submit" variant="primary" disabled={props.busy}>
{props.confirm ?? "Confirm"}
</Button>
</footer>
</form>
</section>
</div>
</Show>
)
}
7 changes: 0 additions & 7 deletions packages/kilo-console/src/layouts/ConfigLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,6 @@ function ConfigContent(props: { children?: JSX.Element }) {
</Card>
)}
</Show>
<Show when={ctx.saving()}>
{(item) => (
<Card class="banner" variant="info">
{item()}...
</Card>
)}
</Show>
<Show when={ctx.data.error}>
<Card class="banner" variant="error">
<strong>Dashboard request failed</strong>
Expand Down
3 changes: 2 additions & 1 deletion packages/kilo-console/src/routes/config/ConfigSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function ConfigSidebar() {
const href = (path: string) => `${path === "/" ? base() : `${base()}${path}`}${loc.search}`
const current = (path: string) => path === active() || (path !== "/" && active().startsWith(`${path}/`))
const group = (item: ConfigNode): item is ConfigGroup => "items" in item
const navigation = createMemo(() => configNav.filter((item) => !project() || !group(item) || !item.globalOnly))

return (
<aside class="config-sidebar" aria-label="Configuration sections">
Expand All @@ -42,7 +43,7 @@ export function ConfigSidebar() {
</span>
</div>
<nav class="config-options">
<For each={configNav}>
<For each={navigation()}>
{(item) => {
if (!group(item)) {
return (
Expand Down
100 changes: 100 additions & 0 deletions packages/kilo-console/src/routes/config/ConsoleUiRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Show } from "solid-js"
import { Button } from "@kilocode/kilo-web-ui/button"
import { Card } from "@kilocode/kilo-web-ui/card"
import { Spinner } from "@kilocode/kilo-web-ui/spinner"
import { CustomSelect, type SelectOption } from "../../components/CustomSelect"
import { ConfigPage } from "./ConfigPage"
import {
MAX_CONTEXT_SIDEBAR_WIDTH,
MIN_CONTEXT_SIDEBAR_WIDTH,
type ConsoleDiffStyle,
useConsoleUiSettings,
} from "./state/console"

const styles = [
{ value: "unified", label: "Unified" },
{ value: "split", label: "Split" },
] satisfies SelectOption<ConsoleDiffStyle>[]

export function ConsoleUiRoute() {
const state = useConsoleUiSettings()

return (
<ConfigPage
title="Console UI"
description="Configure the local Kilo Console interface. These preferences are saved in your user config."
actions={
<>
<Show when={state.configured()}>
<Button variant="secondary" disabled={Boolean(state.ctx.saving())} onClick={state.reset}>
Use default
</Button>
</Show>
<Button
variant="primary"
disabled={Boolean(state.ctx.saving()) || !state.dirty()}
aria-busy={Boolean(state.ctx.saving())}
onClick={state.save}
>
<Show when={state.ctx.saving()}>
<Spinner />
</Show>
Save
</Button>
</>
}
>
<div class="ui-settings">
<Card class="ui-card" padding={0}>
<header class="ui-card-header">
<div>
<h2>Project context sidebar</h2>
<p>Set the default width used by the Context and Changes panel in project consoles.</p>
</div>
</header>
<div class="ui-form">
<label class="ui-field">
<span>Sidebar width</span>
<input
type="number"
min={MIN_CONTEXT_SIDEBAR_WIDTH}
max={MAX_CONTEXT_SIDEBAR_WIDTH}
step="1"
value={state.width()}
onInput={(event) => state.setWidth(event.currentTarget.value)}
/>
<small>
Width in pixels, between {MIN_CONTEXT_SIDEBAR_WIDTH} and {MAX_CONTEXT_SIDEBAR_WIDTH}. You can also
resize the sidebar by dragging its left edge.
</small>
</label>
</div>
</Card>
<Card class="ui-card" padding={0}>
<header class="ui-card-header">
<div>
<h2>Diff review</h2>
<p>Choose the default layout used when reviewing changed files in project consoles.</p>
</div>
</header>
<div class="ui-form">
<div class="ui-field">
<span>Diff layout</span>
<CustomSelect
class="console-diff-select"
label="Diff layout"
value={state.style()}
options={styles}
disabled={Boolean(state.ctx.saving())}
onSelect={state.setStyle}
/>
<small>
Unified shows changes in one column. Split shows the original and modified file side by side.
</small>
</div>
</div>
</Card>
</div>
</ConfigPage>
)
}
10 changes: 10 additions & 0 deletions packages/kilo-console/src/routes/config/sections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { IconProps } from "@kilocode/kilo-web-ui/icon"
import { AgentBuilderRoute, AgentsRoute } from "./AgentsRoute"
import { CliNotificationsRoute } from "./CliNotificationsRoute"
import { CliUiRoute } from "./CliUiRoute"
import { ConsoleUiRoute } from "./ConsoleUiRoute"
import { FormattersRoute, LspRoute } from "./FormattersRoute"
import { IndexingRoute } from "./IndexingRoute"
import { KeybindsRoute } from "./KeybindsRoute"
Expand All @@ -26,6 +27,7 @@ export type ConfigSection = {
export type ConfigGroup = {
id: string
label: string
globalOnly?: boolean
items: ConfigSection[]
}

Expand Down Expand Up @@ -130,6 +132,14 @@ export const configNav: ConfigNode[] = [
},
],
},
{
id: "console",
label: "Console",
globalOnly: true,
items: [
{ path: "/console/ui", href: "/settings/console/ui", icon: "sliders", label: "UI", component: ConsoleUiRoute },
],
},
{
id: "advanced",
label: "Advanced",
Expand Down
36 changes: 36 additions & 0 deletions packages/kilo-console/src/routes/config/state/console.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, test } from "bun:test"
import {
DEFAULT_CONTEXT_SIDEBAR_WIDTH,
MAX_CONTEXT_SIDEBAR_WIDTH,
MIN_CONTEXT_SIDEBAR_WIDTH,
normalizeConsoleDiffStyle,
normalizeContextSidebarWidth,
parseContextSidebarWidth,
} from "./console"

describe("console UI config state", () => {
test("defaults the diff layout to unified", () => {
expect(normalizeConsoleDiffStyle(undefined)).toBe("unified")
expect(normalizeConsoleDiffStyle("unified")).toBe("unified")
expect(normalizeConsoleDiffStyle("split")).toBe("split")
expect(normalizeConsoleDiffStyle("side-by-side")).toBe("unified")
})

test("normalizes missing and out-of-range widths", () => {
expect(normalizeContextSidebarWidth(undefined)).toBe(DEFAULT_CONTEXT_SIDEBAR_WIDTH)
expect(normalizeContextSidebarWidth(Number.NaN)).toBe(DEFAULT_CONTEXT_SIDEBAR_WIDTH)
expect(normalizeContextSidebarWidth(100)).toBe(MIN_CONTEXT_SIDEBAR_WIDTH)
expect(normalizeContextSidebarWidth(900)).toBe(MAX_CONTEXT_SIDEBAR_WIDTH)
expect(normalizeContextSidebarWidth(411.6)).toBe(412)
})

test("accepts only integer widths within the supported range", () => {
expect(parseContextSidebarWidth("352")).toBe(352)
expect(parseContextSidebarWidth(String(MIN_CONTEXT_SIDEBAR_WIDTH))).toBe(MIN_CONTEXT_SIDEBAR_WIDTH)
expect(parseContextSidebarWidth(String(MAX_CONTEXT_SIDEBAR_WIDTH))).toBe(MAX_CONTEXT_SIDEBAR_WIDTH)
expect(parseContextSidebarWidth("249")).toBeUndefined()
expect(parseContextSidebarWidth("801")).toBeUndefined()
expect(parseContextSidebarWidth("352.5")).toBeUndefined()
expect(parseContextSidebarWidth("wide")).toBeUndefined()
})
})
Loading
Loading