From 17c2413be72116b47df5fb87985bd6104d5ced76 Mon Sep 17 00:00:00 2001 From: Sylwester Liljegren Date: Sun, 23 Aug 2026 19:26:17 +0200 Subject: [PATCH 1/2] feat(vscode): add Display setting for prompt navigator rail position The prompt navigator rail was hardcoded to the left edge of the transcript. Add a Prompt Navigator Position setting under Settings > Display so it can be moved to the right edge instead, with left kept as the default. The rail, its hover card placement, tick growth direction, and open animation all mirror to the selected side. The new prompt_rail_position key is added to the shared config schema so the backend accepts it, and to the generated SDK contract. --- .changeset/prompt-navigator-position.md | 5 ++++ packages/core/src/v1/config/config.ts | 5 ++++ .../src/components/chat/MessageList.tsx | 8 +++-- .../src/components/chat/PromptRail.tsx | 28 ++++++++++++----- .../src/components/settings/DisplayTab.tsx | 30 +++++++++++++++++-- .../src/components/settings/settings-io.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/ar.ts | 5 ++++ .../kilo-vscode/webview-ui/src/i18n/br.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/bs.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/da.ts | 5 ++++ .../kilo-vscode/webview-ui/src/i18n/de.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/en.ts | 5 ++++ .../kilo-vscode/webview-ui/src/i18n/es.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/fa.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/fr.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/it.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/ja.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/ko.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/nl.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/no.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/pl.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/ru.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/th.ts | 5 ++++ .../kilo-vscode/webview-ui/src/i18n/tr.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/uk.ts | 6 ++++ .../kilo-vscode/webview-ui/src/i18n/zh.ts | 5 ++++ .../kilo-vscode/webview-ui/src/i18n/zht.ts | 5 ++++ .../webview-ui/src/styles/prompt-rail.css | 29 ++++++++++++++++++ .../webview-ui/src/types/messages/config.ts | 2 ++ packages/sdk/js/src/v2/gen/types.gen.ts | 1 + packages/sdk/openapi.json | 4 +++ 31 files changed, 221 insertions(+), 12 deletions(-) create mode 100644 .changeset/prompt-navigator-position.md diff --git a/.changeset/prompt-navigator-position.md b/.changeset/prompt-navigator-position.md new file mode 100644 index 00000000000..6831a32ada5 --- /dev/null +++ b/.changeset/prompt-navigator-position.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Add a Display setting to choose whether the prompt navigator rail sits on the left or right edge of the chat. The rail, its hover card, and its open animation mirror to the selected side, and the choice applies everywhere the transcript is shown, including the sidebar, Kilo editor tabs, the sub-agent viewer, and Agent Manager. Left remains the default. diff --git a/packages/core/src/v1/config/config.ts b/packages/core/src/v1/config/config.ts index 5dd4d7612cc..5bd7671816d 100644 --- a/packages/core/src/v1/config/config.ts +++ b/packages/core/src/v1/config/config.ts @@ -133,6 +133,11 @@ export const Info = Schema.Struct({ description: "Controls whether MCP and generic tool blocks are expanded or collapsed by default in the VS Code chat UI", }), + // kilocode_change start + prompt_rail_position: Schema.optional(Schema.Literals(["left", "right"])).annotate({ + description: "Controls which edge of the chat the prompt navigator rail appears on", + }), + // kilocode_change end hide_prompt_training_models: Schema.optional(Schema.Boolean).annotate({ description: "Hide Kilo Gateway models that may train on your prompts from model listings", }), diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx index 63b8ec7d946..3a7128a00da 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -27,6 +27,7 @@ import { createAutoScroll } from "@kilocode/kilo-ui/hooks" import { useSession } from "../../context/session" import { useServer } from "../../context/server" import { useLanguage } from "../../context/language" +import { useConfig } from "../../context/config" import { useI18n } from "@kilocode/kilo-ui/context/i18n" import { useProvider } from "../../context/provider" import { useWorktreeMode } from "../../context/worktree-mode" @@ -81,7 +82,7 @@ import { parseProviderAuthError, unwrapError, } from "../../utils/errorUtils" -import type { Part, QuestionRequest, SuggestionRequest, ToolState } from "../../types/messages" +import type { Part, PromptRailPosition, QuestionRequest, SuggestionRequest, ToolState } from "../../types/messages" interface MessageListProps { onSelectSession?: (id: string) => void @@ -104,6 +105,7 @@ export const MessageList: Component = (props) => { const session = useSession() const server = useServer() const language = useLanguage() + const config = useConfig() const provider = useProvider() const i18n = useI18n() const data = useData() @@ -1034,7 +1036,7 @@ export const MessageList: Component = (props) => { window.addEventListener("scrollToMessage", onScrollToMessage) onCleanup(() => window.removeEventListener("scrollToMessage", onScrollToMessage)) - // Prompt rail: one tick per user prompt, positioned to the left of the + // Prompt rail: one tick per user prompt, positioned on the edge of the // readable lane, opening a card of prompt/answer previews on hover. const items = createMemo(() => promptItems(rows())) // Until the transcript is measured there is no height to cap against, and @@ -1042,6 +1044,7 @@ export const MessageList: Component = (props) => { const entries = createMemo(() => railEntries(items(), capacity(height()), session.hasOlderMessages())) const [activeTurn, setActiveTurn] = createSignal() const railActiveKey = createMemo(() => items().find((item) => item.turn === activeTurn())?.key) + const railPosition = createMemo(() => config.config().prompt_rail_position ?? "left") const [seek, setSeek] = createSignal<{ sid: string; count: number }>() let paging = false @@ -1371,6 +1374,7 @@ export const MessageList: Component = (props) => { railActiveKey()} diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptRail.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptRail.tsx index 0e487aa850a..5d4d383304c 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptRail.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptRail.tsx @@ -2,7 +2,7 @@ /** * PromptRail component - * Thin vertical summary rail on the left edge of the transcript. Hovering or + * Thin vertical summary rail on the edge of the transcript. Hovering or * focusing opens a bounded navigator for every loaded prompt; clicking jumps * the virtualized transcript without mounting the intervening rows. */ @@ -15,8 +15,10 @@ import { Portal } from "solid-js/web" import { VList, type VListHandle } from "virtua/solid" import { useLanguage } from "../../context/language" import { RAIL_INSET, ROW_HEIGHT, TICK_MIN, TICK_STEP, type PromptRailEntry, type PromptRailItem } from "./prompt-rail" +import type { PromptRailPosition } from "../../types/messages" interface PromptRailProps { + position: Accessor entries: Accessor items: Accessor /** Row key of the item whose turn is currently at the top of the transcript. */ @@ -47,7 +49,7 @@ export function PromptRail(props: PromptRailProps) { const [open, setOpen] = createSignal(false) const [hover, setHover] = createSignal() const [focused, setFocused] = createSignal() - const [anchor, setAnchor] = createSignal<{ top: number; left: number; height: number }>() + const [anchor, setAnchor] = createSignal<{ top: number; left?: number; right?: number; height: number }>() let rail: HTMLElement | undefined let card: HTMLDivElement | undefined let list: VListHandle | undefined @@ -96,9 +98,11 @@ export function PromptRail(props: PromptRailProps) { const min = Math.max(EDGE, rect.top + 4) const max = Math.min(window.innerHeight - EDGE, rect.bottom - 4) - height const center = rect.top + rect.height / 2 - height / 2 + const isRight = props.position() === "right" setAnchor({ top: max < min ? min : Math.min(Math.max(center, min), max), - left: rect.right + GAP, + left: isRight ? undefined : rect.right + GAP, + right: isRight ? window.innerWidth - rect.left + GAP : undefined, height: limit, }) } @@ -252,6 +256,16 @@ export function PromptRail(props: PromptRailProps) { props.onLatest() } + const cardStyle = (position: { top: number; left?: number; right?: number; height: number }) => { + const style: Record = { + top: `${position.top}px`, + "--prompt-rail-card-height": `${position.height}px`, + } + if (position.left !== undefined) style.left = `${position.left}px` + if (position.right !== undefined) style.right = `${position.right}px` + return style + } + const row = (item: PromptRailItem, index: Accessor) => (