@plannotator/ui 0.53.0 → 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 (145) hide show
  1. package/HANDOFF.md +284 -27
  2. package/README.md +84 -14
  3. package/components/AgentToolAnnouncementDialog.tsx +1 -1
  4. package/components/AgentsTab.tsx +1 -1
  5. package/components/AnnotationPanel.tsx +177 -34
  6. package/components/AnnotationSidebar.tsx +1 -1
  7. package/components/AnnotationToolbar.tsx +64 -9
  8. package/components/AnnotationToolstrip.tsx +45 -40
  9. package/components/ApproveDropdown.tsx +7 -9
  10. package/components/AskSessionAnnouncementDialog.tsx +1 -1
  11. package/components/AttachmentsButton.tsx +1 -1
  12. package/components/BlockRenderer.tsx +19 -5
  13. package/components/CodeFilePopout.tsx +140 -5
  14. package/components/CommentPopover.tsx +46 -8
  15. package/components/DecisionControl.tsx +31 -5
  16. package/components/DiagramBlock.tsx +17 -12
  17. package/components/EditorAnnotationCard.tsx +1 -1
  18. package/components/ExportModal.tsx +77 -7
  19. package/components/HostTitle.tsx +0 -4
  20. package/components/HtmlSurfaceControls.tsx +2 -1
  21. package/components/ImageAnnotator/Toolbar.tsx +25 -1
  22. package/components/ImageAnnotator/index.tsx +24 -4
  23. package/components/ImageLightbox.tsx +658 -120
  24. package/components/ImagePeek.tsx +38 -4
  25. package/components/ImageRegions.tsx +369 -0
  26. package/components/ImageThumbnail.tsx +1 -1
  27. package/components/ImageZoomStage.tsx +796 -0
  28. package/components/InboxAnnouncementDialog.tsx +202 -0
  29. package/components/InlineMarkdown.tsx +152 -24
  30. package/components/KeyboardShortcuts.tsx +23 -0
  31. package/components/LazyBlockBoundary.tsx +100 -0
  32. package/components/ListMarker.tsx +3 -3
  33. package/components/LookAndFeelAnnouncementDialog.tsx +12 -2
  34. package/components/MentionPicker.tsx +51 -0
  35. package/components/MenuVersionSection.tsx +31 -9
  36. package/components/PlanDecisionMenuExtras.tsx +129 -0
  37. package/components/PlanHeaderMenu.tsx +15 -1
  38. package/components/ProviderIcons.tsx +8 -0
  39. package/components/RefHoverCard.tsx +913 -0
  40. package/components/SendFailureToast.tsx +126 -0
  41. package/components/Settings.tsx +617 -126
  42. package/components/SkillReferenceMenu.tsx +39 -12
  43. package/components/TerminalToolsAnnouncementDialog.tsx +1 -1
  44. package/components/ThemeProvider.tsx +14 -1
  45. package/components/ThemeTab.tsx +16 -24
  46. package/components/Tooltip.tsx +49 -18
  47. package/components/Viewer.tsx +316 -47
  48. package/components/ai/DocumentAIChatPanel.tsx +29 -3
  49. package/components/ai/NewChatButton.tsx +74 -0
  50. package/components/blocks/CodeBlock.tsx +1 -1
  51. package/components/blocks/HtmlBlock.tsx +7 -1
  52. package/components/blocks/QuestionBlock.tsx +522 -62
  53. package/components/blocks/QuestionContext.tsx +3 -0
  54. package/components/diagram/DiagramComposer.tsx +5 -1
  55. package/components/diagram/DiagramPending.tsx +12 -2
  56. package/components/diagram/DiagramViewer.tsx +19 -3
  57. package/components/html-embed/HtmlEmbedBlock.tsx +374 -0
  58. package/components/html-embed/SvgFenceBlock.tsx +46 -0
  59. package/components/html-embed/index.ts +3 -0
  60. package/components/html-embed/languages.ts +15 -0
  61. package/components/html-embed/loadGate.ts +107 -0
  62. package/components/html-viewer/HtmlViewer.tsx +69 -6
  63. package/components/html-viewer/bridge-script.asset.js +504 -12
  64. package/components/html-viewer/bridge-script.lite.ts +1 -1
  65. package/components/html-viewer/bridge-script.ts +545 -12
  66. package/components/html-viewer/useHtmlAnnotation.ts +125 -0
  67. package/components/imageZoom.ts +265 -0
  68. package/components/plan-diff/PlanCleanDiffView.tsx +1 -1
  69. package/components/plan-diff/PlanDiffViewer.tsx +13 -2
  70. package/components/plan-diff/PlanRawDiffView.tsx +1 -1
  71. package/components/sidebar/FileBrowser.tsx +16 -2
  72. package/components/sidebar/SidebarTabs.tsx +3 -0
  73. package/config/index.ts +3 -0
  74. package/config/reviewView.ts +41 -1
  75. package/config/settings.ts +155 -4
  76. package/configure.ts +28 -0
  77. package/hooks/pfm/useCodeFilePopout.ts +7 -1
  78. package/hooks/useAIChat.ts +72 -25
  79. package/hooks/useAnnotationDraft.ts +71 -6
  80. package/hooks/useAnnotationHighlighter.ts +57 -3
  81. package/hooks/useCodeAnnotationDraft.ts +113 -17
  82. package/hooks/useCookieHostSetting.ts +96 -0
  83. package/hooks/useDocumentFont.ts +17 -0
  84. package/hooks/useDocumentTextSize.ts +20 -0
  85. package/hooks/useFileBrowser.ts +10 -1
  86. package/hooks/useLinkedDoc.ts +226 -13
  87. package/hooks/useMentionAutocomplete.ts +1 -1
  88. package/hooks/usePackageChangesSetting.ts +90 -0
  89. package/hooks/usePlanLookDialog.ts +67 -0
  90. package/hooks/useSharing.ts +11 -7
  91. package/hooks/useSkillReferenceAutocomplete.ts +7 -3
  92. package/hooks/useThemePaletteHalves.ts +37 -0
  93. package/hooks/useUpdateCheck.ts +22 -4
  94. package/hooks/useUpdateToast.ts +1 -1
  95. package/hooks/useVimSelection.ts +9 -0
  96. package/package.json +23 -22
  97. package/shortcuts/code-review/annotationToolbar.shortcuts.ts +1 -0
  98. package/shortcuts/code-review/explorer.shortcuts.ts +82 -0
  99. package/shortcuts/core.ts +63 -4
  100. package/shortcuts/index.ts +1 -0
  101. package/shortcuts/plan-review/annotationPanel.shortcuts.ts +1 -1
  102. package/shortcuts/plan-review/commentPopover.shortcuts.ts +1 -0
  103. package/shortcuts/runtime.ts +14 -6
  104. package/styles.css +1 -1
  105. package/theme.css +179 -1
  106. package/themes/plannotator-graphite.css +66 -0
  107. package/types.ts +26 -9
  108. package/utils/agentSwitch.ts +41 -20
  109. package/utils/agentToolSetting.ts +1 -1
  110. package/utils/aiChatFormat.ts +23 -7
  111. package/utils/annotationOwnsHighlight.ts +4 -2
  112. package/utils/asciidocParser.ts +570 -0
  113. package/utils/autoUpdateNotice.ts +24 -0
  114. package/utils/belowTitleSlot.ts +174 -0
  115. package/utils/codeLineScroll.ts +100 -0
  116. package/utils/commentSubmitKey.ts +48 -0
  117. package/utils/cssFontFamily.ts +65 -0
  118. package/utils/decisionSpec.ts +190 -2
  119. package/utils/defaultNotesApp.ts +1 -1
  120. package/utils/diffFonts.ts +4 -1
  121. package/utils/documentFont.ts +68 -0
  122. package/utils/documentTextSize.ts +54 -0
  123. package/utils/fontDetect.ts +63 -0
  124. package/utils/inboxAnnouncement.ts +78 -0
  125. package/utils/lookAndFeelAnnouncement.ts +82 -5
  126. package/utils/markdownExtensions.ts +2 -2
  127. package/utils/mentions.ts +34 -5
  128. package/utils/mermaid.ts +20 -3
  129. package/utils/mermaidTheme.ts +3 -51
  130. package/utils/notion.ts +58 -0
  131. package/utils/parser.ts +28 -1
  132. package/utils/platform.ts +46 -0
  133. package/utils/refCard.ts +82 -0
  134. package/utils/richClipboard.ts +380 -0
  135. package/utils/richClipboardLists.ts +57 -0
  136. package/utils/richCopyPolicy.ts +22 -0
  137. package/utils/scrollAnchor.ts +82 -0
  138. package/utils/sectionRefs.ts +123 -0
  139. package/utils/selectionBoundary.ts +40 -0
  140. package/utils/sendFailure.ts +85 -0
  141. package/utils/sharing.ts +5 -2
  142. package/utils/themeRegistry.ts +10 -0
  143. package/utils/themeTokens.ts +74 -0
  144. package/utils/updateNotice.ts +30 -8
  145. package/utils/vscodePanel.ts +9 -0
@@ -18,6 +18,7 @@ import {
18
18
  import type { Annotation, EditorMode, HostDraft, ImageAttachment, InputMethod } from "../../types";
19
19
  import { AnnotationType } from "../../types";
20
20
  import { copyTextPreservingFocus } from "../../utils/clipboard";
21
+ import { getRichCopyEnabled } from "../../utils/richCopyPolicy";
21
22
  import { elementIdentityForAskAI } from "../../utils/parser";
22
23
  import { getIdentity } from "../../utils/identity";
23
24
  import { THUMBS_UP_LABEL } from "../../utils/quickLabels";
@@ -139,6 +140,14 @@ function parseVimBridgeHelp(value: unknown): boolean | null {
139
140
 
140
141
  const MAX_VIM_COPY_TEXT_LENGTH = 2 * 1024 * 1024;
141
142
 
143
+ /**
144
+ * The srcdoc frame's sandbox. `allow-modals` lets the page's alert, confirm
145
+ * and prompt open (#2063: people's own pages and local sites). Never
146
+ * `allow-same-origin`: the page stays at an opaque origin, away from the
147
+ * session's API. Live-app mode is unsandboxed and does not use this.
148
+ */
149
+ export const HTML_VIEWER_SANDBOX = "allow-scripts allow-modals";
150
+
142
151
  /** Default wait for the bridge's `ready` on the `bridgeScriptUrl` path. */
143
152
  export const DEFAULT_BRIDGE_READY_TIMEOUT_MS = 5000;
144
153
 
@@ -239,6 +248,13 @@ export interface HtmlViewerProps {
239
248
  * key. Honored only while the frame holds focus, and at most once per
240
249
  * STEP_MESSAGE_MIN_INTERVAL_MS. Absent: the message is ignored, as before. */
241
250
  onStep?: (dir: 1 | -1) => void;
251
+ /** Escape pressed inside the page in Interact that the page did not use
252
+ * itself (no handler of the page cancelled it or stopped it, and focus was
253
+ * not in one of its text fields): the host may close something of its own
254
+ * (the Inbox leaves full screen). Never sent in Annotate, whose Escape is
255
+ * `onAnnotateModeExit`. Honored only while the frame holds focus, and at
256
+ * most once per STEP_MESSAGE_MIN_INTERVAL_MS. Absent: ignored, as before. */
257
+ onPageEscape?: () => void;
242
258
  /** Opt-in Vim-style keyboard selection. Default false for compatibility. */
243
259
  vimModeEnabled?: boolean;
244
260
  /** Replace the iframe-local compact badge with the shared live key HUD. */
@@ -384,6 +400,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
384
400
  onAnnotateModeToggle,
385
401
  onToolsToggle,
386
402
  onStep,
403
+ onPageEscape,
387
404
  vimModeEnabled = false,
388
405
  vimHudEnabled = false,
389
406
  vimHudKeyPanelEnabled = true,
@@ -452,6 +469,9 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
452
469
  const annotateModeActiveRef = useRef(annotateModeActive);
453
470
  annotateModeActiveRef.current = annotateModeActive;
454
471
  const lastStepAtRef = useRef(0);
472
+ const onPageEscapeRef = useRef(onPageEscape);
473
+ onPageEscapeRef.current = onPageEscape;
474
+ const lastPageEscapeAtRef = useRef(0);
455
475
 
456
476
  /** Single choke point for direct-to-bridge posts: live sessions get the
457
477
  * token + concrete targetOrigin, srcdoc keeps "*" and no token. */
@@ -809,6 +829,17 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
809
829
  onStepRef.current?.(dir);
810
830
  return;
811
831
  }
832
+ if (isRecord(e.data) && e.data.type === `${PREFIX}page-escape`) {
833
+ // Like a step, the page could post this itself: honored only for a
834
+ // key pressed in the frame, in Interact, at most once per interval.
835
+ if (annotateModeActiveRef.current) return;
836
+ if (document.activeElement !== iframeRef.current) return;
837
+ const now = Date.now();
838
+ if (now - lastPageEscapeAtRef.current < STEP_MESSAGE_MIN_INTERVAL_MS) return;
839
+ lastPageEscapeAtRef.current = now;
840
+ onPageEscapeRef.current?.();
841
+ return;
842
+ }
812
843
  const vimCopy = parseVimBridgeCopy(e.data);
813
844
  if (vimCopy !== null) {
814
845
  const iframe = iframeRef.current;
@@ -879,9 +910,15 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
879
910
  return true;
880
911
  }, [readOnly, vimModeEnabled]);
881
912
 
913
+ // A composer hidden with its target (a slide the page hid) is not on
914
+ // screen: it neither blocks Vim's document focus nor counts as an open
915
+ // overlay. Escape still closes it, as its own rung (below).
916
+ const composerShown = !!hook.commentPopover && hook.pinVisible;
917
+ const composerHidden = !!hook.commentPopover && !hook.pinVisible;
918
+
882
919
  useVimDocumentFocus({
883
920
  enabled: !readOnly && vimModeEnabled,
884
- blocked: !!hook.toolbarState || !!hook.commentPopover || !!hook.quickLabelPicker,
921
+ blocked: !!hook.toolbarState || composerShown || !!hook.quickLabelPicker,
885
922
  focusDocument: focusVimDocument,
886
923
  });
887
924
 
@@ -983,6 +1020,27 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
983
1020
  postToBridge({ type: `${PREFIX}set-host-navigates`, active: true });
984
1021
  }, [iframeReadyVersion, hostNavigates]);
985
1022
 
1023
+ // The hidden composer's Escape rung: with focus outside the iframe,
1024
+ // Escape closes the hidden draft (its text stays in the draft store) and
1025
+ // is used, so the next press reaches the rest of the ladder. Capture
1026
+ // phase, so it runs before the Vim document-focus rung. Inside the
1027
+ // iframe the bridge closes the draft and the hook closes the composer.
1028
+ const handleCommentCloseRef = useRef(hook.handleCommentClose);
1029
+ handleCommentCloseRef.current = hook.handleCommentClose;
1030
+ useEffect(() => {
1031
+ if (readOnly || !composerHidden) return;
1032
+ const onKeyDown = (e: KeyboardEvent) => {
1033
+ if (e.key !== 'Escape' || e.defaultPrevented || e.isComposing) return;
1034
+ const target = e.target as HTMLElement | null;
1035
+ if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) return;
1036
+ if (document.querySelector('[data-plannotator-confirm-dialog="true"]')) return;
1037
+ e.preventDefault();
1038
+ handleCommentCloseRef.current();
1039
+ };
1040
+ window.addEventListener('keydown', onKeyDown, true);
1041
+ return () => window.removeEventListener('keydown', onKeyDown, true);
1042
+ }, [readOnly, composerHidden]);
1043
+
986
1044
  // Parent-side Esc rung: with focus outside the iframe the bridge never
987
1045
  // sees the keydown. Any open composer/toolbar/picker still closes first —
988
1046
  // their state is read from this render's closure, so an Esc that closed
@@ -993,6 +1051,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
993
1051
  !!hook.toolbarState || !!hook.commentPopover || !!hook.quickLabelPicker || !!globalCommentPopover;
994
1052
  const onKeyDown = (e: KeyboardEvent) => {
995
1053
  if (e.key !== 'Escape' || e.defaultPrevented) return;
1054
+ // A hidden composer is closed by its own rung above.
996
1055
  if (overlayOpen) return;
997
1056
  // A text field or dialog owns its own Escape.
998
1057
  const target = e.target as HTMLElement | null;
@@ -1035,7 +1094,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
1035
1094
 
1036
1095
  const vimOverlayWasOpenRef = useRef(false);
1037
1096
  useEffect(() => {
1038
- const overlayOpen = !!hook.toolbarState || !!hook.commentPopover || !!hook.quickLabelPicker;
1097
+ const overlayOpen = !!hook.toolbarState || composerShown || !!hook.quickLabelPicker;
1039
1098
  const wasOpen = vimOverlayWasOpenRef.current;
1040
1099
  vimOverlayWasOpenRef.current = overlayOpen;
1041
1100
  if (
@@ -1050,7 +1109,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
1050
1109
  { type: `${PREFIX}focus-vim` },
1051
1110
  );
1052
1111
  }
1053
- }, [hook.commentPopover, hook.quickLabelPicker, hook.toolbarState, readOnly, vimModeEnabled]);
1112
+ }, [composerShown, hook.quickLabelPicker, hook.toolbarState, readOnly, vimModeEnabled]);
1054
1113
 
1055
1114
  useEffect(() => {
1056
1115
  if (iframeReadyVersion === 0) return;
@@ -1236,7 +1295,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
1236
1295
  same-origin XHR) and no srcdoc. Srcdoc mode is unchanged. */}
1237
1296
  <iframe
1238
1297
  ref={iframeRef}
1239
- {...(src ? { src } : { srcDoc: srcdoc, sandbox: "allow-scripts" + (hostNavigates ? " allow-popups" : "") })}
1298
+ {...(src ? { src } : { srcDoc: srcdoc, sandbox: HTML_VIEWER_SANDBOX + (hostNavigates ? " allow-popups" : "") })}
1240
1299
  style={{
1241
1300
  width: "100%",
1242
1301
  height: fullViewport ? "100%" : `${iframeHeight}px`,
@@ -1293,6 +1352,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
1293
1352
  positionMode="center-above"
1294
1353
  element={hook.toolbarState.element}
1295
1354
  copyText={hook.toolbarState.selectionText}
1355
+ getRichCopy={getRichCopyEnabled() ? hook.toolbarState.getRichCopy : undefined}
1296
1356
  // HTML/live surfaces are comment-only: no Delete, no label
1297
1357
  // picker, no Alt+digit labels (commentOnly). Exactly ONE label
1298
1358
  // affordance is restored: the hardcoded 👍 "Looks good". The
@@ -1311,8 +1371,10 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
1311
1371
  document.body,
1312
1372
  )}
1313
1373
 
1314
- {/* Comment popover portal */}
1315
- {!readOnly && hook.commentPopover &&
1374
+ {/* Hidden with its target (a slide the page hid): CommentPopover's draft
1375
+ store keeps the text and images, and shown again it does not take
1376
+ focus from where the person is. */}
1377
+ {!readOnly && hook.pinVisible && hook.commentPopover &&
1316
1378
  createPortal(
1317
1379
  <CommentPopover
1318
1380
  anchorEl={hook.commentPopover.anchorEl}
@@ -1340,6 +1402,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
1340
1402
  onRemoveTargetChip={targetChips ? hook.removeDraftTarget : undefined}
1341
1403
  onHoverTargetChip={targetChips ? hook.flashDraftTarget : undefined}
1342
1404
  refocusToken={targetChips ? hook.composerFocusToken : undefined}
1405
+ autoFocus={hook.composerAutoFocus}
1343
1406
  captureStrayKeys={multiSelectActive}
1344
1407
  yieldState={multiSelectActive ? composerYield : undefined}
1345
1408
  />,