From 59db1389db53b512b8bfda70f3bc0c3bc31bab61 Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Wed, 4 Mar 2026 15:45:22 -0800 Subject: [PATCH 1/3] feat: add What's New v0.11.0 first-open dialog One-time announcement dialog highlighting auto-save drafts, short link sharing with E2E encryption, and Obsidian vault browser. Supersedes the PlanDiffMarketing dialog in the chain. Co-Authored-By: Claude Opus 4.6 --- packages/editor/App.tsx | 35 ++++---- packages/ui/components/WhatsNewV011.tsx | 113 ++++++++++++++++++++++++ packages/ui/utils/whatsNew.ts | 11 +++ 3 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 packages/ui/components/WhatsNewV011.tsx create mode 100644 packages/ui/utils/whatsNew.ts diff --git a/packages/editor/App.tsx b/packages/editor/App.tsx index 405c07e..ae35695 100644 --- a/packages/editor/App.tsx +++ b/packages/editor/App.tsx @@ -34,8 +34,8 @@ import { } from '@plannotator/ui/utils/permissionMode'; import { PermissionModeSetup } from '@plannotator/ui/components/PermissionModeSetup'; import { UIFeaturesSetup } from '@plannotator/ui/components/UIFeaturesSetup'; -import { PlanDiffMarketing } from '@plannotator/ui/components/plan-diff/PlanDiffMarketing'; -import { needsPlanDiffMarketingDialog } from '@plannotator/ui/utils/planDiffMarketing'; +import { WhatsNewV011 } from '@plannotator/ui/components/WhatsNewV011'; +import { needsWhatsNewDialog } from '@plannotator/ui/utils/whatsNew'; import { ImageAnnotator } from '@plannotator/ui/components/ImageAnnotator'; import { deriveImageName } from '@plannotator/ui/components/AttachmentsButton'; import { useSidebar } from '@plannotator/ui/hooks/useSidebar'; @@ -371,7 +371,7 @@ const App: React.FC = () => { const [pendingPasteImage, setPendingPasteImage] = useState<{ file: File; blobUrl: string; initialName: string } | null>(null); const [showPermissionModeSetup, setShowPermissionModeSetup] = useState(false); const [showUIFeaturesSetup, setShowUIFeaturesSetup] = useState(false); - const [showPlanDiffMarketing, setShowPlanDiffMarketing] = useState(false); + const [showWhatsNew, setShowWhatsNew] = useState(false); const [permissionMode, setPermissionMode] = useState('bypassPermissions'); const [sharingEnabled, setSharingEnabled] = useState(true); const [shareBaseUrl, setShareBaseUrl] = useState(undefined); @@ -617,8 +617,8 @@ const App: React.FC = () => { setShowPermissionModeSetup(true); } else if (needsUIFeaturesSetup()) { setShowUIFeaturesSetup(true); - } else if (needsPlanDiffMarketingDialog()) { - setShowPlanDiffMarketing(true); + } else if (needsWhatsNewDialog()) { + setShowWhatsNew(true); } // Load saved permission mode preference setPermissionMode(getPermissionModeSettings().mode); @@ -806,7 +806,7 @@ const App: React.FC = () => { // Don't intercept if any modal is open if (showExport || showImport || showFeedbackPrompt || showClaudeCodeWarning || - showAgentWarning || showPermissionModeSetup || showUIFeaturesSetup || showPlanDiffMarketing || pendingPasteImage) return; + showAgentWarning || showPermissionModeSetup || showUIFeaturesSetup || showWhatsNew || pendingPasteImage) return; // Don't intercept if already submitted or submitting if (submitted || isSubmitting) return; @@ -850,7 +850,7 @@ const App: React.FC = () => { return () => window.removeEventListener('keydown', handleKeyDown); }, [ showExport, showImport, showFeedbackPrompt, showClaudeCodeWarning, showAgentWarning, - showPermissionModeSetup, showUIFeaturesSetup, showPlanDiffMarketing, pendingPasteImage, + showPermissionModeSetup, showUIFeaturesSetup, showWhatsNew, pendingPasteImage, submitted, isSubmitting, isApiMode, linkedDocHook.isActive, annotations.length, annotateMode, origin, getAgentWarning, ]); @@ -977,7 +977,7 @@ const App: React.FC = () => { if (tag === 'INPUT' || tag === 'TEXTAREA') return; if (showExport || showFeedbackPrompt || showClaudeCodeWarning || - showAgentWarning || showPermissionModeSetup || showUIFeaturesSetup || showPlanDiffMarketing || pendingPasteImage) return; + showAgentWarning || showPermissionModeSetup || showUIFeaturesSetup || showWhatsNew || pendingPasteImage) return; if (submitted || !isApiMode) return; @@ -1003,7 +1003,7 @@ const App: React.FC = () => { return () => window.removeEventListener('keydown', handleSaveShortcut); }, [ showExport, showFeedbackPrompt, showClaudeCodeWarning, showAgentWarning, - showPermissionModeSetup, showUIFeaturesSetup, showPlanDiffMarketing, pendingPasteImage, + showPermissionModeSetup, showUIFeaturesSetup, showWhatsNew, pendingPasteImage, submitted, isApiMode, markdown, annotationsOutput, ]); @@ -1537,8 +1537,8 @@ const App: React.FC = () => { setShowPermissionModeSetup(false); if (needsUIFeaturesSetup()) { setShowUIFeaturesSetup(true); - } else if (needsPlanDiffMarketingDialog()) { - setShowPlanDiffMarketing(true); + } else if (needsWhatsNewDialog()) { + setShowWhatsNew(true); } }} /> @@ -1549,18 +1549,17 @@ const App: React.FC = () => { onComplete={(prefs) => { setUiPrefs(prefs); setShowUIFeaturesSetup(false); - if (needsPlanDiffMarketingDialog()) { - setShowPlanDiffMarketing(true); + if (needsWhatsNewDialog()) { + setShowWhatsNew(true); } }} /> - {/* Plan Diff Marketing (feature announcement) */} - { - setShowPlanDiffMarketing(false); + setShowWhatsNew(false); }} /> diff --git a/packages/ui/components/WhatsNewV011.tsx b/packages/ui/components/WhatsNewV011.tsx new file mode 100644 index 0000000..d91f83f --- /dev/null +++ b/packages/ui/components/WhatsNewV011.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import { createPortal } from 'react-dom'; +import { markWhatsNewSeen } from '../utils/whatsNew'; + +const RELEASE_URLS = { + v0100: 'https://github.com/backnotprop/plannotator/releases/tag/v0.10.0', + v0110: 'https://github.com/backnotprop/plannotator/releases/tag/v0.11.0', +}; + +interface WhatsNewV011Props { + isOpen: boolean; + onComplete: () => void; +} + +export const WhatsNewV011: React.FC = ({ + isOpen, + onComplete, +}) => { + if (!isOpen) return null; + + const handleDismiss = () => { + markWhatsNewSeen(); + onComplete(); + }; + + return createPortal( +
+
+ {/* Header */} +
+
+
+ + + +
+

What's New in Plannotator

+
+

+ Here's what's been added since your last update. +

+
+ + {/* Content */} +
+ {/* Feature bullets */} +
+
+
+
+
+

+ Auto-save annotation drafts{' '} + — Your annotations are now automatically saved in the background. If the browser refreshes or the server crashes, you'll be prompted to restore your work. Sorry it took so long to implement this. +

+
+
+
+
+
+

+ Short link sharing{' '} + — Share plans with shorter, more portable links that work across Slack and other platforms, with end-to-end encryption. Currently enabled for large plans that don't fit in the URL; we'll be rolling this out as the default soon. +

+
+
+
+
+
+

+ Obsidian vault browser{' '} + — For Obsidian users, we're building deeper integrations starting with the vault browser. Browse and annotate vault files directly during plan review. Toggle it on under Settings > Saving > Obsidian. +

+
+
+ + {/* Release notes callout */} +

+ Plus many more improvements and bug fixes. See the full release notes:{' '} + + v0.10.0 + + {', '} + + v0.11.0 + +

+
+ + {/* Footer */} +
+ +
+
+
, + document.body + ); +}; diff --git a/packages/ui/utils/whatsNew.ts b/packages/ui/utils/whatsNew.ts new file mode 100644 index 0000000..b124778 --- /dev/null +++ b/packages/ui/utils/whatsNew.ts @@ -0,0 +1,11 @@ +import { storage } from './storage'; + +const STORAGE_KEY = 'plannotator-whats-new-v011-seen'; + +export function needsWhatsNewDialog(): boolean { + return storage.getItem(STORAGE_KEY) !== 'true'; +} + +export function markWhatsNewSeen(): void { + storage.setItem(STORAGE_KEY, 'true'); +} From 1678be2793b28fde708247fe4ae77c9e0405a062 Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Wed, 4 Mar 2026 15:48:57 -0800 Subject: [PATCH 2/3] fix: restore PlanDiffMarketing dialog in the chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PlanDiffMarketing was incorrectly removed. It now stays in the sequence: PermissionMode → UIFeatures → PlanDiffMarketing → WhatsNew v0.11.0 Co-Authored-By: Claude Opus 4.6 --- packages/editor/App.tsx | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/editor/App.tsx b/packages/editor/App.tsx index ae35695..5957af0 100644 --- a/packages/editor/App.tsx +++ b/packages/editor/App.tsx @@ -34,6 +34,8 @@ import { } from '@plannotator/ui/utils/permissionMode'; import { PermissionModeSetup } from '@plannotator/ui/components/PermissionModeSetup'; import { UIFeaturesSetup } from '@plannotator/ui/components/UIFeaturesSetup'; +import { PlanDiffMarketing } from '@plannotator/ui/components/plan-diff/PlanDiffMarketing'; +import { needsPlanDiffMarketingDialog } from '@plannotator/ui/utils/planDiffMarketing'; import { WhatsNewV011 } from '@plannotator/ui/components/WhatsNewV011'; import { needsWhatsNewDialog } from '@plannotator/ui/utils/whatsNew'; import { ImageAnnotator } from '@plannotator/ui/components/ImageAnnotator'; @@ -371,6 +373,7 @@ const App: React.FC = () => { const [pendingPasteImage, setPendingPasteImage] = useState<{ file: File; blobUrl: string; initialName: string } | null>(null); const [showPermissionModeSetup, setShowPermissionModeSetup] = useState(false); const [showUIFeaturesSetup, setShowUIFeaturesSetup] = useState(false); + const [showPlanDiffMarketing, setShowPlanDiffMarketing] = useState(false); const [showWhatsNew, setShowWhatsNew] = useState(false); const [permissionMode, setPermissionMode] = useState('bypassPermissions'); const [sharingEnabled, setSharingEnabled] = useState(true); @@ -617,6 +620,8 @@ const App: React.FC = () => { setShowPermissionModeSetup(true); } else if (needsUIFeaturesSetup()) { setShowUIFeaturesSetup(true); + } else if (needsPlanDiffMarketingDialog()) { + setShowPlanDiffMarketing(true); } else if (needsWhatsNewDialog()) { setShowWhatsNew(true); } @@ -806,7 +811,7 @@ const App: React.FC = () => { // Don't intercept if any modal is open if (showExport || showImport || showFeedbackPrompt || showClaudeCodeWarning || - showAgentWarning || showPermissionModeSetup || showUIFeaturesSetup || showWhatsNew || pendingPasteImage) return; + showAgentWarning || showPermissionModeSetup || showUIFeaturesSetup || showPlanDiffMarketing || showWhatsNew || pendingPasteImage) return; // Don't intercept if already submitted or submitting if (submitted || isSubmitting) return; @@ -977,7 +982,7 @@ const App: React.FC = () => { if (tag === 'INPUT' || tag === 'TEXTAREA') return; if (showExport || showFeedbackPrompt || showClaudeCodeWarning || - showAgentWarning || showPermissionModeSetup || showUIFeaturesSetup || showWhatsNew || pendingPasteImage) return; + showAgentWarning || showPermissionModeSetup || showUIFeaturesSetup || showPlanDiffMarketing || showWhatsNew || pendingPasteImage) return; if (submitted || !isApiMode) return; @@ -1537,6 +1542,8 @@ const App: React.FC = () => { setShowPermissionModeSetup(false); if (needsUIFeaturesSetup()) { setShowUIFeaturesSetup(true); + } else if (needsPlanDiffMarketingDialog()) { + setShowPlanDiffMarketing(true); } else if (needsWhatsNewDialog()) { setShowWhatsNew(true); } @@ -1549,6 +1556,20 @@ const App: React.FC = () => { onComplete={(prefs) => { setUiPrefs(prefs); setShowUIFeaturesSetup(false); + if (needsPlanDiffMarketingDialog()) { + setShowPlanDiffMarketing(true); + } else if (needsWhatsNewDialog()) { + setShowWhatsNew(true); + } + }} + /> + + {/* Plan Diff Marketing (feature announcement) */} + { + setShowPlanDiffMarketing(false); if (needsWhatsNewDialog()) { setShowWhatsNew(true); } From ea9495005affaf81cfa397c3eb560ab928dad862 Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Wed, 4 Mar 2026 15:59:04 -0800 Subject: [PATCH 3/3] fix: restore showPlanDiffMarketing to useEffect dependency arrays The fix commit restored it to guard conditions but missed the dep arrays, creating a stale closure bug for keyboard shortcuts. Co-Authored-By: Claude Opus 4.6 --- packages/editor/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/App.tsx b/packages/editor/App.tsx index 5957af0..90c107c 100644 --- a/packages/editor/App.tsx +++ b/packages/editor/App.tsx @@ -855,7 +855,7 @@ const App: React.FC = () => { return () => window.removeEventListener('keydown', handleKeyDown); }, [ showExport, showImport, showFeedbackPrompt, showClaudeCodeWarning, showAgentWarning, - showPermissionModeSetup, showUIFeaturesSetup, showWhatsNew, pendingPasteImage, + showPermissionModeSetup, showUIFeaturesSetup, showPlanDiffMarketing, showWhatsNew, pendingPasteImage, submitted, isSubmitting, isApiMode, linkedDocHook.isActive, annotations.length, annotateMode, origin, getAgentWarning, ]); @@ -1008,7 +1008,7 @@ const App: React.FC = () => { return () => window.removeEventListener('keydown', handleSaveShortcut); }, [ showExport, showFeedbackPrompt, showClaudeCodeWarning, showAgentWarning, - showPermissionModeSetup, showUIFeaturesSetup, showWhatsNew, pendingPasteImage, + showPermissionModeSetup, showUIFeaturesSetup, showPlanDiffMarketing, showWhatsNew, pendingPasteImage, submitted, isApiMode, markdown, annotationsOutput, ]);