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/worktree-session-history.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": minor
---

Filter `/sessions` history to sessions in the current Agent Manager worktree.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions packages/kilo-vscode/tests/history-accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,29 @@ test.describe("history session accessibility", () => {
await expect(local).toHaveAttribute("aria-selected", "true")
await expect(page.getByRole("tabpanel", { name: "Local" })).toBeVisible()
})

test("filters sessions to the current worktree and includes it in keyboard navigation", async ({ page }) => {
await story(page, "history-sessionlist--worktree-sources")

const local = page.getByRole("tab", { name: "Local" })
const worktree = page.getByRole("tab", { name: "Worktree" })
await local.focus()
await page.keyboard.press("End")
await expect(worktree).toBeFocused()
await page.keyboard.press("Enter")

await expect(worktree).toHaveAttribute("aria-selected", "true")
await expect(page.getByRole("tabpanel", { name: "Worktree" })).toBeVisible()
const rows = page.locator('[data-slot="list-item"]')
await expect(rows.filter({ hasText: "Refactor authentication module" })).toBeVisible()
await expect(rows.filter({ hasText: "Fix TypeScript errors in webview" })).toBeVisible()
await expect(rows.filter({ hasText: "Add screenshot test coverage" })).toHaveCount(0)

await rows.filter({ hasText: "Refactor authentication module" }).click()
await expect(page.locator('[data-slot="selected-session"]')).toHaveText("s1")

await worktree.focus()
await page.keyboard.press("ArrowRight")
await expect(local).toBeFocused()
})
})
11 changes: 11 additions & 0 deletions packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,16 @@ const AgentManagerContent: Component = () => {
return sessionsForWorktree(sel)
})

const activeWorktreeSessionIds = createMemo<ReadonlySet<string> | undefined>(() => {
const sel = selection()
if (!sel || sel === LOCAL) return undefined
return new Set(
managedSessions()
.filter((item) => item.worktreeId === sel)
.map((item) => item.id),
)
})

const activeTabs = createMemo((): SessionInfo[] => {
const sel = selection()
if (sel === LOCAL) return localSessions()
Expand Down Expand Up @@ -2898,6 +2908,7 @@ const AgentManagerContent: Component = () => {
openLocally(id)
}}
onBack={() => setHistory(false)}
worktreeSessionIds={activeWorktreeSessionIds}
/>
</Show>
<Show when={!contextEmpty() && !history()}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* HistoryView component
* Unified panel for local and cloud session history.
* Contains a tab bar ("Local" | "Cloud") and an always-visible "Import session" button.
* Unified panel for local, cloud, and optional worktree session history.
* Contains a source tab bar and an always-visible "Import session" button.
*/

import { Component, createEffect, createSignal, onCleanup } from "solid-js"
import { Component, Show, createEffect, createSignal, onCleanup, type Accessor } from "solid-js"
import { Button } from "@kilocode/kilo-ui/button"
import { useDialog } from "@kilocode/kilo-ui/context/dialog"
import { useLanguage } from "../../context/language"
Expand All @@ -17,21 +17,34 @@ import CloudSessionList from "./CloudSessionList"
interface HistoryViewProps {
onSelectSession: (id: string) => void
onBack?: () => void
worktreeSessionIds?: Accessor<ReadonlySet<string> | undefined>
}

type Source = "local" | "cloud" | "worktree"

const EMPTY_SESSION_IDS = new Set<string>()

const HistoryView: Component<HistoryViewProps> = (props) => {
const language = useLanguage()
const dialog = useDialog()
const session = useSession()
const tabs = useLocalTabs()
const [tab, setTab] = createSignal<"local" | "cloud">("local")
const [tab, setTab] = createSignal<Source>("local")
let local: HTMLButtonElement | undefined
let cloud: HTMLButtonElement | undefined
let worktree: HTMLButtonElement | undefined
let localPanel: HTMLDivElement | undefined
let cloudPanel: HTMLDivElement | undefined
let worktreePanel: HTMLDivElement | undefined

const worktreeIds = () => props.worktreeSessionIds?.()

createEffect(() => {
const panel = tab() === "local" ? localPanel : cloudPanel
if (tab() === "worktree" && !worktreeIds()) setTab("local")
})

createEffect(() => {
const panel = tab() === "local" ? localPanel : tab() === "cloud" ? cloudPanel : worktreePanel

const frame = requestAnimationFrame(() => {
panel
Expand Down Expand Up @@ -60,17 +73,20 @@ const HistoryView: Component<HistoryViewProps> = (props) => {
props.onBack?.()
}

function move(event: KeyboardEvent, current: "local" | "cloud") {
const next =
function move(event: KeyboardEvent, current: Source) {
const sources: Source[] = worktreeIds() ? ["local", "cloud", "worktree"] : ["local", "cloud"]
const index = sources.indexOf(current)
const source =
event.key === "Home"
? local
? sources[0]
: event.key === "End"
? cloud
: event.key === "ArrowLeft" || event.key === "ArrowRight"
? current === "local"
? cloud
: local
: undefined
? sources.at(-1)
: event.key === "ArrowLeft"
? sources[(index - 1 + sources.length) % sources.length]
: event.key === "ArrowRight"
? sources[(index + 1) % sources.length]
: undefined
const next = source === "local" ? local : source === "cloud" ? cloud : source === "worktree" ? worktree : undefined
if (!next) return
event.preventDefault()
next.focus()
Expand Down Expand Up @@ -113,6 +129,23 @@ const HistoryView: Component<HistoryViewProps> = (props) => {
>
{language.t("session.tab.cloud")}
</button>
<Show when={worktreeIds()}>
<button
ref={worktree}
id="history-tab-worktree"
class="history-tab-btn"
classList={{ "history-tab-btn--active": tab() === "worktree" }}
type="button"
role="tab"
aria-selected={tab() === "worktree"}
aria-controls="history-panel-worktree"
tabIndex={tab() === "worktree" ? 0 : -1}
onClick={() => setTab("worktree")}
onKeyDown={(event) => move(event, "worktree")}
>
{language.t("session.tab.worktree")}
</button>
</Show>
</div>
<Button variant="secondary" size="small" onClick={openImport} class="history-import-btn">
{language.t("session.cloud.import")}
Expand All @@ -139,6 +172,23 @@ const HistoryView: Component<HistoryViewProps> = (props) => {
>
{tab() === "cloud" && <CloudSessionList onSelectSession={selectCloudSession} />}
</div>
<Show when={worktreeIds()}>
<div
class="history-view-content"
ref={worktreePanel}
id="history-panel-worktree"
role="tabpanel"
aria-labelledby="history-tab-worktree"
hidden={tab() !== "worktree"}
>
{tab() === "worktree" && (
<SessionList
onSelectSession={props.onSelectSession}
sessionIds={() => worktreeIds() ?? EMPTY_SESSION_IDS}
/>
)}
</div>
</Show>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Header/back button are owned by the parent HistoryView.
*/

import { Component, Show, createSignal, onMount, type JSX } from "solid-js"
import { Component, Show, createMemo, createSignal, onMount, type Accessor, type JSX } from "solid-js"
import { List } from "@kilocode/kilo-ui/list"
import { ContextMenu } from "@kilocode/kilo-ui/context-menu"
import { Dialog } from "@kilocode/kilo-ui/dialog"
Expand Down Expand Up @@ -38,6 +38,7 @@ function dateGroupKey(iso: string): (typeof DATE_GROUP_KEYS)[number] {

interface SessionListProps {
onSelectSession: (id: string) => void
sessionIds?: Accessor<ReadonlySet<string>>
}

const SessionList: Component<SessionListProps> = (props) => {
Expand All @@ -50,14 +51,20 @@ const SessionList: Component<SessionListProps> = (props) => {
const [notice, setNotice] = createSignal("")
let seq = 0

const items = createMemo(() => {
const ids = props.sessionIds?.()
if (!ids) return session.sessions()
return session.sessions().filter((item) => ids.has(item.id))
})

onMount(() => {
console.log("[Kilo New] SessionList mounted, loading sessions")
session.loadSessions()
})

const currentSession = (): SessionInfo | undefined => {
const id = session.currentSessionID()
return session.sessions().find((s) => s.id === id)
return items().find((s) => s.id === id)
}

function startRename(s: SessionInfo) {
Expand Down Expand Up @@ -189,7 +196,7 @@ const SessionList: Component<SessionListProps> = (props) => {
return (
<div class="session-list">
<List<SessionInfo>
items={session.sessions()}
items={items()}
key={(s) => s.id}
filterKeys={["title"]}
current={currentSession()}
Expand Down
1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/ar.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/br.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/bs.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/da.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,7 @@ export const dict = {
"session.tabs.switcher.busy": "In Arbeit",
"session.tab.local": "Lokal",
"session.tab.cloud": "Cloud",
"session.tab.worktree": "Arbeitsbaum",
"session.cloud.repoOnly": "Nur dieses Repository",
"session.cloud.import": "Aus der Cloud importieren",
"feedback.button": "Feedback & Support",
Expand Down
1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,7 @@ export const dict = {
"session.tabs.switcher.busy": "Working",
"session.tab.local": "Local",
"session.tab.cloud": "Cloud",
"session.tab.worktree": "Worktree",
"session.cloud.repoOnly": "Only this repository",
"session.cloud.import": "Import session",
"feedback.button": "Feedback & Support",
Expand Down
1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/es.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/fr.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/it.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/ja.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/ko.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/nl.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/no.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/pl.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/ru.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/th.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/tr.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/uk.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/kilo-vscode/webview-ui/src/i18n/zh.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading