@plannotator/ui 0.22.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 (305) hide show
  1. package/README.md +60 -0
  2. package/assets/diff-options.png +0 -0
  3. package/assets/icon-codex.png +0 -0
  4. package/assets/look-flat.png +0 -0
  5. package/assets/look-grid.png +0 -0
  6. package/assets/review-sections.png +0 -0
  7. package/assets/review-tree.png +0 -0
  8. package/assets/workspaces.webp +0 -0
  9. package/components/AISettingsTab.tsx +145 -0
  10. package/components/ActionMenu.tsx +101 -0
  11. package/components/AgentControls.tsx +216 -0
  12. package/components/AgentsTab.tsx +1294 -0
  13. package/components/AnnotationPanel.tsx +731 -0
  14. package/components/AnnotationSidebar.tsx +86 -0
  15. package/components/AnnotationToolbar.tsx +323 -0
  16. package/components/AnnotationToolstrip.tsx +355 -0
  17. package/components/ApproveDropdown.tsx +170 -0
  18. package/components/AttachmentsButton.tsx +410 -0
  19. package/components/BlockRenderer.tsx +163 -0
  20. package/components/BorderTrail.tsx +38 -0
  21. package/components/CodeFilePicker.tsx +64 -0
  22. package/components/CodeFilePopout.tsx +602 -0
  23. package/components/CodePathValidationContext.tsx +14 -0
  24. package/components/CommentPopover.tsx +512 -0
  25. package/components/CompletionOverlay.tsx +99 -0
  26. package/components/ConfirmDialog.tsx +128 -0
  27. package/components/DocBadges.tsx +231 -0
  28. package/components/EditorAnnotationCard.tsx +65 -0
  29. package/components/ExportModal.tsx +565 -0
  30. package/components/FloatingQuickLabelPicker.tsx +144 -0
  31. package/components/GitHubIcon.tsx +16 -0
  32. package/components/GitLabIcon.tsx +30 -0
  33. package/components/GraphvizBlock.tsx +511 -0
  34. package/components/ImageAnnotator/Canvas.tsx +118 -0
  35. package/components/ImageAnnotator/Toolbar.tsx +218 -0
  36. package/components/ImageAnnotator/index.tsx +272 -0
  37. package/components/ImageAnnotator/types.ts +39 -0
  38. package/components/ImageAnnotator/utils.ts +147 -0
  39. package/components/ImageThumbnail.tsx +126 -0
  40. package/components/ImportModal.tsx +146 -0
  41. package/components/InlineMarkdown.tsx +1094 -0
  42. package/components/KeyboardShortcuts.tsx +208 -0
  43. package/components/Landing.tsx +496 -0
  44. package/components/ListItemBody.tsx +62 -0
  45. package/components/ListMarker.tsx +73 -0
  46. package/components/LookAndFeelAnnouncementDialog.tsx +230 -0
  47. package/components/MarkdownEditor.tsx +44 -0
  48. package/components/MenuVersionSection.tsx +91 -0
  49. package/components/MermaidBlock.tsx +579 -0
  50. package/components/ModeToggle.tsx +68 -0
  51. package/components/OpenInAppButton.tsx +299 -0
  52. package/components/OverlayScrollArea.tsx +82 -0
  53. package/components/PermissionModeSetup.tsx +92 -0
  54. package/components/PinpointOverlay.tsx +99 -0
  55. package/components/PlanAIAnnouncementDialog.tsx +187 -0
  56. package/components/PlanHeaderMenu.tsx +299 -0
  57. package/components/PopoutDialog.tsx +91 -0
  58. package/components/Popover.tsx +27 -0
  59. package/components/ProviderIcons.tsx +51 -0
  60. package/components/PullRequestIcon.tsx +12 -0
  61. package/components/QuickLabelDropdown.tsx +63 -0
  62. package/components/RenderedMarkdown.tsx +54 -0
  63. package/components/RepoIcon.tsx +12 -0
  64. package/components/ResizeHandle.tsx +86 -0
  65. package/components/ReviewAgentsIcon.tsx +18 -0
  66. package/components/SearchableSelect.tsx +157 -0
  67. package/components/Settings.tsx +2220 -0
  68. package/components/SparklesIcon.tsx +45 -0
  69. package/components/StickyHeaderLane.tsx +260 -0
  70. package/components/TableOfContents.tsx +157 -0
  71. package/components/TaterSpritePullup.tsx +34 -0
  72. package/components/TaterSpriteRunning.tsx +54 -0
  73. package/components/TaterSpriteSitting.tsx +37 -0
  74. package/components/TextShimmer.tsx +57 -0
  75. package/components/ThemeProvider.tsx +162 -0
  76. package/components/ThemeTab.tsx +139 -0
  77. package/components/ToolbarButtons.tsx +116 -0
  78. package/components/Tooltip.tsx +45 -0
  79. package/components/Viewer.tsx +977 -0
  80. package/components/ai/AIProviderBar.tsx +95 -0
  81. package/components/ai/DocumentAIChatPanel.tsx +313 -0
  82. package/components/blocks/AlertBlock.tsx +58 -0
  83. package/components/blocks/Callout.tsx +60 -0
  84. package/components/blocks/CodeBlock.tsx +75 -0
  85. package/components/blocks/HtmlBlock.tsx +122 -0
  86. package/components/blocks/MathBlock.tsx +36 -0
  87. package/components/blocks/TableBlock.tsx +146 -0
  88. package/components/blocks/TablePopout.tsx +279 -0
  89. package/components/blocks/TableToolbar.tsx +153 -0
  90. package/components/blocks/proseBody.tsx +102 -0
  91. package/components/core/button.tsx +44 -0
  92. package/components/core/textarea.tsx +26 -0
  93. package/components/diagramLanguages.ts +14 -0
  94. package/components/goal-setup/GoalSetupSurface.tsx +1354 -0
  95. package/components/html-viewer/HtmlViewer.tsx +386 -0
  96. package/components/html-viewer/bridge-script.ts +505 -0
  97. package/components/html-viewer/index.ts +1 -0
  98. package/components/html-viewer/useHtmlAnnotation.ts +400 -0
  99. package/components/icons/AgentIcons.tsx +66 -0
  100. package/components/icons/AppIcon.tsx +56 -0
  101. package/components/icons/MessagesIcon.tsx +11 -0
  102. package/components/icons/ObsidianIcons.tsx +208 -0
  103. package/components/icons/app/android-studio.svg +369 -0
  104. package/components/icons/app/antigravity.svg +97 -0
  105. package/components/icons/app/cursor.svg +16 -0
  106. package/components/icons/app/file-explorer.svg +20 -0
  107. package/components/icons/app/finder.png +0 -0
  108. package/components/icons/app/ghostty.svg +13 -0
  109. package/components/icons/app/iterm2.svg +13 -0
  110. package/components/icons/app/powershell.svg +14 -0
  111. package/components/icons/app/sublime-text.svg +17 -0
  112. package/components/icons/app/terminal.png +0 -0
  113. package/components/icons/app/textmate.png +0 -0
  114. package/components/icons/app/vscode.svg +39 -0
  115. package/components/icons/app/warp.png +0 -0
  116. package/components/icons/app/xcode.png +0 -0
  117. package/components/icons/app/zed-dark.svg +15 -0
  118. package/components/icons/app/zed.svg +15 -0
  119. package/components/icons/themeIcons.tsx +47 -0
  120. package/components/mermaidSvg.ts +33 -0
  121. package/components/plan-diff/PlanCleanDiffView.tsx +884 -0
  122. package/components/plan-diff/PlanDiffBadge.tsx +48 -0
  123. package/components/plan-diff/PlanDiffModeSwitcher.tsx +103 -0
  124. package/components/plan-diff/PlanDiffViewer.tsx +214 -0
  125. package/components/plan-diff/PlanRawDiffView.tsx +102 -0
  126. package/components/plan-diff/VSCodeIcon.tsx +132 -0
  127. package/components/settings/HooksTab.tsx +208 -0
  128. package/components/sidebar/ArchiveBrowser.tsx +98 -0
  129. package/components/sidebar/CountBadge.tsx +12 -0
  130. package/components/sidebar/FileBrowser.tsx +498 -0
  131. package/components/sidebar/MessagesBrowser.tsx +109 -0
  132. package/components/sidebar/SidebarContainer.tsx +353 -0
  133. package/components/sidebar/SidebarTabs.tsx +155 -0
  134. package/components/sidebar/VersionBrowser.tsx +141 -0
  135. package/components/types.d.ts +7 -0
  136. package/components/ui/badge.tsx +44 -0
  137. package/components/ui/button.tsx +83 -0
  138. package/components/ui/card.tsx +58 -0
  139. package/components/ui/dialog.tsx +106 -0
  140. package/components/ui/dropdown-menu.tsx +240 -0
  141. package/components/ui/state-pill.tsx +45 -0
  142. package/components/ui/tabs.tsx +46 -0
  143. package/components/ui/textarea.tsx +25 -0
  144. package/config/configStore.ts +214 -0
  145. package/config/index.ts +4 -0
  146. package/config/reviewView.ts +42 -0
  147. package/config/settings.ts +310 -0
  148. package/config/useConfig.ts +20 -0
  149. package/configure.ts +69 -0
  150. package/globals.d.ts +14 -0
  151. package/hooks/pfm/useCodeFilePopout.ts +111 -0
  152. package/hooks/useAIChat.ts +532 -0
  153. package/hooks/useAIProviderConfig.ts +115 -0
  154. package/hooks/useActiveSection.ts +78 -0
  155. package/hooks/useAgentJobs.ts +306 -0
  156. package/hooks/useAgentSettings.ts +579 -0
  157. package/hooks/useAgents.ts +90 -0
  158. package/hooks/useAnnotationDraft.ts +523 -0
  159. package/hooks/useAnnotationHighlighter.ts +1170 -0
  160. package/hooks/useArchive.ts +171 -0
  161. package/hooks/useAutoClose.ts +104 -0
  162. package/hooks/useCodeAnnotationDraft.ts +194 -0
  163. package/hooks/useCodeFilePopout.ts +1 -0
  164. package/hooks/useDismissOnOutsideAndEscape.ts +50 -0
  165. package/hooks/useDraggable.ts +108 -0
  166. package/hooks/useEditorAnnotations.ts +64 -0
  167. package/hooks/useExternalAnnotationHighlights.ts +105 -0
  168. package/hooks/useExternalAnnotations.ts +268 -0
  169. package/hooks/useFileBrowser.ts +402 -0
  170. package/hooks/useInputMethodSwitch.ts +89 -0
  171. package/hooks/useIsMobile.ts +17 -0
  172. package/hooks/useLinkedDoc.ts +494 -0
  173. package/hooks/useOverlayViewport.ts +36 -0
  174. package/hooks/usePinpoint.ts +182 -0
  175. package/hooks/usePlanDiff.ts +176 -0
  176. package/hooks/usePrintMode.ts +27 -0
  177. package/hooks/useResizablePanel.ts +173 -0
  178. package/hooks/useScrollViewport.ts +38 -0
  179. package/hooks/useSharing.ts +441 -0
  180. package/hooks/useSidebar.ts +55 -0
  181. package/hooks/useUpdateCheck.ts +132 -0
  182. package/hooks/useValidatedCodePaths.ts +94 -0
  183. package/icons/GitUser.tsx +17 -0
  184. package/lib/utils.ts +11 -0
  185. package/package.json +117 -0
  186. package/plannotator.webp +0 -0
  187. package/print.css +427 -0
  188. package/shortcuts/code-review/ai.shortcuts.ts +25 -0
  189. package/shortcuts/code-review/allFilesDiff.shortcuts.ts +46 -0
  190. package/shortcuts/code-review/annotationToolbar.shortcuts.ts +30 -0
  191. package/shortcuts/code-review/fileTree.shortcuts.ts +35 -0
  192. package/shortcuts/code-review/prComments.shortcuts.ts +18 -0
  193. package/shortcuts/code-review/suggestionModal.shortcuts.ts +25 -0
  194. package/shortcuts/code-review/tourDialog.shortcuts.ts +18 -0
  195. package/shortcuts/core.ts +399 -0
  196. package/shortcuts/index.ts +21 -0
  197. package/shortcuts/plan-review/annotationPanel.shortcuts.ts +25 -0
  198. package/shortcuts/plan-review/annotationToolbar.shortcuts.ts +39 -0
  199. package/shortcuts/plan-review/commentPopover.shortcuts.ts +21 -0
  200. package/shortcuts/plan-review/goalSetup.shortcuts.ts +10 -0
  201. package/shortcuts/plan-review/imageAnnotator.shortcuts.ts +42 -0
  202. package/shortcuts/plan-review/inputMethod.shortcuts.ts +30 -0
  203. package/shortcuts/plan-review/sidebar.shortcuts.ts +34 -0
  204. package/shortcuts/plan-review/viewer.shortcuts.ts +24 -0
  205. package/shortcuts/runtime.ts +259 -0
  206. package/sprite_package_additional/index.html +34 -0
  207. package/sprite_package_additional/sprite.png +0 -0
  208. package/sprite_package_new/index.html +34 -0
  209. package/sprite_package_new/sprite.png +0 -0
  210. package/sprite_package_pulluphang/index.html +34 -0
  211. package/sprite_package_pulluphang/sprite.png +0 -0
  212. package/styles.css +1 -0
  213. package/theme.css +893 -0
  214. package/themes/adwaita.css +62 -0
  215. package/themes/andromeeda.css +66 -0
  216. package/themes/aurora-x.css +66 -0
  217. package/themes/ayu-dark.css +66 -0
  218. package/themes/caffeine.css +113 -0
  219. package/themes/catppuccin.css +62 -0
  220. package/themes/claude-plus.css +60 -0
  221. package/themes/cursor-hc.css +34 -0
  222. package/themes/cursor-midnight.css +34 -0
  223. package/themes/cursor.css +62 -0
  224. package/themes/dark-plus.css +66 -0
  225. package/themes/doom-64.css +109 -0
  226. package/themes/dracula.css +33 -0
  227. package/themes/everforest-hard.css +62 -0
  228. package/themes/everforest-soft.css +62 -0
  229. package/themes/everforest.css +62 -0
  230. package/themes/github.css +66 -0
  231. package/themes/gruvbox.css +62 -0
  232. package/themes/houston.css +66 -0
  233. package/themes/kanagawa-dragon.css +34 -0
  234. package/themes/kanagawa-lotus.css +34 -0
  235. package/themes/kanagawa-wave.css +34 -0
  236. package/themes/laserwave.css +66 -0
  237. package/themes/material.css +62 -0
  238. package/themes/min.css +66 -0
  239. package/themes/monokai-pro.css +34 -0
  240. package/themes/neutral.css +59 -0
  241. package/themes/night-owl.css +66 -0
  242. package/themes/nord.css +66 -0
  243. package/themes/one-dark-pro.css +66 -0
  244. package/themes/one-light.css +66 -0
  245. package/themes/paulmillr.css +34 -0
  246. package/themes/plannotator.css +60 -0
  247. package/themes/plastic.css +66 -0
  248. package/themes/poimandres.css +66 -0
  249. package/themes/quantum-rose.css +109 -0
  250. package/themes/red.css +66 -0
  251. package/themes/rose-pine.css +62 -0
  252. package/themes/simple.css +124 -0
  253. package/themes/slack.css +66 -0
  254. package/themes/snazzy-light.css +66 -0
  255. package/themes/soft-pop.css +60 -0
  256. package/themes/solar-dusk.css +109 -0
  257. package/themes/solarized.css +66 -0
  258. package/themes/synthwave-84.css +34 -0
  259. package/themes/terminal.css +62 -0
  260. package/themes/tinacious.css +57 -0
  261. package/themes/tokyo-night.css +62 -0
  262. package/themes/vesper.css +62 -0
  263. package/themes/vitesse-black.css +66 -0
  264. package/themes/vitesse.css +62 -0
  265. package/types.ts +264 -0
  266. package/utils/agentSwitch.ts +71 -0
  267. package/utils/aiChatFormat.ts +33 -0
  268. package/utils/aiPrompt.ts +29 -0
  269. package/utils/aiProvider.ts +217 -0
  270. package/utils/anchors.ts +11 -0
  271. package/utils/annotateAgentTerminal.ts +29 -0
  272. package/utils/annotationHelpers.ts +101 -0
  273. package/utils/bear.ts +59 -0
  274. package/utils/blockTargeting.ts +240 -0
  275. package/utils/callback.ts +99 -0
  276. package/utils/commentContent.ts +8 -0
  277. package/utils/defaultNotesApp.ts +20 -0
  278. package/utils/diffFonts.ts +33 -0
  279. package/utils/editorMode.ts +31 -0
  280. package/utils/fileBrowser.ts +40 -0
  281. package/utils/generateId.ts +7 -0
  282. package/utils/generateIdentity.ts +29 -0
  283. package/utils/identity.ts +112 -0
  284. package/utils/inlineTransforms.ts +41 -0
  285. package/utils/inputMethod.ts +17 -0
  286. package/utils/lookAndFeelAnnouncement.ts +18 -0
  287. package/utils/obsidian.ts +201 -0
  288. package/utils/octarine.ts +53 -0
  289. package/utils/parser.ts +991 -0
  290. package/utils/permissionMode.ts +77 -0
  291. package/utils/planAIAnnouncement.ts +17 -0
  292. package/utils/planAgentInstructions.ts +137 -0
  293. package/utils/planDiffEngine.ts +590 -0
  294. package/utils/planSave.ts +49 -0
  295. package/utils/platform.ts +11 -0
  296. package/utils/quickLabels.ts +77 -0
  297. package/utils/reviewAgentInstructions.ts +182 -0
  298. package/utils/sanitizeHtml.ts +45 -0
  299. package/utils/sharing.ts +350 -0
  300. package/utils/slugify.ts +37 -0
  301. package/utils/storage.ts +141 -0
  302. package/utils/themeRegistry.ts +567 -0
  303. package/utils/uiPreferences.ts +34 -0
  304. package/utils/upload.ts +56 -0
  305. package/utils/wideMode.ts +48 -0
@@ -0,0 +1,400 @@
1
+ import { useState, useEffect, useCallback, useRef, type RefObject } from "react";
2
+ import { AnnotationType, type Annotation, type EditorMode, type ImageAttachment } from "../../types";
3
+ import type { QuickLabel } from "../../utils/quickLabels";
4
+ import { getIdentity } from "../../utils/identity";
5
+ import type {
6
+ ToolbarState,
7
+ CommentPopoverState,
8
+ QuickLabelPickerState,
9
+ UseAnnotationHighlighterReturn,
10
+ } from "../../hooks/useAnnotationHighlighter";
11
+
12
+ const PREFIX = "plannotator-bridge-";
13
+
14
+ // Collision-proof annotation ids. `Date.now()` alone repeats within a millisecond,
15
+ // so two quick annotations could share a data-bind-id and clobber each other.
16
+ let htmlAnnSeq = 0;
17
+ function nextHtmlAnnId(): string {
18
+ return `html-ann-${Date.now().toString(36)}-${(htmlAnnSeq++).toString(36)}`;
19
+ }
20
+
21
+ interface BridgeSelectionMessage {
22
+ type: `${typeof PREFIX}selection`;
23
+ text: string;
24
+ rect: { top: number; left: number; width: number; height: number };
25
+ }
26
+
27
+ interface BridgeMarkClickMessage {
28
+ type: `${typeof PREFIX}mark-click`;
29
+ id: string;
30
+ }
31
+
32
+ interface BridgeResizeMessage {
33
+ type: `${typeof PREFIX}resize`;
34
+ height: number;
35
+ }
36
+
37
+ type BridgeMessage = BridgeSelectionMessage | BridgeMarkClickMessage | BridgeResizeMessage | { type: string };
38
+
39
+ export interface UseHtmlAnnotationOptions {
40
+ iframeRef: RefObject<HTMLIFrameElement | null>;
41
+ annotations: Annotation[];
42
+ onAddAnnotation?: (ann: Annotation) => void;
43
+ onSelectAnnotation?: (id: string | null) => void;
44
+ selectedAnnotationId: string | null;
45
+ mode: EditorMode;
46
+ onResize?: (height: number) => void;
47
+ }
48
+
49
+ function postToIframe(iframe: HTMLIFrameElement | null, msg: Record<string, unknown>) {
50
+ iframe?.contentWindow?.postMessage(msg, "*");
51
+ }
52
+
53
+ export function useHtmlAnnotation({
54
+ iframeRef,
55
+ annotations,
56
+ onAddAnnotation,
57
+ onSelectAnnotation,
58
+ selectedAnnotationId,
59
+ mode,
60
+ onResize,
61
+ }: UseHtmlAnnotationOptions): Omit<UseAnnotationHighlighterReturn, "highlighterRef"> {
62
+ const [toolbarState, setToolbarState] = useState<ToolbarState | null>(null);
63
+ const [commentPopover, setCommentPopover] = useState<CommentPopoverState | null>(null);
64
+ const [quickLabelPicker, setQuickLabelPicker] = useState<QuickLabelPickerState | null>(null);
65
+
66
+ const pendingTextRef = useRef<string>("");
67
+ const modeRef = useRef(mode);
68
+ modeRef.current = mode;
69
+ // Mirror toolbar visibility into a ref so the (stable) message handler can gate
70
+ // type-to-comment on "the markup toolbar is showing", like AnnotationToolbar does.
71
+ const toolbarStateRef = useRef(toolbarState);
72
+ toolbarStateRef.current = toolbarState;
73
+ // Mirror the open comment/quick-label state so the selection-clear handler can
74
+ // tell whether the user is mid-compose and must keep the captured text alive.
75
+ const commentPopoverRef = useRef(commentPopover);
76
+ commentPopoverRef.current = commentPopover;
77
+ const quickLabelPickerRef = useRef(quickLabelPicker);
78
+ quickLabelPickerRef.current = quickLabelPicker;
79
+
80
+ const onAddRef = useRef(onAddAnnotation);
81
+ onAddRef.current = onAddAnnotation;
82
+ const onSelectRef = useRef(onSelectAnnotation);
83
+ onSelectRef.current = onSelectAnnotation;
84
+
85
+ const anchorRef = useRef<HTMLDivElement | null>(null);
86
+
87
+ const getOrCreateAnchor = useCallback(() => {
88
+ if (!anchorRef.current) {
89
+ const div = document.createElement("div");
90
+ div.style.position = "fixed";
91
+ div.style.pointerEvents = "none";
92
+ div.style.width = "1px";
93
+ div.style.height = "1px";
94
+ document.body.appendChild(div);
95
+ anchorRef.current = div;
96
+ }
97
+ return anchorRef.current;
98
+ }, []);
99
+
100
+ const positionAnchor = useCallback(
101
+ (bridgeRect: { top: number; left: number; width: number; height: number }) => {
102
+ const iframe = iframeRef.current;
103
+ if (!iframe) return null;
104
+ const iframeRect = iframe.getBoundingClientRect();
105
+ // Fresh anchor per selection. The toolbar/popover recompute position only
106
+ // when their `element` node identity changes, so reusing one anchor div
107
+ // leaves them pinned to the previous selection. Drop the old one first.
108
+ if (anchorRef.current) anchorRef.current.remove();
109
+ anchorRef.current = null;
110
+ const anchor = getOrCreateAnchor();
111
+ anchor.style.top = `${iframeRect.top + bridgeRect.top}px`;
112
+ anchor.style.left = `${iframeRect.left + bridgeRect.left + bridgeRect.width / 2}px`;
113
+ return anchor;
114
+ },
115
+ [iframeRef, getOrCreateAnchor],
116
+ );
117
+
118
+ useEffect(() => {
119
+ function handler(e: MessageEvent<BridgeMessage>) {
120
+ if (!e.data || typeof e.data.type !== "string" || !e.data.type.startsWith(PREFIX)) return;
121
+
122
+ const type = e.data.type;
123
+
124
+ if (type === `${PREFIX}selection`) {
125
+ const msg = e.data as BridgeSelectionMessage;
126
+ pendingTextRef.current = msg.text;
127
+ const anchor = positionAnchor(msg.rect);
128
+ if (!anchor) return;
129
+
130
+ const currentMode = modeRef.current;
131
+
132
+ if (currentMode === "redline") {
133
+ const id = nextHtmlAnnId();
134
+ postToIframe(iframeRef.current, { type: `${PREFIX}create-mark`, id, annotationType: "deletion" });
135
+ onAddRef.current?.({
136
+ id,
137
+ blockId: "",
138
+ startOffset: 0,
139
+ endOffset: 0,
140
+ type: AnnotationType.DELETION,
141
+ originalText: msg.text,
142
+ author: getIdentity(),
143
+ createdA: Date.now(),
144
+ });
145
+ pendingTextRef.current = "";
146
+ } else if (currentMode === "comment") {
147
+ // Release iframe focus so the popover's textarea autofocus lands in the
148
+ // parent (otherwise the iframe keeps focus and swallows further keys).
149
+ iframeRef.current?.blur();
150
+ setCommentPopover({
151
+ anchorEl: anchor,
152
+ contextText: msg.text,
153
+ selectedText: msg.text,
154
+ });
155
+ } else if (currentMode === "quickLabel") {
156
+ setQuickLabelPicker({
157
+ anchorEl: anchor,
158
+ cursorHint: { x: parseFloat(anchor.style.left), y: parseFloat(anchor.style.top) },
159
+ });
160
+ } else {
161
+ setToolbarState({
162
+ element: anchor,
163
+ source: null,
164
+ selectionText: msg.text,
165
+ });
166
+ }
167
+ }
168
+
169
+ if (type === `${PREFIX}selection-clear`) {
170
+ setToolbarState(null);
171
+ // Keep the captured text alive while a comment/quick-label is open: the user
172
+ // is composing, and the selection collapsing or scrolling out of view must
173
+ // not drop the annotation on submit. It's overwritten on the next selection.
174
+ if (!commentPopoverRef.current && !quickLabelPickerRef.current) {
175
+ pendingTextRef.current = "";
176
+ }
177
+ }
178
+
179
+ if (type === `${PREFIX}selection-rect`) {
180
+ // The iframe content scrolled — move the anchor to the selection's new
181
+ // position and nudge the toolbar/popover (which listen to window scroll) to
182
+ // recompute, so they stay attached to the selection.
183
+ const iframe = iframeRef.current;
184
+ const anchor = anchorRef.current;
185
+ if (!iframe || !anchor) return;
186
+ const r = (e.data as unknown as { rect: { top: number; left: number; width: number; height: number } }).rect;
187
+ const iframeRect = iframe.getBoundingClientRect();
188
+ anchor.style.top = `${iframeRect.top + r.top}px`;
189
+ anchor.style.left = `${iframeRect.left + r.left + r.width / 2}px`;
190
+ window.dispatchEvent(new Event("scroll"));
191
+ }
192
+
193
+ if (type === `${PREFIX}keytype`) {
194
+ // Type-to-comment: only when the markup toolbar is showing (matches the
195
+ // markdown path, where AnnotationToolbar owns this keydown). Open a comment
196
+ // pre-filled with the typed char.
197
+ if (!toolbarStateRef.current) return;
198
+ const key = (e.data as { key?: string }).key;
199
+ const text = pendingTextRef.current;
200
+ if (!key || !text) return;
201
+ const anchor = anchorRef.current ?? getOrCreateAnchor();
202
+ // Release iframe focus so the popover textarea can take it (and the rest of
203
+ // the typing) — otherwise the iframe keeps focus and the bridge eats keys.
204
+ iframeRef.current?.blur();
205
+ setToolbarState(null);
206
+ setCommentPopover({ anchorEl: anchor, contextText: text, selectedText: text, initialText: key });
207
+ }
208
+
209
+ if (type === `${PREFIX}mark-click`) {
210
+ const msg = e.data as BridgeMarkClickMessage;
211
+ onSelectRef.current?.(msg.id);
212
+ }
213
+
214
+ if (type === `${PREFIX}resize`) {
215
+ const msg = e.data as BridgeResizeMessage;
216
+ onResize?.(msg.height);
217
+ }
218
+ }
219
+
220
+ window.addEventListener("message", handler);
221
+ return () => {
222
+ window.removeEventListener("message", handler);
223
+ if (anchorRef.current) {
224
+ anchorRef.current.remove();
225
+ anchorRef.current = null;
226
+ }
227
+ };
228
+ }, [iframeRef, positionAnchor, onResize, getOrCreateAnchor]);
229
+
230
+ useEffect(() => {
231
+ if (selectedAnnotationId) {
232
+ postToIframe(iframeRef.current, {
233
+ type: `${PREFIX}scroll-to`,
234
+ id: selectedAnnotationId,
235
+ });
236
+ } else {
237
+ postToIframe(iframeRef.current, {
238
+ type: `${PREFIX}focus-mark`,
239
+ id: null,
240
+ });
241
+ }
242
+ }, [selectedAnnotationId, iframeRef]);
243
+
244
+ const handleAnnotate = useCallback(
245
+ (type: AnnotationType) => {
246
+ const text = pendingTextRef.current;
247
+ if (!text || type !== AnnotationType.DELETION) return;
248
+
249
+ const id = nextHtmlAnnId();
250
+ postToIframe(iframeRef.current, { type: `${PREFIX}create-mark`, id, annotationType: "deletion" });
251
+ onAddRef.current?.({
252
+ id,
253
+ blockId: "",
254
+ startOffset: 0,
255
+ endOffset: 0,
256
+ type: AnnotationType.DELETION,
257
+ originalText: text,
258
+ author: getIdentity(),
259
+ createdA: Date.now(),
260
+ });
261
+
262
+ setToolbarState(null);
263
+ pendingTextRef.current = "";
264
+ },
265
+ [iframeRef],
266
+ );
267
+
268
+ const handleRequestComment = useCallback(
269
+ (initialChar?: string) => {
270
+ const text = pendingTextRef.current;
271
+ if (!text) return;
272
+ const anchor = anchorRef.current ?? getOrCreateAnchor();
273
+ setToolbarState(null);
274
+ setCommentPopover({ anchorEl: anchor, contextText: text, selectedText: text, initialText: initialChar });
275
+ },
276
+ [getOrCreateAnchor],
277
+ );
278
+
279
+ const handleCommentSubmit = useCallback(
280
+ (comment: string, images?: ImageAttachment[]) => {
281
+ // Prefer the text captured when the popover opened — it can't be clobbered by
282
+ // a later selection change or clear while the user is composing the comment.
283
+ const text = commentPopoverRef.current?.selectedText || pendingTextRef.current;
284
+ if (!text) return;
285
+
286
+ const id = nextHtmlAnnId();
287
+ postToIframe(iframeRef.current, { type: `${PREFIX}create-mark`, id, annotationType: "comment" });
288
+ onAddRef.current?.({
289
+ id,
290
+ blockId: "",
291
+ startOffset: 0,
292
+ endOffset: 0,
293
+ type: AnnotationType.COMMENT,
294
+ text: comment,
295
+ originalText: text,
296
+ author: getIdentity(),
297
+ createdA: Date.now(),
298
+ images,
299
+ });
300
+
301
+ setCommentPopover(null);
302
+ pendingTextRef.current = "";
303
+ },
304
+ [iframeRef],
305
+ );
306
+
307
+ const handleCommentClose = useCallback(() => {
308
+ setCommentPopover(null);
309
+ pendingTextRef.current = "";
310
+ }, []);
311
+
312
+ const handleToolbarClose = useCallback(() => {
313
+ setToolbarState(null);
314
+ pendingTextRef.current = "";
315
+ }, []);
316
+
317
+ const applyQuickLabel = useCallback(
318
+ (label: QuickLabel, clearState: () => void) => {
319
+ const text = pendingTextRef.current;
320
+ if (!text) return;
321
+ const id = nextHtmlAnnId();
322
+ postToIframe(iframeRef.current, { type: `${PREFIX}create-mark`, id, annotationType: "comment" });
323
+ onAddRef.current?.({
324
+ id,
325
+ blockId: "",
326
+ startOffset: 0,
327
+ endOffset: 0,
328
+ type: AnnotationType.COMMENT,
329
+ text: label.text,
330
+ originalText: text,
331
+ isQuickLabel: true,
332
+ quickLabelTip: label.tip,
333
+ author: getIdentity(),
334
+ createdA: Date.now(),
335
+ });
336
+ clearState();
337
+ pendingTextRef.current = "";
338
+ },
339
+ [iframeRef],
340
+ );
341
+
342
+ const handleQuickLabel = useCallback(
343
+ (label: QuickLabel) => applyQuickLabel(label, () => setToolbarState(null)),
344
+ [applyQuickLabel],
345
+ );
346
+
347
+ const handleFloatingQuickLabel = useCallback(
348
+ (label: QuickLabel) => applyQuickLabel(label, () => setQuickLabelPicker(null)),
349
+ [applyQuickLabel],
350
+ );
351
+
352
+ const handleQuickLabelPickerDismiss = useCallback(() => {
353
+ setQuickLabelPicker(null);
354
+ pendingTextRef.current = "";
355
+ }, []);
356
+
357
+ const removeHighlight = useCallback(
358
+ (id: string) => {
359
+ postToIframe(iframeRef.current, { type: `${PREFIX}remove-mark`, id });
360
+ },
361
+ [iframeRef],
362
+ );
363
+
364
+ const clearAllHighlights = useCallback(() => {
365
+ postToIframe(iframeRef.current, { type: `${PREFIX}clear-marks` });
366
+ }, [iframeRef]);
367
+
368
+ const applyAnnotations = useCallback(
369
+ (anns: Annotation[]) => {
370
+ for (const ann of anns) {
371
+ if (ann.type === AnnotationType.GLOBAL_COMMENT) continue;
372
+ const annType = ann.type === AnnotationType.DELETION ? "deletion" : "comment";
373
+ postToIframe(iframeRef.current, {
374
+ type: `${PREFIX}find-and-mark`,
375
+ id: ann.id,
376
+ originalText: ann.originalText,
377
+ annotationType: annType,
378
+ });
379
+ }
380
+ },
381
+ [iframeRef],
382
+ );
383
+
384
+ return {
385
+ toolbarState,
386
+ commentPopover,
387
+ quickLabelPicker,
388
+ handleAnnotate,
389
+ handleQuickLabel,
390
+ handleToolbarClose,
391
+ handleRequestComment,
392
+ handleCommentSubmit,
393
+ handleCommentClose,
394
+ handleFloatingQuickLabel,
395
+ handleQuickLabelPickerDismiss,
396
+ removeHighlight,
397
+ clearAllHighlights,
398
+ applyAnnotations,
399
+ };
400
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Agent brand marks, inlined as components so the single-file review bundle has
3
+ * no external asset dependency. Claude is a vector path; Codex ships only as a
4
+ * PNG in the repo, so it rides as a base64 data-URI.
5
+ */
6
+
7
+ export function ClaudeIcon({ className }: { className?: string }) {
8
+ return (
9
+ <svg viewBox="0 0 32 32" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
10
+ <path d="m6.283 21.28 6.293-3.531.106-.306-.106-.171h-.307l-1.051-.065-3.596-.097-3.118-.13-3.021-.162-.761-.161-.712-.94.073-.469.639-.429.916.08 2.023.138 3.037.209 2.203.13 3.263.339h.518l.073-.21-.177-.129-.138-.13-3.142-2.129-3.401-2.25-1.782-1.296-.963-.656-.486-.616-.21-1.343.875-.963 1.175.08.3.08 1.19.915 2.542 1.967 3.319 2.445.486.404.194-.138.024-.097-.218-.365-1.806-3.263-1.926-3.32-.857-1.375-.227-.825c-.08-.339-.138-.624-.138-.972L8.384.177 8.935 0l1.328.177.56.486.824 1.887 1.337 2.972 2.073 4.04.607 1.199.324 1.11.121.339h.21v-.194l.17-2.276.315-2.795.307-3.596.106-1.012.501-1.214.995-.657.778.372.639.916-.088.591-.381 2.471-.745 3.87-.485 2.591h.282l.324-.324 1.311-1.74 2.203-2.754.972-1.093 1.133-1.207.728-.574h1.376l1.013 1.505-.454 1.555-1.416 1.797-1.175 1.522-1.685 2.268-1.051 1.814.097.144.25-.023 3.805-.81 2.056-.372 2.454-.421 1.11.518.12.527-.436 1.078-2.624.648-3.077.615-4.582 1.084-.057.041.065.08 2.065.195.883.047h2.162l4.025.3 1.052.696.63.851-.106.647-1.619.825-2.186-.518-5.1-1.214-1.75-.436h-.242v.145l1.458 1.425 2.671 2.412 3.346 3.11.17.769-.43.607-.453-.065-2.939-2.211-1.134-.996-2.568-2.162h-.17v.227l.591.866 3.125 4.697.162 1.441-.226.468-.81.283-.89-.162-1.829-2.568-1.888-2.891-1.522-2.592-.186.106-.898 9.677-.421.495-.972.371-.81-.615-.43-.996.43-1.967.518-2.568.422-2.041.38-2.535.226-.842-.015-.056-.185.023-1.912 2.624-2.906 3.928-2.3 2.462-.551.218-.954-.494.088-.883.533-.787 3.184-4.049 1.919-2.509 1.24-1.449-.009-.21h-.073l-8.455 5.49-1.505.194-.648-.607.08-.995.307-.324 2.542-1.749-.009.008z" fill="#d97757" />
11
+ </svg>
12
+ );
13
+ }
14
+
15
+ const CODEX_DATA_URI =
16
+ "data:image/png;base64,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";
17
+
18
+ export function CodexIcon({ className }: { className?: string }) {
19
+ return <img src={CODEX_DATA_URI} className={className} alt="" aria-hidden="true" draggable={false} />;
20
+ }
21
+
22
+ // Pi brand mark — path data from packages/ui/components/ProviderIcons.tsx's
23
+ // PiIcon (itself extracted from apps/marketing/public/assets/icon-pi.svg).
24
+ // currentColor so it adapts to theme, like OpenCodeIcon below.
25
+ export function PiIcon({ className }: { className?: string }) {
26
+ return (
27
+ <svg viewBox="0 0 800 800" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
28
+ <path fill="currentColor" fillRule="evenodd" d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z" />
29
+ <path fill="currentColor" d="M517.36 400H634.72V634.72H517.36Z" />
30
+ </svg>
31
+ );
32
+ }
33
+
34
+ // GitHub Copilot brand mark — path from the official agent icon set
35
+ // (welcome.developers.workers.dev/icons/agents/copilot); the dark/light
36
+ // variants there differ only by fill, so one currentColor component covers
37
+ // both themes, like PiIcon/OpenCodeIcon.
38
+ export function CopilotIcon({ className }: { className?: string }) {
39
+ return (
40
+ <svg viewBox="0 0 256 208" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
41
+ <path fill="currentColor" d="M205.3 31.4c14 14.8 20 35.2 22.5 63.6 6.6 0 12.8 1.5 17 7.2l7.8 10.6c2.2 3 3.4 6.6 3.4 10.4v28.7a12 12 0 0 1-4.8 9.5C215.9 187.2 172.3 208 128 208c-49 0-98.2-28.3-123.2-46.6a12 12 0 0 1-4.8-9.5v-28.7c0-3.8 1.2-7.4 3.4-10.5l7.8-10.5c4.2-5.7 10.4-7.2 17-7.2 2.5-28.4 8.4-48.8 22.5-63.6C77.3 3.2 112.6 0 127.6 0h.4c14.7 0 50.4 2.9 77.3 31.4ZM128 78.7c-3 0-6.5.2-10.3.6a27.1 27.1 0 0 1-6 12.1 45 45 0 0 1-32 13c-6.8 0-13.9-1.5-19.7-5.2-5.5 1.9-10.8 4.5-11.2 11-.5 12.2-.6 24.5-.6 36.8 0 6.1 0 12.3-.2 18.5 0 3.6 2.2 6.9 5.5 8.4C79.9 185.9 105 192 128 192s48-6 74.5-18.1a9.4 9.4 0 0 0 5.5-8.4c.3-18.4 0-37-.8-55.3-.4-6.6-5.7-9.1-11.2-11-5.8 3.7-13 5.1-19.7 5.1a45 45 0 0 1-32-12.9 27.1 27.1 0 0 1-6-12.1c-3.4-.4-6.9-.5-10.3-.6Zm-27 44c5.8 0 10.5 4.6 10.5 10.4v19.2a10.4 10.4 0 0 1-20.8 0V133c0-5.8 4.6-10.4 10.4-10.4Zm53.4 0c5.8 0 10.4 4.6 10.4 10.4v19.2a10.4 10.4 0 0 1-20.8 0V133c0-5.8 4.7-10.4 10.4-10.4Zm-73-94.4c-11.2 1.1-20.6 4.8-25.4 10-10.4 11.3-8.2 40.1-2.2 46.2A31.2 31.2 0 0 0 75 91.7c6.8 0 19.6-1.5 30.1-12.2 4.7-4.5 7.5-15.7 7.2-27-.3-9.1-2.9-16.7-6.7-19.9-4.2-3.6-13.6-5.2-24.2-4.3Zm69 4.3c-3.8 3.2-6.4 10.8-6.7 19.9-.3 11.3 2.5 22.5 7.2 27a41.7 41.7 0 0 0 30 12.2c8.9 0 17-2.9 21.3-7.2 6-6.1 8.2-34.9-2.2-46.3-4.8-5-14.2-8.8-25.4-9.9-10.6-1-20 .7-24.2 4.3ZM128 56c-2.6 0-5.6.2-9 .5.4 1.7.5 3.7.7 5.7 0 1.5 0 3-.2 4.5 3.2-.3 6-.3 8.5-.3 2.6 0 5.3 0 8.5.3-.2-1.6-.2-3-.2-4.5.2-2 .3-4 .7-5.7-3.4-.3-6.4-.5-9-.5Z" />
42
+ </svg>
43
+ );
44
+ }
45
+
46
+ // Cursor brand mark — from packages/ui/components/icons/app/cursor.svg
47
+ // (black rounded square + white cursor facet).
48
+ export function CursorIcon({ className }: { className?: string }) {
49
+ return (
50
+ <svg viewBox="0 0 40 40" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
51
+ <path d="M28.375 4H11.625C7.41383 4 4 7.41383 4 11.625V28.375C4 32.5862 7.41383 36 11.625 36H28.375C32.5862 36 36 32.5862 36 28.375V11.625C36 7.41383 32.5862 4 28.375 4Z" fill="#000" />
52
+ <path d="M29.6214 14.2072L20.9502 9.12249C20.6718 8.95917 20.3282 8.95917 20.0498 9.12249L11.3786 14.2072C11.1445 14.3445 11 14.5984 11 14.8733V25.1267C11 25.4012 11.1445 25.6555 11.3786 25.7928L20.0498 30.8775C20.3282 31.0408 20.6718 31.0408 20.9502 30.8775L29.6214 25.7928C29.8555 25.6555 30 25.4016 30 25.1267V14.8733C30 14.5988 29.8555 14.3445 29.6214 14.2072ZM29.0767 15.2843L20.706 30.0101C20.6494 30.1093 20.5 30.0688 20.5 29.9538V20.3115C20.5 20.1189 20.3986 19.9407 20.2342 19.8439L12.0124 15.0226C11.9147 14.9651 11.9546 14.8134 12.0678 14.8134H28.8093C29.047 14.8134 29.1956 15.0751 29.0767 15.2843Z" fill="#fff" />
53
+ </svg>
54
+ );
55
+ }
56
+
57
+ // OpenCode brand mark — extracted from apps/marketing/public/assets/icon-opencode-dark.svg
58
+ // (same vector ProviderIcons.tsx uses; currentColor so it adapts to theme).
59
+ export function OpenCodeIcon({ className }: { className?: string }) {
60
+ return (
61
+ <svg viewBox="0 0 32 32" className={className} xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
62
+ <path d="M3 32V0h26v32zM22 7H10v18h12z" fill="currentColor" />
63
+ <path d="M10 13h12v12H10z" fill="currentColor" opacity={0.4} />
64
+ </svg>
65
+ );
66
+ }