@plannotator/ui 0.53.1 → 0.55.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 (149) hide show
  1. package/HANDOFF.md +274 -14
  2. package/README.md +114 -11
  3. package/components/AgentToolAnnouncementDialog.tsx +1 -1
  4. package/components/AgentsTab.tsx +1 -1
  5. package/components/AnnotationPanel.tsx +142 -24
  6. package/components/AnnotationSidebar.tsx +1 -1
  7. package/components/AnnotationToolbar.tsx +64 -9
  8. package/components/ApproveDropdown.tsx +7 -9
  9. package/components/AskSessionAnnouncementDialog.tsx +1 -1
  10. package/components/AttachmentsButton.tsx +1 -1
  11. package/components/BlockRenderer.tsx +22 -5
  12. package/components/CodeFilePopout.tsx +139 -4
  13. package/components/CodeSnippetPreview.tsx +174 -0
  14. package/components/CommentPopover.tsx +44 -8
  15. package/components/DecisionControl.tsx +31 -5
  16. package/components/DeferredMarkdownContentsRail.tsx +38 -0
  17. package/components/DiagramBlock.tsx +17 -12
  18. package/components/DiffFileTree.tsx +7 -6
  19. package/components/DocumentContents.tsx +74 -0
  20. package/components/EditorAnnotationCard.tsx +1 -1
  21. package/components/ExportModal.tsx +77 -7
  22. package/components/HostTitle.tsx +0 -4
  23. package/components/ImageAnnotator/Toolbar.tsx +25 -1
  24. package/components/ImageAnnotator/index.tsx +24 -4
  25. package/components/ImageLightbox.tsx +722 -126
  26. package/components/ImagePeek.tsx +38 -4
  27. package/components/ImageRegions.tsx +369 -0
  28. package/components/ImageThumbnail.tsx +1 -1
  29. package/components/ImageZoomStage.tsx +796 -0
  30. package/components/InboxAnnouncementDialog.tsx +202 -0
  31. package/components/InlineMarkdown.tsx +167 -174
  32. package/components/KeyboardShortcuts.tsx +23 -0
  33. package/components/LazyBlockBoundary.tsx +100 -0
  34. package/components/ListMarker.tsx +3 -3
  35. package/components/LookAndFeelAnnouncementDialog.tsx +12 -2
  36. package/components/MarkdownContentsRail.tsx +777 -0
  37. package/components/MenuVersionSection.tsx +31 -9
  38. package/components/PlanDecisionMenuExtras.tsx +129 -0
  39. package/components/PlanHeaderMenu.tsx +15 -1
  40. package/components/ProviderIcons.tsx +8 -0
  41. package/components/RefHoverCard.tsx +913 -0
  42. package/components/SendFailureToast.tsx +126 -0
  43. package/components/Settings.tsx +617 -126
  44. package/components/TableOfContents.tsx +20 -6
  45. package/components/TerminalToolsAnnouncementDialog.tsx +1 -1
  46. package/components/ThemeProvider.tsx +14 -1
  47. package/components/ThemeTab.tsx +16 -24
  48. package/components/Tooltip.tsx +49 -18
  49. package/components/Viewer.tsx +288 -43
  50. package/components/ai/DocumentAIChatPanel.tsx +29 -3
  51. package/components/ai/NewChatButton.tsx +74 -0
  52. package/components/blocks/CodeBlock.tsx +1 -1
  53. package/components/blocks/HtmlBlock.tsx +7 -1
  54. package/components/blocks/QuestionBlock.tsx +532 -62
  55. package/components/diagram/DiagramComposer.tsx +5 -1
  56. package/components/diagram/DiagramPending.tsx +12 -2
  57. package/components/diagram/DiagramViewer.tsx +19 -3
  58. package/components/html-embed/HtmlEmbedBlock.tsx +374 -0
  59. package/components/html-embed/SvgFenceBlock.tsx +46 -0
  60. package/components/html-embed/index.ts +3 -0
  61. package/components/html-embed/languages.ts +15 -0
  62. package/components/html-embed/loadGate.ts +107 -0
  63. package/components/html-viewer/HtmlViewer.tsx +82 -6
  64. package/components/html-viewer/bridge-script.asset.js +173 -1
  65. package/components/html-viewer/bridge-script.ts +173 -1
  66. package/components/html-viewer/useHtmlAnnotation.ts +125 -0
  67. package/components/imageZoom.ts +276 -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/contents-rail.css +337 -0
  78. package/hooks/pfm/useCodeFilePopout.ts +7 -1
  79. package/hooks/useAIChat.ts +72 -25
  80. package/hooks/useAnnotationDraft.ts +71 -6
  81. package/hooks/useAnnotationHighlighter.ts +57 -3
  82. package/hooks/useCodeAnnotationDraft.ts +113 -17
  83. package/hooks/useCookieHostSetting.ts +96 -0
  84. package/hooks/useDocumentFont.ts +17 -0
  85. package/hooks/useDocumentTextSize.ts +20 -0
  86. package/hooks/useFileBrowser.ts +10 -1
  87. package/hooks/useHeadingScrollSpy.ts +77 -0
  88. package/hooks/useHoverPreview.ts +94 -0
  89. package/hooks/useLinkedDoc.ts +226 -13
  90. package/hooks/usePackageChangesSetting.ts +90 -0
  91. package/hooks/usePlanLookDialog.ts +67 -0
  92. package/hooks/usePrefersReducedMotion.ts +23 -0
  93. package/hooks/useSharing.ts +11 -7
  94. package/hooks/useSwipeStep.ts +597 -0
  95. package/hooks/useThemePaletteHalves.ts +37 -0
  96. package/hooks/useUpdateCheck.ts +22 -4
  97. package/hooks/useUpdateToast.ts +1 -1
  98. package/package.json +26 -23
  99. package/shortcuts/code-review/annotationToolbar.shortcuts.ts +1 -0
  100. package/shortcuts/code-review/explorer.shortcuts.ts +82 -0
  101. package/shortcuts/core.ts +63 -4
  102. package/shortcuts/index.ts +1 -0
  103. package/shortcuts/plan-review/annotationPanel.shortcuts.ts +1 -1
  104. package/shortcuts/plan-review/commentPopover.shortcuts.ts +1 -0
  105. package/shortcuts/runtime.ts +4 -3
  106. package/styles.css +1 -1
  107. package/theme.css +179 -1
  108. package/themes/plannotator-graphite.css +66 -0
  109. package/types.ts +26 -9
  110. package/utils/agentSwitch.ts +41 -20
  111. package/utils/agentToolSetting.ts +1 -1
  112. package/utils/aiChatFormat.ts +23 -7
  113. package/utils/annotationHelpers.ts +32 -4
  114. package/utils/annotationOwnsHighlight.ts +4 -2
  115. package/utils/asciidocParser.ts +570 -0
  116. package/utils/autoUpdateNotice.ts +24 -0
  117. package/utils/belowTitleSlot.ts +174 -0
  118. package/utils/blockParse.ts +45 -0
  119. package/utils/codeLineScroll.ts +100 -0
  120. package/utils/commentSubmitKey.ts +48 -0
  121. package/utils/cssFontFamily.ts +65 -0
  122. package/utils/decisionSpec.ts +190 -2
  123. package/utils/defaultNotesApp.ts +1 -1
  124. package/utils/diffFonts.ts +4 -1
  125. package/utils/documentFont.ts +68 -0
  126. package/utils/documentTextSize.ts +54 -0
  127. package/utils/fontDetect.ts +63 -0
  128. package/utils/headingScrollSpy.ts +92 -0
  129. package/utils/inboxAnnouncement.ts +78 -0
  130. package/utils/lookAndFeelAnnouncement.ts +82 -5
  131. package/utils/markdownExtensions.ts +2 -2
  132. package/utils/mermaid.ts +20 -3
  133. package/utils/mermaidTheme.ts +3 -51
  134. package/utils/notion.ts +58 -0
  135. package/utils/parser.ts +28 -1
  136. package/utils/platform.ts +46 -0
  137. package/utils/refCard.ts +82 -0
  138. package/utils/richClipboard.ts +380 -0
  139. package/utils/richClipboardLists.ts +57 -0
  140. package/utils/richCopyPolicy.ts +22 -0
  141. package/utils/scrollAnchor.ts +82 -0
  142. package/utils/sectionRefs.ts +123 -0
  143. package/utils/selectionBoundary.ts +40 -0
  144. package/utils/sendFailure.ts +85 -0
  145. package/utils/sharing.ts +5 -2
  146. package/utils/themeRegistry.ts +10 -0
  147. package/utils/themeTokens.ts +74 -0
  148. package/utils/updateNotice.ts +30 -8
  149. package/utils/vscodePanel.ts +9 -0
@@ -2,7 +2,11 @@ import React from 'react';
2
2
  import {
3
3
  formatShortcutBindingTokens,
4
4
  listScopeShortcuts,
5
+ resolveShortcutBindings,
6
+ reviewExplorerShortcuts,
7
+ reviewMarkdownPreviewShortcuts,
5
8
  vimSelectionShortcuts,
9
+ type ShortcutScopeDefinition,
6
10
  } from '../shortcuts';
7
11
  import { isMac, modKey, altKey } from '../utils/platform';
8
12
 
@@ -141,6 +145,23 @@ const vimShortcuts: ShortcutSection[] = (() => {
141
145
  return Array.from(sections, ([title, shortcuts]) => ({ title, shortcuts }));
142
146
  })();
143
147
 
148
+ /** Sections read from registry scopes (this system's keys), so they cannot drift. */
149
+ function scopeSections(scope: ShortcutScopeDefinition<any>): ShortcutSection[] {
150
+ const sections = new Map<string, Shortcut[]>();
151
+ const entries = listScopeShortcuts(scope)
152
+ .sort((left, right) => (left.displayOrder ?? 0) - (right.displayOrder ?? 0));
153
+ for (const entry of entries) {
154
+ const shortcuts = sections.get(entry.section) ?? [];
155
+ shortcuts.push({
156
+ keys: formatShortcutBindingTokens(resolveShortcutBindings(entry)[0] ?? ''),
157
+ desc: entry.description,
158
+ hint: entry.hint,
159
+ });
160
+ sections.set(entry.section, shortcuts);
161
+ }
162
+ return Array.from(sections, ([title, shortcuts]) => ({ title, shortcuts }));
163
+ }
164
+
144
165
  const planActionShortcuts: ShortcutSection = {
145
166
  title: 'Actions',
146
167
  shortcuts: [
@@ -213,6 +234,8 @@ const reviewShortcuts: ShortcutSection[] = [
213
234
  { keys: ['Z'], desc: 'Undo collapse', hint: 'Reopens the last collapsed file and scrolls to it' },
214
235
  ],
215
236
  },
237
+ ...scopeSections(reviewExplorerShortcuts),
238
+ ...scopeSections(reviewMarkdownPreviewShortcuts),
216
239
  {
217
240
  title: 'Annotations',
218
241
  shortcuts: [
@@ -0,0 +1,100 @@
1
+ import React, { useEffect, useState } from 'react';
2
+
3
+ /**
4
+ * A part of the page that a code-split build loads as its own chunk (a
5
+ * diagram, an HTML or SVG embed, the diagram popout, the Markdown preview)
6
+ * can fail to arrive: a flaky link, a server that has stopped, or a tab older
7
+ * than the server's build, whose chunk names no longer exist. React.lazy then
8
+ * throws, and without a boundary React unmounts the whole app, taking the
9
+ * user's unsent comments and edits off the screen with it.
10
+ *
11
+ * This boundary keeps the failure in place of that one part. The caller's
12
+ * `fallback` gets a small notice with a Reload action (`LazyLoadNotice`) and
13
+ * draws it beside whatever it can still show, usually the block's source.
14
+ * The rest of the page stays mounted. Nothing reloads by itself.
15
+ *
16
+ * With no error it renders its children and nothing else.
17
+ */
18
+ export class LazyBlockBoundary extends React.Component<
19
+ { fallback: (notice: React.ReactNode) => React.ReactNode; children: React.ReactNode },
20
+ { failed: boolean; error: unknown }
21
+ > {
22
+ state = { failed: false, error: null as unknown };
23
+
24
+ static getDerivedStateFromError(error: unknown) {
25
+ return { failed: true, error };
26
+ }
27
+
28
+ componentDidCatch(error: unknown) {
29
+ console.error('[plannotator] A part of the page could not load:', error);
30
+ }
31
+
32
+ render() {
33
+ if (this.state.failed) return this.props.fallback(<LazyLoadNotice error={this.state.error} />);
34
+ return this.props.children;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * The URL of the chunk a failed dynamic import asked for, when the error names
40
+ * one on this page's origin. Chromium and Firefox put it in the message
41
+ * ("Failed to fetch dynamically imported module: http://…/assets/x.js");
42
+ * Safari does not, and then the notice says the general thing.
43
+ */
44
+ export function failedChunkUrl(error: unknown): string | null {
45
+ if (typeof window === 'undefined') return null;
46
+ const message = error instanceof Error ? error.message : typeof error === 'string' ? error : '';
47
+ const match = /(https?:\/\/[^\s'"]+|\/assets\/[^\s'"]+)/.exec(message);
48
+ if (!match) return null;
49
+ try {
50
+ const url = new URL(match[1], window.location.href);
51
+ return url.origin === window.location.origin ? url.href : null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * One quiet line: why the part is missing, and Reload. A chunk the server
59
+ * answers with 404 is a tab older than the server's build (the server was
60
+ * updated on the same port), so the line says that instead. Finding out costs
61
+ * one HEAD request, made only after a failure.
62
+ */
63
+ export const LazyLoadNotice: React.FC<{ error: unknown }> = ({ error }) => {
64
+ const [updated, setUpdated] = useState(false);
65
+ useEffect(() => {
66
+ const url = failedChunkUrl(error);
67
+ if (url === null || typeof fetch !== 'function') return;
68
+ let live = true;
69
+ fetch(url, { method: 'HEAD', cache: 'no-store' }).then(
70
+ (response) => {
71
+ if (live && response.status === 404) setUpdated(true);
72
+ },
73
+ () => {},
74
+ );
75
+ return () => {
76
+ live = false;
77
+ };
78
+ }, [error]);
79
+
80
+ return (
81
+ <div
82
+ role="status"
83
+ data-lazy-load-failed={updated ? 'updated' : ''}
84
+ className="mb-1.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted-foreground"
85
+ >
86
+ <span>{updated ? 'This page was updated.' : "Couldn't load this view."}</span>
87
+ {/* The hit area is padding cancelled by a negative margin, so the line
88
+ looks the same: 24px tall everywhere, 44px on a touch screen. On
89
+ touch it grows upward into the block's top margin, never down over
90
+ the source under it. */}
91
+ <button
92
+ type="button"
93
+ onClick={() => window.location.reload()}
94
+ className="relative -mx-1 -my-1 px-1 py-1 font-medium text-foreground underline-offset-2 hover:underline [@media(pointer:coarse)]:-mb-1.5 [@media(pointer:coarse)]:-mt-[22px] [@media(pointer:coarse)]:pb-1.5 [@media(pointer:coarse)]:pt-[22px]"
95
+ >
96
+ {updated ? 'Reload to see this' : 'Reload'}
97
+ </button>
98
+ </div>
99
+ );
100
+ };
@@ -60,17 +60,17 @@ export const ListMarker: React.FC<ListMarkerProps> = ({
60
60
  aria-checked={interactive ? checked : undefined}
61
61
  >
62
62
  {showNumeral && (
63
- <span className="text-primary/60 tabular-nums text-right" style={{ minWidth: '1.5rem' }}>
63
+ <span className="text-primary/60 tabular-nums text-right" style={{ minWidth: '1.5rem' }} data-list-numeral="">
64
64
  {orderedIndex}.
65
65
  </span>
66
66
  )}
67
67
  {isCheckbox ? (
68
68
  checked ? (
69
- <svg className="w-4 h-4 text-success mt-[3px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}>
69
+ <svg data-list-check="" className="w-4 h-4 text-success mt-[3px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}>
70
70
  <path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
71
71
  </svg>
72
72
  ) : (
73
- <svg className={`w-4 h-4 text-muted-foreground/50 mt-[3px]${interactive ? ' hover:text-muted-foreground transition-colors' : ''}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
73
+ <svg data-list-check="" className={`w-4 h-4 text-muted-foreground/50 mt-[3px]${interactive ? ' hover:text-muted-foreground transition-colors' : ''}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
74
74
  <circle cx="12" cy="12" r="9" />
75
75
  </svg>
76
76
  )
@@ -12,6 +12,12 @@ interface LookAndFeelAnnouncementDialogProps {
12
12
  onToggleGrid: (value: boolean) => void;
13
13
  /** Persists the current choice and closes the dialog. */
14
14
  onDismiss: () => void;
15
+ /**
16
+ * `first-run` (default): the first-use choice. `moved`: the one-time notice
17
+ * for someone moved from Grid to Clean when Clean became the default; the
18
+ * same chooser, with a line saying what changed.
19
+ */
20
+ variant?: 'first-run' | 'moved';
15
21
  }
16
22
 
17
23
  const LOOK_OPTIONS: {
@@ -48,6 +54,7 @@ export const LookAndFeelAnnouncementDialog: React.FC<LookAndFeelAnnouncementDial
48
54
  gridEnabled,
49
55
  onToggleGrid,
50
56
  onDismiss,
57
+ variant = 'first-run',
51
58
  }) => {
52
59
  const dialogRef = useRef<HTMLDivElement>(null);
53
60
  const continueRef = useRef<HTMLButtonElement>(null);
@@ -115,6 +122,7 @@ export const LookAndFeelAnnouncementDialog: React.FC<LookAndFeelAnnouncementDial
115
122
  <div
116
123
  ref={dialogRef}
117
124
  role="dialog"
125
+ data-plan-look-prompt={variant}
118
126
  aria-modal="true"
119
127
  aria-labelledby="plan-look-choice-title"
120
128
  aria-describedby="plan-look-choice-description"
@@ -129,13 +137,15 @@ export const LookAndFeelAnnouncementDialog: React.FC<LookAndFeelAnnouncementDial
129
137
  id="plan-look-choice-title"
130
138
  className="text-balance text-xl font-semibold tracking-tight sm:text-2xl"
131
139
  >
132
- Choose how plans look
140
+ {variant === 'moved' ? 'Plans now open in Clean' : 'Choose how plans look'}
133
141
  </h2>
134
142
  <p
135
143
  id="plan-look-choice-description"
136
144
  className="mt-1 text-sm leading-relaxed text-muted-foreground"
137
145
  >
138
- Pick a starting view. You can change it anytime in Settings.
146
+ {variant === 'moved'
147
+ ? "Grid is one switch away in Settings, or pick it here."
148
+ : "Pick a starting view. You can change it anytime in Settings."}
139
149
  </p>
140
150
  </header>
141
151