@plannotator/ui 0.53.1 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/HANDOFF.md +217 -11
  2. package/README.md +65 -10
  3. package/components/AgentToolAnnouncementDialog.tsx +1 -1
  4. package/components/AgentsTab.tsx +1 -1
  5. package/components/AnnotationPanel.tsx +142 -24
  6. package/components/AnnotationSidebar.tsx +1 -1
  7. package/components/AnnotationToolbar.tsx +64 -9
  8. package/components/ApproveDropdown.tsx +7 -9
  9. package/components/AskSessionAnnouncementDialog.tsx +1 -1
  10. package/components/AttachmentsButton.tsx +1 -1
  11. package/components/BlockRenderer.tsx +19 -5
  12. package/components/CodeFilePopout.tsx +139 -4
  13. package/components/CommentPopover.tsx +44 -8
  14. package/components/DecisionControl.tsx +31 -5
  15. package/components/DiagramBlock.tsx +17 -12
  16. package/components/EditorAnnotationCard.tsx +1 -1
  17. package/components/ExportModal.tsx +77 -7
  18. package/components/HostTitle.tsx +0 -4
  19. package/components/ImageAnnotator/Toolbar.tsx +25 -1
  20. package/components/ImageAnnotator/index.tsx +24 -4
  21. package/components/ImageLightbox.tsx +658 -120
  22. package/components/ImagePeek.tsx +38 -4
  23. package/components/ImageRegions.tsx +369 -0
  24. package/components/ImageThumbnail.tsx +1 -1
  25. package/components/ImageZoomStage.tsx +796 -0
  26. package/components/InboxAnnouncementDialog.tsx +202 -0
  27. package/components/InlineMarkdown.tsx +152 -24
  28. package/components/KeyboardShortcuts.tsx +23 -0
  29. package/components/LazyBlockBoundary.tsx +100 -0
  30. package/components/ListMarker.tsx +3 -3
  31. package/components/LookAndFeelAnnouncementDialog.tsx +12 -2
  32. package/components/MenuVersionSection.tsx +31 -9
  33. package/components/PlanDecisionMenuExtras.tsx +129 -0
  34. package/components/PlanHeaderMenu.tsx +15 -1
  35. package/components/ProviderIcons.tsx +8 -0
  36. package/components/RefHoverCard.tsx +913 -0
  37. package/components/SendFailureToast.tsx +126 -0
  38. package/components/Settings.tsx +617 -126
  39. package/components/TerminalToolsAnnouncementDialog.tsx +1 -1
  40. package/components/ThemeProvider.tsx +14 -1
  41. package/components/ThemeTab.tsx +16 -24
  42. package/components/Tooltip.tsx +49 -18
  43. package/components/Viewer.tsx +280 -41
  44. package/components/ai/DocumentAIChatPanel.tsx +29 -3
  45. package/components/ai/NewChatButton.tsx +74 -0
  46. package/components/blocks/CodeBlock.tsx +1 -1
  47. package/components/blocks/HtmlBlock.tsx +7 -1
  48. package/components/blocks/QuestionBlock.tsx +522 -62
  49. package/components/diagram/DiagramComposer.tsx +5 -1
  50. package/components/diagram/DiagramPending.tsx +12 -2
  51. package/components/diagram/DiagramViewer.tsx +19 -3
  52. package/components/html-embed/HtmlEmbedBlock.tsx +374 -0
  53. package/components/html-embed/SvgFenceBlock.tsx +46 -0
  54. package/components/html-embed/index.ts +3 -0
  55. package/components/html-embed/languages.ts +15 -0
  56. package/components/html-embed/loadGate.ts +107 -0
  57. package/components/html-viewer/HtmlViewer.tsx +47 -6
  58. package/components/html-viewer/bridge-script.asset.js +173 -1
  59. package/components/html-viewer/bridge-script.ts +173 -1
  60. package/components/html-viewer/useHtmlAnnotation.ts +125 -0
  61. package/components/imageZoom.ts +265 -0
  62. package/components/plan-diff/PlanCleanDiffView.tsx +1 -1
  63. package/components/plan-diff/PlanDiffViewer.tsx +13 -2
  64. package/components/plan-diff/PlanRawDiffView.tsx +1 -1
  65. package/components/sidebar/FileBrowser.tsx +16 -2
  66. package/components/sidebar/SidebarTabs.tsx +3 -0
  67. package/config/index.ts +3 -0
  68. package/config/reviewView.ts +41 -1
  69. package/config/settings.ts +155 -4
  70. package/configure.ts +28 -0
  71. package/hooks/pfm/useCodeFilePopout.ts +7 -1
  72. package/hooks/useAIChat.ts +72 -25
  73. package/hooks/useAnnotationDraft.ts +71 -6
  74. package/hooks/useAnnotationHighlighter.ts +57 -3
  75. package/hooks/useCodeAnnotationDraft.ts +113 -17
  76. package/hooks/useCookieHostSetting.ts +96 -0
  77. package/hooks/useDocumentFont.ts +17 -0
  78. package/hooks/useDocumentTextSize.ts +20 -0
  79. package/hooks/useFileBrowser.ts +10 -1
  80. package/hooks/useLinkedDoc.ts +226 -13
  81. package/hooks/usePackageChangesSetting.ts +90 -0
  82. package/hooks/usePlanLookDialog.ts +67 -0
  83. package/hooks/useSharing.ts +11 -7
  84. package/hooks/useThemePaletteHalves.ts +37 -0
  85. package/hooks/useUpdateCheck.ts +22 -4
  86. package/hooks/useUpdateToast.ts +1 -1
  87. package/package.json +23 -22
  88. package/shortcuts/code-review/annotationToolbar.shortcuts.ts +1 -0
  89. package/shortcuts/code-review/explorer.shortcuts.ts +82 -0
  90. package/shortcuts/core.ts +63 -4
  91. package/shortcuts/index.ts +1 -0
  92. package/shortcuts/plan-review/annotationPanel.shortcuts.ts +1 -1
  93. package/shortcuts/plan-review/commentPopover.shortcuts.ts +1 -0
  94. package/shortcuts/runtime.ts +4 -3
  95. package/styles.css +1 -1
  96. package/theme.css +179 -1
  97. package/themes/plannotator-graphite.css +66 -0
  98. package/types.ts +26 -9
  99. package/utils/agentSwitch.ts +41 -20
  100. package/utils/agentToolSetting.ts +1 -1
  101. package/utils/aiChatFormat.ts +23 -7
  102. package/utils/annotationOwnsHighlight.ts +4 -2
  103. package/utils/asciidocParser.ts +570 -0
  104. package/utils/autoUpdateNotice.ts +24 -0
  105. package/utils/belowTitleSlot.ts +174 -0
  106. package/utils/codeLineScroll.ts +100 -0
  107. package/utils/commentSubmitKey.ts +48 -0
  108. package/utils/cssFontFamily.ts +65 -0
  109. package/utils/decisionSpec.ts +190 -2
  110. package/utils/defaultNotesApp.ts +1 -1
  111. package/utils/diffFonts.ts +4 -1
  112. package/utils/documentFont.ts +68 -0
  113. package/utils/documentTextSize.ts +54 -0
  114. package/utils/fontDetect.ts +63 -0
  115. package/utils/inboxAnnouncement.ts +78 -0
  116. package/utils/lookAndFeelAnnouncement.ts +82 -5
  117. package/utils/markdownExtensions.ts +2 -2
  118. package/utils/mermaid.ts +20 -3
  119. package/utils/mermaidTheme.ts +3 -51
  120. package/utils/notion.ts +58 -0
  121. package/utils/parser.ts +28 -1
  122. package/utils/platform.ts +46 -0
  123. package/utils/refCard.ts +82 -0
  124. package/utils/richClipboard.ts +380 -0
  125. package/utils/richClipboardLists.ts +57 -0
  126. package/utils/richCopyPolicy.ts +22 -0
  127. package/utils/scrollAnchor.ts +82 -0
  128. package/utils/sectionRefs.ts +123 -0
  129. package/utils/selectionBoundary.ts +40 -0
  130. package/utils/sendFailure.ts +85 -0
  131. package/utils/sharing.ts +5 -2
  132. package/utils/themeRegistry.ts +10 -0
  133. package/utils/themeTokens.ts +74 -0
  134. package/utils/updateNotice.ts +30 -8
  135. package/utils/vscodePanel.ts +9 -0
@@ -13,6 +13,14 @@ function fallbackCommand(origin?: Origin | null, isWSL = false): string {
13
13
  : 'curl -fsSL https://plannotator.ai/install.sh | bash';
14
14
  }
15
15
 
16
+ /** "Oct 26, 9:14 AM", from the server's ISO time. */
17
+ function failingSince(iso: string): string {
18
+ const date = new Date(iso);
19
+ return Number.isNaN(date.getTime())
20
+ ? iso
21
+ : date.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
22
+ }
23
+
16
24
  interface MenuVersionSectionProps {
17
25
  appVersion: string;
18
26
  updateInfo?: UpdateInfo | null;
@@ -37,6 +45,8 @@ export const MenuVersionSection: React.FC<MenuVersionSectionProps> = ({
37
45
  const [copied, setCopied] = useState(false);
38
46
  const [updating, setUpdating] = useState<'idle' | 'starting' | 'started' | string>('idle');
39
47
  const hasUpdate = !!updateInfo?.updateAvailable;
48
+ // Checking for updates has failed for hours: a quiet line and the command (no dot, no toast).
49
+ const checkFailing = updateInfo?.checkFailing ?? null;
40
50
  const command = updateInfo?.command ?? fallbackCommand(origin, isWSL);
41
51
 
42
52
  const handleCopy = async () => {
@@ -86,17 +96,29 @@ export const MenuVersionSection: React.FC<MenuVersionSectionProps> = ({
86
96
  {hasUpdate ? "What's new" : 'Release notes'}
87
97
  </a>
88
98
  </span>
99
+ {!hasUpdate && checkFailing && (
100
+ <span className="text-muted-foreground" data-menu-update-check-failing="">
101
+ Can&apos;t check for updates since {failingSince(checkFailing.since)} ({checkFailing.error}). To update now:
102
+ </span>
103
+ )}
104
+ {(hasUpdate || checkFailing) && (
105
+ <div className="w-full mt-1 flex items-center gap-1.5 rounded-md border border-border bg-background px-2 py-1" data-menu-update-command="">
106
+ {/* Shown whole when checking fails (it is the only way forward there); one scrolling line beside an update, as before. */}
107
+ <code
108
+ className={`flex-1 min-w-0 font-mono text-[10px] text-foreground ${hasUpdate ? 'overflow-x-auto whitespace-nowrap' : 'whitespace-pre-wrap break-all'}`}
109
+ >
110
+ {command}
111
+ </code>
112
+ <button
113
+ onClick={handleCopy}
114
+ className="shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium border border-border hover:bg-muted transition-colors"
115
+ >
116
+ {copied ? 'Copied' : 'Copy'}
117
+ </button>
118
+ </div>
119
+ )}
89
120
  {hasUpdate && (
90
121
  <>
91
- <div className="w-full mt-1 flex items-center gap-1.5 rounded-md border border-border bg-background px-2 py-1">
92
- <code className="flex-1 min-w-0 overflow-x-auto whitespace-nowrap font-mono text-[10px] text-foreground">{command}</code>
93
- <button
94
- onClick={handleCopy}
95
- className="shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium border border-border hover:bg-muted transition-colors"
96
- >
97
- {copied ? 'Copied' : 'Copy'}
98
- </button>
99
- </div>
100
122
  {updateInfo!.action === 'script' && (
101
123
  <button
102
124
  onClick={handleUpdate}
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import { Check } from 'lucide-react';
3
+ import type { Agent } from '../hooks/useAgents';
4
+ import { useConfigValue } from '../config/useConfig';
5
+ import {
6
+ getAgentSwitchDefaults,
7
+ saveAgentSwitchSettings,
8
+ type AgentSwitchSettings,
9
+ } from '../utils/agentSwitch';
10
+ import { ActionMenuDivider } from './ActionMenu';
11
+ import { cn } from '../lib/utils';
12
+
13
+ /**
14
+ * Rows plan review puts under the decisions in DecisionControl's menu
15
+ * (`menuFooter`, #1057). Neither is a decision: picking an agent keeps the
16
+ * menu open, and the permission line only reports what Approve already sends.
17
+ *
18
+ * NOT host-supported surface (like DecisionControl): Plannotator's own plan
19
+ * review chrome.
20
+ */
21
+
22
+ function isSelected(agentId: string, setting: AgentSwitchSettings): boolean {
23
+ if (setting.switchTo === 'custom' || setting.switchTo === 'disabled') return false;
24
+ return agentId.toLowerCase() === setting.switchTo.toLowerCase();
25
+ }
26
+
27
+ const RadioRow: React.FC<{
28
+ checked: boolean;
29
+ onSelect: () => void;
30
+ children: React.ReactNode;
31
+ note?: string;
32
+ }> = ({ checked, onSelect, children, note }) => (
33
+ <button
34
+ type="button"
35
+ role="menuitemradio"
36
+ aria-checked={checked}
37
+ onClick={onSelect}
38
+ data-agent-switch-option="true"
39
+ className={cn(
40
+ 'flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors outline-none focus-visible:bg-muted',
41
+ checked ? 'bg-primary/10 font-medium text-primary' : 'text-popover-foreground hover:bg-muted',
42
+ )}
43
+ >
44
+ <span className="w-3.5 flex-shrink-0">{checked && <Check className="size-3.5" aria-hidden="true" />}</span>
45
+ <span className="truncate">{children}</span>
46
+ {note && <span className="ml-auto text-[10px] text-muted-foreground">{note}</span>}
47
+ </button>
48
+ );
49
+
50
+ /**
51
+ * OpenCode: the agent an approval hands off to, as a radio section of the
52
+ * decision menu (it replaces ApproveDropdown's separate caret). Reads and
53
+ * writes the same `agentSwitch` setting Settings does.
54
+ */
55
+ export const AgentSwitchMenuSection: React.FC<{ agents: Agent[] }> = ({ agents }) => {
56
+ // Reactive: config.json's value lands through configStore.init() once
57
+ // /api/plan answers, which may be after this mounts (#1390).
58
+ const stored = useConfigValue('agentSwitch');
59
+ const setting: AgentSwitchSettings = stored ?? getAgentSwitchDefaults('plan');
60
+ const isCustom = setting.switchTo === 'custom' && !!setting.customName;
61
+ const title = 'After approval, switch to';
62
+
63
+ return (
64
+ <>
65
+ <ActionMenuDivider />
66
+ <div
67
+ aria-hidden="true"
68
+ className="px-3 pt-1.5 pb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70"
69
+ >
70
+ {title}
71
+ </div>
72
+ <div role="group" aria-label={title} data-agent-switch-section="true">
73
+ {agents.map((agent) => (
74
+ <RadioRow
75
+ key={agent.id}
76
+ checked={isSelected(agent.id, setting)}
77
+ onSelect={() => saveAgentSwitchSettings({ switchTo: agent.id })}
78
+ >
79
+ {agent.name}
80
+ </RadioRow>
81
+ ))}
82
+ {isCustom && (
83
+ <RadioRow checked onSelect={() => {}} note="(custom)">
84
+ {setting.customName}
85
+ </RadioRow>
86
+ )}
87
+ <RadioRow
88
+ checked={setting.switchTo === 'disabled'}
89
+ onSelect={() => saveAgentSwitchSettings({ switchTo: 'disabled' })}
90
+ >
91
+ No switch
92
+ </RadioRow>
93
+ </div>
94
+ </>
95
+ );
96
+ };
97
+
98
+ /**
99
+ * Claude Code: the permission mode Approve already sends, made visible. Read
100
+ * only; "Change" opens Settings, where the mode is set.
101
+ */
102
+ export const PermissionModeMenuLine: React.FC<{
103
+ agentName: string;
104
+ modeLabel: string;
105
+ onChange: () => void;
106
+ }> = ({ agentName, modeLabel, onChange }) => (
107
+ <>
108
+ <ActionMenuDivider />
109
+ <div
110
+ data-permission-mode-line="true"
111
+ className="flex items-center justify-between gap-2 px-3 py-1.5 text-[10px] text-muted-foreground"
112
+ >
113
+ <span>
114
+ After approval, {agentName} runs in{' '}
115
+ <span className="font-medium text-foreground">{modeLabel}</span>
116
+ </span>
117
+ <button
118
+ type="button"
119
+ role="menuitem"
120
+ onClick={onChange}
121
+ data-permission-mode-change="true"
122
+ aria-label={`Change the permission mode (${modeLabel}) in Settings`}
123
+ className="rounded px-1 py-0.5 text-[10px] text-primary outline-none hover:underline focus-visible:ring-1 focus-visible:ring-ring/60"
124
+ >
125
+ Change
126
+ </button>
127
+ </div>
128
+ </>
129
+ );
@@ -27,12 +27,14 @@ interface PlanHeaderMenuProps {
27
27
  onSaveToObsidian: () => void;
28
28
  onSaveToBear: () => void;
29
29
  onSaveToOctarine: () => void;
30
+ onSaveToNotion?: () => void;
30
31
  sharingEnabled: boolean;
31
32
  isApiMode: boolean;
32
33
  agentInstructionsEnabled: boolean;
33
34
  obsidianConfigured: boolean;
34
35
  bearConfigured: boolean;
35
36
  octarineConfigured: boolean;
37
+ notionConfigured?: boolean;
36
38
  compactTouchLayout?: boolean;
37
39
  compactSessionActions?: CompactPlanAction[];
38
40
  compactDocumentActions?: CompactPlanAction[];
@@ -61,12 +63,14 @@ export const PlanHeaderMenu: React.FC<PlanHeaderMenuProps> = ({
61
63
  onSaveToObsidian,
62
64
  onSaveToBear,
63
65
  onSaveToOctarine,
66
+ onSaveToNotion,
64
67
  sharingEnabled,
65
68
  isApiMode,
66
69
  agentInstructionsEnabled,
67
70
  obsidianConfigured,
68
71
  bearConfigured,
69
72
  octarineConfigured,
73
+ notionConfigured = false,
70
74
  compactTouchLayout = false,
71
75
  compactSessionActions = [],
72
76
  compactDocumentActions = [],
@@ -76,7 +80,7 @@ export const PlanHeaderMenu: React.FC<PlanHeaderMenuProps> = ({
76
80
  const showUpdateDot = !!updateInfo?.updateAvailable && !updateInfo.dismissed;
77
81
 
78
82
  const anyNotesAppConfigured =
79
- isApiMode && (obsidianConfigured || bearConfigured || octarineConfigured);
83
+ isApiMode && (obsidianConfigured || bearConfigured || octarineConfigured || notionConfigured);
80
84
 
81
85
  return (
82
86
  <ActionMenu
@@ -263,6 +267,16 @@ export const PlanHeaderMenu: React.FC<PlanHeaderMenuProps> = ({
263
267
  label="Save to Octarine"
264
268
  />
265
269
  )}
270
+ {notionConfigured && onSaveToNotion && (
271
+ <ActionMenuItem
272
+ onClick={() => {
273
+ closeMenu();
274
+ onSaveToNotion();
275
+ }}
276
+ icon={<NoteIcon />}
277
+ label="Save to Notion"
278
+ />
279
+ )}
266
280
  </>
267
281
  )}
268
282
 
@@ -36,6 +36,13 @@ export const OpenCodeIcon: React.FC<{ className?: string }> = ({ className = 'w-
36
36
  </svg>
37
37
  );
38
38
 
39
+ /** Qwen Code icon: the Qwen Code logo (QwenLM/qwen-code), in its brand purple. */
40
+ export const QwenIcon: React.FC<{ className?: string }> = ({ className = 'w-4 h-4' }) => (
41
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 141.38 140" className={className}>
42
+ <path d="m140.93 85-16.35-28.33-1.93-3.34 8.66-15a3.323 3.323 0 0 0 0-3.34l-9.62-16.67c-.3-.51-.72-.93-1.22-1.22s-1.07-.45-1.67-.45H82.23l-8.66-15a3.33 3.33 0 0 0-2.89-1.67H51.43c-.59 0-1.17.16-1.66.45-.5.29-.92.71-1.22 1.22L32.19 29.98l-1.92 3.33H12.96c-.59 0-1.17.16-1.66.45-.5.29-.93.71-1.22 1.22L.45 51.66a3.323 3.323 0 0 0 0 3.34l18.28 31.67-8.66 15a3.32 3.32 0 0 0 0 3.34l9.62 16.67c.3.51.72.93 1.22 1.22s1.07.45 1.67.45h36.56l8.66 15a3.35 3.35 0 0 0 2.89 1.67h19.25a3.34 3.34 0 0 0 2.89-1.67l18.28-31.67h17.32c.6 0 1.17-.16 1.67-.45s.92-.71 1.22-1.22l9.62-16.67a3.323 3.323 0 0 0 0-3.34ZM51.44 3.33 61.07 20l-9.63 16.66h76.98l-9.62 16.66H45.67l-11.54-20zM57.21 120H22.58l9.63-16.67h19.25l-38.5-66.67h19.25l9.62 16.67L68.78 100l-11.55 20Zm61.59-33.34-9.62-16.67-38.49 66.67-9.63-16.67 9.63-16.66 26.94-46.67h23.1l17.32 30z" fill="#6D44E8" />
43
+ </svg>
44
+ );
45
+
39
46
  /** Generic fallback icon for unknown providers */
40
47
  const GenericProviderIcon: React.FC<{ className?: string }> = ({ className = 'w-4 h-4' }) => (
41
48
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} className={className}>
@@ -56,6 +63,7 @@ export const PROVIDER_META: Record<string, { label: string; icon: React.FC<{ cla
56
63
  'codex-sdk': { label: 'Codex', icon: CodexIcon },
57
64
  'pi-sdk': { label: 'Pi', icon: PiIcon },
58
65
  'opencode-sdk': { label: 'OpenCode', icon: OpenCodeIcon },
66
+ 'qwen-sdk': { label: 'Qwen Code', icon: QwenIcon },
59
67
  'session-bridge': { label: 'Ask this session', icon: SessionIcon },
60
68
  };
61
69