@plannotator/ui 0.28.0 → 0.29.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 (105) hide show
  1. package/README.md +6 -0
  2. package/components/AISettingsTab.tsx +5 -4
  3. package/components/ActionMenu.tsx +5 -1
  4. package/components/AgentsTab.tsx +10 -23
  5. package/components/AnnotationPanel.tsx +9 -5
  6. package/components/AnnotationToolbar.tsx +5 -13
  7. package/components/AnnotationToolstrip.tsx +2 -2
  8. package/components/ApproveDropdown.tsx +1 -1
  9. package/components/BlockRenderer.tsx +1 -1
  10. package/components/CodeFilePopout.tsx +5 -4
  11. package/components/CommentPopover.tsx +391 -65
  12. package/components/DocBadges.tsx +29 -11
  13. package/components/ExportModal.tsx +16 -8
  14. package/components/GraphvizBlock.tsx +1 -1
  15. package/components/InlineMarkdown.tsx +30 -13
  16. package/components/KeyboardShortcuts.tsx +37 -2
  17. package/components/Landing.tsx +7 -7
  18. package/components/MenuVersionSection.tsx +4 -4
  19. package/components/MermaidBlock.tsx +1 -1
  20. package/components/ModeToggle.tsx +7 -6
  21. package/components/OpenInAppButton.tsx +2 -5
  22. package/components/PinpointOverlay.tsx +9 -7
  23. package/components/PlanHeaderMenu.tsx +8 -8
  24. package/components/PopoutDialog.tsx +6 -1
  25. package/components/ResizeHandle.tsx +1 -0
  26. package/components/Settings.tsx +172 -12
  27. package/components/SkillReferenceMenu.tsx +260 -0
  28. package/components/StickyHeaderLane.tsx +7 -0
  29. package/components/ThemeProvider.tsx +131 -32
  30. package/components/ThemeTab.tsx +123 -77
  31. package/components/ToolbarButtons.tsx +29 -8
  32. package/components/Viewer.tsx +396 -130
  33. package/components/VimKeyHud.tsx +695 -0
  34. package/components/VimModeAnnouncementDialog.tsx +557 -0
  35. package/components/VimModeOverlay.tsx +235 -0
  36. package/components/VimTargetReticle.tsx +284 -0
  37. package/components/ai/DocumentAIChatPanel.tsx +1 -1
  38. package/components/blocks/CodeBlock.tsx +18 -18
  39. package/components/blocks/TablePopout.tsx +7 -8
  40. package/components/blocks/TableToolbar.tsx +7 -8
  41. package/components/goal-setup/GoalSetupSurface.tsx +16 -3
  42. package/components/html-viewer/HtmlViewer.tsx +450 -47
  43. package/components/html-viewer/annotationNumbering.ts +37 -0
  44. package/components/html-viewer/bridge-script.ts +4051 -298
  45. package/components/html-viewer/composerYield.ts +51 -0
  46. package/components/html-viewer/srcdoc.ts +18 -3
  47. package/components/html-viewer/useHtmlAnnotation.ts +457 -32
  48. package/components/icons/themeIcons.tsx +1 -1
  49. package/components/plan-diff/PlanCleanDiffView.tsx +9 -9
  50. package/components/plan-diff/PlanDiffBadge.tsx +22 -1
  51. package/components/settings/HooksTab.tsx +12 -8
  52. package/components/sidebar/FileBrowser.tsx +4 -1
  53. package/components/themeModes.tsx +28 -0
  54. package/config/configStore.ts +76 -1
  55. package/config/settings.ts +152 -0
  56. package/configure.ts +9 -0
  57. package/globals.d.ts +7 -1
  58. package/hooks/useAIChat.ts +5 -2
  59. package/hooks/useAIProviderActivation.ts +47 -0
  60. package/hooks/useAIProviderConfig.ts +5 -1
  61. package/hooks/useAgentSettings.ts +64 -23
  62. package/hooks/useAgents.ts +4 -4
  63. package/hooks/useAnnotationHighlighter.ts +100 -3
  64. package/hooks/useArchive.ts +2 -1
  65. package/hooks/useFenceTheme.ts +17 -0
  66. package/hooks/useLinkedDoc.ts +68 -1
  67. package/hooks/usePinpoint.ts +76 -75
  68. package/hooks/usePlanDiff.ts +73 -2
  69. package/hooks/useSkillReferenceAutocomplete.ts +239 -0
  70. package/hooks/useUpdateCheck.ts +1 -2
  71. package/hooks/useVimDocumentFocus.ts +116 -0
  72. package/hooks/useVimSelection.ts +1063 -0
  73. package/package.json +4 -4
  74. package/print.css +14 -13
  75. package/shortcuts/core.ts +38 -13
  76. package/shortcuts/index.ts +10 -0
  77. package/shortcuts/plan-review/commentPopover.shortcuts.ts +7 -0
  78. package/shortcuts/plan-review/vimSelection.shortcuts.ts +251 -0
  79. package/shortcuts/runtime.ts +111 -12
  80. package/styles.css +1 -1
  81. package/theme.css +504 -0
  82. package/themes/colorblind.css +89 -0
  83. package/themes/plannotator.css +2 -2
  84. package/types.ts +93 -10
  85. package/utils/agentSwitch.ts +33 -7
  86. package/utils/blockTargeting.ts +462 -178
  87. package/utils/clipboard.ts +110 -0
  88. package/utils/codeBlockMark.ts +50 -0
  89. package/utils/codeHighlight.ts +293 -0
  90. package/utils/codexModels.ts +79 -0
  91. package/utils/domSelection.ts +84 -0
  92. package/utils/htmlChrome.ts +73 -0
  93. package/utils/inputMethod.ts +79 -6
  94. package/utils/parser.ts +517 -21
  95. package/utils/preferenceTtl.ts +15 -0
  96. package/utils/sharing.ts +0 -1
  97. package/utils/skillCatalog.ts +269 -0
  98. package/utils/skillReferences.ts +475 -0
  99. package/utils/syntaxTheme.ts +83 -0
  100. package/utils/themeRegistry.ts +154 -0
  101. package/utils/vimHud.ts +263 -0
  102. package/utils/vimModeAnnouncement.ts +23 -0
  103. package/utils/vimNavigation.ts +417 -0
  104. package/utils/vimReticle.ts +88 -0
  105. package/utils/vimScroll.ts +162 -0
package/README.md CHANGED
@@ -24,6 +24,8 @@ configurePlannotatorUI({
24
24
  draftTransport,
25
25
  externalAnnotationTransport, // live/agent comments
26
26
  aiTransport,
27
+ skillCatalogTransport, // skill-reference catalog for comment composers
28
+ skillContentTransport, // human-only skill contents for feedback injection
27
29
  serverSync,
28
30
  });
29
31
  ```
@@ -78,6 +80,10 @@ Requires `@plannotator/markdown-editor ^0.3.2` and `@plannotator/atomic-editor ^
78
80
 
79
81
  Requires `@plannotator/markdown-editor ^0.4.0` and `@plannotator/atomic-editor ^0.8.0` (which adds a `@codemirror/merge` peer — declared by this package). See HANDOFF.md § "Frozen markdown diff (0.28.0)".
80
82
 
83
+ ### Raw-HTML annotation viewer (`HtmlViewer`)
84
+
85
+ `components/html-viewer` is supported host surface as of 0.29.0: the overlay-projection annotation viewer for raw HTML (placed comment markers, pinpoint element anchors, shift-click multi-target comments). Its contract is **props plus the validated iframe message protocol** — not `configurePlannotatorUI()`, which only governs the backend surfaces around it. Drive the `annotations` prop (marker numbering derives from its array order); `readOnly` keeps markers painted and clickable while disabling all authoring. **0.29.0 also carries a breaking migration:** highlight.js is gone and `.hljs` selectors are inert — style code via the exported `pn-code` class (`CODE_BLOCK_CLASS` in `utils/codeHighlight`). See HANDOFF.md § "Raw-HTML annotation viewer + syntax-highlighting migration (0.29.0)" before upgrading from 0.28.0.
86
+
81
87
  ## Consuming it (e.g. from Workspaces)
82
88
 
83
89
  ```bash
@@ -2,7 +2,6 @@ import type React from 'react';
2
2
  import { getProviderMeta } from './ProviderIcons';
3
3
  import {
4
4
  getAIProviderSettings,
5
- resolveAIModelForProvider,
6
5
  resolveAIProviderSelection,
7
6
  saveAIProviderSelection,
8
7
  savePreferredModel,
@@ -37,9 +36,11 @@ export const AISettingsTab: React.FC<AISettingsTabProps> = ({
37
36
 
38
37
  const handleSelectProvider = (providerId: string) => {
39
38
  onProviderChange(providerId);
40
- const provider = providers.find(p => p.id === providerId) ?? null;
41
- const model = resolveAIModelForProvider(provider, getAIProviderSettings().preferredModels);
42
- saveAIProviderSelection({ providerId, model, origin });
39
+ // Persist only the provider choice. Writing the resolved model here would
40
+ // clobber a saved preference the currently-advertised model list doesn't
41
+ // include (e.g. before deferred Codex discovery has run) with the static
42
+ // fallback id — the saved model only changes when the user picks one.
43
+ saveAIProviderSelection({ providerId, model: null, origin });
43
44
  };
44
45
 
45
46
  const handleModelChange = (providerId: string, modelId: string) => {
@@ -3,6 +3,7 @@ import React, { useEffect, useRef, useState } from 'react';
3
3
  interface ActionMenuProps {
4
4
  className?: string;
5
5
  panelClassName?: string;
6
+ panelWidth?: 'default' | 'wide';
6
7
  renderTrigger: (props: {
7
8
  isOpen: boolean;
8
9
  toggleMenu: () => void;
@@ -13,6 +14,7 @@ interface ActionMenuProps {
13
14
  export const ActionMenu: React.FC<ActionMenuProps> = ({
14
15
  className,
15
16
  panelClassName,
17
+ panelWidth = 'default',
16
18
  renderTrigger,
17
19
  children,
18
20
  }) => {
@@ -50,7 +52,9 @@ export const ActionMenu: React.FC<ActionMenuProps> = ({
50
52
  })}
51
53
 
52
54
  {isOpen && (
53
- <div className={panelClassName ?? 'absolute top-full right-0 mt-1 w-56 rounded-lg border border-border bg-popover py-1 shadow-xl z-[70]'}>
55
+ <div
56
+ className={panelClassName ?? `absolute top-full right-0 mt-1 ${panelWidth === 'wide' ? 'w-64' : 'w-56'} rounded-lg border border-border bg-popover py-1 shadow-xl z-[70]`}
57
+ >
54
58
  {children({ closeMenu: () => setIsOpen(false) })}
55
59
  </div>
56
60
  )}
@@ -22,6 +22,7 @@ import { useAgentSettings } from '../hooks/useAgentSettings';
22
22
  import type { AgentEngine, AgentMode, ReviewEngine } from '../hooks/useAgentSettings';
23
23
  import type { AgentLaunchParams } from '../hooks/useAgentJobs';
24
24
  import { ConfigRow, SegmentedPicker, Toggle, SelectMenu } from './AgentControls';
25
+ import { CODEX_MODELS, CODEX_EFFORT_LABELS, codexReasoningOptions } from '../utils/codexModels';
25
26
 
26
27
  export type { AgentLaunchParams } from '../hooks/useAgentJobs';
27
28
 
@@ -29,6 +30,7 @@ export type { AgentLaunchParams } from '../hooks/useAgentJobs';
29
30
 
30
31
  export const CLAUDE_MODELS: Array<{ value: string; label: string }> = [
31
32
  { value: 'claude-fable-5', label: 'Fable 5' },
33
+ { value: 'claude-opus-5', label: 'Opus 5' },
32
34
  { value: 'claude-opus-4-8', label: 'Opus 4.8' },
33
35
  { value: 'claude-opus-4-8[1m]', label: 'Opus 4.8 (1M)' },
34
36
  { value: 'claude-sonnet-5', label: 'Sonnet 5' },
@@ -49,25 +51,10 @@ export const CLAUDE_EFFORT: Array<{ value: string; label: string }> = [
49
51
  { value: 'max', label: 'Max' },
50
52
  ];
51
53
 
52
- export const CODEX_MODELS: Array<{ value: string; label: string }> = [
53
- { value: 'gpt-5.5', label: 'GPT-5.5' },
54
- { value: 'gpt-5.4', label: 'GPT-5.4' },
55
- { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
56
- { value: 'gpt-5.3-codex-spark', label: 'GPT-5.3 Codex Spark' },
57
- { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
58
- { value: 'gpt-5.2', label: 'GPT-5.2' },
59
- { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
60
- { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
61
- { value: 'gpt-5.4-mini', label: 'GPT-5.4 Mini' },
62
- ];
63
-
64
- export const CODEX_REASONING: Array<{ value: string; label: string }> = [
65
- { value: 'minimal', label: 'Minimal' },
66
- { value: 'low', label: 'Low' },
67
- { value: 'medium', label: 'Medium' },
68
- { value: 'high', label: 'High' },
69
- { value: 'xhigh', label: 'XHigh' },
70
- ];
54
+ // Codex model catalog + per-model reasoning efforts live in
55
+ // utils/codexModels (useAgentSettings needs them too, for effort clamping);
56
+ // re-exported here so both launch surfaces keep one import site.
57
+ export { CODEX_MODELS, codexReasoningOptions } from '../utils/codexModels';
71
58
 
72
59
  // Tour/guide Claude catalog: the CLI's latest-resolving aliases on top
73
60
  // (verified against `claude --help`: "Provide an alias for the latest model
@@ -270,7 +257,7 @@ function formatEffort(value: string): string {
270
257
  }
271
258
 
272
259
  function formatReasoning(value: string): string {
273
- return catalogLabel(CODEX_REASONING, value);
260
+ return CODEX_EFFORT_LABELS[value] ?? value;
274
261
  }
275
262
 
276
263
  // --- Add-a-review dialog: a type-ahead picker over every discovered skill ---
@@ -1112,7 +1099,7 @@ export const AgentsTab: React.FC<AgentsTabProps> = ({
1112
1099
  <SelectMenu value={codexModel} options={CODEX_MODELS} onChange={setCodexModel} />
1113
1100
  </ConfigRow>
1114
1101
  <ConfigRow label="Reasoning" stacked>
1115
- <SegmentedPicker options={CODEX_REASONING} value={codexReasoning} onChange={setCodexReasoning} />
1102
+ <SegmentedPicker options={codexReasoningOptions(codexModel)} value={codexReasoning} onChange={setCodexReasoning} />
1116
1103
  </ConfigRow>
1117
1104
  <ConfigRow label="Fast mode">
1118
1105
  <Toggle checked={codexFast} onChange={setCodexFast} />
@@ -1155,7 +1142,7 @@ export const AgentsTab: React.FC<AgentsTabProps> = ({
1155
1142
  {tourEngine === 'codex' && (
1156
1143
  <>
1157
1144
  <ConfigRow label="Reasoning" stacked>
1158
- <SegmentedPicker options={CODEX_REASONING} value={tourCodexReasoning} onChange={setTourCodexReasoning} />
1145
+ <SegmentedPicker options={codexReasoningOptions(tourCodexModel)} value={tourCodexReasoning} onChange={setTourCodexReasoning} />
1159
1146
  </ConfigRow>
1160
1147
  <ConfigRow label="Fast mode">
1161
1148
  <Toggle checked={tourCodexFast} onChange={setTourCodexFast} />
@@ -1190,7 +1177,7 @@ export const AgentsTab: React.FC<AgentsTabProps> = ({
1190
1177
  deliberately not offered for guide. */}
1191
1178
  {guideEngine === 'codex' && (
1192
1179
  <ConfigRow label="Reasoning" stacked>
1193
- <SegmentedPicker options={CODEX_REASONING} value={guideCodexReasoning} onChange={setGuideCodexReasoning} />
1180
+ <SegmentedPicker options={codexReasoningOptions(guideCodexModel)} value={guideCodexReasoning} onChange={setGuideCodexReasoning} />
1194
1181
  </ConfigRow>
1195
1182
  )}
1196
1183
 
@@ -66,7 +66,10 @@ interface PanelProps {
66
66
  editorAnnotations?: EditorAnnotation[];
67
67
  onDeleteEditorAnnotation?: (id: string) => void;
68
68
  onClose?: () => void;
69
- onQuickCopy?: () => Promise<void>;
69
+ /** Copy the full feedback payload. May resolve a success boolean; resolving
70
+ * `false` suppresses the "Copied" flash. A void resolution (existing hosts)
71
+ * is treated as success, preserving the original behavior. */
72
+ onQuickCopy?: () => Promise<void | boolean>;
70
73
  onShare?: () => void;
71
74
  otherFileAnnotations?: { count: number; files: number };
72
75
  onOtherFileAnnotationsClick?: () => void;
@@ -77,7 +80,7 @@ interface PanelProps {
77
80
  * resolve UI). The panel stays presentation-only; clicks inside the slot
78
81
  * do not select the card. Default: nothing rendered. */
79
82
  renderCardFooter?: (annotation: Annotation) => React.ReactNode;
80
- /** Hide every mutation affordance (delete/edit buttons on all card kinds).
83
+ /** Hide every mutation affordance (delete/edit, direct-edit discard, and host card footers).
81
84
  * Selection and scrolling still work. Default false — today's behavior. */
82
85
  readOnly?: boolean;
83
86
  }
@@ -178,7 +181,7 @@ export const AnnotationPanel: React.FC<PanelProps> = ({
178
181
  <OverlayScrollArea className="flex-1 min-h-0">
179
182
  <div ref={listRef} className="p-2 flex flex-col gap-1.5">
180
183
  {directEdits?.map((item) => (
181
- <DirectEditsCard key={item.id} {...item} />
184
+ <DirectEditsCard key={item.id} {...item} onDiscard={readOnly ? undefined : item.onDiscard} />
182
185
  ))}
183
186
  {totalCount === 0 ? (
184
187
  (!directEdits || directEdits.length === 0) && (
@@ -204,7 +207,7 @@ export const AnnotationPanel: React.FC<PanelProps> = ({
204
207
  onDelete={() => onDelete(entry.annotation.id)}
205
208
  onEdit={onEdit ? (updates: Partial<Annotation>) => onEdit(entry.annotation.id, updates) : undefined}
206
209
  readOnly={readOnly}
207
- footer={renderCardFooter?.(entry.annotation)}
210
+ footer={readOnly ? undefined : renderCardFooter?.(entry.annotation)}
208
211
  />
209
212
  ) : (
210
213
  <CodeAnnotationCard
@@ -249,7 +252,8 @@ export const AnnotationPanel: React.FC<PanelProps> = ({
249
252
  {onQuickCopy && (
250
253
  <button
251
254
  onClick={async () => {
252
- await onQuickCopy();
255
+ const result = await onQuickCopy();
256
+ if (result === false) return;
253
257
  setCopiedText(true);
254
258
  setTimeout(() => setCopiedText(false), 2000);
255
259
  }}
@@ -3,6 +3,7 @@ import { AnnotationType } from "../types";
3
3
  import { createPortal } from "react-dom";
4
4
  import { useDismissOnOutsideAndEscape } from "../hooks/useDismissOnOutsideAndEscape";
5
5
  import { type QuickLabel, getQuickLabels } from "../utils/quickLabels";
6
+ import { copyTextToClipboard } from "../utils/clipboard";
6
7
  import { FloatingQuickLabelPicker } from "./FloatingQuickLabelPicker";
7
8
 
8
9
  type PositionMode = 'center-above' | 'top-right';
@@ -72,19 +73,10 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
72
73
  const codeEl = element.querySelector('code');
73
74
  textToCopy = codeEl?.textContent || element.textContent || '';
74
75
  }
75
- try {
76
- await navigator.clipboard.writeText(textToCopy);
77
- } catch {
78
- const textarea = document.createElement('textarea');
79
- textarea.value = textToCopy;
80
- textarea.style.cssText = 'position:fixed;opacity:0';
81
- document.body.appendChild(textarea);
82
- textarea.select();
83
- document.execCommand('copy');
84
- textarea.remove();
76
+ if (await copyTextToClipboard(textToCopy)) {
77
+ setCopied(true);
78
+ setTimeout(() => setCopied(false), 1500);
85
79
  }
86
- setCopied(true);
87
- setTimeout(() => setCopied(false), 1500);
88
80
  };
89
81
 
90
82
  // Update position on scroll/resize
@@ -123,7 +115,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
123
115
  // Type-to-comment + Alt+N / bare digit quick label shortcuts
124
116
  useEffect(() => {
125
117
  const handleKeyDown = (e: KeyboardEvent) => {
126
- if (e.isComposing) return;
118
+ if (e.defaultPrevented || e.isComposing) return;
127
119
  if (isEditableElement(e.target) || isEditableElement(document.activeElement)) return;
128
120
 
129
121
  // When picker is open, let FloatingQuickLabelPicker own all keyboard input
@@ -210,8 +210,8 @@ export const AnnotationToolstrip: React.FC<AnnotationToolstripProps> = ({
210
210
  width="100%"
211
211
  height="100%"
212
212
  src={helpTab === 'selection'
213
- ? 'https://www.youtube.com/embed/ZNt9jtfx9TY?autoplay=1'
214
- : 'https://www.youtube.com/embed/a_AT7cEN_9I?autoplay=1'
213
+ ? 'https://www.youtube-nocookie.com/embed/ZNt9jtfx9TY?autoplay=1'
214
+ : 'https://www.youtube-nocookie.com/embed/a_AT7cEN_9I?autoplay=1'
215
215
  }
216
216
  title={helpTab === 'selection' ? 'How Selection Modes Work' : 'How Plannotator Works'}
217
217
  frameBorder="0"
@@ -36,7 +36,7 @@ export const ApproveDropdown: React.FC<ApproveDropdownProps> = ({
36
36
  disabled = false,
37
37
  isLoading = false,
38
38
  }) => {
39
- const [setting, setSetting] = useState<AgentSwitchSettings>(() => getAgentSwitchSettings());
39
+ const [setting, setSetting] = useState<AgentSwitchSettings>(() => getAgentSwitchSettings('plan'));
40
40
  const [isOpen, setIsOpen] = useState(false);
41
41
  const dropdownRef = useRef<HTMLDivElement>(null);
42
42
 
@@ -106,7 +106,7 @@ export const BlockRenderer: React.FC<{
106
106
  }
107
107
 
108
108
  case 'code':
109
- return <CodeBlock block={block} onHover={() => {}} onLeave={() => {}} isHovered={false} />;
109
+ return <CodeBlock block={block} isHovered={false} />;
110
110
 
111
111
  case 'table':
112
112
  return (
@@ -6,6 +6,7 @@ import { useTheme } from './ThemeProvider';
6
6
  import { CommentPopover } from './CommentPopover';
7
7
  import { ImageThumbnail } from './ImageThumbnail';
8
8
  import type { CodeAnnotation, ImageAttachment } from '../types';
9
+ import { copyTextToClipboard } from '../utils/clipboard';
9
10
 
10
11
  export interface CodeFileAnnotationInput {
11
12
  filePath: string;
@@ -457,12 +458,11 @@ export const CodeFilePopout: React.FC<CodeFilePopoutProps> = ({
457
458
  }, [onAddAnnotation, openCommentForRange]);
458
459
 
459
460
  const handleCopy = async () => {
460
- try {
461
- await navigator.clipboard.writeText(contents);
461
+ if (await copyTextToClipboard(contents)) {
462
462
  setCopied(true);
463
463
  setTimeout(() => setCopied(false), 1500);
464
- } catch (err) {
465
- console.error('Failed to copy:', err);
464
+ } else {
465
+ console.error('Failed to copy');
466
466
  }
467
467
  };
468
468
 
@@ -583,6 +583,7 @@ export const CodeFilePopout: React.FC<CodeFilePopoutProps> = ({
583
583
  anchorRect={pendingComment.anchorRect}
584
584
  contextText={pendingComment.contextText}
585
585
  isGlobal={false}
586
+ skillReferences
586
587
  onSubmit={(text, images) => {
587
588
  onAddAnnotation({
588
589
  filePath: filepath,