diff --git a/src/panel/diagnostics.ts b/src/panel/diagnostics.ts index 7d4f6502..f84e375b 100644 --- a/src/panel/diagnostics.ts +++ b/src/panel/diagnostics.ts @@ -9,6 +9,11 @@ export interface DiagnosticRow { level?: "normal" | "warning" | "error"; } +export interface DiagnosticSection { + title: string; + rows: DiagnosticRow[]; +} + export type DiagnosticAlertLevel = "normal" | "warning" | "error"; export interface ConnectionDiagnosticsInput { @@ -19,24 +24,32 @@ export interface ConnectionDiagnosticsInput { diagnostics: AppServerDiagnostics; } -export function connectionDiagnosticRows(input: ConnectionDiagnosticsInput): DiagnosticRow[] { +export function connectionDiagnosticSections(input: ConnectionDiagnosticsInput): DiagnosticSection[] { + const mcpRows = mcpServerDiagnosticRows(input.diagnostics.mcpServers); return [ - { label: "connection", value: input.connected ? "connected" : "offline" }, - { label: "configured command", value: input.configuredCommand }, - { label: "running app-server", value: appServerIdentity(input.initializeResponse) }, - { label: "panel client", value: CLIENT_VERSION }, - { label: "platform", value: appServerPlatform(input.initializeResponse) }, - { label: "codexHome", value: input.initializeResponse?.codexHome ?? "(not connected)" }, - { label: "active thread CLI", value: input.activeThreadCliVersion ?? "(none)" }, - ...CAPABILITY_PROBE_METHODS.map((method) => capabilityDiagnosticRow(input.diagnostics.probes[method])), - ...mcpServerDiagnosticRows(input.diagnostics.mcpServers), + { + title: "Process", + rows: [ + { label: "connection", value: input.connected ? "connected" : "offline" }, + { label: "configured command", value: input.configuredCommand }, + { label: "running app-server", value: appServerIdentity(input.initializeResponse) }, + { label: "panel client", value: CLIENT_VERSION }, + { label: "platform", value: appServerPlatform(input.initializeResponse) }, + { label: "codexHome", value: input.initializeResponse?.codexHome ?? "(not connected)" }, + { label: "active thread CLI", value: input.activeThreadCliVersion ?? "(none)" }, + ], + }, + { + title: "Capabilities", + rows: CAPABILITY_PROBE_METHODS.map((method) => capabilityDiagnosticRow(input.diagnostics.probes[method])), + }, + { + title: "MCP issues", + rows: mcpRows.length > 0 ? mcpRows : [{ label: "issues", value: "(none)" }], + }, ]; } -export function connectionDiagnosticLines(rows: DiagnosticRow[]): string[] { - return ["Connection diagnostics", ...rows.map((row) => `${row.label}: ${row.value}`)]; -} - export function diagnosticAlertLevel(diagnostics: AppServerDiagnostics): DiagnosticAlertLevel { let hasWarning = false; for (const probe of Object.values(diagnostics.probes)) { @@ -52,7 +65,7 @@ export function diagnosticAlertLevel(diagnostics: AppServerDiagnostics): Diagnos function capabilityDiagnosticRow(probe: CapabilityProbeResult): DiagnosticRow { const detail = probe.message ? ` - ${probe.message}` : probe.summary ? ` (${probe.summary})` : ""; return { - label: `capability ${probe.method}`, + label: probe.method, value: `${probe.status}${detail}`, level: capabilityLevel(probe.status), }; diff --git a/src/panel/slash-commands.ts b/src/panel/slash-commands.ts index 19cfb783..fdc3d06a 100644 --- a/src/panel/slash-commands.ts +++ b/src/panel/slash-commands.ts @@ -32,7 +32,7 @@ export interface SlashCommandExecutionContext { setRequestedModel: (model: string | null) => void | Promise; setRequestedReasoningEffort: (effort: ReasoningEffort | null) => void | Promise; statusSummaryLines: () => string[]; - connectionDiagnosticLines: () => string[]; + connectionDiagnosticDetails: () => DisplayDetailSection[]; mcpStatusLines: () => Promise; modelStatusLines: () => string[]; effortStatusLines: () => string[]; @@ -181,7 +181,7 @@ export async function executeSlashCommand( } if (command === "doctor") { - context.addStructuredSystemMessage("Connection diagnostics", detailsFromLines(context.connectionDiagnosticLines())); + context.addStructuredSystemMessage("Connection diagnostics", context.connectionDiagnosticDetails()); return; } diff --git a/src/panel/view.ts b/src/panel/view.ts index da21f6c8..d2263700 100644 --- a/src/panel/view.ts +++ b/src/panel/view.ts @@ -21,7 +21,7 @@ import { nextCollaborationMode, } from "../runtime/collaboration-mode"; import { PanelController } from "./controller"; -import { connectionDiagnosticLines, connectionDiagnosticRows, diagnosticAlertLevel } from "./diagnostics"; +import { connectionDiagnosticSections, diagnosticAlertLevel } from "./diagnostics"; import { rollbackCandidateFromItems } from "./rollback"; import { contextSummary, effectiveConfigSections, rateLimitSummary } from "../runtime/view"; import { @@ -547,7 +547,7 @@ export class CodexPanelView extends ItemView { setRequestedModel: (model) => this.setRequestedModel(model), setRequestedReasoningEffort: (effort) => this.setRequestedReasoningEffort(effort), statusSummaryLines: () => this.statusSummaryLines(), - connectionDiagnosticLines: () => this.connectionDiagnosticLines(), + connectionDiagnosticDetails: () => this.connectionDiagnosticDetails(), mcpStatusLines: () => this.mcpStatusLines(), modelStatusLines: () => this.modelStatusLines(), effortStatusLines: () => this.effortStatusLines(), @@ -864,7 +864,7 @@ export class CodexPanelView extends ItemView { modelChoices: this.modelToolbarChoices(), effortChoices: this.effortToolbarChoices(), connectLabel: this.connection.isConnected() ? "Reconnect" : "Connect", - diagnostics: this.connectionDiagnosticRows(), + diagnostics: this.connectionDiagnosticSections(), diagnosticAlertLevel: diagnosticAlertLevel(this.state.appServerDiagnostics), }; } @@ -1026,8 +1026,8 @@ export class CodexPanelView extends ItemView { ]; } - private connectionDiagnosticRows() { - return connectionDiagnosticRows({ + private connectionDiagnosticSections() { + return connectionDiagnosticSections({ connected: this.connection.isConnected(), configuredCommand: this.plugin.settings.codexPath, initializeResponse: this.state.initializeResponse, @@ -1036,8 +1036,11 @@ export class CodexPanelView extends ItemView { }); } - private connectionDiagnosticLines(): string[] { - return connectionDiagnosticLines(this.connectionDiagnosticRows()); + private connectionDiagnosticDetails(): DisplayDetailSection[] { + return this.connectionDiagnosticSections().map((section) => ({ + title: section.title, + rows: section.rows.map((row) => ({ key: row.label, value: row.value })), + })); } private async mcpStatusLines(): Promise { diff --git a/src/runtime/state.ts b/src/runtime/state.ts index 06724b32..d0052097 100644 --- a/src/runtime/state.ts +++ b/src/runtime/state.ts @@ -35,9 +35,6 @@ export interface RuntimeSnapshot { export interface TurnRuntimeSettings { collaborationMode: CollaborationMode | null; - model: string | null | undefined; - effort: ReasoningEffort | null | undefined; - approvalsReviewer: ApprovalsReviewer | undefined; warning: string | null; } @@ -98,9 +95,6 @@ export function requestedTurnRuntimeSettings(snapshot: RuntimeSnapshot): TurnRun : null; return { collaborationMode, - model: runtimeOverridePayload(snapshot.requestedModel), - effort: runtimeOverridePayload(snapshot.requestedReasoningEffort), - approvalsReviewer: snapshot.requestedApprovalsReviewer ?? undefined, warning: model ? null : "No effective model is available. Sending without a mode override.", }; } @@ -140,10 +134,6 @@ export function resetRuntimeOverride(): RuntimeOverride { return { kind: "resetPending" }; } -export function commitRuntimeOverride(override: RuntimeOverride): RuntimeOverride { - return override.kind === "resetPending" ? defaultRuntimeOverride() : override; -} - export function runtimeOverridePayload(override: RuntimeOverride): T | null | undefined { if (override.kind === "set") return override.value; if (override.kind === "resetPending") return null; @@ -152,8 +142,8 @@ export function runtimeOverridePayload(override: RuntimeOverride): T | nul export function runtimeOverrideLabel(override: RuntimeOverride): string { if (override.kind === "set") return String(override.value); - if (override.kind === "resetPending") return "(reset pending)"; - return "(default)"; + if (override.kind === "resetPending") return "(reset to config)"; + return "(none)"; } function configuredServiceTier(config: Record): ServiceTier | null { diff --git a/src/runtime/view.ts b/src/runtime/view.ts index d102df96..2a9e40b8 100644 --- a/src/runtime/view.ts +++ b/src/runtime/view.ts @@ -7,8 +7,8 @@ import { currentModel, currentReasoningEffort, fastModeLabel, + runtimeOverrideLabel, serviceTierLabel, - type RuntimeOverride, type RuntimeSnapshot, } from "./state"; @@ -118,10 +118,10 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st rows: [ { key: "model", value: currentModel(snapshot, config) ?? "(from default)" }, { key: "config model", value: configuredModel(snapshot, config) ?? "(not reported)" }, - { key: "model change", value: pendingRuntimeChangeLabel(snapshot.requestedModel) }, + { key: "model change", value: runtimeOverrideLabel(snapshot.requestedModel) }, { key: "effort", value: currentReasoningEffort(snapshot, config) ?? "(from default)" }, { key: "config effort", value: configuredReasoningEffort(snapshot, config) ?? "(not reported)" }, - { key: "effort change", value: pendingRuntimeChangeLabel(snapshot.requestedReasoningEffort) }, + { key: "effort change", value: runtimeOverrideLabel(snapshot.requestedReasoningEffort) }, { key: "reasoning summary", value: stringValue(config.model_reasoning_summary, "(from default)") }, { key: "verbosity", value: stringValue(config.model_verbosity, "(from default)") }, { key: "mode", value: snapshot.activeCollaborationMode === "plan" ? "Plan" : "Default" }, @@ -184,12 +184,6 @@ function modeLabel(mode: RuntimeSnapshot["requestedCollaborationMode"]): string return mode === "plan" ? "Plan" : "Default"; } -function pendingRuntimeChangeLabel(override: RuntimeOverride): string { - if (override.kind === "set") return String(override.value); - if (override.kind === "resetPending") return "(reset to config)"; - return "(none)"; -} - function rateLimitWindowSummary( fallbackLabel: string, window: RateLimitWindow | null, diff --git a/src/ui/toolbar.ts b/src/ui/toolbar.ts index bfd2aa05..d1b3453d 100644 --- a/src/ui/toolbar.ts +++ b/src/ui/toolbar.ts @@ -35,6 +35,11 @@ export interface ToolbarDiagnosticRow { level?: "normal" | "warning" | "error"; } +export interface ToolbarDiagnosticSection { + title: string; + rows: ToolbarDiagnosticRow[]; +} + export interface ToolbarViewModel { connected: boolean; status: string; @@ -57,7 +62,7 @@ export interface ToolbarViewModel { modelChoices: ToolbarChoice[]; effortChoices: ToolbarChoice[]; connectLabel: string; - diagnostics: ToolbarDiagnosticRow[]; + diagnostics: ToolbarDiagnosticSection[]; diagnosticAlertLevel: ToolbarDiagnosticAlertLevel; } @@ -109,7 +114,10 @@ export function toolbarSignature(model: ToolbarViewModel): string { modelChoices: model.modelChoices.map((choice) => `${choice.label}:${choice.selected}:${choice.disabled}:${choice.meta ?? ""}`), effortChoices: model.effortChoices.map((choice) => `${choice.label}:${choice.selected}:${choice.disabled}:${choice.meta ?? ""}`), connectLabel: model.connectLabel, - diagnostics: model.diagnostics.map((row) => `${row.label}:${row.value}:${row.level ?? "normal"}`), + diagnostics: model.diagnostics.map((section) => ({ + title: section.title, + rows: section.rows.map((row) => `${row.label}:${row.value}:${row.level ?? "normal"}`), + })), diagnosticAlertLevel: model.diagnosticAlertLevel, }); } @@ -282,16 +290,19 @@ function renderRateLimitPanel(parent: HTMLElement, rateLimit: RateLimitSummary | } } -function renderConnectionDiagnostics(parent: HTMLElement, rows: ToolbarDiagnosticRow[]): void { +function renderConnectionDiagnostics(parent: HTMLElement, sections: ToolbarDiagnosticSection[]): void { const diagnostics = parent.createDiv({ cls: "codex-panel__connection-diagnostics" }); - diagnostics.createDiv({ cls: "codex-panel__connection-diagnostics-title", text: "Connection diagnostics" }); - const list = diagnostics.createEl("dl", { cls: "codex-panel__connection-diagnostics-list" }); - for (const row of rows) { - const item = list.createDiv({ - cls: `codex-panel__connection-diagnostics-row codex-panel__connection-diagnostics-row--${row.level ?? "normal"}`, - }); - item.createEl("dt", { text: row.label }); - item.createEl("dd", { text: row.value }); + diagnostics.createDiv({ cls: "codex-panel__connection-diagnostics-title", text: "Connection" }); + for (const section of sections) { + diagnostics.createDiv({ cls: "codex-panel__connection-diagnostics-section", text: section.title }); + const list = diagnostics.createEl("dl", { cls: "codex-panel__connection-diagnostics-list" }); + for (const row of section.rows) { + const item = list.createDiv({ + cls: `codex-panel__connection-diagnostics-row codex-panel__connection-diagnostics-row--${row.level ?? "normal"}`, + }); + item.createEl("dt", { text: row.label }); + item.createEl("dd", { text: row.value }); + } } } diff --git a/styles.css b/styles.css index 9f521c33..b68b307c 100644 --- a/styles.css +++ b/styles.css @@ -560,6 +560,18 @@ line-height: var(--line-height-tight); } +.codex-panel__connection-diagnostics-section { + padding: 6px 8px 2px; + color: var(--text-faint); + font-size: var(--font-ui-smaller); + font-weight: var(--font-semibold); + line-height: var(--line-height-tight); +} + +.codex-panel__connection-diagnostics-title + .codex-panel__connection-diagnostics-section { + padding-top: 0; +} + .codex-panel__connection-diagnostics-list { margin: 0; cursor: text; @@ -568,7 +580,7 @@ .codex-panel__connection-diagnostics-row { display: grid; - grid-template-columns: minmax(96px, 36%) minmax(0, 1fr); + grid-template-columns: minmax(0, 36%) minmax(0, 1fr); gap: var(--codex-panel-section-gap); padding: 4px 8px; } @@ -583,6 +595,7 @@ .codex-panel__connection-diagnostics-row dt { color: var(--text-faint); + overflow-wrap: anywhere; } .codex-panel__connection-diagnostics-row dd { diff --git a/tests/composer/slash-commands.test.ts b/tests/composer/slash-commands.test.ts index 4ae05238..4514b6ea 100644 --- a/tests/composer/slash-commands.test.ts +++ b/tests/composer/slash-commands.test.ts @@ -28,7 +28,7 @@ function context(overrides: Partial = {}): SlashCo setRequestedModel: vi.fn(), setRequestedReasoningEffort: vi.fn(), statusSummaryLines: () => ["status"], - connectionDiagnosticLines: () => ["doctor"], + connectionDiagnosticDetails: () => [{ title: "Process", rows: [{ key: "connection", value: "connected" }] }], mcpStatusLines: vi.fn().mockResolvedValue(["mcp"]), modelStatusLines: () => ["model"], effortStatusLines: () => ["effort"], @@ -322,6 +322,16 @@ describe("slash commands", () => { ]); }); + it("shows doctor diagnostics as shared structured sections", async () => { + const details = [{ title: "Process", rows: [{ key: "connection", value: "connected" }] }]; + const ctx = context({ connectionDiagnosticDetails: () => details }); + + await executeSlashCommand("doctor", "", ctx); + + expect(ctx.addSystemMessage).not.toHaveBeenCalled(); + expect(ctx.addStructuredSystemMessage).toHaveBeenCalledWith("Connection diagnostics", details); + }); + it("documents that /plan can take a message", () => { expect(slashCommandHelpLines().find((line) => line.startsWith("/plan"))).toBe( "/plan - Toggle Plan mode, optionally sending a message.", diff --git a/tests/panel/diagnostics.test.ts b/tests/panel/diagnostics.test.ts index 2adfc305..d6d1db26 100644 --- a/tests/panel/diagnostics.test.ts +++ b/tests/panel/diagnostics.test.ts @@ -6,7 +6,7 @@ import { createAppServerDiagnostics, upsertMcpServerDiagnostic, } from "../../src/app-server/compatibility"; -import { connectionDiagnosticLines, connectionDiagnosticRows, diagnosticAlertLevel } from "../../src/panel/diagnostics"; +import { connectionDiagnosticSections, diagnosticAlertLevel } from "../../src/panel/diagnostics"; describe("connection diagnostics", () => { it("formats base rows, capability probes, and MCP issues for /doctor", () => { @@ -28,7 +28,7 @@ describe("connection diagnostics", () => { message: null, }); - const rows = connectionDiagnosticRows({ + const sections = connectionDiagnosticSections({ connected: true, configuredCommand: "/opt/homebrew/bin/codex", initializeResponse: { @@ -41,18 +41,22 @@ describe("connection diagnostics", () => { diagnostics, }); + const rows = sections.flatMap((section) => section.rows); + expect(sections.map((section) => section.title)).toEqual(["Process", "Capabilities", "MCP issues"]); expect(rows.map((row) => `${row.label}: ${row.value}`)).toEqual( expect.arrayContaining([ "connection: connected", - "capability model/list: ok (12 models)", - "capability skills/list: unsupported - unknown method skills/list", + "model/list: ok (12 models)", + "skills/list: unsupported - unknown method skills/list", "mcp docs: ready - auth notLoggedIn", "mcp github: failed - missing token", ]), ); - expect(rows.find((row) => row.label === "capability skills/list")?.level).toBe("warning"); + expect(rows.find((row) => row.label === "skills/list")?.level).toBe("warning"); expect(rows.find((row) => row.label === "mcp github")?.level).toBe("error"); - expect(connectionDiagnosticLines(rows)[0]).toBe("Connection diagnostics"); + expect(sections.find((section) => section.title === "MCP issues")?.rows).toEqual( + expect.arrayContaining([expect.objectContaining({ label: "mcp github", value: "failed - missing token" })]), + ); }); it("derives a top-level diagnostic alert without treating unknown or unsupported probes as warnings", () => { diff --git a/tests/runtime/runtime-settings.test.ts b/tests/runtime/runtime-settings.test.ts index dfe03839..c55d7f37 100644 --- a/tests/runtime/runtime-settings.test.ts +++ b/tests/runtime/runtime-settings.test.ts @@ -19,6 +19,7 @@ import { requestedOrConfiguredServiceTier, requestedTurnRuntimeSettings, resetRuntimeOverride, + runtimeOverridePayload, setRuntimeOverride, serviceTierLabel, type RuntimeSnapshot, @@ -61,7 +62,7 @@ describe("runtime settings", () => { expect(compactContextLabel(null, "1.2K tokens")).toBe("1.2K tokens"); }); - it("keeps runtime defaults, resets, and turn payload semantics distinct", () => { + it("keeps runtime defaults, resets, and collaboration mode semantics distinct", () => { const snapshot = runtimeSnapshot({ requestedModel: resetRuntimeOverride(), requestedReasoningEffort: resetRuntimeOverride(), @@ -70,16 +71,16 @@ describe("runtime settings", () => { expect(currentModel(snapshot)).toBe("gpt-5.5"); expect(currentReasoningEffort(snapshot)).toBe("high"); expect(requestedTurnRuntimeSettings(snapshot)).toMatchObject({ - model: null, - effort: null, collaborationMode: { mode: "default", settings: { model: "gpt-5.5", reasoning_effort: "high" }, }, }); + expect(runtimeOverridePayload(snapshot.requestedModel)).toBeNull(); + expect(runtimeOverridePayload(snapshot.requestedReasoningEffort)).toBeNull(); }); - it("serializes explicit runtime overrides as turn payload values", () => { + it("uses explicit runtime overrides as current values and settings payload values", () => { const snapshot = runtimeSnapshot({ requestedModel: setRuntimeOverride("gpt-5.4"), requestedReasoningEffort: setRuntimeOverride("low"), @@ -87,10 +88,8 @@ describe("runtime settings", () => { expect(currentModel(snapshot)).toBe("gpt-5.4"); expect(currentReasoningEffort(snapshot)).toBe("low"); - expect(requestedTurnRuntimeSettings(snapshot)).toMatchObject({ - model: "gpt-5.4", - effort: "low", - }); + expect(runtimeOverridePayload(snapshot.requestedModel)).toBe("gpt-5.4"); + expect(runtimeOverridePayload(snapshot.requestedReasoningEffort)).toBe("low"); }); it("resolves approval reviewer from requested, active, then effective config", () => { @@ -120,13 +119,12 @@ describe("runtime settings", () => { ).toBe("guardian_subagent"); }); - it("serializes requested approval reviewer as a turn override", () => { + it("resolves requested approval reviewer without adding it to turn runtime settings", () => { const snapshot = runtimeSnapshot({ requestedApprovalsReviewer: "auto_review" }); expect(autoReviewActive(snapshot)).toBe(true); - expect(requestedTurnRuntimeSettings(snapshot)).toMatchObject({ - approvalsReviewer: "auto_review", - }); + expect(currentApprovalsReviewer(snapshot)).toBe("auto_review"); + expect(requestedTurnRuntimeSettings(snapshot)).not.toHaveProperty("approvalsReviewer"); }); it("treats active thread runtime as display state without persisting it into turn overrides", () => { @@ -139,13 +137,13 @@ describe("runtime settings", () => { expect(currentModel(snapshot)).toBe("gpt-5-active"); expect(currentServiceTier(snapshot)).toBe("fast"); expect(requestedTurnRuntimeSettings(snapshot)).toMatchObject({ - model: undefined, - effort: undefined, collaborationMode: { mode: "default", settings: { model: "gpt-5-active" }, }, }); + expect(requestedTurnRuntimeSettings(snapshot)).not.toHaveProperty("model"); + expect(requestedTurnRuntimeSettings(snapshot)).not.toHaveProperty("effort"); }); it("separates effective runtime, config defaults, and pending changes in status details", () => { diff --git a/tests/ui/view-renderers.test.ts b/tests/ui/view-renderers.test.ts index d14190a3..2b368c38 100644 --- a/tests/ui/view-renderers.test.ts +++ b/tests/ui/view-renderers.test.ts @@ -1558,7 +1558,10 @@ describe("toolbar renderer decisions", () => { toolbarSignature({ ...baseModel, effortChoices: [...baseModel.effortChoices, { label: "xhigh", onClick: vi.fn() }] }), ); expect(toolbarSignature(baseModel)).not.toBe( - toolbarSignature({ ...baseModel, diagnostics: [{ label: "compatibility", value: "model/list failed", level: "error" }] }), + toolbarSignature({ + ...baseModel, + diagnostics: [{ title: "Capabilities", rows: [{ label: "compatibility", value: "model/list failed", level: "error" }] }], + }), ); expect(toolbarSignature(baseModel)).not.toBe(toolbarSignature({ ...baseModel, autoReviewActive: true })); expect(toolbarSignature(baseModel)).not.toBe( @@ -1628,14 +1631,16 @@ describe("toolbar renderer decisions", () => { statusPanelOpen: true, openPanel: "status", diagnostics: [ - { label: "running app-server", value: "codex-cli/1.2.3" }, - { label: "compatibility", value: "model/list failed", level: "error" }, + { title: "Process", rows: [{ label: "running app-server", value: "codex-cli/1.2.3" }] }, + { title: "Capabilities", rows: [{ label: "compatibility", value: "model/list failed", level: "error" }] }, ], }), toolbarActions({ refreshDiagnostics }), ); - expect(parent.querySelector(".codex-panel__connection-diagnostics-title")?.textContent).toBe("Connection diagnostics"); + expect(parent.querySelector(".codex-panel__connection-diagnostics-title")?.textContent).toBe("Connection"); + expect(parent.textContent).toContain("Process"); + expect(parent.textContent).toContain("Capabilities"); expect(parent.textContent).toContain("Effective Codex config"); expect(parent.textContent).toContain("Refresh diagnostics"); expect(parent.textContent).toContain("codex-cli/1.2.3"); @@ -2153,7 +2158,7 @@ function toolbarModel(overrides: Partial = {}): ToolbarViewMod modelChoices: [{ label: "Default", selected: true, onClick: vi.fn() }], effortChoices: [{ label: "Default", selected: true, onClick: vi.fn() }], connectLabel: "Reconnect", - diagnostics: [{ label: "running app-server", value: "codex-cli/test" }], + diagnostics: [{ title: "Process", rows: [{ label: "running app-server", value: "codex-cli/test" }] }], diagnosticAlertLevel: "normal", ...overrides, };