Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions e2e-tests/notification_banner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const testWithNotificationsEnabled = testWithConfig({
fs.mkdirSync(userDataDir, { recursive: true });
fs.writeFileSync(
path.join(userDataDir, "user-settings.json"),
JSON.stringify({ enableChatCompletionNotifications: true }, null, 2),
JSON.stringify({ enableChatEventNotifications: true }, null, 2),
);
},
});
Expand All @@ -21,9 +21,7 @@ test("notification banner - skip hides permanently", async ({ po }) => {
// Banner should be visible since notifications are not enabled
const banner = po.page.getByTestId("notification-tip-banner");
await expect(banner).toBeVisible();
await expect(banner).toContainText(
"Get notified when chat responses finish.",
);
await expect(banner).toContainText("Get notified about chat events.");

// Record settings before skipping
const beforeSettings = po.settings.recordSettings();
Expand Down Expand Up @@ -69,7 +67,7 @@ test("notification banner - Enable enables notifications and hides banner", asyn
// Banner should be hidden after enabling
await expect(banner).not.toBeVisible();

// Verify settings were updated with enableChatCompletionNotifications: true
// Verify settings were updated with enableChatEventNotifications: true
po.settings.snapshotSettingsDelta(beforeSettings);

// Navigate away and back to verify banner stays hidden
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
+ "enableChatCompletionNotifications": true
+ "enableChatEventNotifications": true
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { Switch } from "@/components/ui/switch";
import { MacNotificationGuideDialog } from "./MacNotificationGuideDialog";
import { useEnableNotifications } from "@/hooks/useEnableNotifications";

export function ChatCompletionNotificationSwitch() {
export function ChatEventNotificationSwitch() {
const { isEnabled, enable, disable, showMacGuide, setShowMacGuide } =
useEnableNotifications();

return (
<>
<div className="flex items-center space-x-2">
<Switch
id="chat-completion-notifications"
id="chat-event-notifications"
checked={isEnabled}
onCheckedChange={async (checked) => {
if (checked) {
Expand All @@ -21,9 +21,7 @@ export function ChatCompletionNotificationSwitch() {
}
}}
/>
<Label htmlFor="chat-completion-notifications">
Show notification when chat completes
</Label>
<Label htmlFor="chat-event-notifications">Enable notifications</Label>
</div>
<MacNotificationGuideDialog
open={showMacGuide}
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat/NotificationBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function NotificationBanner() {

const showBanner =
settings &&
settings.enableChatCompletionNotifications !== true &&
settings.enableChatEventNotifications !== true &&
settings.skipNotificationBanner !== true;

const handleSkip = () => {
Expand All @@ -22,7 +22,7 @@ export function NotificationBanner() {
{showBanner && (
<SkippableBanner
icon={Bell}
message="Get notified when chat responses finish."
message="Get notified about chat events."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM | microcopy

Banner message uses developer jargon

"Get notified about chat events" uses internal terminology ("chat events") that won't resonate with users. The previous message "Get notified when chat responses finish" was more concrete and user-friendly.

💡 Suggestion: Use a message that describes the actual benefit, e.g.:

Suggested change
message="Get notified about chat events."
message="Get notified when responses finish or input is needed."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM | microcopy

"Chat events" is vague, user-unfriendly microcopy

"Get notified about chat events." uses developer jargon — users don't think in terms of "chat events." The previous copy ("Get notified when chat responses finish.") was more specific and action-oriented. Since the feature now covers two cases, the banner should communicate both clearly.

💡 Suggestion:

Suggested change
message="Get notified about chat events."
message="Get notified when responses finish or input is needed."

enableLabel="Enable"
onEnable={enable}
onSkip={handleSkip}
Expand Down
8 changes: 4 additions & 4 deletions src/hooks/useEnableNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { detectIsMac } from "@/hooks/useChatModeToggle";
function sendTestNotification() {
if (Notification.permission === "granted") {
new Notification("Dyad", {
body: "Notifications are working! You'll be notified when chat responses complete.",
body: "Notifications are working! You'll be notified when responses finish or input is needed.",
});
}
}

export function useEnableNotifications() {
const { settings, updateSettings } = useSettings();
const [showMacGuide, setShowMacGuide] = useState(false);
const isEnabled = settings?.enableChatCompletionNotifications === true;
const isEnabled = settings?.enableChatEventNotifications === true;
const isMac = detectIsMac();
const openMacGuide = useCallback(() => {
if (isMac) {
Expand All @@ -33,13 +33,13 @@ export function useEnableNotifications() {
return;
}
}
await updateSettings({ enableChatCompletionNotifications: true });
await updateSettings({ enableChatEventNotifications: true });
sendTestNotification();
openMacGuide();
}, [updateSettings, openMacGuide]);

const disable = useCallback(async () => {
await updateSettings({ enableChatCompletionNotifications: false });
await updateSettings({ enableChatEventNotifications: false });
}, [updateSettings]);

return { isEnabled, enable, disable, showMacGuide, setShowMacGuide };
Expand Down
13 changes: 13 additions & 0 deletions src/hooks/usePlanEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,19 @@ export function usePlanEvents() {
next.set(payload.chatId, payload);
return next;
});

// Show native notification if enabled and window is not focused
const notificationsEnabled =
settingsRef.current?.enableChatEventNotifications === true;
if (
notificationsEnabled &&
Notification.permission === "granted" &&
!document.hasFocus()
) {
new Notification("Dyad", {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use a more helpful notification title,

new Notification(appName, {

body: "A questionnaire needs your input",
});
}
},
);

Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useStreamChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export function useStreamChat({
// Show native notification if enabled and window is not focused
// Fire-and-forget to avoid blocking UI updates
const notificationsEnabled =
settings?.enableChatCompletionNotifications === true;
settings?.enableChatEventNotifications === true;
if (
notificationsEnabled &&
Notification.permission === "granted" &&
Expand Down
7 changes: 6 additions & 1 deletion src/lib/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ const BaseUserSettingsFields = {

enableAutoFixProblems: z.boolean().optional(),
autoExpandPreviewPanel: z.boolean().optional(),
enableChatCompletionNotifications: z.boolean().optional(),
enableChatEventNotifications: z.boolean().optional(),
enableNativeGit: z.boolean().optional(),
enableMcpServersForBuildMode: z.boolean().optional(),
enableAutoUpdate: z.boolean(),
Expand Down Expand Up @@ -365,6 +365,8 @@ export const StoredUserSettingsSchema = z
// Use StoredChatModeSchema to allow deprecated "agent" value
selectedChatMode: StoredChatModeSchema.optional(),
defaultChatMode: StoredChatModeSchema.optional(),
// Deprecated: renamed to enableChatEventNotifications
enableChatCompletionNotifications: z.boolean().optional(),
})
// Allow unknown properties to pass through (e.g. future settings
// that should be preserved if user downgrades to an older version)
Expand Down Expand Up @@ -419,6 +421,9 @@ export function migrateStoredSettings(
...stored,
selectedChatMode: migrateStoredChatMode(stored.selectedChatMode),
defaultChatMode: migrateStoredChatMode(stored.defaultChatMode),
enableChatEventNotifications:
stored.enableChatEventNotifications ??
stored.enableChatCompletionNotifications,
Comment on lines +424 to +426
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the legacy notification key in sync on write

This migration only fixes the upgrade path. readSettings()/writeSettings() still preserve deprecated fields for backward compatibility (src/main/settings.ts), but the renderer now updates only enableChatEventNotifications, so after a user toggles notifications in this build the JSON can contain a stale enableChatCompletionNotifications. Reopening the same profile in an older Dyad release will then restore the wrong notification preference instead of the value the user last chose.

Useful? React with 👍 / 👎.

};
}

Expand Down
17 changes: 12 additions & 5 deletions src/lib/settingsSearchIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const SETTING_IDS = {
autoApprove: "setting-auto-approve",
autoFix: "setting-auto-fix",
autoExpandPreview: "setting-auto-expand-preview",
chatCompletionNotification: "setting-chat-completion-notification",
chatEventNotification: "setting-chat-event-notification",
thinkingBudget: "setting-thinking-budget",
maxChatTurns: "setting-max-chat-turns",
maxToolCallSteps: "setting-max-tool-call-steps",
Expand Down Expand Up @@ -134,11 +134,18 @@ export const SETTINGS_SEARCH_INDEX: SearchableSettingItem[] = [
sectionLabel: "Workflow",
},
{
id: SETTING_IDS.chatCompletionNotification,
label: "Chat Completion Notification",
id: SETTING_IDS.chatEventNotification,
label: "Notifications",
description:
"Show a native notification when a chat response completes while the app is not focused",
keywords: ["notification", "chat", "complete", "alert", "background"],
"Show native notifications when a chat response completes or a questionnaire needs your input while the app is not focused",
keywords: [
"notification",
"chat",
"complete",
"questionnaire",
"alert",
"background",
],
sectionId: SECTION_IDS.workflow,
sectionLabel: "Workflow",
},
Expand Down
13 changes: 5 additions & 8 deletions src/pages/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { AutoFixProblemsSwitch } from "@/components/AutoFixProblemsSwitch";
import { AutoExpandPreviewSwitch } from "@/components/AutoExpandPreviewSwitch";
import { ChatCompletionNotificationSwitch } from "@/components/ChatCompletionNotificationSwitch";
import { ChatEventNotificationSwitch } from "@/components/ChatEventNotificationSwitch";
import { AutoUpdateSwitch } from "@/components/AutoUpdateSwitch";
import { ReleaseChannelSelector } from "@/components/ReleaseChannelSelector";
import { NeonIntegration } from "@/components/NeonIntegration";
Expand Down Expand Up @@ -408,14 +408,11 @@ export function WorkflowSettings() {
</div>
</div>

<div
id={SETTING_IDS.chatCompletionNotification}
className="space-y-1 mt-4"
>
<ChatCompletionNotificationSwitch />
<div id={SETTING_IDS.chatEventNotification} className="space-y-1 mt-4">
<ChatEventNotificationSwitch />
<div className="text-sm text-gray-500 dark:text-gray-400">
Show a native notification when a chat response completes while the
app is not focused.
Show native notifications when a chat response completes or a
questionnaire needs your input while the app is not focused.
</div>
</div>
</div>
Expand Down
Loading