Skip to content

Commit 96e60ce

Browse files
committed
Budget multi-line settings banner and keep diagnostics on unconfigured path
1 parent 10516d9 commit 96e60ce

5 files changed

Lines changed: 57 additions & 7 deletions

File tree

src/config.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,29 @@ describe("loadConfig", () => {
9494
}
9595
});
9696

97+
test("threads local settings diagnostics on unconfigured early return", async () => {
98+
const cwd = await emptyCwd();
99+
try {
100+
await mkdir(join(cwd, ".corbits"), { recursive: true });
101+
await writeFile(
102+
join(cwd, ".corbits", "settings.json"),
103+
JSON.stringify({ unknownKey: true, anotherJunk: 1 }),
104+
);
105+
const result = await loadConfig(["--cwd", cwd, "do it"], {
106+
globalSettingsPath: NO_SETTINGS,
107+
allowUnconfigured: true,
108+
});
109+
expect(result.configured).toBe(false);
110+
if (result.configured === false) {
111+
expect(result.settingsDiagnostics).toBeDefined();
112+
expect(result.settingsDiagnostics!.length).toBeGreaterThan(0);
113+
expect(result.settingsDiagnostics!.some((d) => /unknown/i.test(d.message))).toBe(true);
114+
}
115+
} finally {
116+
await rm(cwd, { recursive: true, force: true });
117+
}
118+
});
119+
97120
test("UnconfiguredConfig.globalSettingsPath reflects --config path, not the global default", async () => {
98121
const cwd = await emptyCwd();
99122
try {

src/config/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ export type UnconfiguredConfig = {
269269
globalSettingsPath: string;
270270
// The original error message, used for non-TUI (exec) error output.
271271
providerError: string;
272+
/**
273+
* Fail-open diagnostics from local settings load. Still threaded on the
274+
* unconfigured path so junk local files surface via stderr/banner rather
275+
* than disappearing when provider setup fails early.
276+
*/
277+
settingsDiagnostics?: SettingsLoadDiagnostic[];
272278
};
273279

274280
export type LoadConfigOptions = {
@@ -458,6 +464,9 @@ export async function loadConfig(
458464
command,
459465
globalSettingsPath: effectiveSettingsPath,
460466
providerError: err instanceof Error ? err.message : String(err),
467+
// Keep diagnostics even when provider setup fails early so junk local
468+
// files still reach stderr (exec) / banner (TUI after onboarding).
469+
...(settingsDiagnostics.length > 0 ? { settingsDiagnostics } : {}),
461470
};
462471
}
463472

src/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ export async function mainWithRunners(
2020
runners: Runners,
2121
): Promise<number> {
2222
const config = await loadConfig(argv, { allowUnconfigured: true });
23-
// Exec has no Ink surface for settings diagnostics — fail-open must still
24-
// tell the operator what was ignored and how to fix it.
25-
if (config.configured && config.command === "exec" && config.settingsDiagnostics !== undefined) {
23+
// Exec has no Ink banner; unconfigured TUI goes to onboarding without the
24+
// main-screen notice. Surface fail-open diagnostics on stderr for those
25+
// paths so junk local files are never silent.
26+
const surfaceDiagnosticsOnStderr =
27+
config.command === "exec" || !config.configured;
28+
if (surfaceDiagnosticsOnStderr && config.settingsDiagnostics !== undefined) {
2629
for (const d of config.settingsDiagnostics) {
2730
process.stderr.write(`settings: ${d.message}\n fix: ${d.fix}\n`);
2831
}

src/tui/app.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ import {
8585
taskChromeRowCount,
8686
pluginChromeRowCount,
8787
extraChromeRowCount,
88+
settingsNoticeRowCount,
8889
} from "./chrome-geometry.js";
8990
import { progressChromeRowCount } from "./chrome-zones.js";
9091
import {
@@ -563,7 +564,11 @@ export function App({
563564
const extraChromeRows = extraChromeRowCount({
564565
mcpNeedsAuthCount: mcpStatus.needsAuth.length,
565566
commandMessagePresent: commandMessage !== null,
566-
settingsNoticePresent: settingsNotice !== null,
567+
// Multi-line banner: 2 rows per diagnostic + Esc hint; 0 when dismissed.
568+
settingsNoticeRows:
569+
settingsNotice === null
570+
? 0
571+
: settingsNoticeRowCount(settingsDiagnostics?.length ?? 0),
567572
goalChromeRows,
568573
taskChromeRows,
569574
pluginChromeRows,

src/tui/chrome-geometry.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,21 @@ export function pluginChromeRowCount(args: {
6868
return 6 + list.length + widestCreds + 2;
6969
}
7070

71+
/**
72+
* Rows for the settings diagnostics banner: each diagnostic is two lines
73+
* (`Settings warning: …` + ` Fix: …`) plus one Esc-dismiss hint. Pass 0 when
74+
* the banner is absent or dismissed.
75+
*/
76+
export function settingsNoticeRowCount(diagnosticCount: number): number {
77+
if (diagnosticCount <= 0) return 0;
78+
return diagnosticCount * 2 + 1;
79+
}
80+
7181
export function extraChromeRowCount(args: {
7282
mcpNeedsAuthCount: number;
7383
commandMessagePresent: boolean;
74-
settingsNoticePresent?: boolean;
84+
/** Multi-line settings banner rows (0 when dismissed). Prefer settingsNoticeRowCount. */
85+
settingsNoticeRows?: number;
7586
goalChromeRows: number;
7687
taskChromeRows: number;
7788
pluginChromeRows: number;
@@ -87,8 +98,7 @@ export function extraChromeRowCount(args: {
8798
return (
8899
(args.mcpNeedsAuthCount > 0 ? 1 : 0) +
89100
(args.commandMessagePresent ? 1 : 0) +
90-
// Settings diagnostics banner is multi-line; budget 2 rows (message + Esc hint).
91-
(args.settingsNoticePresent === true ? 2 : 0) +
101+
(args.settingsNoticeRows ?? 0) +
92102
args.goalChromeRows +
93103
args.taskChromeRows +
94104
args.pluginChromeRows +

0 commit comments

Comments
 (0)