@plannotator/ui 0.30.0 → 0.31.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 (74) hide show
  1. package/README.md +1 -0
  2. package/components/ActionMenu.tsx +6 -1
  3. package/components/AgentsTab.tsx +8 -9
  4. package/components/AnalysisLayerToggle.tsx +48 -0
  5. package/components/AnnotationPanel.tsx +62 -32
  6. package/components/AnnotationToolbar.tsx +28 -8
  7. package/components/AnnotationToolstrip.tsx +9 -0
  8. package/components/CommentPopover.tsx +212 -46
  9. package/components/ConfirmDialog.tsx +42 -28
  10. package/components/InlineMarkdown.tsx +3 -2
  11. package/components/KeyboardShortcuts.tsx +9 -0
  12. package/components/Landing.tsx +1 -1
  13. package/components/LookAndFeelAnnouncementDialog.tsx +147 -178
  14. package/components/MarkdownEditor/embedPicker.ts +349 -0
  15. package/components/MarkdownEditor.tsx +12 -0
  16. package/components/ModeToggle.tsx +2 -1
  17. package/components/PermissionModeSetup.tsx +24 -5
  18. package/components/PinpointOverlay.tsx +11 -6
  19. package/components/PlanHeaderMenu.tsx +140 -1
  20. package/components/SearchableSelect.tsx +2 -0
  21. package/components/Settings.tsx +136 -10
  22. package/components/SkillReferenceMenu.tsx +9 -0
  23. package/components/StickyHeaderLane.tsx +9 -2
  24. package/components/TableOfContents.tsx +9 -4
  25. package/components/TextShimmer.tsx +8 -5
  26. package/components/ThemeProvider.tsx +43 -1
  27. package/components/ThemeTab.tsx +52 -1
  28. package/components/Tooltip.tsx +3 -1
  29. package/components/Viewer.tsx +19 -5
  30. package/components/VimTargetReticle.tsx +12 -4
  31. package/components/ai/DocumentAIChatPanel.tsx +1 -0
  32. package/components/core/button.tsx +14 -6
  33. package/components/html-viewer/HtmlViewer.tsx +208 -39
  34. package/components/html-viewer/bridge-script.ts +319 -65
  35. package/components/html-viewer/composerYield.ts +1 -51
  36. package/components/html-viewer/useHtmlAnnotation.ts +146 -57
  37. package/components/plan-diff/PlanCleanDiffView.tsx +1 -0
  38. package/components/sidebar/FileBrowser.tsx +17 -5
  39. package/components/sidebar/SidebarContainer.tsx +124 -28
  40. package/components/ui/button.tsx +10 -8
  41. package/components/ui/dialog.tsx +35 -25
  42. package/config/index.ts +6 -1
  43. package/config/reviewView.ts +42 -9
  44. package/config/settings.ts +141 -0
  45. package/hooks/useAIProviderConfig.ts +8 -7
  46. package/hooks/useActiveSection.ts +6 -4
  47. package/hooks/useAgentJobs.ts +3 -0
  48. package/hooks/useAnnotationHighlighter.ts +20 -0
  49. package/hooks/useIsMobile.ts +37 -0
  50. package/hooks/useLinkedDoc.ts +7 -0
  51. package/hooks/useScrollViewport.ts +74 -0
  52. package/hooks/useViewportEnvironment.ts +350 -0
  53. package/package.json +3 -2
  54. package/shortcuts/index.ts +3 -0
  55. package/shortcuts/plan-review/annotationMode.shortcuts.ts +91 -0
  56. package/shortcuts/plan-review/documentView.shortcuts.ts +26 -0
  57. package/shortcuts/plan-review/htmlAnnotate.shortcuts.ts +24 -0
  58. package/styles.css +1 -1
  59. package/theme.css +229 -0
  60. package/types.ts +34 -0
  61. package/utils/annotateAgentTerminal.ts +36 -5
  62. package/utils/blockTargeting.ts +6 -3
  63. package/utils/composerYield.ts +45 -0
  64. package/utils/htmlChrome.ts +20 -16
  65. package/utils/lookAndFeelAnnouncement.ts +12 -8
  66. package/utils/markdownExtensions.ts +57 -0
  67. package/utils/parser.ts +37 -2
  68. package/utils/vimNavigation.ts +4 -1
  69. package/utils/vimScroll.ts +9 -4
  70. package/utils/wideMode.ts +20 -0
  71. package/components/PlanAIAnnouncementDialog.tsx +0 -187
  72. package/components/VimModeAnnouncementDialog.tsx +0 -557
  73. package/utils/planAIAnnouncement.ts +0 -17
  74. package/utils/vimModeAnnouncement.ts +0 -23
package/README.md CHANGED
@@ -60,6 +60,7 @@ Building your own tooltip and removing the built-in double-click reset are host-
60
60
  const editorExtensions = [wikiLinks({ suggest, resolve, onOpen })]; // stable reference!
61
61
  <MarkdownEditor markdown={md} documentId={docId} editorHandleRef={ref} extensions={editorExtensions} />
62
62
  ```
63
+ - **`embedPicker(config)` and `embedSlashItem()` are re-exported from the same surface.** Compose the static item into `slashCommands({ items: [...] })` and pass the picker beside it in the stable `extensions` array. `getTargets`, `buildInsertLine`, optional `uploadTarget`, and optional `getNotice` stay live through callbacks. The host owns embed grammar and upload error UI; the package owns filtering, async anchor mapping, single-flight upload state, and paragraph-safe insertion through the re-exported `planEmbedInsert()`.
63
64
  - **The viewer resolves wiki-links synchronously.** `InlineMarkdown` takes `resolveLinkedDoc?: (target) => { label?; status?: 'active' | 'deleted' } | null` — called with the raw stored target (opaque ids like `doc_01XYZ`, no `.md` normalization). Return a `label` to display live titles (stored label is the fallback, target the last resort); return `status: 'deleted'` for a muted non-link ("Document deleted") instead of a live link. Absent or `null` → rendering is unchanged. Sync-only by design: back it with an in-memory cache.
64
65
 
65
66
  Requires `@plannotator/markdown-editor ^0.3.2` and `@plannotator/atomic-editor ^0.7.0`. See HANDOFF.md § "Wiki-link seams (0.27.0)".
@@ -68,6 +68,7 @@ interface ActionMenuItemProps {
68
68
  label: string;
69
69
  subtitle?: string;
70
70
  badge?: React.ReactNode;
71
+ disabled?: boolean;
71
72
  }
72
73
 
73
74
  export const ActionMenuItem: React.FC<ActionMenuItemProps> = ({
@@ -76,10 +77,14 @@ export const ActionMenuItem: React.FC<ActionMenuItemProps> = ({
76
77
  label,
77
78
  subtitle,
78
79
  badge,
80
+ disabled = false,
79
81
  }) => (
80
82
  <button
83
+ data-pn-touch-target
84
+ type="button"
81
85
  onClick={onClick}
82
- className="flex w-full items-center gap-2 px-3 py-2 text-left text-xs transition-colors hover:bg-muted"
86
+ disabled={disabled}
87
+ className="flex w-full items-center gap-2 px-3 py-2 text-left text-xs transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-45 disabled:hover:bg-transparent"
83
88
  >
84
89
  <span className="text-muted-foreground">{icon}</span>
85
90
  {subtitle ? (
@@ -15,6 +15,7 @@ import {
15
15
  } from 'lucide-react';
16
16
  import type { AgentJobInfo, AgentCapabilities } from '../types';
17
17
  import { isTerminalStatus } from '@plannotator/core/agent-jobs';
18
+ import { GUIDE_ENGINE_LABEL } from '@plannotator/core/guide';
18
19
  import { cn } from '../lib/utils';
19
20
  import { ReviewAgentsIcon } from './ReviewAgentsIcon';
20
21
  import { ClaudeIcon, CodexIcon, CopilotIcon, CursorIcon, OpenCodeIcon, PiIcon } from './icons/AgentIcons';
@@ -127,14 +128,7 @@ const ENGINE_ICON: Record<AgentEngine, React.FC<{ className?: string }>> = {
127
128
  // review surface offers the wider set (Cursor/OpenCode). Exported so the guide
128
129
  // takeover surfaces (GuideScreen, GuideEmptyState in packages/review-editor)
129
130
  // share this one source of truth instead of keeping their own copies in sync.
130
- export const REVIEW_ENGINE_LABEL: Record<ReviewEngine, string> = {
131
- claude: 'Claude',
132
- codex: 'Codex',
133
- cursor: 'Cursor',
134
- opencode: 'OpenCode',
135
- pi: 'Pi',
136
- copilot: 'Copilot',
137
- };
131
+ export const REVIEW_ENGINE_LABEL: Record<ReviewEngine, string> = GUIDE_ENGINE_LABEL;
138
132
 
139
133
  // Review-only icon map — the wide set. Tour keeps the narrow ENGINE_ICON.
140
134
  const REVIEW_ENGINE_ICON: Record<ReviewEngine, React.FC<{ className?: string }>> = {
@@ -872,7 +866,12 @@ export const AgentsTab: React.FC<AgentsTabProps> = ({
872
866
  ? { effort: tourClaudeEffort }
873
867
  : { reasoningEffort: tourCodexReasoning, ...(tourCodexFast && { fastMode: true }) }),
874
868
  });
875
- const buildGuideLaunch = (): LaunchParams => {
869
+ // Guide extra instructions (#1265) are server-stored: this surface has no
870
+ // editor, so it sends none and the server applies the stored standing
871
+ // instructions itself, keeping sidebar launches identical to launch-page
872
+ // ones without a second read path.
873
+ const buildGuideLaunch = (): LaunchParams => buildGuideEngineParams();
874
+ const buildGuideEngineParams = (): LaunchParams => {
876
875
  if (guideEngine === 'cursor') {
877
876
  // Same omission rules as buildReviewLaunch: auto/empty ⇒ engine default.
878
877
  // Guide-scoped model — deliberately NOT the shared cursorModel (see
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+
3
+ interface AnalysisLayerToggleProps {
4
+ readonly checked: boolean;
5
+ readonly onChange: (checked: boolean) => void;
6
+ readonly label: React.ReactNode;
7
+ readonly description: string;
8
+ readonly disabled?: boolean;
9
+ readonly className?: string;
10
+ }
11
+
12
+ /** Full-row switch shared by Settings and the code-review analysis welcome. */
13
+ export function AnalysisLayerToggle({
14
+ checked,
15
+ onChange,
16
+ label,
17
+ description,
18
+ disabled = false,
19
+ className = '',
20
+ }: AnalysisLayerToggleProps) {
21
+ return (
22
+ <button
23
+ type="button"
24
+ role="switch"
25
+ aria-checked={checked}
26
+ disabled={disabled}
27
+ onClick={() => onChange(!checked)}
28
+ className={`flex min-h-11 w-full items-center justify-between gap-4 bg-transparent text-left disabled:cursor-not-allowed disabled:opacity-50 ${className}`}
29
+ >
30
+ <span className="min-w-0">
31
+ <span className="block text-sm font-medium text-foreground">{label}</span>
32
+ <span className="mt-0.5 block text-xs leading-relaxed text-muted-foreground">{description}</span>
33
+ </span>
34
+ <span
35
+ aria-hidden="true"
36
+ className={`relative h-6 w-11 shrink-0 rounded-full transition-colors duration-150 ease-out motion-reduce:transition-none ${
37
+ checked ? 'bg-primary' : 'bg-muted'
38
+ }`}
39
+ >
40
+ <span
41
+ className={`absolute left-1 top-1 h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-150 ease-out motion-reduce:transition-none ${
42
+ checked ? 'translate-x-5' : 'translate-x-0'
43
+ }`}
44
+ />
45
+ </span>
46
+ </button>
47
+ );
48
+ }
@@ -86,6 +86,9 @@ interface PanelProps {
86
86
  * gates what belongs in it. Selection and scrolling still work.
87
87
  * Default false — today's behavior. */
88
88
  readOnly?: boolean;
89
+ /** Embed only the timeline body in a host-owned stage. The host owns the
90
+ * title, close control, visible-viewport geometry, and focus boundary. */
91
+ presentation?: 'panel' | 'embedded';
89
92
  }
90
93
 
91
94
  export const AnnotationPanel: React.FC<PanelProps> = ({
@@ -111,8 +114,11 @@ export const AnnotationPanel: React.FC<PanelProps> = ({
111
114
  directEdits = null,
112
115
  renderCardFooter,
113
116
  readOnly = false,
117
+ presentation = 'panel',
114
118
  }) => {
115
119
  const isMobile = useIsMobile();
120
+ const embedded = presentation === 'embedded';
121
+ const mobilePanel = isMobile && !embedded;
116
122
  const [copiedText, setCopiedText] = useState(false);
117
123
  const listRef = useRef<HTMLDivElement>(null);
118
124
  const sortedAnnotations = [...annotations].sort((a, b) => a.createdA - b.createdA);
@@ -138,47 +144,61 @@ export const AnnotationPanel: React.FC<PanelProps> = ({
138
144
  <aside
139
145
  data-annotation-panel="true"
140
146
  data-plan-sidebar="right"
141
- className={`border-l border-border/50 bg-card flex flex-col flex-shrink-0 ${
142
- isMobile ? 'fixed top-12 bottom-0 right-0 z-[60] w-full max-w-sm shadow-2xl bg-card' : ''
147
+ className={`bg-card flex flex-col ${embedded ? 'h-full min-h-0 w-full flex-1' : 'flex-shrink-0 border-l border-border/50'} ${
148
+ mobilePanel ? 'fixed top-12 bottom-0 right-0 z-[60] w-full max-w-sm shadow-2xl bg-card' : ''
143
149
  }`}
144
- style={isMobile ? undefined : { width: width ?? 288 }}
150
+ style={embedded || mobilePanel ? undefined : { width: width ?? 288 }}
145
151
  >
146
152
  {/* Header */}
147
- <div className="border-b border-border/50">
148
- <div className="flex h-10 items-center justify-between px-3">
149
- <div className="flex items-center gap-2">
150
- <h2 className="text-xs font-medium text-foreground">
151
- Annotations
152
- </h2>
153
- {totalCount > 0 && (
154
- <span className="flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary/10 px-1 font-mono text-[10px] font-medium tabular-nums text-primary">
155
- {totalCount}
156
- </span>
153
+ {!embedded && (
154
+ <div className="border-b border-border/50">
155
+ <div className="flex h-10 items-center justify-between px-3">
156
+ <div className="flex items-center gap-2">
157
+ <h2 className="text-xs font-medium text-foreground">
158
+ Annotations
159
+ </h2>
160
+ {totalCount > 0 && (
161
+ <span className="flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary/10 px-1 font-mono text-[10px] font-medium tabular-nums text-primary">
162
+ {totalCount}
163
+ </span>
164
+ )}
165
+ </div>
166
+ {mobilePanel && onClose && (
167
+ <button
168
+ onClick={onClose}
169
+ className="relative rounded-md p-1.5 text-muted-foreground transition-colors before:absolute before:-inset-1.5 before:content-[''] hover:text-foreground md:hidden"
170
+ title="Close panel"
171
+ aria-label="Close panel"
172
+ >
173
+ <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
174
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
175
+ </svg>
176
+ </button>
157
177
  )}
158
178
  </div>
159
- {isMobile && onClose && (
179
+ {otherFileAnnotations && otherFileAnnotations.count > 0 && (
160
180
  <button
161
- onClick={onClose}
162
- className="relative rounded-md p-1.5 text-muted-foreground transition-colors before:absolute before:-inset-1.5 before:content-[''] hover:text-foreground md:hidden"
163
- title="Close panel"
164
- aria-label="Close panel"
181
+ onClick={onOtherFileAnnotationsClick}
182
+ className="px-3 pb-2 text-[10px] text-primary/70 hover:text-primary transition-colors cursor-pointer"
183
+ title="Show annotated files in sidebar"
165
184
  >
166
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
167
- <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
168
- </svg>
185
+ +{otherFileAnnotations.count} in {otherFileAnnotations.files} other file{otherFileAnnotations.files === 1 ? '' : 's'}
169
186
  </button>
170
187
  )}
171
188
  </div>
172
- {otherFileAnnotations && otherFileAnnotations.count > 0 && (
173
- <button
174
- onClick={onOtherFileAnnotationsClick}
175
- className="px-3 pb-2 text-[10px] text-primary/70 hover:text-primary transition-colors cursor-pointer"
176
- title="Show annotated files in sidebar"
177
- >
178
- +{otherFileAnnotations.count} in {otherFileAnnotations.files} other file{otherFileAnnotations.files === 1 ? '' : 's'}
179
- </button>
180
- )}
181
- </div>
189
+ )}
190
+
191
+ {embedded && otherFileAnnotations && otherFileAnnotations.count > 0 && (
192
+ <button
193
+ type="button"
194
+ data-pn-touch-target="true"
195
+ onClick={onOtherFileAnnotationsClick}
196
+ className="min-h-11 flex-shrink-0 border-b border-border/50 px-3 text-left text-xs text-primary/80 active:bg-muted"
197
+ title="Show annotated files in navigator"
198
+ >
199
+ {otherFileAnnotations.count} more in {otherFileAnnotations.files} other file{otherFileAnnotations.files === 1 ? '' : 's'}
200
+ </button>
201
+ )}
182
202
 
183
203
  {/* List */}
184
204
  <OverlayScrollArea className="flex-1 min-h-0">
@@ -297,7 +317,7 @@ export const AnnotationPanel: React.FC<PanelProps> = ({
297
317
  </aside>
298
318
  );
299
319
 
300
- if (isMobile) {
320
+ if (mobilePanel) {
301
321
  return (
302
322
  <>
303
323
  <div
@@ -493,6 +513,7 @@ const AnnotationCard: React.FC<{
493
513
  const editComposer = (
494
514
  <div onClick={(e: React.MouseEvent) => e.stopPropagation()}>
495
515
  <textarea
516
+ data-pn-mobile-editable="true"
496
517
  ref={textareaRef}
497
518
  value={editText}
498
519
  onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setEditText(e.target.value)}
@@ -534,6 +555,14 @@ const AnnotationCard: React.FC<{
534
555
  diff
535
556
  </span>
536
557
  )}
558
+ {annotation.pageUrl && (
559
+ <span
560
+ className="text-[9px] px-1.5 py-0.5 rounded font-medium bg-muted text-muted-foreground truncate max-w-[10rem]"
561
+ title={annotation.pageUrl}
562
+ >
563
+ {annotation.pageUrl}
564
+ </span>
565
+ )}
537
566
  <span className="text-[10px] text-muted-foreground/50 truncate">
538
567
  {annotation.author ? `${annotation.author}${isMe ? ' (me)' : ''} · ` : ''}{formatTimestamp(annotation.createdA)}
539
568
  </span>
@@ -724,6 +753,7 @@ const CodeAnnotationCard: React.FC<{
724
753
  {isEditing ? (
725
754
  <div className="mt-1.5" onClick={(e) => e.stopPropagation()}>
726
755
  <textarea
756
+ data-pn-mobile-editable="true"
727
757
  ref={textareaRef}
728
758
  value={editText}
729
759
  onChange={(e) => setEditText(e.target.value)}
@@ -4,6 +4,7 @@ import { createPortal } from "react-dom";
4
4
  import { useDismissOnOutsideAndEscape } from "../hooks/useDismissOnOutsideAndEscape";
5
5
  import { type QuickLabel, getQuickLabels } from "../utils/quickLabels";
6
6
  import { copyTextToClipboard } from "../utils/clipboard";
7
+ import { acquireTypeToCommentCapture } from "../shortcuts/plan-review/annotationMode.shortcuts";
7
8
  import { FloatingQuickLabelPicker } from "./FloatingQuickLabelPicker";
8
9
 
9
10
  type PositionMode = 'center-above' | 'top-right';
@@ -33,6 +34,10 @@ interface AnnotationToolbarProps {
33
34
  onQuickLabel?: (label: QuickLabel) => void;
34
35
  /** Text to copy when the button is clicked */
35
36
  copyText?: string;
37
+ /** Comment-only surfaces (HTML / live-app viewer): hide the Delete action.
38
+ * Markdown surfaces keep the full toolbar. Quick labels are already gated
39
+ * by the presence of onQuickLabel. */
40
+ commentOnly?: boolean;
36
41
  /** Hide the copy button (set when a keyboard copy handler exists) */
37
42
  hideCopyButton?: boolean;
38
43
  /** Close toolbar when element scrolls out of viewport */
@@ -52,6 +57,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
52
57
  onRequestComment,
53
58
  onQuickLabel,
54
59
  copyText,
60
+ commentOnly = false,
55
61
  hideCopyButton = false,
56
62
  closeOnScrollOut = false,
57
63
  isExiting = false,
@@ -146,7 +152,14 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
146
152
  };
147
153
 
148
154
  window.addEventListener("keydown", handleKeyDown);
149
- return () => window.removeEventListener("keydown", handleKeyDown);
155
+ // While this listener owns printable keys, the Shift+1..4 annotation-mode
156
+ // shortcuts must not fire: Shift+3 is "#", and a user typing "#" into a
157
+ // starting comment must not silently arm Redline (#1244 follow-up).
158
+ const releaseCapture = acquireTypeToCommentCapture();
159
+ return () => {
160
+ window.removeEventListener("keydown", handleKeyDown);
161
+ releaseCapture();
162
+ };
150
163
  }, [onClose, onRequestComment, onQuickLabel, quickLabels, showQuickLabels]);
151
164
 
152
165
  useDismissOnOutsideAndEscape({
@@ -197,7 +210,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
197
210
  to { opacity: 0; transform: translateY(8px)${translateX}; }
198
211
  }
199
212
  `}</style>
200
- <div className="flex items-center p-1 gap-0.5">
213
+ <div data-pn-annotation-toolbar-row="true" className="flex items-center p-1 gap-0.5">
201
214
  {!hideCopyButton && (
202
215
  <>
203
216
  <ToolbarButton
@@ -209,12 +222,14 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
209
222
  <div className="w-px h-5 bg-border mx-0.5" />
210
223
  </>
211
224
  )}
212
- <ToolbarButton
213
- onClick={() => handleTypeSelect(AnnotationType.DELETION)}
214
- icon={<TrashIcon />}
215
- label="Delete"
216
- className="text-destructive hover:bg-destructive/10"
217
- />
225
+ {!commentOnly && (
226
+ <ToolbarButton
227
+ onClick={() => handleTypeSelect(AnnotationType.DELETION)}
228
+ icon={<TrashIcon />}
229
+ label="Delete"
230
+ className="text-destructive hover:bg-destructive/10"
231
+ />
232
+ )}
218
233
  <ToolbarButton
219
234
  onClick={() => handleTypeSelect(AnnotationType.COMMENT)}
220
235
  icon={<CommentIcon />}
@@ -306,6 +321,11 @@ const ToolbarButton = React.forwardRef<HTMLButtonElement, {
306
321
  }>(({ onClick, icon, label, className }, ref) => (
307
322
  <button
308
323
  ref={ref}
324
+ // Icon-only controls: the markers are inert outside the compact touch
325
+ // scope, where theme.css grows them to var(--pn-touch-target). Desktop
326
+ // geometry is unchanged.
327
+ data-pn-touch-target="true"
328
+ data-pn-touch-target-icon="true"
309
329
  onClick={onClick}
310
330
  title={label}
311
331
  className={`p-1.5 rounded-md transition-colors ${className}`}
@@ -25,6 +25,11 @@ interface AnnotationToolstripProps {
25
25
  * narrow and leaves room for the diff badges.
26
26
  */
27
27
  iconOnly?: boolean;
28
+ /**
29
+ * Hide the drag/pinpoint input group entirely. Live app sessions are
30
+ * pinpoint-only, so the switch would be a dead control there.
31
+ */
32
+ hideInputMethodSwitch?: boolean;
28
33
  }
29
34
 
30
35
  export const AnnotationToolstrip: React.FC<AnnotationToolstripProps> = ({
@@ -36,6 +41,7 @@ export const AnnotationToolstrip: React.FC<AnnotationToolstripProps> = ({
36
41
  compact = false,
37
42
  showHelpLink = true,
38
43
  iconOnly = false,
44
+ hideInputMethodSwitch = false,
39
45
  }) => {
40
46
  const [showHelp, setShowHelp] = useState(false);
41
47
  const [helpTab, setHelpTab] = useState<'selection' | 'plannotator'>('selection');
@@ -50,6 +56,7 @@ export const AnnotationToolstrip: React.FC<AnnotationToolstripProps> = ({
50
56
  <>
51
57
  <div className={`flex items-center flex-wrap ${compact ? 'gap-1' : 'gap-1.5'}`}>
52
58
  {/* Input method group */}
59
+ {!hideInputMethodSwitch && (
53
60
  <div className="inline-flex items-center gap-0.5 bg-muted/50 rounded-lg p-0.5 border border-border/30">
54
61
  <ToolstripButton
55
62
  active={inputMethod === 'drag'}
@@ -89,6 +96,7 @@ export const AnnotationToolstrip: React.FC<AnnotationToolstripProps> = ({
89
96
  }
90
97
  />
91
98
  </div>
99
+ )}
92
100
 
93
101
  {/* Action mode group */}
94
102
  <div className="inline-flex items-center gap-0.5 bg-muted/50 rounded-lg p-0.5 border border-border/30">
@@ -321,6 +329,7 @@ const ToolstripButton: React.FC<{
321
329
  onClick={onClick}
322
330
  onMouseEnter={() => setHovered(true)}
323
331
  onMouseLeave={() => setHovered(false)}
332
+ aria-pressed={active}
324
333
  className={`relative flex items-center h-7 rounded-md overflow-hidden ${colorClass}`}
325
334
  style={{ width: currentWidth, transition }}
326
335
  >