@plannotator/ui 0.43.0 → 0.43.1
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.
- package/HANDOFF.md +113 -2
- package/README.md +13 -1
- package/components/AnnotationToolbar.tsx +12 -3
- package/components/Viewer.tsx +37 -2
- package/components/html-viewer/HtmlViewer.tsx +17 -1
- package/components/html-viewer/useHtmlAnnotation.ts +4 -1
- package/hooks/useAnnotationHighlighter.ts +22 -3
- package/package.json +1 -1
- package/types.ts +1 -0
package/HANDOFF.md
CHANGED
|
@@ -214,7 +214,7 @@ We deliberately did **not** restructure the exports map in this PR (move-don't-r
|
|
|
214
214
|
| `hooks/useHtmlRefresh` | Re-fetch a rendered HTML document through a host-supplied `fetchSnapshot`, remount the viewer on a reload generation, acknowledge the restore report once. See "HTML annotation parity seams". |
|
|
215
215
|
| `shortcuts` (`useHtmlAnnotateShortcuts`, `defineShortcutScope`, the scope registry) | The declarative keyboard-shortcut engine and the per-surface scopes, including the HTML annotate scope (Mod+Shift+A toggles annotate mode, Mod+Shift+X shows/hides the tools). Pure: React plus `utils/platform`; no backend. |
|
|
216
216
|
| `utils/selectionActions` + `components/SelectionActionsDropdown` | The host selection-actions seam: `SelectionAction`, `SelectionActionContext`, the pure `buildSelectionActionContext`, and the dropdown `AnnotationToolbar` opens. Pure React; no backend. *(Blessed in 0.43.0.)* |
|
|
217
|
-
| `utils/mentions` + `components/MentionPicker` + `hooks/useMentionAutocomplete` | The `@` mention seam behind `CommentPopover`'s `mentionSource
|
|
217
|
+
| `utils/mentions` + `components/MentionPicker` + `hooks/useMentionAutocomplete` | The `@` mention seam behind `CommentPopover`'s `mentionSource` (and, since 0.43.1, `Viewer`'s and `HtmlViewer`'s): the pure grammar (`mentionTrigger`, `mentionMatches`, `applyMentionPick`, `survivingMentions`), the portaled picker, and the keyboard state machine. Pure React; no backend. *(Blessed in 0.43.0.)* |
|
|
218
218
|
| `utils/inputMethod` (`getInputMethod`, `saveInputMethod`, `refreshInputMethodStamp`) | The per-surface pinpoint/drag input-method preference with its TTL. Persists through the `storageBackend` seam; no backend of its own. |
|
|
219
219
|
| `utils/codeHighlight` / `utils/codeBlockMark` / `utils/syntaxTheme` | The Shiki-based fence highlighter, swap-surviving annotation marks, and palette→Shiki theme mapping. Replaces all `.hljs` styling. *(Blessed in 0.29.0.)* |
|
|
220
220
|
| `utils/math` (`loadMathRenderer`, `getMathRenderer`, `getMathRendererSource`, `setMathRenderer`, `setMathRendererLoader`, `getMathRendererLoader`, `resetMathRenderer`) and `utils/math-eager` | The math renderer slot and its eager KaTeX registration. Import `utils/math-eager` for synchronous typesetting on the first commit; call `loadMathRenderer()` to pre-warm the lazy path. `resetMathRenderer()` empties the slot and keeps the registered loader; `setMathRendererLoader(null)` drops it. See "Lazy renderers and eager entries". |
|
|
@@ -1026,7 +1026,7 @@ the people and what a mention means.
|
|
|
1026
1026
|
|
|
1027
1027
|
### Threading points
|
|
1028
1028
|
|
|
1029
|
-
- `AnnotationToolbar` (`selectionActions`, `quickLabels`) — the props live here.
|
|
1029
|
+
- `AnnotationToolbar` (`selectionActions`, `selectionActionsIcon`, `quickLabels`) — the props live here. `selectionActionsIcon?: React.ReactNode` (0.43.1) is the glyph on the wand button, forwarded by `Viewer` (both toolbars) and `HtmlViewer`; absent → the package's own wand, which 0.43.1 also simplified to one thick diagonal with a single star (the six-spark glyph read as noise at 16px). Name, `data-selection-actions`, size and behavior of the button are untouched either way.
|
|
1030
1030
|
- `Viewer` forwards both to BOTH of its toolbars (the text-selection toolbar
|
|
1031
1031
|
and the code-block hover toolbar).
|
|
1032
1032
|
- `HtmlViewer` forwards `selectionActions` to its selection toolbar. It does
|
|
@@ -1038,6 +1038,7 @@ the people and what a mention means.
|
|
|
1038
1038
|
- `CommentPopover` (`mentionSource`). `Viewer` does NOT forward it: the viewer
|
|
1039
1039
|
owns several composers and a per-composer decision belongs to the host that
|
|
1040
1040
|
renders them. Ask if you would rather pass it once on `Viewer`.
|
|
1041
|
+
**Answered in 0.43.1 — both viewers now forward it; see the next section.**
|
|
1041
1042
|
|
|
1042
1043
|
### The no-op guarantee, and how it is pinned
|
|
1043
1044
|
|
|
@@ -1058,8 +1059,118 @@ Tests: `utils/mentions.test.ts` (11, DOM-free),
|
|
|
1058
1059
|
`components/AnnotationToolbar.selectionActions.test.tsx` (8, DOM-gated),
|
|
1059
1060
|
`components/CommentPopover.mentionSource.test.tsx` (11, DOM-gated).
|
|
1060
1061
|
|
|
1062
|
+
## `mentionSource` on the viewers (0.43.1)
|
|
1063
|
+
|
|
1064
|
+
0.43.0 left `mentionSource` on `CommentPopover` alone, with an open question
|
|
1065
|
+
("ask if you would rather pass it once on `Viewer`"). The answer is yes, so
|
|
1066
|
+
0.43.1 threads it one level up and gives the picked ids somewhere to land.
|
|
1067
|
+
Same ruling as 0.43.0: an opt-in host capability that changes nothing for
|
|
1068
|
+
Plannotator's own users when it is not supplied. `packages/editor` and
|
|
1069
|
+
`packages/review-editor` are untouched by this release; core is UNCHANGED at
|
|
1070
|
+
`0.25.5`, so **ui 0.43.1 publishes alone**.
|
|
1071
|
+
|
|
1072
|
+
### 1. The prop
|
|
1073
|
+
|
|
1074
|
+
`Viewer` and `HtmlViewer` each gain `mentionSource?: MentionSource` — the same
|
|
1075
|
+
type, unchanged, from `@plannotator/ui/types` (also `utils/mentions`) — and
|
|
1076
|
+
each forwards it to EVERY comment composer it mounts:
|
|
1077
|
+
|
|
1078
|
+
- `Viewer` → the text-selection composer (`useAnnotationHighlighter`'s) and the
|
|
1079
|
+
global / code-block one.
|
|
1080
|
+
- `HtmlViewer` → the pinpoint (selection) composer and the global one.
|
|
1081
|
+
|
|
1082
|
+
A host that wants mentions on a surface passes one prop instead of reaching
|
|
1083
|
+
into the viewer's composers. Passing it directly to a `CommentPopover` you
|
|
1084
|
+
mount yourself still works and is unchanged.
|
|
1085
|
+
|
|
1086
|
+
Deliberately NOT threaded: `plan-diff/PlanCleanDiffView`, `CodeFilePopout` and
|
|
1087
|
+
`goal-setup/GoalSetupSurface` mount composers too, but they are Plannotator-only
|
|
1088
|
+
surfaces off the supported-import list — the same line 0.43.0 drew for
|
|
1089
|
+
`selectionActions`. Ask if a host needs one.
|
|
1090
|
+
|
|
1091
|
+
### 2. `Annotation.mentions` — where the ids go
|
|
1092
|
+
|
|
1093
|
+
```ts
|
|
1094
|
+
interface Annotation {
|
|
1095
|
+
// …
|
|
1096
|
+
mentions?: readonly string[]; // opaque host ids, additive
|
|
1097
|
+
}
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
`onSubmit(text, images?, mentions?)` used to stop at the viewer: the third
|
|
1101
|
+
argument was received and dropped. It now rides onto the annotation the viewer
|
|
1102
|
+
hands `onAddAnnotation`, on every creation path behind those composers
|
|
1103
|
+
(`createAnnotationFromSource`, `createAnnotationFromMathSource`, the code-block
|
|
1104
|
+
path, both global comments, and the HTML pinpoint comment).
|
|
1105
|
+
|
|
1106
|
+
**The presence rule** is the whole no-op guarantee, so it is worth stating
|
|
1107
|
+
exactly: the key exists only when a `mentionSource` was supplied AND at least
|
|
1108
|
+
one id survived to submit. No source → no third argument → no key. A source
|
|
1109
|
+
whose tokens the author deleted before submitting → `[]` from the composer →
|
|
1110
|
+
still no key, never an empty array. Every write is a conditional spread
|
|
1111
|
+
(`...(mentions && mentions.length > 0 ? { mentions } : {})`), not `mentions,`,
|
|
1112
|
+
because the bare shorthand would put the key on the object with an `undefined`
|
|
1113
|
+
value and `'mentions' in ann` would start answering true for Plannotator.
|
|
1114
|
+
|
|
1115
|
+
The ids are opaque to the package. Mapping one to a person, notifying them, or
|
|
1116
|
+
rendering an avatar is entirely the host's business — `MentionPerson.id` is
|
|
1117
|
+
reported back verbatim, exactly as it was handed in.
|
|
1118
|
+
|
|
1119
|
+
### 3. What the field does NOT touch
|
|
1120
|
+
|
|
1121
|
+
An id from a host's directory means nothing outside that host, so the field
|
|
1122
|
+
stays out of everything Plannotator produces:
|
|
1123
|
+
|
|
1124
|
+
- **Export.** `exportAnnotations`, `exportAnnotationEntry` and
|
|
1125
|
+
`exportLinkedDocAnnotations` never print it: an annotation carrying
|
|
1126
|
+
`mentions` exports byte-identically to the same annotation without it
|
|
1127
|
+
(`utils/parser.mentions.test.ts`). The readable `@Label` token is in the
|
|
1128
|
+
comment body, which is what the coding agent reads.
|
|
1129
|
+
- **Share links.** Dropped exactly like `htmlAnchor`, `elementContext` and
|
|
1130
|
+
`diagramAnchor` — the compact tuple format never carried extra fields, and a
|
|
1131
|
+
round trip restores `mentions: undefined` (pinned in
|
|
1132
|
+
`utils/sharing.multiTarget.test.ts`).
|
|
1133
|
+
- **External annotations.** `POST /api/external-annotations` builds its rows
|
|
1134
|
+
from an explicit field list and `PATCH` from an allowlist, so a `mentions`
|
|
1135
|
+
key on the wire is dropped as any unknown key is. No change was needed in
|
|
1136
|
+
`@plannotator/core/external-annotation`, in either runtime.
|
|
1137
|
+
- **The feedback archive.** `packages/shared/feedback-archive.ts` copies named
|
|
1138
|
+
fields into its record; `mentions` is not one of them and never reaches
|
|
1139
|
+
`index.jsonl` or a sidecar. No change needed.
|
|
1140
|
+
- **Drafts** carry it for free (annotations are opaque JSON to the draft
|
|
1141
|
+
transport), which is the behavior a host wants: a restored draft still knows
|
|
1142
|
+
who was named.
|
|
1143
|
+
|
|
1144
|
+
### 4. Edit and reply paths
|
|
1145
|
+
|
|
1146
|
+
There is none to thread in these two viewers: both `Viewer` composers and both
|
|
1147
|
+
`HtmlViewer` composers are CREATION composers. Editing an existing comment
|
|
1148
|
+
happens in `AnnotationPanel`'s card, a plain textarea that has never had an
|
|
1149
|
+
`@` picker and takes no `mentionSource`; replies (`inReplyTo`) are created by
|
|
1150
|
+
the WebMCP catalog, not by a composer. So no annotation's `mentions` is
|
|
1151
|
+
rewritten after creation by this package — a host that edits a comment owns
|
|
1152
|
+
the field from then on. If you want the panel editor to pick people too, that
|
|
1153
|
+
is a separate prop on `AnnotationPanel` and worth asking for.
|
|
1154
|
+
|
|
1155
|
+
### No-op guarantee, and how it is pinned
|
|
1156
|
+
|
|
1157
|
+
With no `mentionSource`, both viewers mount the composers they always did, no
|
|
1158
|
+
`@` listener is registered, no picker DOM exists, and the annotation object
|
|
1159
|
+
handed to `onAddAnnotation` has no `mentions` key at all (`'mentions' in ann`
|
|
1160
|
+
is false, asserted rather than `toBeUndefined()` — the difference between the
|
|
1161
|
+
conditional spread and the bare shorthand is invisible to the latter).
|
|
1162
|
+
|
|
1163
|
+
Tests (both DOM-gated, both in the workflow's DOM_TESTS step):
|
|
1164
|
+
`components/Viewer.mentionSource.test.tsx` (5) drives the real Viewer — the
|
|
1165
|
+
selection composer through the Vim toolbar's type-to-comment gesture and the
|
|
1166
|
+
global composer through its button — and
|
|
1167
|
+
`components/html-viewer/HtmlViewer.mentionSource.test.tsx` (3) drives the real
|
|
1168
|
+
HtmlViewer through a bridge pinpoint message and its global button. Plus the two DOM-free pins in
|
|
1169
|
+
§3 above.
|
|
1170
|
+
|
|
1061
1171
|
## Publishing & versioning
|
|
1062
1172
|
|
|
1173
|
+
- **ui 0.43.1 (`mentionSource` on the viewers): `@plannotator/ui` only — `@plannotator/core` is UNCHANGED at `0.25.5`, so this publishes alone** (core 0.25.5 must already be published). Purely additive over 0.43.0: `mentionSource` on `Viewer` and `HtmlViewer` (forwarded to every comment composer each mounts) and the optional `Annotation.mentions` field the picked ids land on, set only when a source was supplied and a token survived. Nothing is removed, no new modules, no export-, share- or archive-visible change, and Plannotator passes none of it. See "`mentionSource` on the viewers (0.43.1)".
|
|
1063
1174
|
- **ui 0.43.0 (host toolbar seams): `@plannotator/ui` only — `@plannotator/core` is UNCHANGED at `0.25.5`, so this publishes alone** (core 0.25.5 must already be published). Purely additive: `selectionActions` + `quickLabels` on `AnnotationToolbar` (forwarded by `Viewer`; `selectionActions` also by `HtmlViewer`), `mentionSource` on `CommentPopover`, an optional third `mentions` argument on that component's `onSubmit`, and the new supported modules `utils/selectionActions`, `utils/mentions`, `components/SelectionActionsDropdown`, `components/MentionPicker`, `hooks/useMentionAutocomplete`. Nothing is removed and Plannotator passes none of it. See "Host toolbar seams (0.43.0)".
|
|
1064
1175
|
- **core 0.25.5 / ui 0.42.0 (diagram FILES, `.mmd`/`.mermaid`/`.dot`/`.gv`): additive on both packages, so the next publish is core-first.** `@plannotator/core/annotatable` gains `DiagramRenderKind`, `diagramRenderKindForPath`, `isDiagramRenderKind` and `annotateDiagramRenderKind`, and its built-in annotatable sets now include the four diagram extensions (`shouldStripFrontmatter` returns false for them — Mermaid's `--- … ---` config block is content — and they can no longer be registered through `markdownExtensions`). `@plannotator/ui` gains `diagramDocumentBlocks(text, kind)` on `utils/parser` (the ONE `code` block a whole-file diagram source renders as), `shareableDocumentMarkdown(markdown, renderAs)` on `utils/sharing`, the `DocumentRenderAs` type on `types` (`'markdown' | 'html' | DiagramRenderKind`, re-exporting core's kind), and an optional `Block.diagramSourceLineOffset` that `DiagramBlock` prefers over `Block.startLine` when resolving a diagram comment's `sourceLine` (unset on every parser-produced fence, so fences are byte-identical). `useLinkedDoc`'s `renderAs`/`setRenderAs`/`LinkedDocLoadData.renderAs` widen from `'markdown' | 'html'` to `DocumentRenderAs` — source-compatible for a host that only ever passes the old two, but a host whose own state is typed `'markdown' | 'html'` must widen its setter. Since core changes, **publish `core` first** and update UI's exact core dependency before packing ui.
|
|
1065
1176
|
- **The current pair is `@plannotator/ui` `0.41.2` on `@plannotator/core` `0.25.4`.** Core is UNCHANGED from 0.41.1, so 0.41.2 publishes alone (`ui` only; core 0.25.4 must already be published). 0.41.2 ships the palette-derived Mermaid node shadow at a default of 70% (`DEFAULT_MERMAID_SHADOW_AMOUNT`) and the Settings → Display "Diagram Shadow" control (0 / 40 / 70 / 100); see "Node shadow (the one thing that is not a colour)" under "Theme-aware Mermaid diagrams (0.40.0)" for the mapping — no API removal, only additive exports (`buildMermaidShadow`, `DEFAULT_MERMAID_SHADOW_AMOUNT`, `utils/diagramShadow`).
|
package/README.md
CHANGED
|
@@ -238,6 +238,10 @@ in 0.42.0 (proven by diffing the mounted `outerHTML` against the base commit).
|
|
|
238
238
|
`startOffset: 0` on a surface with no blocks, such as raw HTML) — and the
|
|
239
239
|
toolbar closes. **The package creates no annotation:** what an action does is yours.
|
|
240
240
|
An empty array renders no button.
|
|
241
|
+
- **`AnnotationToolbar` `selectionActionsIcon`** (forwarded by `Viewer` and
|
|
242
|
+
`HtmlViewer`): the glyph on the `selectionActions` button, so a host can match
|
|
243
|
+
the wand it draws elsewhere. Absent means the package's own wand; nothing else
|
|
244
|
+
about the button changes.
|
|
241
245
|
- **`AnnotationToolbar` `quickLabels`** (default `true`): `false` hides the Zap
|
|
242
246
|
picker and makes the Alt+digit label shortcuts inert on that toolbar. The
|
|
243
247
|
one-click 👍 is unaffected, and mode state is still yours to clamp.
|
|
@@ -252,9 +256,17 @@ in 0.42.0 (proven by diffing the mounted `outerHTML` against the base commit).
|
|
|
252
256
|
no-access dialog) and inserts normally when you do not. Not wired to any
|
|
253
257
|
Plannotator data and not a `configurePlannotatorUI` seam — pass it where you
|
|
254
258
|
render the composer.
|
|
259
|
+
- **`Viewer` / `HtmlViewer` `mentionSource`** (0.43.1): the same prop on the two
|
|
260
|
+
viewers, forwarded to every comment composer each of them mounts, so a host
|
|
261
|
+
wires mentions once per surface instead of per composer. The ids the author
|
|
262
|
+
kept ride onto the created annotation as `Annotation.mentions`
|
|
263
|
+
(`readonly string[]`, present only when a source was supplied and at least
|
|
264
|
+
one token survived). It is host data: the package never renders, exports,
|
|
265
|
+
shares or archives it.
|
|
255
266
|
|
|
256
267
|
See HANDOFF.md § "Host toolbar seams (0.43.0)" for the grammar, the keyboard
|
|
257
|
-
rules and the threading points.
|
|
268
|
+
rules and the threading points, and § "mentionSource on the viewers (0.43.1)"
|
|
269
|
+
for the two viewer props and the annotation field.
|
|
258
270
|
|
|
259
271
|
### WebMCP provider (`@plannotator/ui/webmcp`; 0.32.0)
|
|
260
272
|
|
|
@@ -44,6 +44,12 @@ interface AnnotationToolbarProps {
|
|
|
44
44
|
* Absent or empty → no button, no dropdown: today's toolbar.
|
|
45
45
|
*/
|
|
46
46
|
selectionActions?: SelectionAction[];
|
|
47
|
+
/**
|
|
48
|
+
* Opt-in host capability: the glyph on the `selectionActions` button, so a
|
|
49
|
+
* host can match the wand it draws elsewhere. Absent → the package's own
|
|
50
|
+
* wand. Nothing else about the button changes (name, data attribute, size).
|
|
51
|
+
*/
|
|
52
|
+
selectionActionsIcon?: React.ReactNode;
|
|
47
53
|
/**
|
|
48
54
|
* Whether the package's own quick labels are offered (default true).
|
|
49
55
|
* `false` hides the Zap picker button and the Alt+digit label shortcuts on
|
|
@@ -69,6 +75,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
|
|
|
69
75
|
onRequestComment,
|
|
70
76
|
onQuickLabel,
|
|
71
77
|
selectionActions,
|
|
78
|
+
selectionActionsIcon,
|
|
72
79
|
quickLabels: quickLabelsEnabled = true,
|
|
73
80
|
copyText,
|
|
74
81
|
commentOnly = false,
|
|
@@ -273,7 +280,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
|
|
|
273
280
|
<ToolbarButton
|
|
274
281
|
ref={actionsButtonRef}
|
|
275
282
|
onClick={() => setShowSelectionActions(prev => !prev)}
|
|
276
|
-
icon={<WandIcon />}
|
|
283
|
+
icon={selectionActionsIcon ?? <WandIcon />}
|
|
277
284
|
label="Actions"
|
|
278
285
|
className={selectionActionsOpen ? "text-primary bg-primary/10" : "text-primary hover:bg-primary/10"}
|
|
279
286
|
dataAttributes={{ 'data-selection-actions': 'true' }}
|
|
@@ -370,9 +377,11 @@ const ZapIcon = () => (
|
|
|
370
377
|
</svg>
|
|
371
378
|
);
|
|
372
379
|
|
|
380
|
+
// One thick diagonal wand with a single four-point star at its tip: the
|
|
381
|
+
// earlier glyph carried six sparks and read as noise at 16px.
|
|
373
382
|
const WandIcon = () => (
|
|
374
|
-
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
375
|
-
<path strokeLinecap="round" strokeLinejoin="round" d="
|
|
383
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
|
384
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M3 21L13.5 10.5M17 3v7M13.5 6.5h7" />
|
|
376
385
|
</svg>
|
|
377
386
|
);
|
|
378
387
|
|
package/components/Viewer.tsx
CHANGED
|
@@ -60,6 +60,7 @@ import { isGraphvizLanguage, isMermaidLanguage } from './diagramLanguages';
|
|
|
60
60
|
import { getIdentity } from '../utils/identity';
|
|
61
61
|
import { type QuickLabel } from '../utils/quickLabels';
|
|
62
62
|
import type { SelectionAction } from '../utils/selectionActions';
|
|
63
|
+
import type { MentionSource } from '../utils/mentions';
|
|
63
64
|
import { DocBadges, type DocBadgesProps, type LinkedDocBadgeInfo } from './DocBadges';
|
|
64
65
|
import { PinpointOverlay } from './PinpointOverlay';
|
|
65
66
|
import { usePinpoint } from '../hooks/usePinpoint';
|
|
@@ -98,12 +99,22 @@ export interface ViewerProps {
|
|
|
98
99
|
* one wand button that opens the package's dropdown. Absent → unchanged.
|
|
99
100
|
*/
|
|
100
101
|
selectionActions?: SelectionAction[];
|
|
102
|
+
/** Opt-in host capability: the glyph on the `selectionActions` button on
|
|
103
|
+
* both toolbars. Absent → the package's own wand. */
|
|
104
|
+
selectionActionsIcon?: React.ReactNode;
|
|
101
105
|
/**
|
|
102
106
|
* Whether the package's quick labels are offered on the selection toolbars
|
|
103
107
|
* (default true). `false` hides the Zap picker and the Alt+digit label
|
|
104
108
|
* shortcuts; the 👍 button is unaffected.
|
|
105
109
|
*/
|
|
106
110
|
quickLabels?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Opt-in host capability, forwarded to BOTH comment composers this viewer
|
|
113
|
+
* mounts (the text-selection composer and the global / code-block one): the
|
|
114
|
+
* `@` mention source for the composer's picker. The picked ids ride onto the
|
|
115
|
+
* created annotation as `Annotation.mentions`. Absent → unchanged.
|
|
116
|
+
*/
|
|
117
|
+
mentionSource?: MentionSource;
|
|
107
118
|
blocks: Block[];
|
|
108
119
|
markdown: string;
|
|
109
120
|
frontmatter?: Frontmatter | null;
|
|
@@ -374,7 +385,9 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
374
385
|
inputMethod = 'drag',
|
|
375
386
|
taterMode,
|
|
376
387
|
selectionActions,
|
|
388
|
+
selectionActionsIcon,
|
|
377
389
|
quickLabels,
|
|
390
|
+
mentionSource,
|
|
378
391
|
globalAttachments = [],
|
|
379
392
|
onAddGlobalAttachment,
|
|
380
393
|
onRemoveGlobalAttachment,
|
|
@@ -558,6 +571,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
558
571
|
images?: ImageAttachment[],
|
|
559
572
|
isQuickLabel?: boolean,
|
|
560
573
|
quickLabelTip?: string,
|
|
574
|
+
mentions?: readonly string[],
|
|
561
575
|
) => {
|
|
562
576
|
if (readOnlyRef.current) return;
|
|
563
577
|
|
|
@@ -577,6 +591,9 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
577
591
|
createdA: Date.now(),
|
|
578
592
|
author: getIdentity(),
|
|
579
593
|
images,
|
|
594
|
+
// Host capability: present only when a mentionSource was supplied AND a
|
|
595
|
+
// token survived, so a code-block comment without one is unchanged.
|
|
596
|
+
...(mentions && mentions.length > 0 ? { mentions } : {}),
|
|
580
597
|
...(isQuickLabel ? { isQuickLabel: true } : {}),
|
|
581
598
|
...(quickLabelTip ? { quickLabelTip } : {}),
|
|
582
599
|
};
|
|
@@ -957,7 +974,11 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
957
974
|
setCodeBlockToolbar(null);
|
|
958
975
|
};
|
|
959
976
|
|
|
960
|
-
const handleViewerCommentSubmit = (
|
|
977
|
+
const handleViewerCommentSubmit = (
|
|
978
|
+
text: string,
|
|
979
|
+
images?: ImageAttachment[],
|
|
980
|
+
mentions?: readonly string[],
|
|
981
|
+
) => {
|
|
961
982
|
if (readOnlyRef.current || !viewerCommentPopover) return;
|
|
962
983
|
|
|
963
984
|
if (viewerCommentPopover.isGlobal) {
|
|
@@ -975,12 +996,22 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
975
996
|
createdA: Date.now(),
|
|
976
997
|
author: getIdentity(),
|
|
977
998
|
images,
|
|
999
|
+
...(mentions && mentions.length > 0 ? { mentions } : {}),
|
|
978
1000
|
};
|
|
979
1001
|
onAddAnnotation(newAnnotation);
|
|
980
1002
|
} else if (viewerCommentPopover.codeBlock) {
|
|
981
1003
|
const codeEl = viewerCommentPopover.codeBlock.element.querySelector('code');
|
|
982
1004
|
if (codeEl) {
|
|
983
|
-
applyCodeBlockAnnotation(
|
|
1005
|
+
applyCodeBlockAnnotation(
|
|
1006
|
+
viewerCommentPopover.codeBlock.block.id,
|
|
1007
|
+
codeEl,
|
|
1008
|
+
AnnotationType.COMMENT,
|
|
1009
|
+
text,
|
|
1010
|
+
images,
|
|
1011
|
+
undefined,
|
|
1012
|
+
undefined,
|
|
1013
|
+
mentions,
|
|
1014
|
+
);
|
|
984
1015
|
}
|
|
985
1016
|
}
|
|
986
1017
|
|
|
@@ -1312,6 +1343,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
1312
1343
|
onRequestComment={handleRequestComment}
|
|
1313
1344
|
onQuickLabel={handleQuickLabel}
|
|
1314
1345
|
selectionActions={selectionActions}
|
|
1346
|
+
selectionActionsIcon={selectionActionsIcon}
|
|
1315
1347
|
quickLabels={quickLabels}
|
|
1316
1348
|
copyText={toolbarState.selectionText}
|
|
1317
1349
|
hideCopyButton={!isTouchDevice}
|
|
@@ -1369,6 +1401,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
1369
1401
|
onRequestComment={handleCodeBlockRequestComment}
|
|
1370
1402
|
onQuickLabel={handleCodeBlockQuickLabel}
|
|
1371
1403
|
selectionActions={selectionActions}
|
|
1404
|
+
selectionActionsIcon={selectionActionsIcon}
|
|
1372
1405
|
quickLabels={quickLabels}
|
|
1373
1406
|
isExiting={isCodeBlockToolbarExiting}
|
|
1374
1407
|
onMouseEnter={() => {
|
|
@@ -1447,6 +1480,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
1447
1480
|
draftKey={`plan:${commentDraftScope}:${hookCommentPopover.draftKey}`}
|
|
1448
1481
|
onSubmit={hookCommentSubmit}
|
|
1449
1482
|
onClose={hookCommentClose}
|
|
1483
|
+
mentionSource={mentionSource}
|
|
1450
1484
|
allowImages={allowImages}
|
|
1451
1485
|
skillReferences
|
|
1452
1486
|
onAskAI={onAskAI}
|
|
@@ -1471,6 +1505,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
|
|
|
1471
1505
|
}`}
|
|
1472
1506
|
onSubmit={handleViewerCommentSubmit}
|
|
1473
1507
|
onClose={handleViewerCommentClose}
|
|
1508
|
+
mentionSource={mentionSource}
|
|
1474
1509
|
allowImages={allowImages}
|
|
1475
1510
|
skillReferences
|
|
1476
1511
|
onAskAI={onAskAI}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useMemo,
|
|
7
7
|
useRef,
|
|
8
8
|
useState,
|
|
9
|
+
type ReactNode,
|
|
9
10
|
} from "react";
|
|
10
11
|
import { createPortal } from "react-dom";
|
|
11
12
|
import { useVimDocumentFocus } from "../../hooks/useVimDocumentFocus";
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
} from "../../utils/vimHud";
|
|
26
27
|
import { AnnotationToolbar } from "../AnnotationToolbar";
|
|
27
28
|
import type { SelectionAction } from "../../utils/selectionActions";
|
|
29
|
+
import type { MentionSource } from "../../utils/mentions";
|
|
28
30
|
import { AttachmentsButton } from "../AttachmentsButton";
|
|
29
31
|
import {
|
|
30
32
|
CommentPopover,
|
|
@@ -270,6 +272,14 @@ export interface HtmlViewerProps {
|
|
|
270
272
|
* toolbar: the host's own commands for the current selection, rendered as
|
|
271
273
|
* one wand button that opens the package's dropdown. Absent → unchanged. */
|
|
272
274
|
selectionActions?: SelectionAction[];
|
|
275
|
+
/** Opt-in host capability: the glyph on the `selectionActions` button.
|
|
276
|
+
* Absent → the package's own wand. */
|
|
277
|
+
selectionActionsIcon?: ReactNode;
|
|
278
|
+
/** Opt-in host capability, forwarded to BOTH comment composers this viewer
|
|
279
|
+
* mounts (the pinpoint/selection composer and the global one): the `@`
|
|
280
|
+
* mention source for the composer's picker. The picked ids ride onto the
|
|
281
|
+
* created annotation as `Annotation.mentions`. Absent → unchanged. */
|
|
282
|
+
mentionSource?: MentionSource;
|
|
273
283
|
/** scrollIntoView behavior when a selected annotation is scrolled into
|
|
274
284
|
* view inside the page. Default 'smooth'; pass 'auto' to carry the
|
|
275
285
|
* parent's reduced-motion preference across the iframe boundary. */
|
|
@@ -358,6 +368,8 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
358
368
|
onUnanchoredChange,
|
|
359
369
|
maxAdditionalTargets,
|
|
360
370
|
selectionActions,
|
|
371
|
+
selectionActionsIcon,
|
|
372
|
+
mentionSource,
|
|
361
373
|
scrollBehavior,
|
|
362
374
|
title = "HTML Plan Viewer",
|
|
363
375
|
bridgeScriptUrl,
|
|
@@ -995,7 +1007,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
995
1007
|
}));
|
|
996
1008
|
|
|
997
1009
|
const handleGlobalCommentSubmit = useCallback(
|
|
998
|
-
(text: string, images?: ImageAttachment[]) => {
|
|
1010
|
+
(text: string, images?: ImageAttachment[], mentions?: readonly string[]) => {
|
|
999
1011
|
if (readOnly) return;
|
|
1000
1012
|
onAddAnnotation({
|
|
1001
1013
|
id: `global-${Date.now()}`,
|
|
@@ -1008,6 +1020,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
1008
1020
|
author: getIdentity(),
|
|
1009
1021
|
createdA: Date.now(),
|
|
1010
1022
|
images,
|
|
1023
|
+
...(mentions && mentions.length > 0 ? { mentions } : {}),
|
|
1011
1024
|
});
|
|
1012
1025
|
setGlobalCommentPopover(null);
|
|
1013
1026
|
},
|
|
@@ -1209,6 +1222,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
1209
1222
|
// future toolbar path can emit an arbitrary label here.
|
|
1210
1223
|
commentOnly
|
|
1211
1224
|
selectionActions={selectionActions}
|
|
1225
|
+
selectionActionsIcon={selectionActionsIcon}
|
|
1212
1226
|
onQuickLabel={(label) => {
|
|
1213
1227
|
if (label.id === THUMBS_UP_LABEL.id) hook.handleQuickLabel(label);
|
|
1214
1228
|
}}
|
|
@@ -1229,6 +1243,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
1229
1243
|
isGlobal={false}
|
|
1230
1244
|
draftKey={`html:${hook.commentPopover.draftKey}`}
|
|
1231
1245
|
onSubmit={hook.handleCommentSubmit}
|
|
1246
|
+
mentionSource={mentionSource}
|
|
1232
1247
|
// Pinpoint clicks open this composer directly, so it carries
|
|
1233
1248
|
// the surface's one-click "Looks good" (the global composer
|
|
1234
1249
|
// does not: a document-wide thumbs-up is not a thing).
|
|
@@ -1260,6 +1275,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
1260
1275
|
isGlobal={true}
|
|
1261
1276
|
onSubmit={handleGlobalCommentSubmit}
|
|
1262
1277
|
onClose={() => setGlobalCommentPopover(null)}
|
|
1278
|
+
mentionSource={mentionSource}
|
|
1263
1279
|
skillReferences
|
|
1264
1280
|
onAskAI={onAskAI}
|
|
1265
1281
|
askAIContext={{ kind: "general", label: "Document" }}
|
|
@@ -911,7 +911,7 @@ export function useHtmlAnnotation({
|
|
|
911
911
|
);
|
|
912
912
|
|
|
913
913
|
const handleCommentSubmit = useCallback(
|
|
914
|
-
(comment: string, images?: ImageAttachment[]) => {
|
|
914
|
+
(comment: string, images?: ImageAttachment[], mentions?: readonly string[]) => {
|
|
915
915
|
if (!enabledRef.current) return;
|
|
916
916
|
// Prefer the text captured when the popover opened — it can't be clobbered by
|
|
917
917
|
// a later selection change or clear while the user is composing the comment.
|
|
@@ -944,6 +944,9 @@ export function useHtmlAnnotation({
|
|
|
944
944
|
author: getIdentity(),
|
|
945
945
|
createdA: Date.now(),
|
|
946
946
|
images,
|
|
947
|
+
// Host capability: present only when a mentionSource was supplied AND
|
|
948
|
+
// a token survived, so a comment without one is unchanged.
|
|
949
|
+
...(mentions && mentions.length > 0 ? { mentions } : {}),
|
|
947
950
|
htmlAnchor: pendingAnchorRef.current ?? undefined,
|
|
948
951
|
elementContext: pendingContextRef.current ?? undefined,
|
|
949
952
|
htmlAdditionalTargets: additionalTargets,
|
|
@@ -631,7 +631,13 @@ export interface UseAnnotationHighlighterReturn {
|
|
|
631
631
|
handleQuickLabel: (label: QuickLabel) => void;
|
|
632
632
|
handleToolbarClose: () => void;
|
|
633
633
|
handleRequestComment: (initialChar?: string) => void;
|
|
634
|
-
|
|
634
|
+
/** The composer's submit. `mentions` arrives only from a `CommentPopover`
|
|
635
|
+
* the host gave a `mentionSource`; the ids ride onto the new annotation. */
|
|
636
|
+
handleCommentSubmit: (
|
|
637
|
+
text: string,
|
|
638
|
+
images?: ImageAttachment[],
|
|
639
|
+
mentions?: readonly string[],
|
|
640
|
+
) => void;
|
|
635
641
|
handleCommentClose: () => void;
|
|
636
642
|
handleFloatingQuickLabel: (label: QuickLabel) => void;
|
|
637
643
|
handleQuickLabelPickerDismiss: () => void;
|
|
@@ -870,6 +876,7 @@ export function useAnnotationHighlighter({
|
|
|
870
876
|
images?: ImageAttachment[],
|
|
871
877
|
isQuickLabel?: boolean,
|
|
872
878
|
quickLabelTip?: string,
|
|
879
|
+
mentions?: readonly string[],
|
|
873
880
|
) => {
|
|
874
881
|
const doms = highlighter.getDoms(source.id);
|
|
875
882
|
let blockId = '';
|
|
@@ -904,6 +911,9 @@ export function useAnnotationHighlighter({
|
|
|
904
911
|
startMeta: source.startMeta,
|
|
905
912
|
endMeta: source.endMeta,
|
|
906
913
|
images,
|
|
914
|
+
// Host capability: present only when a mentionSource was supplied AND a
|
|
915
|
+
// token survived, so an annotation created without one is unchanged.
|
|
916
|
+
...(mentions && mentions.length > 0 ? { mentions } : {}),
|
|
907
917
|
...(mathTargets.length > 0 ? {
|
|
908
918
|
mathTargets: mathTargets.map(target => ({
|
|
909
919
|
blockId: target.blockId,
|
|
@@ -933,6 +943,7 @@ export function useAnnotationHighlighter({
|
|
|
933
943
|
images?: ImageAttachment[],
|
|
934
944
|
isQuickLabel?: boolean,
|
|
935
945
|
quickLabelTip?: string,
|
|
946
|
+
mentions?: readonly string[],
|
|
936
947
|
) => {
|
|
937
948
|
const id = annotationId();
|
|
938
949
|
applyMathAnnotationClass(source.element, id, type, source.displayMode);
|
|
@@ -951,6 +962,7 @@ export function useAnnotationHighlighter({
|
|
|
951
962
|
createdA: Date.now(),
|
|
952
963
|
author: getIdentity(),
|
|
953
964
|
images,
|
|
965
|
+
...(mentions && mentions.length > 0 ? { mentions } : {}),
|
|
954
966
|
...(isQuickLabel ? { isQuickLabel: true } : {}),
|
|
955
967
|
...(quickLabelTip ? { quickLabelTip } : {}),
|
|
956
968
|
};
|
|
@@ -1695,7 +1707,11 @@ export function useAnnotationHighlighter({
|
|
|
1695
1707
|
setToolbarState(null);
|
|
1696
1708
|
};
|
|
1697
1709
|
|
|
1698
|
-
const handleCommentSubmit = (
|
|
1710
|
+
const handleCommentSubmit = (
|
|
1711
|
+
text: string,
|
|
1712
|
+
images?: ImageAttachment[],
|
|
1713
|
+
mentions?: readonly string[],
|
|
1714
|
+
) => {
|
|
1699
1715
|
if (!commentPopover) return;
|
|
1700
1716
|
if (isMathAnnotationSource(commentPopover.source)) {
|
|
1701
1717
|
createAnnotationFromMathSource(
|
|
@@ -1703,6 +1719,9 @@ export function useAnnotationHighlighter({
|
|
|
1703
1719
|
AnnotationType.COMMENT,
|
|
1704
1720
|
text,
|
|
1705
1721
|
images,
|
|
1722
|
+
undefined,
|
|
1723
|
+
undefined,
|
|
1724
|
+
mentions,
|
|
1706
1725
|
);
|
|
1707
1726
|
clearPendingSelection();
|
|
1708
1727
|
window.getSelection()?.removeAllRanges();
|
|
@@ -1712,7 +1731,7 @@ export function useAnnotationHighlighter({
|
|
|
1712
1731
|
if (commentPopover.source && highlighterRef.current) {
|
|
1713
1732
|
createAnnotationFromSource(
|
|
1714
1733
|
highlighterRef.current, commentPopover.source,
|
|
1715
|
-
AnnotationType.COMMENT, text, images
|
|
1734
|
+
AnnotationType.COMMENT, text, images, undefined, undefined, mentions
|
|
1716
1735
|
);
|
|
1717
1736
|
clearPendingSelection();
|
|
1718
1737
|
window.getSelection()?.removeAllRanges();
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -83,6 +83,7 @@ export interface Annotation {
|
|
|
83
83
|
author?: string; // Tater identity for collaborative sharing
|
|
84
84
|
source?: string; // External tool identifier (e.g., "eslint") — set when annotation comes from external API
|
|
85
85
|
images?: ImageAttachment[]; // Attached images with human-readable names
|
|
86
|
+
mentions?: readonly string[]; // opaque host ids named with `@` in the comment body, set ONLY when a host supplied a `mentionSource` to the composer and at least one token survived; the key is absent otherwise. Host data: the package never renders, exports, shares or archives it.
|
|
86
87
|
isQuickLabel?: boolean; // true if created via quick label chip
|
|
87
88
|
quickLabelTip?: string; // optional instruction tip from the label definition
|
|
88
89
|
diffContext?: 'added' | 'removed' | 'modified'; // set when annotation created in plan diff view
|