From 5accc903a0a2758b3389b1ac92fb746c90029569 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:45:48 +0800 Subject: [PATCH] refactor(tui): simplify session rows --- tui/src/app.ts | 13 +++++++++++-- tui/src/session-menu.test.ts | 31 ++++++++++++++++++++++++++++++- tui/src/session-menu.ts | 30 +++++++++++++++++++++++------- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/tui/src/app.ts b/tui/src/app.ts index 9b6b23fec..b17c35ffc 100644 --- a/tui/src/app.ts +++ b/tui/src/app.ts @@ -2147,7 +2147,12 @@ export class NanobotTui { this.updateTitle() } const limit = this.renderer.height >= 20 ? 8 : 4 - this.sessionMenu.open(sessions, this.client.activeChatId, limit) + this.sessionMenu.open( + sessions, + this.client.activeChatId, + limit, + this.defaultModelPreset, + ) this.startSessionRefresh() this.renderTitleColor() this.sessionMenu.update(this.composer.plainText, limit) @@ -2361,7 +2366,11 @@ export class NanobotTui { this.apiReauthenticator, ) if (this.quitting || loadId !== this.sessionLoadId || !this.sessionMenu.visible) return - this.sessionMenu.replace(sessions, this.client.activeChatId) + this.sessionMenu.replace( + sessions, + this.client.activeChatId, + this.defaultModelPreset, + ) this.status.content = sessions.length ? `${sessions.length} sessions` : "No saved sessions" } catch { // Keep the existing picker usable during a transient refresh failure. diff --git a/tui/src/session-menu.test.ts b/tui/src/session-menu.test.ts index c1d59dfb7..4df6c1818 100644 --- a/tui/src/session-menu.test.ts +++ b/tui/src/session-menu.test.ts @@ -52,10 +52,12 @@ describe("SessionMenu", () => { warning: "#F5C451", }) setup.renderer.root.add(menu.root) - menu.open(sessions, "one", 6) + menu.open(sessions, "one", 6, "Codex") await setup.renderOnce() expect(setup.captureCharFrame()).toContain("› ● API migration") + expect(setup.captureCharFrame()).not.toContain("Move authentication") + expect(setup.captureCharFrame()).not.toContain("Codex") expect(menu.choose()?.chatId).toBe("one") menu.update("release stable", 6) @@ -231,4 +233,31 @@ describe("SessionMenu", () => { expect(duplicates).toContain("frontend/nanobot") expect(duplicates).toContain("backend/nanobot") }) + + test("shows only model overrides and keeps previews searchable", async () => { + setup = await createTestRenderer({ width: 80, height: 18, screenMode: "alternate-screen" }) + const menu = new SessionMenu(setup.renderer, { + text: "#FFFFFF", + muted: "#999999", + border: "#555555", + accent: "#FF8A33", + warning: "#F5C451", + }) + setup.renderer.root.add(menu.root) + + menu.open(sessions, "one", 6, "Codex") + await setup.renderOnce() + const frame = setup.captureCharFrame() + expect(frame).not.toContain("Codex") + expect(frame).not.toContain("Prepare the stable release") + + menu.update("prepare stable", 6) + await setup.renderOnce() + expect(menu.choose()?.chatId).toBe("two") + + menu.replace([{ ...sessions[1]!, modelPreset: "Deep Research" }], "one", "Codex") + await setup.renderOnce() + expect(setup.captureCharFrame()).toContain("Deep Research") + menu.hide() + }) }) diff --git a/tui/src/session-menu.ts b/tui/src/session-menu.ts index e13237309..419c265ec 100644 --- a/tui/src/session-menu.ts +++ b/tui/src/session-menu.ts @@ -32,6 +32,7 @@ export class SessionMenu { private spinnerFrame = 0 private spinnerTimer: ReturnType | null = null private rows: SessionMenuRow[] = [] + private defaultModelPreset = "" private readonly snapshots = new Map { const age = updatedLabel(session.updatedAt) - const preview = session.preview.trim() const detail = [ this.showWorkspaces ? this.workspaceLabel(session) : "", - session.modelPreset, - age, - preview && preview !== sessionLabel(session) ? preview : "", + this.modelOverride(session), ] .filter(Boolean) .join(" · ") @@ -73,7 +71,9 @@ export class SessionMenu { : selected ? this.theme.text : this.theme.muted return [ ...(marker ? [chunk(`${marker.text} `, marker.color)] : []), - chunk(`${sessionLabel(session)}${detail ? ` ${detail}` : ""}`, foreground), + chunk(sessionLabel(session), foreground), + ...(detail ? [chunk(` ${detail}`, this.theme.muted)] : []), + ...(age ? [chunk(` ${age}`, this.theme.muted)] : []), ] }, emptyText: "No matching sessions", @@ -86,14 +86,25 @@ export class SessionMenu { return this.picker.visible } - open(sessions: SessionSummary[], currentChatId: string, limit: number): void { + open( + sessions: SessionSummary[], + currentChatId: string, + limit: number, + defaultModelPreset = "", + ): void { + this.defaultModelPreset = defaultModelPreset this.observe(sessions, currentChatId) this.rows = this.prepareRows(sessions, currentChatId) this.picker.show(this.rows, "", limit) this.syncSpinner() } - replace(sessions: SessionSummary[], currentChatId: string): void { + replace( + sessions: SessionSummary[], + currentChatId: string, + defaultModelPreset = this.defaultModelPreset, + ): void { + this.defaultModelPreset = defaultModelPreset this.observe(sessions, currentChatId) this.rows = this.prepareRows(sessions, currentChatId) this.picker.replace(this.rows) @@ -236,6 +247,11 @@ export class SessionMenu { private workspaceLabel(session: SessionSummary): string { return this.workspaceLabels.get(normalizeWorkspacePath(session.workspaceScope?.project_path)) || "" } + + private modelOverride(session: SessionSummary): string { + const preset = session.modelPreset?.trim() || "" + return preset && preset !== this.defaultModelPreset ? preset : "" + } } function chunk(text: string, color: string): TextChunk {