refactor(tui): simplify session rows

This commit is contained in:
Xubin Ren
2026-08-23 23:45:48 +08:00
parent ac7fbe7aef
commit 080f80dfba
3 changed files with 64 additions and 10 deletions
+11 -2
View File
@@ -2147,7 +2147,12 @@ export class NanobotTui {
this.updateTitle() this.updateTitle()
} }
const limit = this.renderer.height >= 20 ? 8 : 4 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.startSessionRefresh()
this.renderTitleColor() this.renderTitleColor()
this.sessionMenu.update(this.composer.plainText, limit) this.sessionMenu.update(this.composer.plainText, limit)
@@ -2361,7 +2366,11 @@ export class NanobotTui {
this.apiReauthenticator, this.apiReauthenticator,
) )
if (this.quitting || loadId !== this.sessionLoadId || !this.sessionMenu.visible) return 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" this.status.content = sessions.length ? `${sessions.length} sessions` : "No saved sessions"
} catch { } catch {
// Keep the existing picker usable during a transient refresh failure. // Keep the existing picker usable during a transient refresh failure.
+30 -1
View File
@@ -52,10 +52,12 @@ describe("SessionMenu", () => {
warning: "#F5C451", warning: "#F5C451",
}) })
setup.renderer.root.add(menu.root) setup.renderer.root.add(menu.root)
menu.open(sessions, "one", 6) menu.open(sessions, "one", 6, "Codex")
await setup.renderOnce() await setup.renderOnce()
expect(setup.captureCharFrame()).toContain(" ● API migration") 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") expect(menu.choose()?.chatId).toBe("one")
menu.update("release stable", 6) menu.update("release stable", 6)
@@ -231,4 +233,31 @@ describe("SessionMenu", () => {
expect(duplicates).toContain("frontend/nanobot") expect(duplicates).toContain("frontend/nanobot")
expect(duplicates).toContain("backend/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()
})
}) })
+23 -7
View File
@@ -32,6 +32,7 @@ export class SessionMenu {
private spinnerFrame = 0 private spinnerFrame = 0
private spinnerTimer: ReturnType<typeof setInterval> | null = null private spinnerTimer: ReturnType<typeof setInterval> | null = null
private rows: SessionMenuRow[] = [] private rows: SessionMenuRow[] = []
private defaultModelPreset = ""
private readonly snapshots = new Map<string, { private readonly snapshots = new Map<string, {
preview: string preview: string
runStartedAt: number | null runStartedAt: number | null
@@ -58,12 +59,9 @@ export class SessionMenu {
].join(" "), ].join(" "),
render: (session, selected) => { render: (session, selected) => {
const age = updatedLabel(session.updatedAt) const age = updatedLabel(session.updatedAt)
const preview = session.preview.trim()
const detail = [ const detail = [
this.showWorkspaces ? this.workspaceLabel(session) : "", this.showWorkspaces ? this.workspaceLabel(session) : "",
session.modelPreset, this.modelOverride(session),
age,
preview && preview !== sessionLabel(session) ? preview : "",
] ]
.filter(Boolean) .filter(Boolean)
.join(" · ") .join(" · ")
@@ -73,7 +71,9 @@ export class SessionMenu {
: selected ? this.theme.text : this.theme.muted : selected ? this.theme.text : this.theme.muted
return [ return [
...(marker ? [chunk(`${marker.text} `, marker.color)] : []), ...(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", emptyText: "No matching sessions",
@@ -86,14 +86,25 @@ export class SessionMenu {
return this.picker.visible 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.observe(sessions, currentChatId)
this.rows = this.prepareRows(sessions, currentChatId) this.rows = this.prepareRows(sessions, currentChatId)
this.picker.show(this.rows, "", limit) this.picker.show(this.rows, "", limit)
this.syncSpinner() 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.observe(sessions, currentChatId)
this.rows = this.prepareRows(sessions, currentChatId) this.rows = this.prepareRows(sessions, currentChatId)
this.picker.replace(this.rows) this.picker.replace(this.rows)
@@ -236,6 +247,11 @@ export class SessionMenu {
private workspaceLabel(session: SessionSummary): string { private workspaceLabel(session: SessionSummary): string {
return this.workspaceLabels.get(normalizeWorkspacePath(session.workspaceScope?.project_path)) || "" 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 { function chunk(text: string, color: string): TextChunk {