diff --git a/packages/review-editor/components/EditModeAnnouncementDialog.test.tsx b/packages/review-editor/components/EditModeAnnouncementDialog.test.tsx index ad62b89aa..ca2857b0e 100644 --- a/packages/review-editor/components/EditModeAnnouncementDialog.test.tsx +++ b/packages/review-editor/components/EditModeAnnouncementDialog.test.tsx @@ -70,8 +70,10 @@ describe('EditModeAnnouncementDialog', () => { expect(dialog?.textContent).toContain('never writes to your files on disk'); expect(dialog?.textContent).toContain('One file at a time'); expect(dialog?.textContent).toContain('Settings → Editor → Edit Code to Suggest'); - expect(buttonWithText('Turn it on')).toBeTruthy(); - expect(buttonWithText('Keep it off')).toBeTruthy(); + const enableSwitch = document.querySelector('[role="switch"]'); + expect(enableSwitch).not.toBeNull(); + expect(enableSwitch?.getAttribute('aria-checked')).toBe('false'); + expect(buttonWithText('Done')).toBeTruthy(); }); test.skipIf(!hasDom)('renders the bundled demo recording by default', async () => { @@ -108,30 +110,53 @@ describe('EditModeAnnouncementDialog', () => { expect(document.querySelector('[data-edit-mode-demo-placeholder]')).toBeNull(); }); - test.skipIf(!hasDom)('"Turn it on" fires onEnable and never onDismiss', async () => { + test.skipIf(!hasDom)('Done with the switch untouched dismisses without enabling', async () => { const onEnable = mock(() => {}); const onDismiss = mock(() => {}); await mountDialog({ onEnable, onDismiss }); - expect(document.activeElement?.textContent).toContain('Turn it on'); + // The focused default action must be the consent-neutral one: pressing it + // blind keeps the feature off. + expect(document.activeElement?.textContent).toContain('Done'); - await act(async () => buttonWithText('Turn it on').click()); + await act(async () => buttonWithText('Done').click()); + + expect(onDismiss).toHaveBeenCalledTimes(1); + expect(onEnable).not.toHaveBeenCalled(); + expect(document.querySelector('[data-edit-mode-announcement-dialog]')).toBeNull(); + }); + + test.skipIf(!hasDom)('flipping the switch then Done fires onEnable and never onDismiss', async () => { + const onEnable = mock(() => {}); + const onDismiss = mock(() => {}); + await mountDialog({ onEnable, onDismiss }); + + const enableSwitch = document.querySelector('[role="switch"]'); + expect(enableSwitch).not.toBeNull(); + await act(async () => enableSwitch!.click()); + expect(enableSwitch!.getAttribute('aria-checked')).toBe('true'); + + await act(async () => buttonWithText('Done').click()); expect(onEnable).toHaveBeenCalledTimes(1); expect(onDismiss).not.toHaveBeenCalled(); expect(document.querySelector('[data-edit-mode-announcement-dialog]')).toBeNull(); }); - test.skipIf(!hasDom)('"Keep it off" dismisses without enabling', async () => { + test.skipIf(!hasDom)('the switch is a toggle: on then off again ends with a plain dismissal', async () => { const onEnable = mock(() => {}); const onDismiss = mock(() => {}); await mountDialog({ onEnable, onDismiss }); - await act(async () => buttonWithText('Keep it off').click()); + const enableSwitch = document.querySelector('[role="switch"]'); + await act(async () => enableSwitch!.click()); + await act(async () => enableSwitch!.click()); + expect(enableSwitch!.getAttribute('aria-checked')).toBe('false'); + + await act(async () => buttonWithText('Done').click()); expect(onDismiss).toHaveBeenCalledTimes(1); expect(onEnable).not.toHaveBeenCalled(); - expect(document.querySelector('[data-edit-mode-announcement-dialog]')).toBeNull(); }); test.skipIf(!hasDom)('Escape dismisses the dialog and restores prior focus', async () => { diff --git a/packages/review-editor/components/EditModeAnnouncementDialog.tsx b/packages/review-editor/components/EditModeAnnouncementDialog.tsx index 9e115425e..d17f0c5ee 100644 --- a/packages/review-editor/components/EditModeAnnouncementDialog.tsx +++ b/packages/review-editor/components/EditModeAnnouncementDialog.tsx @@ -1,6 +1,7 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Ban, MessageSquarePlus, Pencil, Send } from 'lucide-react'; +import { TextShimmer } from '@plannotator/ui/components/TextShimmer'; import { EDIT_MODE_DEMO_POSTER_SRC, EDIT_MODE_DEMO_VIDEO_SRC } from './editModeDemoMedia'; /** @@ -127,6 +128,7 @@ export function EditModeAnnouncementDialog({ const primaryActionRef = useRef(null); const previousFocusRef = useRef(null); const onDismissRef = useRef(onDismiss); + const [enableChoice, setEnableChoice] = useState(false); useEffect(() => { onDismissRef.current = onDismiss; @@ -135,6 +137,8 @@ export function EditModeAnnouncementDialog({ useEffect(() => { if (!isOpen) return; + // A reopened dialog must not remember a previously flipped switch. + setEnableChoice(false); previousFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null; @@ -198,8 +202,8 @@ export function EditModeAnnouncementDialog({ className="mt-1.5 max-w-3xl text-sm leading-relaxed text-muted-foreground" > A new way to give review feedback: make the change you want to see, right in the - diff. It is experimental and off by default. Turn it on now or keep it off, and - nothing else changes. + diff. It is experimental and off by default. Turn it on with the switch below, or + leave it off, and nothing else changes.

@@ -251,21 +255,41 @@ export function EditModeAnnouncementDialog({

Change this anytime in Settings → Editor → Edit Code to Suggest.

-
- + {/* The enable decision is an explicit switch, deliberately separate from the + dismiss action: a primary "Turn it on" button reads as a generic continue + and gets clicked blind. Done applies whatever the switch says; with the + switch untouched it is a plain dismissal. */} +
+
+ + + Enable Edit Mode + + + +
diff --git a/packages/review-editor/utils/editModeAnnouncement.test.ts b/packages/review-editor/utils/editModeAnnouncement.test.ts index 6af673a3e..4c10824e4 100644 --- a/packages/review-editor/utils/editModeAnnouncement.test.ts +++ b/packages/review-editor/utils/editModeAnnouncement.test.ts @@ -35,7 +35,7 @@ describe('Edit Mode announcement persistence', () => { markEditModeAnnouncementSeen(); - expect(stored.get(SEEN_KEY)).toBe('1'); + expect(stored.get(SEEN_KEY)).toBe('3'); expect(needsEditModeAnnouncement()).toBe(false); }); @@ -48,7 +48,7 @@ describe('Edit Mode announcement persistence', () => { enableEditSuggestionsFromAnnouncement(); expect(stored.get(SETTING_KEY)).toBe('true'); - expect(stored.get(SEEN_KEY)).toBe('1'); + expect(stored.get(SEEN_KEY)).toBe('3'); expect(needsEditModeAnnouncement()).toBe(false); }); }); diff --git a/packages/review-editor/utils/editModeAnnouncement.ts b/packages/review-editor/utils/editModeAnnouncement.ts index 926145679..e7bded223 100644 --- a/packages/review-editor/utils/editModeAnnouncement.ts +++ b/packages/review-editor/utils/editModeAnnouncement.ts @@ -8,7 +8,9 @@ import { configStore } from '@plannotator/ui/config'; */ const STORAGE_KEY = 'plannotator-edit-mode-announcement-seen'; // Bump to re-show the announcement after a meaningful revision. -const CURRENT_VERSION = '1'; +// '3': shimmer label on the enable switch. '2': footer redesigned from a Turn it on / Keep it off button pair to an +// explicit enable switch plus a neutral Done (pre-release, so nobody re-sees). +const CURRENT_VERSION = '3'; export function needsEditModeAnnouncement(): boolean { return storage.getItem(STORAGE_KEY) !== CURRENT_VERSION;