Skip to content

Commit e0a7269

Browse files
authored
Add expandable notification details and diagnostics copy (#410)
- Default notification details to collapsed - Add redacted diagnostics copy with troubleshooting tips - Expose new notification detail settings in chat preferences
1 parent 1e9ef5b commit e0a7269

8 files changed

Lines changed: 362 additions & 68 deletions

File tree

apps/web/src/appSettings.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ describe("AppSettingsSchema", () => {
1010
expect(settings.codeViewerAutosave).toBe(false);
1111
});
1212

13+
it("defaults notification detail toggles to false", () => {
14+
const settings = Schema.decodeUnknownSync(AppSettingsSchema)({});
15+
16+
expect(settings.showNotificationDetails).toBe(false);
17+
expect(settings.includeDiagnosticsTipsInCopy).toBe(false);
18+
});
19+
1320
it("preserves an explicit codeViewerAutosave setting", () => {
1421
const settings = Schema.decodeUnknownSync(AppSettingsSchema)({
1522
codeViewerAutosave: true,
@@ -18,6 +25,16 @@ describe("AppSettingsSchema", () => {
1825
expect(settings.codeViewerAutosave).toBe(true);
1926
});
2027

28+
it("preserves explicit notification detail settings", () => {
29+
const settings = Schema.decodeUnknownSync(AppSettingsSchema)({
30+
showNotificationDetails: true,
31+
includeDiagnosticsTipsInCopy: true,
32+
});
33+
34+
expect(settings.showNotificationDetails).toBe(true);
35+
expect(settings.includeDiagnosticsTipsInCopy).toBe(true);
36+
});
37+
2138
it("defaults the PR request changes button tone to warning", () => {
2239
const settings = Schema.decodeUnknownSync(AppSettingsSchema)({});
2340

apps/web/src/appSettings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export const AppSettingsSchema = Schema.Struct({
8080
rebaseBeforeCommit: Schema.Boolean.pipe(withDefaults(() => false)),
8181
enableAssistantStreaming: Schema.Boolean.pipe(withDefaults(() => false)),
8282
showAuthFailuresAsErrors: Schema.Boolean.pipe(withDefaults(() => true)),
83+
showNotificationDetails: Schema.Boolean.pipe(withDefaults(() => false)),
84+
includeDiagnosticsTipsInCopy: Schema.Boolean.pipe(withDefaults(() => false)),
8385
locale: AppLocale.pipe(withDefaults(() => DEFAULT_APP_LOCALE)),
8486
openLinksExternally: Schema.Boolean.pipe(withDefaults(() => false)),
8587
sidebarProjectSortOrder: SidebarProjectSortOrder.pipe(

apps/web/src/components/ChatView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4859,6 +4859,8 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
48594859
<ErrorNotificationBar
48604860
threadError={activeThread.error}
48614861
showAuthFailuresAsErrors={settings.showAuthFailuresAsErrors}
4862+
showNotificationDetails={settings.showNotificationDetails}
4863+
includeDiagnosticsTipsInCopy={settings.includeDiagnosticsTipsInCopy}
48624864
onDismissThreadError={() => setThreadError(activeThread.id, null)}
48634865
providerStatus={activeProviderStatus}
48644866
transportState={transportState}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type { ServerProviderStatus } from "@okcode/contracts";
2+
import type { ComponentProps, ReactElement } from "react";
3+
import { renderToStaticMarkup } from "react-dom/server";
4+
import { act, create, type ReactTestRenderer } from "react-test-renderer";
5+
import { afterEach, describe, expect, it, vi } from "vitest";
6+
7+
import { ErrorNotificationBar } from "./ErrorNotificationBar";
8+
9+
function makeProviderStatus(overrides: Partial<ServerProviderStatus> = {}): ServerProviderStatus {
10+
return {
11+
provider: "codex",
12+
status: "warning",
13+
available: true,
14+
authStatus: "authenticated",
15+
checkedAt: "2026-04-10T12:00:00.000Z",
16+
message: "Provider is checking state.",
17+
...overrides,
18+
};
19+
}
20+
21+
const THREAD_ERROR =
22+
"Git command failed in GitCore.createWorktree: OPENAI_API_KEY=sk-proj-secret (/repo) - Base branch 'main' does not resolve to a commit yet.";
23+
24+
function renderBar(
25+
overrides: Partial<ComponentProps<typeof ErrorNotificationBar>> = {},
26+
): ReactElement {
27+
const { onDismissThreadError, transportState, ...restOverrides } = overrides;
28+
return (
29+
<ErrorNotificationBar
30+
threadError={THREAD_ERROR}
31+
showAuthFailuresAsErrors
32+
showNotificationDetails={false}
33+
includeDiagnosticsTipsInCopy={true}
34+
providerStatus={makeProviderStatus()}
35+
isMobileCompanion={false}
36+
{...restOverrides}
37+
{...(onDismissThreadError ? { onDismissThreadError } : {})}
38+
{...(transportState ? { transportState } : {})}
39+
/>
40+
);
41+
}
42+
43+
afterEach(() => {
44+
vi.restoreAllMocks();
45+
});
46+
47+
describe("ErrorNotificationBar", () => {
48+
it("keeps raw error text out of the collapsed bar and shows the aggregate count", () => {
49+
const markup = renderToStaticMarkup(renderBar());
50+
51+
expect(markup).toContain("Show 2 notifications");
52+
expect(markup).not.toContain("OPENAI_API_KEY=sk-proj-secret");
53+
expect(markup).not.toContain("Base branch 'main' does not resolve to a commit yet.");
54+
});
55+
56+
it("expands to show redacted error text and diagnostics copy", async () => {
57+
let renderer: ReactTestRenderer | null = null;
58+
await act(async () => {
59+
renderer = create(renderBar());
60+
});
61+
62+
const root = renderer!.root;
63+
const toggle = root.findByProps({ "aria-label": "Show 2 notifications" });
64+
65+
await act(async () => {
66+
toggle.props.onClick();
67+
});
68+
69+
expect(root.findByProps({ "aria-label": "Hide 2 notifications" })).toBeTruthy();
70+
expect(root.findByProps({ "aria-label": "Copy diagnostics" })).toBeTruthy();
71+
expect(JSON.stringify(renderer!.toJSON())).toContain("Worktree thread could not start");
72+
expect(JSON.stringify(renderer!.toJSON())).toContain(
73+
"Base branch 'main' does not resolve to a commit yet.",
74+
);
75+
});
76+
77+
it("starts expanded when notification details are enabled", () => {
78+
const markup = renderToStaticMarkup(
79+
renderBar({
80+
showNotificationDetails: true,
81+
}),
82+
);
83+
84+
expect(markup).toContain("Hide 2 notifications");
85+
expect(markup).toContain("Worktree thread could not start");
86+
expect(markup).toContain("Base branch &#x27;main&#x27; does not resolve to a commit yet.");
87+
});
88+
});

0 commit comments

Comments
 (0)