feat(app): add chat outline navigation (#2792)

* feat(app): add chat outline navigation

Long agent sessions need prompt navigation without coupling viewport movement to disposable server windows. Keep fetched timeline pages in client state so repeated jumps stay local, and settle prompt jumps against virtualized row measurement changes.

* fix(app): preserve chat outline timeline coverage

* fix(app): reconcile outline window submissions

* fix(app): harden outline timeline navigation
This commit is contained in:
Mohamed Boudra
2026-08-02 20:48:23 +08:00
committed by GitHub
parent 0ad51e26a1
commit 553ac53643
49 changed files with 3385 additions and 84 deletions
@@ -70,6 +70,26 @@ describe("loadAppSettingsFromStorage", () => {
expect(result.workspaceTitleSource).toBe("title");
});
it("enables the chat outline by default", async () => {
const deps = makeDeps();
const result = await loadAppSettingsFromStorage(deps);
expect(result.chatOutlineEnabled).toBe(true);
});
it("loads a disabled chat outline preference", async () => {
const deps = makeDeps({
storage: createInMemoryKeyValueStorage({
[APP_SETTINGS_KEY]: JSON.stringify({ chatOutlineEnabled: false }),
}),
});
const result = await loadAppSettingsFromStorage(deps);
expect(result.chatOutlineEnabled).toBe(false);
});
it("loads configured terminal scrollback lines from app settings", async () => {
const deps = makeDeps({
storage: createInMemoryKeyValueStorage({
+14 -3
View File
@@ -43,6 +43,7 @@ export interface AppSettings {
workspaceTitleSource: WorkspaceTitleSource;
autoExpandReasoning: boolean;
toolCallDetailLevel: ToolCallDetailLevel;
chatOutlineEnabled: boolean;
vimKeybindings: boolean;
}
@@ -67,6 +68,7 @@ export const DEFAULT_CLIENT_SETTINGS: AppSettings = {
workspaceTitleSource: "title",
autoExpandReasoning: false,
toolCallDetailLevel: "detailed",
chatOutlineEnabled: true,
vimKeybindings: false,
};
@@ -188,6 +190,17 @@ function parseToolCallDetailLevel(stored: StoredAppSettings): ToolCallDetailLeve
return null;
}
function pickBooleanAppSettings(stored: StoredAppSettings): Partial<AppSettings> {
const result: Partial<AppSettings> = {};
if (typeof stored.vimKeybindings === "boolean") {
result.vimKeybindings = stored.vimKeybindings;
}
if (typeof stored.chatOutlineEnabled === "boolean") {
result.chatOutlineEnabled = stored.chatOutlineEnabled;
}
return result;
}
function pickAppSettings(stored: StoredAppSettings): Partial<AppSettings> {
const result: Partial<AppSettings> = {};
if (typeof stored.theme === "string" && VALID_THEMES.has(stored.theme)) {
@@ -235,9 +248,7 @@ function pickAppSettings(stored: StoredAppSettings): Partial<AppSettings> {
if (typeof stored.syntaxTheme === "string" && isSyntaxThemeId(stored.syntaxTheme)) {
result.syntaxTheme = stored.syntaxTheme;
}
if (typeof stored.vimKeybindings === "boolean") {
result.vimKeybindings = stored.vimKeybindings;
}
Object.assign(result, pickBooleanAppSettings(stored));
if (
typeof stored.workspaceTitleSource === "string" &&
VALID_WORKSPACE_TITLE_SOURCES.has(stored.workspaceTitleSource)