mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-30 17:14:40 +08:00
refactor: made this change configurable
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@kilocode/cli": minor
|
||||
"@kilocode/sdk": minor
|
||||
---
|
||||
|
||||
Show terminal title status indicators when sessions are working, need attention, or have finished.
|
||||
Add opt-in Unicode or emoji terminal title indicators for sessions that are working, need attention, or have finished.
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { Button } from "@kilocode/kilo-web-ui/button"
|
||||
import { Card } from "@kilocode/kilo-web-ui/card"
|
||||
import { CustomSelect, type SelectOption } from "../../components/CustomSelect"
|
||||
import { ConfigPage, ConfigTag as Tag } from "./ConfigPage"
|
||||
import { useTuiNotificationSettings } from "./state/ui"
|
||||
import { type TitleIcon, useTuiNotificationSettings } from "./state/ui"
|
||||
|
||||
const icons = [
|
||||
{ value: "none", label: "None" },
|
||||
{ value: "unicode", label: "Unicode" },
|
||||
{ value: "emojis", label: "Emojis" },
|
||||
] satisfies SelectOption<TitleIcon>[]
|
||||
|
||||
function Toggle(props: {
|
||||
label: string
|
||||
@@ -34,57 +41,82 @@ export function CliNotificationsRoute() {
|
||||
return (
|
||||
<ConfigPage
|
||||
title="CLI Notifications"
|
||||
description="Configure TUI attention alerts, desktop notifications, and sound defaults."
|
||||
description="Configure terminal title indicators, TUI attention alerts, desktop notifications, and sound defaults."
|
||||
actions={
|
||||
<Button variant="primary" disabled={Boolean(state.ctx.saving()) || !state.dirty()} onClick={state.save}>
|
||||
Save
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Card class="ui-card" padding={0}>
|
||||
<header class="ui-card-header">
|
||||
<div>
|
||||
<h2>Attention</h2>
|
||||
<p>Control when the TUI asks for attention and how it notifies you.</p>
|
||||
<div class="ui-settings">
|
||||
<Card class="ui-card" padding={0}>
|
||||
<header class="ui-card-header">
|
||||
<div>
|
||||
<h2>Terminal title</h2>
|
||||
<p>Choose how session status appears in the terminal tab title.</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="ui-form">
|
||||
<div class="ui-field">
|
||||
<span>Title Icon</span>
|
||||
<CustomSelect
|
||||
class="title-icon-select"
|
||||
label="Title Icon"
|
||||
value={state.icon()}
|
||||
options={icons}
|
||||
disabled={Boolean(state.ctx.saving())}
|
||||
onSelect={state.setIcon}
|
||||
/>
|
||||
<small>None hides status icons. Unicode and Emojis show working, attention, and finished states.</small>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="ui-form attention-form">
|
||||
<Toggle
|
||||
label="Attention alerts"
|
||||
description="Turn on TUI attention events."
|
||||
checked={state.alert()}
|
||||
disabled={Boolean(state.ctx.saving())}
|
||||
onChange={() => state.setAlert(!state.alert())}
|
||||
/>
|
||||
<Toggle
|
||||
label="Desktop notifications"
|
||||
description="Show desktop notifications when attention alerts fire."
|
||||
checked={state.notify()}
|
||||
disabled={Boolean(state.ctx.saving()) || !state.alert()}
|
||||
onChange={() => state.setNotify(!state.notify())}
|
||||
/>
|
||||
<Toggle
|
||||
label="Sound"
|
||||
description="Play an attention sound when alerts fire."
|
||||
checked={state.sound()}
|
||||
disabled={Boolean(state.ctx.saving()) || !state.alert()}
|
||||
onChange={() => state.setSound(!state.sound())}
|
||||
/>
|
||||
<label class="ui-field">
|
||||
<span>Volume</span>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
value={state.volume()}
|
||||
disabled={!state.alert()}
|
||||
onInput={(event) => state.setVolume(event.currentTarget.value)}
|
||||
</Card>
|
||||
|
||||
<Card class="ui-card" padding={0}>
|
||||
<header class="ui-card-header">
|
||||
<div>
|
||||
<h2>Attention</h2>
|
||||
<p>Control when the TUI asks for attention and how it notifies you.</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="ui-form attention-form">
|
||||
<Toggle
|
||||
label="Attention alerts"
|
||||
description="Turn on TUI attention events."
|
||||
checked={state.alert()}
|
||||
disabled={Boolean(state.ctx.saving())}
|
||||
onChange={() => state.setAlert(!state.alert())}
|
||||
/>
|
||||
<small>Use a value from 0 to 1. The docs example uses 0.4.</small>
|
||||
</label>
|
||||
</div>
|
||||
</Card>
|
||||
<Toggle
|
||||
label="Desktop notifications"
|
||||
description="Show desktop notifications when attention alerts fire."
|
||||
checked={state.notify()}
|
||||
disabled={Boolean(state.ctx.saving()) || !state.alert()}
|
||||
onChange={() => state.setNotify(!state.notify())}
|
||||
/>
|
||||
<Toggle
|
||||
label="Sound"
|
||||
description="Play an attention sound when alerts fire."
|
||||
checked={state.sound()}
|
||||
disabled={Boolean(state.ctx.saving()) || !state.alert()}
|
||||
onChange={() => state.setSound(!state.sound())}
|
||||
/>
|
||||
<label class="ui-field">
|
||||
<span>Volume</span>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
value={state.volume()}
|
||||
disabled={!state.alert()}
|
||||
onInput={(event) => state.setVolume(event.currentTarget.value)}
|
||||
/>
|
||||
<small>Use a value from 0 to 1. The docs example uses 0.4.</small>
|
||||
</label>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</ConfigPage>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export type Theme = {
|
||||
}
|
||||
|
||||
type Diff = "auto" | "stacked"
|
||||
export type TitleIcon = NonNullable<TuiPatch["title_icon"]>
|
||||
|
||||
const fallback = ["#0c0a09", "#fafaf9", "#f9f76f", "#a6a09b", "#3794ff", "#44403b"]
|
||||
|
||||
@@ -204,15 +205,18 @@ export function useTuiNotificationSettings() {
|
||||
const [notify, setNotify] = createSignal(true)
|
||||
const [sound, setSound] = createSignal(true)
|
||||
const [volume, setVolume] = createSignal("0.4")
|
||||
const [icon, setIcon] = createSignal<TitleIcon>("none")
|
||||
const [dirty, setDirty] = createSignal(false)
|
||||
|
||||
createEffect(() => {
|
||||
if (dirty()) return
|
||||
const cfg = ctx.data()?.tui.attention
|
||||
const tui = ctx.data()?.tui
|
||||
const cfg = tui?.attention
|
||||
setAlert(bool(cfg?.enabled, false))
|
||||
setNotify(bool(cfg?.notifications, true))
|
||||
setSound(bool(cfg?.sound, true))
|
||||
setVolume(String(cfg?.volume ?? 0.4))
|
||||
setIcon(tui?.title_icon ?? "none")
|
||||
})
|
||||
|
||||
function change(run: () => void) {
|
||||
@@ -228,6 +232,7 @@ export function useTuiNotificationSettings() {
|
||||
}
|
||||
|
||||
ctx.tui({
|
||||
title_icon: icon(),
|
||||
attention: {
|
||||
enabled: alert(),
|
||||
notifications: notify(),
|
||||
@@ -248,6 +253,8 @@ export function useTuiNotificationSettings() {
|
||||
setSound: (value: boolean) => change(() => setSound(value)),
|
||||
volume,
|
||||
setVolume: (value: string) => change(() => setVolume(value)),
|
||||
icon,
|
||||
setIcon: (value: TitleIcon) => change(() => setIcon(value)),
|
||||
dirty,
|
||||
save,
|
||||
}
|
||||
|
||||
@@ -8,17 +8,20 @@
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
.kilo-console .ui-card:has(.console-diff-select[open]) {
|
||||
.kilo-console .ui-card:has(.console-diff-select[open]),
|
||||
.kilo-console .ui-card:has(.title-icon-select[open]) {
|
||||
position: relative;
|
||||
z-index: 40;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.kilo-console .console-diff-select[open] {
|
||||
.kilo-console .console-diff-select[open],
|
||||
.kilo-console .title-icon-select[open] {
|
||||
z-index: 41;
|
||||
}
|
||||
|
||||
.kilo-console .console-diff-select .models-select-menu {
|
||||
.kilo-console .console-diff-select .models-select-menu,
|
||||
.kilo-console .title-icon-select .models-select-menu {
|
||||
z-index: 42;
|
||||
}
|
||||
|
||||
|
||||
@@ -371,6 +371,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
base: titleDefault,
|
||||
sync,
|
||||
done: untrack(done),
|
||||
icon: tuiConfig.title_icon,
|
||||
})
|
||||
if (kiloTitle) {
|
||||
const id = kiloTitle.id
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Schema } from "effect"
|
||||
import { isRecord } from "@/util/record"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { TuiAttentionSoundNames, type TuiAttentionSoundName } from "@kilocode/plugin/tui"
|
||||
import { KiloTitleIcon } from "@/kilocode/cli/cmd/tui/title-icon" // kilocode_change
|
||||
|
||||
export type TuiAttentionSoundPaths = Partial<Record<TuiAttentionSoundName, string>>
|
||||
|
||||
@@ -69,6 +70,7 @@ export const TuiInfo = Schema.Struct({
|
||||
plugin_enabled: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
|
||||
leader_timeout: Schema.optional(KeymapLeaderTimeout),
|
||||
attention: Schema.optional(Attention),
|
||||
title_icon: Schema.optional(KiloTitleIcon.Value), // kilocode_change
|
||||
scroll_speed: Schema.optional(ScrollSpeed).annotate({
|
||||
description: "TUI scroll speed",
|
||||
}),
|
||||
|
||||
@@ -25,6 +25,7 @@ import { initializeTUIDependencies } from "@kilocode/kilo-gateway/tui"
|
||||
import { DialogProcessList } from "@/kilocode/cli/cmd/tui/component/dialog-process-list"
|
||||
import { useIndexingWarnings } from "@/kilocode/cli/cmd/tui/indexing-warning"
|
||||
import { KiloTerminalTitle } from "./terminal-title"
|
||||
import type { KiloTitleIcon } from "./title-icon"
|
||||
import { Session as SessionApi } from "@/session/session"
|
||||
|
||||
// Re-export so upstream can render the route without importing directly
|
||||
@@ -122,10 +123,11 @@ export function getTerminalTitle(input: {
|
||||
base: string
|
||||
sync: ReturnType<typeof useSync>
|
||||
done: Record<string, true>
|
||||
icon?: KiloTitleIcon.Value
|
||||
}): KiloTerminalTitle.Result | undefined {
|
||||
if (input.route.data.type === "home") {
|
||||
return {
|
||||
title: KiloTerminalTitle.format({ base: input.base, indicator: "none" }),
|
||||
title: KiloTerminalTitle.format({ base: input.base, indicator: "none", icon: input.icon }),
|
||||
active: false,
|
||||
indicator: "none",
|
||||
}
|
||||
@@ -137,18 +139,24 @@ export function getTerminalTitle(input: {
|
||||
id: input.route.data.sessionID,
|
||||
data: input.sync.data,
|
||||
done: input.done,
|
||||
icon: input.icon,
|
||||
})
|
||||
const session = input.sync.session.get(input.route.data.sessionID)
|
||||
const title = !session || SessionApi.isDefaultTitle(session.title) ? undefined : session.title
|
||||
return {
|
||||
...state,
|
||||
title: KiloTerminalTitle.format({ base: input.base, title, indicator: state.indicator }),
|
||||
title: KiloTerminalTitle.format({ base: input.base, title, indicator: state.indicator, icon: input.icon }),
|
||||
}
|
||||
}
|
||||
|
||||
if (input.route.data.type === "plugin") {
|
||||
return {
|
||||
title: KiloTerminalTitle.format({ base: input.base, title: input.route.data.id, indicator: "none" }),
|
||||
title: KiloTerminalTitle.format({
|
||||
base: input.base,
|
||||
title: input.route.data.id,
|
||||
indicator: "none",
|
||||
icon: input.icon,
|
||||
}),
|
||||
active: false,
|
||||
indicator: "none",
|
||||
}
|
||||
@@ -156,7 +164,7 @@ export function getTerminalTitle(input: {
|
||||
|
||||
if (input.route.data.type === "kiloclaw") {
|
||||
return {
|
||||
title: KiloTerminalTitle.format({ base: input.base, title: "KiloClaw", indicator: "none" }),
|
||||
title: KiloTerminalTitle.format({ base: input.base, title: "KiloClaw", indicator: "none", icon: input.icon }),
|
||||
active: false,
|
||||
indicator: "none",
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { TuiKeybind } from "@/cli/cmd/tui/config/keybind"
|
||||
import { KeymapLeaderTimeoutDefault } from "@/cli/cmd/tui/config/tui-schema"
|
||||
import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { KiloTitleIcon } from "@/kilocode/cli/cmd/tui/title-icon"
|
||||
|
||||
export type SetTuiConfig = (next: TuiConfig.Info) => void
|
||||
|
||||
@@ -25,6 +26,7 @@ export namespace KiloTuiConfig {
|
||||
const keybinds = TuiKeybind.parse(next.keybinds ?? {})
|
||||
const config: TuiConfig.Resolved = {
|
||||
...next,
|
||||
title_icon: next.title_icon ?? KiloTitleIcon.Default,
|
||||
attention: {
|
||||
enabled: next.attention?.enabled ?? false,
|
||||
notifications: next.attention?.notifications ?? true,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { KiloTitleIcon } from "./title-icon"
|
||||
|
||||
type Session = {
|
||||
id: string
|
||||
title: string
|
||||
@@ -44,28 +46,48 @@ export namespace KiloTerminalTitle {
|
||||
indicator: Indicator
|
||||
}
|
||||
|
||||
const icon = {
|
||||
none: "",
|
||||
working: "◔",
|
||||
attention: "⚠",
|
||||
finished: "✓",
|
||||
} satisfies Record<Indicator, string>
|
||||
const icons = {
|
||||
none: {
|
||||
none: "",
|
||||
working: "",
|
||||
attention: "",
|
||||
finished: "",
|
||||
},
|
||||
unicode: {
|
||||
none: "",
|
||||
working: "◔",
|
||||
attention: "⚠",
|
||||
finished: "✓",
|
||||
},
|
||||
emojis: {
|
||||
none: "",
|
||||
working: "💭",
|
||||
attention: "🔶",
|
||||
finished: "✅",
|
||||
},
|
||||
} satisfies Record<KiloTitleIcon.Value, Record<Indicator, string>>
|
||||
|
||||
export function format(input: { base: string; title?: string; indicator: Indicator }) {
|
||||
export function format(input: { base: string; title?: string; indicator: Indicator; icon?: KiloTitleIcon.Value }) {
|
||||
const text = input.title ? `${input.base} | ${truncate(input.title)}` : input.base
|
||||
const prefix = icon[input.indicator]
|
||||
const prefix = icons[input.icon ?? KiloTitleIcon.Default][input.indicator]
|
||||
if (!prefix) return text
|
||||
return `${prefix} ${text}`
|
||||
}
|
||||
|
||||
export function session(input: { base: string; id: string; data: Data; done: Record<string, true> }): Result {
|
||||
export function session(input: {
|
||||
base: string
|
||||
id: string
|
||||
data: Data
|
||||
done: Record<string, true>
|
||||
icon?: KiloTitleIcon.Value
|
||||
}): Result {
|
||||
const info = input.data.session.find((item) => item.id === input.id)
|
||||
const id = root(input.data.session, input.id)
|
||||
const ids = family(input.data.session, id)
|
||||
const indicator = state({ data: input.data, ids, done: input.done[id] === true })
|
||||
|
||||
return {
|
||||
title: format({ base: input.base, title: info?.title, indicator }),
|
||||
title: format({ base: input.base, title: info?.title, indicator, icon: input.icon }),
|
||||
id,
|
||||
active: indicator === "working" || indicator === "attention",
|
||||
indicator,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Schema } from "effect"
|
||||
|
||||
export namespace KiloTitleIcon {
|
||||
export const Value = Schema.Literals(["none", "unicode", "emojis"]).annotate({
|
||||
description: "Status icon style shown in terminal titles",
|
||||
})
|
||||
export type Value = Schema.Schema.Type<typeof Value>
|
||||
export const Default = "none" satisfies Value
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigPlugin } from "@/config/plugin"
|
||||
import { KilocodeKeybinds } from "@/kilocode/tui/keybinds"
|
||||
import { KiloTitleIcon } from "@/kilocode/cli/cmd/tui/title-icon"
|
||||
import { Authorization } from "@/server/routes/instance/httpapi/middleware/authorization"
|
||||
import { InstanceContextMiddleware } from "@/server/routes/instance/httpapi/middleware/instance-context"
|
||||
import {
|
||||
@@ -108,6 +109,7 @@ const TuiConfigShape = {
|
||||
keybinds: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
||||
plugin: Schema.optional(Schema.Array(ConfigPlugin.Spec)),
|
||||
plugin_enabled: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
|
||||
title_icon: Schema.optional(KiloTitleIcon.Value),
|
||||
scroll_speed: Schema.optional(Schema.Number),
|
||||
scroll_acceleration: Schema.optional(Schema.Struct({ enabled: Schema.Boolean })),
|
||||
diff_style: Schema.optional(Schema.Literals(["auto", "stacked"])),
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { TuiKeybind } from "@/cli/cmd/tui/config/keybind"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { KiloTuiConfig } from "@/kilocode/cli/cmd/tui/context/tui-config"
|
||||
import { KiloTerminalTitle } from "@/kilocode/cli/cmd/tui/terminal-title"
|
||||
|
||||
function cfg(input: Partial<TuiConfig.Info>): TuiConfig.Info {
|
||||
return input as TuiConfig.Info
|
||||
@@ -18,6 +19,7 @@ function resolve(input: TuiConfig.Info): TuiConfig.Resolved {
|
||||
const keybinds = TuiKeybind.parse(input.keybinds ?? {})
|
||||
return {
|
||||
...input,
|
||||
title_icon: input.title_icon ?? "none",
|
||||
attention: {
|
||||
enabled: input.attention?.enabled ?? false,
|
||||
notifications: input.attention?.notifications ?? true,
|
||||
@@ -40,31 +42,52 @@ describe("KiloTuiConfig.makeStore", () => {
|
||||
|
||||
const exits: Array<string | undefined> = []
|
||||
const themes: Array<string | undefined> = []
|
||||
const icons: Array<string | undefined> = []
|
||||
const titles: string[] = []
|
||||
let dispose!: () => void
|
||||
createRoot((d) => {
|
||||
dispose = d
|
||||
createEffect(() => exits.push(store.config.keybinds.get("app.exit")[0]?.key as string | undefined))
|
||||
createEffect(() => themes.push(store.config.theme))
|
||||
createEffect(() => icons.push(store.config.title_icon))
|
||||
createEffect(() =>
|
||||
titles.push(
|
||||
KiloTerminalTitle.format({ base: "Kilo CLI", indicator: "working", icon: store.config.title_icon }),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
// Initial tracked reads.
|
||||
expect(exits).toEqual(["ctrl+c"])
|
||||
expect(themes).toEqual(["kilo"])
|
||||
expect(icons).toEqual(["none"])
|
||||
expect(titles).toEqual(["Kilo CLI"])
|
||||
|
||||
store.set(cfg({ keybinds: { app_exit: "ctrl+q", leader: "ctrl+x" }, theme: "nord" }))
|
||||
store.set(cfg({ keybinds: { app_exit: "ctrl+q", leader: "ctrl+x" }, theme: "nord", title_icon: "emojis" }))
|
||||
|
||||
// Direct store reads reflect the update synchronously.
|
||||
expect(store.config.keybinds.get("app.exit")[0]?.key).toBe("ctrl+q")
|
||||
expect(store.config.keybinds.get("leader")[0]?.key).toBe("ctrl+x")
|
||||
expect(store.config.theme).toBe("nord")
|
||||
expect(store.config.title_icon).toBe("emojis")
|
||||
|
||||
// Tracked reactive reads re-ran with the new values (the hot-reload contract).
|
||||
expect(exits).toEqual(["ctrl+c", "ctrl+q"])
|
||||
expect(themes).toEqual(["kilo", "nord"])
|
||||
expect(icons).toEqual(["none", "emojis"])
|
||||
expect(titles).toEqual(["Kilo CLI", "💭 Kilo CLI"])
|
||||
|
||||
dispose()
|
||||
})
|
||||
|
||||
test("set() restores the default title icon when the setting is removed", () => {
|
||||
const store = KiloTuiConfig.makeStore(resolve(cfg({ title_icon: "unicode" })))
|
||||
|
||||
store.set(cfg({}))
|
||||
|
||||
expect(store.config.title_icon).toBe("none")
|
||||
})
|
||||
|
||||
test("set() does not re-notify a tracked read when its value is unchanged", () => {
|
||||
const store = KiloTuiConfig.makeStore(resolve(cfg({ keybinds: { app_exit: "ctrl+c" }, theme: "kilo" })))
|
||||
|
||||
|
||||
@@ -74,15 +74,16 @@ describe("TUI config routes", () => {
|
||||
"content-type": "application/json",
|
||||
"x-kilo-directory": tmp.path,
|
||||
},
|
||||
body: JSON.stringify({ theme: "nord" }),
|
||||
body: JSON.stringify({ theme: "nord", title_icon: "emojis" }),
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
const body = (await response.json()) as { theme?: string }
|
||||
const body = (await response.json()) as { theme?: string; title_icon?: string }
|
||||
expect(body.theme).toBe("nord")
|
||||
expect(body.title_icon).toBe("emojis")
|
||||
|
||||
const saved = await Bun.file(path.join(tmp.path, ".kilo", "tui.json")).json()
|
||||
expect(saved).toEqual({ theme: "nord" })
|
||||
expect(saved).toEqual({ theme: "nord", title_icon: "emojis" })
|
||||
})
|
||||
|
||||
test("patches attention config without dropping advanced notification settings", async () => {
|
||||
|
||||
@@ -18,26 +18,26 @@ function data(input: Partial<KiloTerminalTitle.Data> = {}): KiloTerminalTitle.Da
|
||||
}
|
||||
|
||||
describe("KiloTerminalTitle", () => {
|
||||
test("format_noIndicator_returnsBaseTitle", () => {
|
||||
expect(KiloTerminalTitle.format({ base, indicator: "none" })).toBe("Kilo CLI")
|
||||
})
|
||||
|
||||
test("format_workingPrefix_prependsThinkingIcon", () => {
|
||||
test("format_noneStyle_hidesStatusIcon", () => {
|
||||
expect(KiloTerminalTitle.format({ base, title: "Build status", indicator: "working" })).toBe(
|
||||
"◔ Kilo CLI | Build status",
|
||||
"Kilo CLI | Build status",
|
||||
)
|
||||
})
|
||||
|
||||
test("format_attentionPrefix_prependsWarningIcon", () => {
|
||||
expect(KiloTerminalTitle.format({ base, title: "Build status", indicator: "attention" })).toBe(
|
||||
"⚠ Kilo CLI | Build status",
|
||||
)
|
||||
test("format_noIndicator_returnsBaseTitle", () => {
|
||||
expect(KiloTerminalTitle.format({ base, indicator: "none", icon: "unicode" })).toBe("Kilo CLI")
|
||||
})
|
||||
|
||||
test("format_finishedPrefix_prependsCheckIcon", () => {
|
||||
expect(KiloTerminalTitle.format({ base, title: "Build status", indicator: "finished" })).toBe(
|
||||
"✓ Kilo CLI | Build status",
|
||||
)
|
||||
test("format_unicodeStyle_usesUnicodeIcons", () => {
|
||||
expect(KiloTerminalTitle.format({ base, indicator: "working", icon: "unicode" })).toBe("◔ Kilo CLI")
|
||||
expect(KiloTerminalTitle.format({ base, indicator: "attention", icon: "unicode" })).toBe("⚠ Kilo CLI")
|
||||
expect(KiloTerminalTitle.format({ base, indicator: "finished", icon: "unicode" })).toBe("✓ Kilo CLI")
|
||||
})
|
||||
|
||||
test("format_emojiStyle_usesEmojiIcons", () => {
|
||||
expect(KiloTerminalTitle.format({ base, indicator: "working", icon: "emojis" })).toBe("💭 Kilo CLI")
|
||||
expect(KiloTerminalTitle.format({ base, indicator: "attention", icon: "emojis" })).toBe("🔶 Kilo CLI")
|
||||
expect(KiloTerminalTitle.format({ base, indicator: "finished", icon: "emojis" })).toBe("✅ Kilo CLI")
|
||||
})
|
||||
|
||||
test("format_longSessionTitle_truncatesToExistingLimit", () => {
|
||||
@@ -46,6 +46,7 @@ describe("KiloTerminalTitle", () => {
|
||||
base,
|
||||
title: "12345678901234567890123456789012345678901234567890",
|
||||
indicator: "working",
|
||||
icon: "unicode",
|
||||
}),
|
||||
).toBe("◔ Kilo CLI | 1234567890123456789012345678901234567...")
|
||||
})
|
||||
@@ -68,6 +69,7 @@ describe("KiloTerminalTitle", () => {
|
||||
id: "parent",
|
||||
data: data({ session_status: { parent: { type: "busy" } } }),
|
||||
done: {},
|
||||
icon: "unicode",
|
||||
}),
|
||||
).toEqual({ title: "◔ Kilo CLI | Build status", id: "parent", active: true, indicator: "working" })
|
||||
})
|
||||
|
||||
@@ -11,6 +11,12 @@ describe("terminal title done tracking", () => {
|
||||
expect(content).toContain("done: untrack(done)")
|
||||
})
|
||||
|
||||
test("app.tsx reads the reactive title icon setting", () => {
|
||||
const content = fs.readFileSync(APP_FILE, "utf-8")
|
||||
|
||||
expect(content).toContain("icon: tuiConfig.title_icon")
|
||||
})
|
||||
|
||||
test("app.tsx untracks the done guard before setDone", () => {
|
||||
const content = fs.readFileSync(APP_FILE, "utf-8")
|
||||
|
||||
|
||||
@@ -5267,6 +5267,7 @@ export class Config3 extends HeyApiClient {
|
||||
plugin_enabled?: {
|
||||
[key: string]: boolean
|
||||
}
|
||||
title_icon?: "none" | "unicode" | "emojis"
|
||||
scroll_speed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
scroll_acceleration?: {
|
||||
enabled: boolean
|
||||
@@ -5295,6 +5296,7 @@ export class Config3 extends HeyApiClient {
|
||||
{ in: "body", key: "keybinds" },
|
||||
{ in: "body", key: "plugin" },
|
||||
{ in: "body", key: "plugin_enabled" },
|
||||
{ in: "body", key: "title_icon" },
|
||||
{ in: "body", key: "scroll_speed" },
|
||||
{ in: "body", key: "scroll_acceleration" },
|
||||
{ in: "body", key: "diff_style" },
|
||||
|
||||
@@ -2243,6 +2243,10 @@ export type TuiConfigGetResponse = {
|
||||
plugin_enabled?: {
|
||||
[key: string]: boolean
|
||||
}
|
||||
/**
|
||||
* Status icon style shown in terminal titles
|
||||
*/
|
||||
title_icon?: "none" | "unicode" | "emojis"
|
||||
scroll_speed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
scroll_acceleration?: {
|
||||
enabled: boolean
|
||||
@@ -8644,6 +8648,10 @@ export type TuiConfigUpdateData = {
|
||||
plugin_enabled?: {
|
||||
[key: string]: boolean
|
||||
}
|
||||
/**
|
||||
* Status icon style shown in terminal titles
|
||||
*/
|
||||
title_icon?: "none" | "unicode" | "emojis"
|
||||
scroll_speed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
scroll_acceleration?: {
|
||||
enabled: boolean
|
||||
|
||||
@@ -10912,6 +10912,11 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"title_icon": {
|
||||
"type": "string",
|
||||
"enum": ["none", "unicode", "emojis"],
|
||||
"description": "Status icon style shown in terminal titles"
|
||||
},
|
||||
"scroll_speed": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -21217,6 +21222,11 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"title_icon": {
|
||||
"type": "string",
|
||||
"enum": ["none", "unicode", "emojis"],
|
||||
"description": "Status icon style shown in terminal titles"
|
||||
},
|
||||
"scroll_speed": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user