From bd43b2de86e89f8056d3e11304d60dea48d20383 Mon Sep 17 00:00:00 2001 From: Lanh Hoang Date: Mon, 17 Aug 2026 22:29:58 -0400 Subject: [PATCH 1/6] docs: update phase 1 plan --- ...usbar-sidebar-visibility-phase-1-config.md | 81 +++++++++++-------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/docs/superpowers/plans/2026-08-17-statusbar-sidebar-visibility-phase-1-config.md b/docs/superpowers/plans/2026-08-17-statusbar-sidebar-visibility-phase-1-config.md index 62cb4b4..ef2d39a 100644 --- a/docs/superpowers/plans/2026-08-17-statusbar-sidebar-visibility-phase-1-config.md +++ b/docs/superpowers/plans/2026-08-17-statusbar-sidebar-visibility-phase-1-config.md @@ -65,7 +65,10 @@ describe("config — surface visibility", () => { const store = new MemoryConfigStore(); store.seed( getConfigPath("/agent"), - JSON.stringify({ zones: DEFAULT_ZONES, extensionSegments: { hidden: [] } }), + JSON.stringify({ + zones: DEFAULT_ZONES, + extensionSegments: { hidden: [] }, + }), ); expect(loadConfig({ agentDir: "/agent", store })).toMatchObject({ @@ -74,57 +77,63 @@ describe("config — surface visibility", () => { }); }); - it.each([true, null, 0, "false", {}, []])( - "treats non-false statusbar/sidebar values as enabled: %j", - (value) => { + it.each(["statusbarEnabled", "sidebarEnabled"] as const)( + "treats non-false %s values as enabled", + (field) => { + for (const value of [true, null, 0, "false", {}, []]) { + const store = new MemoryConfigStore(); + store.seed( + getConfigPath("/agent"), + JSON.stringify({ + zones: DEFAULT_ZONES, + extensionSegments: { hidden: [] }, + [field]: value, + }), + ); + + expect(loadConfig({ agentDir: "/agent", store })[field]).toBe(true); + } + }, + ); + + it.each([ + { statusbarEnabled: false, sidebarEnabled: true }, + { statusbarEnabled: true, sidebarEnabled: false }, + { statusbarEnabled: false, sidebarEnabled: false }, + ] as const)( + "disables only the surface whose stored value is literal false: %j", + (surfaceValues) => { const store = new MemoryConfigStore(); store.seed( getConfigPath("/agent"), JSON.stringify({ zones: DEFAULT_ZONES, extensionSegments: { hidden: [] }, - statusbarEnabled: value, - sidebarEnabled: value, + ...surfaceValues, }), ); - expect(loadConfig({ agentDir: "/agent", store })).toMatchObject({ - statusbarEnabled: true, - sidebarEnabled: true, - }); + expect(loadConfig({ agentDir: "/agent", store })).toMatchObject( + surfaceValues, + ); }, ); - it("disables each surface only for the literal false value", () => { - const store = new MemoryConfigStore(); - store.seed( - getConfigPath("/agent"), - JSON.stringify({ - zones: DEFAULT_ZONES, - extensionSegments: { hidden: [] }, - statusbarEnabled: false, - sidebarEnabled: false, - }), - ); - - expect(loadConfig({ agentDir: "/agent", store })).toMatchObject({ - statusbarEnabled: false, - sidebarEnabled: false, - }); - }); - it("round-trips both surface fields and publishes them in the schema", () => { const store = new MemoryConfigStore(); const disabled = { ...config, statusbarEnabled: false, - sidebarEnabled: false, + sidebarEnabled: true, } as PiStatusConfig; saveConfig(disabled, { agentDir: "/agent", store }); const written = JSON.parse(store.read(getConfigPath("/agent")) as string); - expect(written).toMatchObject({ statusbarEnabled: false, sidebarEnabled: false }); + expect(written).toMatchObject({ + statusbarEnabled: false, + sidebarEnabled: true, + }); expect(loadConfig({ agentDir: "/agent", store })).toMatchObject(disabled); expect(Object.keys(written).sort()).toContain("sidebarEnabled"); expect(Object.keys(written).sort()).toContain("statusbarEnabled"); @@ -194,7 +203,9 @@ function cloneDefaultConfig(): PiStatusConfig { extensionSegments: { hidden: [...DEFAULT_CONFIG.extensionSegments.hidden] }, extensionStatusZone: DEFAULT_CONFIG.extensionStatusZone, completionNotifications: DEFAULT_CONFIG.completionNotifications, - sidebarPanelLayout: cloneSidebarPanelLayout(DEFAULT_CONFIG.sidebarPanelLayout), + sidebarPanelLayout: cloneSidebarPanelLayout( + DEFAULT_CONFIG.sidebarPanelLayout, + ), sidebarHiddenSegments: [...DEFAULT_CONFIG.sidebarHiddenSegments], }; } @@ -239,7 +250,9 @@ const next: PiStatusConfig = { extensionStatusZone: config.extensionStatusZone, completionNotifications: config.completionNotifications, sidebarPanelLayout: sidebar.sidebarPanelLayout, - sidebarHiddenSegments: sidebar.sidebarHiddenSegments.filter(isPersistedSidebarSegmentId), + sidebarHiddenSegments: sidebar.sidebarHiddenSegments.filter( + isPersistedSidebarSegmentId, + ), }; ``` @@ -270,8 +283,8 @@ Expected: both commands exit successfully; no typed fixture reports a missing su ## Phase acceptance checklist - [ ] Missing fields load as `true`/`true`. -- [ ] Literal `false` loads as disabled for each field. -- [ ] Invalid non-false values load as enabled. +- [ ] Literal `false` independently disables either field without affecting the other. +- [ ] Invalid non-false values load as enabled for either field. - [ ] Save/reload preserves both fields. - [ ] Serialized schema contains both keys. - [ ] Full TypeScript typecheck passes. From 3b198e93c9d80b16c8dd8a3fbb8bba117e7e3fef Mon Sep 17 00:00:00 2001 From: Lanh Hoang Date: Mon, 17 Aug 2026 22:36:46 -0400 Subject: [PATCH 2/6] feat(config): add statusbarEnabled and sidebarEnabled fields Phase 1 of the statusbar/sidebar visibility plan. Both fields are required booleans on PiStatusConfig with enabled-by-default semantics; only the literal stored value false disables a field, all other values (missing, true, null, 0, strings, objects, arrays) normalize to true. saveConfig emits both fields in the canonical serialized schema. --- src/core/config.ts | 8 ++++++++ src/shared/types.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/core/config.ts b/src/core/config.ts index 9108b1f..c57b71c 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -39,6 +39,8 @@ export { }; export const DEFAULT_CONFIG: PiStatusConfig = { + statusbarEnabled: true, + sidebarEnabled: true, zones: cloneZones(DEFAULT_ZONES), extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", @@ -49,6 +51,8 @@ export const DEFAULT_CONFIG: PiStatusConfig = { function cloneDefaultConfig(): PiStatusConfig { return { + statusbarEnabled: DEFAULT_CONFIG.statusbarEnabled, + sidebarEnabled: DEFAULT_CONFIG.sidebarEnabled, zones: cloneZones(DEFAULT_CONFIG.zones), extensionSegments: { hidden: [...DEFAULT_CONFIG.extensionSegments.hidden] }, extensionStatusZone: DEFAULT_CONFIG.extensionStatusZone, @@ -249,6 +253,8 @@ function normalizeLegacyHiddenSegments(input: unknown): string[] { function normalizeConfig(input: Record): PiStatusConfig { const layout = normalizeSidebarLayout(input); return { + statusbarEnabled: input.statusbarEnabled !== false, + sidebarEnabled: input.sidebarEnabled !== false, zones: Object.hasOwn(input, "zones") ? normalizeZones(input.zones) : Object.hasOwn(input, "segments") && Array.isArray(input.segments) @@ -340,6 +346,8 @@ export function saveConfig( sidebarHiddenSegments: config.sidebarHiddenSegments, }); const next: PiStatusConfig = { + statusbarEnabled: config.statusbarEnabled, + sidebarEnabled: config.sidebarEnabled, zones: cloneZones(config.zones), extensionSegments: { hidden: [...config.extensionSegments.hidden] }, extensionStatusZone: config.extensionStatusZone, diff --git a/src/shared/types.ts b/src/shared/types.ts index f7de5f2..46542fb 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -198,6 +198,8 @@ export interface StatusLineZones { } export type PiStatusConfig = { + statusbarEnabled: boolean; + sidebarEnabled: boolean; zones: StatusLineZones; extensionSegments: ExtensionSegments; extensionStatusZone: StatusLineZone; From aa2322ca78fc98a82b7f18a2d1aeadeefa075c09 Mon Sep 17 00:00:00 2001 From: Lanh Hoang Date: Mon, 17 Aug 2026 22:36:48 -0400 Subject: [PATCH 3/6] test(config): cover statusbar/sidebar visibility defaults and round-trip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of the statusbar/sidebar visibility plan. Adds a new `config — surface visibility` describe block asserting: - both fields default to true when absent - non-false values (true, null, 0, 'false', {}, []) normalize to true - literal false disables only the targeted surface - saveConfig round-trips both fields and includes them in the serialized schema Also adds the new fields to the canonical in-memory fixture and updates exact-equality expectations in malformed/missing-config and serialized- key tests to match the new schema. --- tests/core/config.test.ts | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tests/core/config.test.ts b/tests/core/config.test.ts index 8fd3fcc..3aa480c 100644 --- a/tests/core/config.test.ts +++ b/tests/core/config.test.ts @@ -40,6 +40,8 @@ const expectedDefaultLayout = (): SidebarPanelLayout => const expectedDefault = expectedDefaultLayout; const config: PiStatusConfig = { + statusbarEnabled: true, + sidebarEnabled: true, zones: { topLeft: ["git-branch"], topRight: [], bottomLeft: [], bottomRight: [] }, extensionSegments: { hidden: ["alpha"] }, extensionStatusZone: "bottomRight", @@ -500,6 +502,8 @@ describe("config — direct extension file", () => { ); expect(loadConfig({ agentDir, store })).toEqual({ + statusbarEnabled: true, + sidebarEnabled: true, zones: DEFAULT_ZONES, extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", @@ -534,6 +538,8 @@ describe("config — direct extension file", () => { store.seed(getConfigPath("/agent"), content); expect(loadConfig({ agentDir: "/agent", store })).toEqual({ + statusbarEnabled: true, + sidebarEnabled: true, zones: DEFAULT_ZONES, extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", @@ -674,6 +680,8 @@ describe("config — filesystem", () => { const path = join(agentDir, "extensions", "statusline.json"); expect(loadConfig()).toEqual({ + statusbarEnabled: true, + sidebarEnabled: true, zones: DEFAULT_ZONES, extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", @@ -783,8 +791,10 @@ describe("config — completion notifications", () => { "completionNotifications", "extensionSegments", "extensionStatusZone", + "sidebarEnabled", "sidebarHiddenSegments", "sidebarPanelLayout", + "statusbarEnabled", "zones", ].sort(), ); @@ -807,3 +817,81 @@ describe("config — completion notifications", () => { expect(loadConfig({ agentDir: "/agent", store }).completionNotifications).toBe(false); }); }); + +describe("config — surface visibility", () => { + it("defaults both surfaces to enabled when the fields are absent", () => { + const store = new MemoryConfigStore(); + store.seed( + getConfigPath("/agent"), + JSON.stringify({ + zones: DEFAULT_ZONES, + extensionSegments: { hidden: [] }, + }), + ); + + expect(loadConfig({ agentDir: "/agent", store })).toMatchObject({ + statusbarEnabled: true, + sidebarEnabled: true, + }); + }); + + it.each(["statusbarEnabled", "sidebarEnabled"] as const)( + "treats non-false %s values as enabled", + (field) => { + for (const value of [true, null, 0, "false", {}, []]) { + const store = new MemoryConfigStore(); + store.seed( + getConfigPath("/agent"), + JSON.stringify({ + zones: DEFAULT_ZONES, + extensionSegments: { hidden: [] }, + [field]: value, + }), + ); + + expect(loadConfig({ agentDir: "/agent", store })[field]).toBe(true); + } + }, + ); + + it.each([ + { statusbarEnabled: false, sidebarEnabled: true }, + { statusbarEnabled: true, sidebarEnabled: false }, + { statusbarEnabled: false, sidebarEnabled: false }, + ] as const)( + "disables only the surface whose stored value is literal false: %j", + (surfaceValues) => { + const store = new MemoryConfigStore(); + store.seed( + getConfigPath("/agent"), + JSON.stringify({ + zones: DEFAULT_ZONES, + extensionSegments: { hidden: [] }, + ...surfaceValues, + }), + ); + + expect(loadConfig({ agentDir: "/agent", store })).toMatchObject(surfaceValues); + }, + ); + + it("round-trips both surface fields and publishes them in the schema", () => { + const store = new MemoryConfigStore(); + const disabled = { + ...config, + statusbarEnabled: false, + sidebarEnabled: true, + } as PiStatusConfig; + + saveConfig(disabled, { agentDir: "/agent", store }); + + const written = JSON.parse(store.read(getConfigPath("/agent")) as string); + expect(written).toMatchObject({ + statusbarEnabled: false, + sidebarEnabled: true, + }); + expect(loadConfig({ agentDir: "/agent", store })).toMatchObject(disabled); + expect(Object.keys(written).sort()).toContain("sidebarEnabled"); + expect(Object.keys(written).sort()).toContain("statusbarEnabled"); + }); +}); From c3000d9793a9b07c04f3d7985516dba353fc5628 Mon Sep 17 00:00:00 2001 From: Lanh Hoang Date: Mon, 17 Aug 2026 22:36:51 -0400 Subject: [PATCH 4/6] test: add statusbarEnabled and sidebarEnabled to typed config fixtures Phase 1 of the statusbar/sidebar visibility plan. Both fields are now required on PiStatusConfig, so every in-memory typed fixture across the test suite needs the defaults (true, true). Also updates one persisted-schema expectation in index.test.ts to include the new keys in the round-tripped JSON. --- tests/core/resolve-footer.test.ts | 2 ++ tests/core/runtime-state.test.ts | 4 ++++ tests/core/sidebar-layout.test.ts | 2 ++ tests/index-save.test.ts | 2 ++ tests/index-sidebar-layout.test.ts | 4 ++++ tests/index.test.ts | 2 ++ tests/tui/dashboard-render.test.ts | 2 ++ tests/tui/dashboard-state.test.ts | 2 ++ tests/tui/dashboard.test.ts | 2 ++ tests/tui/sidebar-render.test.ts | 2 ++ 10 files changed, 24 insertions(+) diff --git a/tests/core/resolve-footer.test.ts b/tests/core/resolve-footer.test.ts index d007b97..aa0f592 100644 --- a/tests/core/resolve-footer.test.ts +++ b/tests/core/resolve-footer.test.ts @@ -29,6 +29,8 @@ const identityTheme: ThemeLike = { fg: (_c, t) => t, rainbow: (t) => t }; function footerConfig(overrides: Partial = {}): PiStatusConfig { return { + statusbarEnabled: true, + sidebarEnabled: true, zones: structuredClone(DEFAULT_ZONES), extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", diff --git a/tests/core/runtime-state.test.ts b/tests/core/runtime-state.test.ts index ddb3168..b1cc97c 100644 --- a/tests/core/runtime-state.test.ts +++ b/tests/core/runtime-state.test.ts @@ -8,6 +8,8 @@ import { } from "../../src/shared/types.ts"; const defaultConfig: PiStatusConfig = { + statusbarEnabled: true, + sidebarEnabled: true, zones: { topLeft: ["model-with-reasoning"], topRight: [], @@ -83,6 +85,8 @@ describe("RuntimeStateMachine", () => { it("updates config on config_reload", () => { const sm = createRuntimeStateMachine(defaultConfig, "medium"); const newConfig: PiStatusConfig = { + statusbarEnabled: true, + sidebarEnabled: true, zones: { topLeft: ["git-branch"], topRight: [], bottomLeft: [], bottomRight: [] }, extensionSegments: { hidden: ["x"] }, extensionStatusZone: "bottomRight", diff --git a/tests/core/sidebar-layout.test.ts b/tests/core/sidebar-layout.test.ts index 5f19d02..fd82a6d 100644 --- a/tests/core/sidebar-layout.test.ts +++ b/tests/core/sidebar-layout.test.ts @@ -25,6 +25,8 @@ import { function config(overrides: Partial = {}): PiStatusConfig { return { + statusbarEnabled: true, + sidebarEnabled: true, zones: structuredClone(DEFAULT_ZONES), extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", diff --git a/tests/index-save.test.ts b/tests/index-save.test.ts index add6d04..73f3c75 100644 --- a/tests/index-save.test.ts +++ b/tests/index-save.test.ts @@ -24,6 +24,8 @@ afterEach(() => { function config(): PiStatusConfig { return { + statusbarEnabled: true, + sidebarEnabled: true, zones: { topLeft: ["model-with-reasoning"], topRight: [], diff --git a/tests/index-sidebar-layout.test.ts b/tests/index-sidebar-layout.test.ts index a996b11..013b193 100644 --- a/tests/index-sidebar-layout.test.ts +++ b/tests/index-sidebar-layout.test.ts @@ -31,6 +31,8 @@ function result(details: unknown, overrides: Record = {}) { function configWithModelIn(panelId: SidebarPanelId): PiStatusConfig { return { + statusbarEnabled: true, + sidebarEnabled: true, zones: structuredClone(DEFAULT_ZONES), extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", @@ -46,6 +48,8 @@ function configWithModelIn(panelId: SidebarPanelId): PiStatusConfig { function configWithPanelsVisible(panels: SidebarPanelId[]): PiStatusConfig { return { + statusbarEnabled: true, + sidebarEnabled: true, zones: structuredClone(DEFAULT_ZONES), extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", diff --git a/tests/index.test.ts b/tests/index.test.ts index ceec6b3..61debe0 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -643,6 +643,8 @@ describe("extension wiring", () => { expect( JSON.parse(readFileSync(join(agentDir, "extensions", "statusline.json"), "utf8")), ).toEqual({ + statusbarEnabled: true, + sidebarEnabled: true, zones: { topLeft: ["model-with-reasoning"], topRight: [], diff --git a/tests/tui/dashboard-render.test.ts b/tests/tui/dashboard-render.test.ts index 4db4db3..391befb 100644 --- a/tests/tui/dashboard-render.test.ts +++ b/tests/tui/dashboard-render.test.ts @@ -30,6 +30,8 @@ function zones(overrides: Partial = {}): StatusLineZones { function config(overrides: Partial = {}): PiStatusConfig { return { + statusbarEnabled: true, + sidebarEnabled: true, zones: zones(), extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", diff --git a/tests/tui/dashboard-state.test.ts b/tests/tui/dashboard-state.test.ts index 1c43d81..55d62fd 100644 --- a/tests/tui/dashboard-state.test.ts +++ b/tests/tui/dashboard-state.test.ts @@ -31,6 +31,8 @@ function zones(overrides: Partial = {}): StatusLineZones { function config(overrides: Partial = {}): PiStatusConfig { return { + statusbarEnabled: true, + sidebarEnabled: true, zones: zones(), extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", diff --git a/tests/tui/dashboard.test.ts b/tests/tui/dashboard.test.ts index 69eac1e..252ec2a 100644 --- a/tests/tui/dashboard.test.ts +++ b/tests/tui/dashboard.test.ts @@ -18,6 +18,8 @@ import { noTheme } from "../../src/tui/theme.ts"; function config(): PiStatusConfig { return { + statusbarEnabled: true, + sidebarEnabled: true, zones: { topLeft: ["model-with-reasoning"], topRight: [], diff --git a/tests/tui/sidebar-render.test.ts b/tests/tui/sidebar-render.test.ts index 40bb815..bd8b688 100644 --- a/tests/tui/sidebar-render.test.ts +++ b/tests/tui/sidebar-render.test.ts @@ -45,6 +45,8 @@ function makeInput(overrides: Partial = {}): SidebarR return { footer, config: { + statusbarEnabled: true, + sidebarEnabled: true, zones: footer.zones, extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", From f00093fea9da48970ca2084bcb4f8875f9649561 Mon Sep 17 00:00:00 2001 From: Lanh Hoang Date: Mon, 17 Aug 2026 22:46:06 -0400 Subject: [PATCH 5/6] test: update FIXED_CONFIG with explicit typing and required statusbar/sidebar fields --- tests/tui/sidebar.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/tui/sidebar.test.ts b/tests/tui/sidebar.test.ts index 795fa28..9514232 100644 --- a/tests/tui/sidebar.test.ts +++ b/tests/tui/sidebar.test.ts @@ -42,14 +42,16 @@ const FIXED_SNAPSHOT: SidebarSnapshot = buildSidebarSnapshot({ sidebarPanels: [], }); -const FIXED_CONFIG = { +const FIXED_CONFIG: PiStatusConfig = { + statusbarEnabled: true, + sidebarEnabled: true, zones: DEFAULT_ZONES, extensionSegments: { hidden: [] }, extensionStatusZone: "bottomRight", completionNotifications: false, sidebarPanelLayout: [...DEFAULT_SIDEBAR_PANEL_LAYOUT], sidebarHiddenSegments: [], -} as unknown as PiStatusConfig; +}; function sidebarView(snapshot = FIXED_SNAPSHOT) { const catalog = buildSidebarSegmentCatalog(snapshot); From 2306af284e56bc676749168ea4c737a763739e8d Mon Sep 17 00:00:00 2001 From: Lanh Hoang Date: Mon, 17 Aug 2026 22:48:35 -0400 Subject: [PATCH 6/6] test: remove redundant type assertion and property existence checks in config tests --- tests/core/config.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/core/config.test.ts b/tests/core/config.test.ts index 3aa480c..4102786 100644 --- a/tests/core/config.test.ts +++ b/tests/core/config.test.ts @@ -881,7 +881,7 @@ describe("config — surface visibility", () => { ...config, statusbarEnabled: false, sidebarEnabled: true, - } as PiStatusConfig; + }; saveConfig(disabled, { agentDir: "/agent", store }); @@ -891,7 +891,5 @@ describe("config — surface visibility", () => { sidebarEnabled: true, }); expect(loadConfig({ agentDir: "/agent", store })).toMatchObject(disabled); - expect(Object.keys(written).sort()).toContain("sidebarEnabled"); - expect(Object.keys(written).sort()).toContain("statusbarEnabled"); }); });