@plannotator/ui 0.53.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HANDOFF.md +284 -27
- package/README.md +84 -14
- package/components/AgentToolAnnouncementDialog.tsx +1 -1
- package/components/AgentsTab.tsx +1 -1
- package/components/AnnotationPanel.tsx +177 -34
- package/components/AnnotationSidebar.tsx +1 -1
- package/components/AnnotationToolbar.tsx +64 -9
- package/components/AnnotationToolstrip.tsx +45 -40
- package/components/ApproveDropdown.tsx +7 -9
- package/components/AskSessionAnnouncementDialog.tsx +1 -1
- package/components/AttachmentsButton.tsx +1 -1
- package/components/BlockRenderer.tsx +19 -5
- package/components/CodeFilePopout.tsx +140 -5
- package/components/CommentPopover.tsx +46 -8
- package/components/DecisionControl.tsx +31 -5
- package/components/DiagramBlock.tsx +17 -12
- package/components/EditorAnnotationCard.tsx +1 -1
- package/components/ExportModal.tsx +77 -7
- package/components/HostTitle.tsx +0 -4
- package/components/HtmlSurfaceControls.tsx +2 -1
- package/components/ImageAnnotator/Toolbar.tsx +25 -1
- package/components/ImageAnnotator/index.tsx +24 -4
- package/components/ImageLightbox.tsx +658 -120
- package/components/ImagePeek.tsx +38 -4
- package/components/ImageRegions.tsx +369 -0
- package/components/ImageThumbnail.tsx +1 -1
- package/components/ImageZoomStage.tsx +796 -0
- package/components/InboxAnnouncementDialog.tsx +202 -0
- package/components/InlineMarkdown.tsx +152 -24
- package/components/KeyboardShortcuts.tsx +23 -0
- package/components/LazyBlockBoundary.tsx +100 -0
- package/components/ListMarker.tsx +3 -3
- package/components/LookAndFeelAnnouncementDialog.tsx +12 -2
- package/components/MentionPicker.tsx +51 -0
- package/components/MenuVersionSection.tsx +31 -9
- package/components/PlanDecisionMenuExtras.tsx +129 -0
- package/components/PlanHeaderMenu.tsx +15 -1
- package/components/ProviderIcons.tsx +8 -0
- package/components/RefHoverCard.tsx +913 -0
- package/components/SendFailureToast.tsx +126 -0
- package/components/Settings.tsx +617 -126
- package/components/SkillReferenceMenu.tsx +39 -12
- package/components/TerminalToolsAnnouncementDialog.tsx +1 -1
- package/components/ThemeProvider.tsx +14 -1
- package/components/ThemeTab.tsx +16 -24
- package/components/Tooltip.tsx +49 -18
- package/components/Viewer.tsx +316 -47
- package/components/ai/DocumentAIChatPanel.tsx +29 -3
- package/components/ai/NewChatButton.tsx +74 -0
- package/components/blocks/CodeBlock.tsx +1 -1
- package/components/blocks/HtmlBlock.tsx +7 -1
- package/components/blocks/QuestionBlock.tsx +522 -62
- package/components/blocks/QuestionContext.tsx +3 -0
- package/components/diagram/DiagramComposer.tsx +5 -1
- package/components/diagram/DiagramPending.tsx +12 -2
- package/components/diagram/DiagramViewer.tsx +19 -3
- package/components/html-embed/HtmlEmbedBlock.tsx +374 -0
- package/components/html-embed/SvgFenceBlock.tsx +46 -0
- package/components/html-embed/index.ts +3 -0
- package/components/html-embed/languages.ts +15 -0
- package/components/html-embed/loadGate.ts +107 -0
- package/components/html-viewer/HtmlViewer.tsx +69 -6
- package/components/html-viewer/bridge-script.asset.js +504 -12
- package/components/html-viewer/bridge-script.lite.ts +1 -1
- package/components/html-viewer/bridge-script.ts +545 -12
- package/components/html-viewer/useHtmlAnnotation.ts +125 -0
- package/components/imageZoom.ts +265 -0
- package/components/plan-diff/PlanCleanDiffView.tsx +1 -1
- package/components/plan-diff/PlanDiffViewer.tsx +13 -2
- package/components/plan-diff/PlanRawDiffView.tsx +1 -1
- package/components/sidebar/FileBrowser.tsx +16 -2
- package/components/sidebar/SidebarTabs.tsx +3 -0
- package/config/index.ts +3 -0
- package/config/reviewView.ts +41 -1
- package/config/settings.ts +155 -4
- package/configure.ts +28 -0
- package/hooks/pfm/useCodeFilePopout.ts +7 -1
- package/hooks/useAIChat.ts +72 -25
- package/hooks/useAnnotationDraft.ts +71 -6
- package/hooks/useAnnotationHighlighter.ts +57 -3
- package/hooks/useCodeAnnotationDraft.ts +113 -17
- package/hooks/useCookieHostSetting.ts +96 -0
- package/hooks/useDocumentFont.ts +17 -0
- package/hooks/useDocumentTextSize.ts +20 -0
- package/hooks/useFileBrowser.ts +10 -1
- package/hooks/useLinkedDoc.ts +226 -13
- package/hooks/useMentionAutocomplete.ts +1 -1
- package/hooks/usePackageChangesSetting.ts +90 -0
- package/hooks/usePlanLookDialog.ts +67 -0
- package/hooks/useSharing.ts +11 -7
- package/hooks/useSkillReferenceAutocomplete.ts +7 -3
- package/hooks/useThemePaletteHalves.ts +37 -0
- package/hooks/useUpdateCheck.ts +22 -4
- package/hooks/useUpdateToast.ts +1 -1
- package/hooks/useVimSelection.ts +9 -0
- package/package.json +23 -22
- package/shortcuts/code-review/annotationToolbar.shortcuts.ts +1 -0
- package/shortcuts/code-review/explorer.shortcuts.ts +82 -0
- package/shortcuts/core.ts +63 -4
- package/shortcuts/index.ts +1 -0
- package/shortcuts/plan-review/annotationPanel.shortcuts.ts +1 -1
- package/shortcuts/plan-review/commentPopover.shortcuts.ts +1 -0
- package/shortcuts/runtime.ts +14 -6
- package/styles.css +1 -1
- package/theme.css +179 -1
- package/themes/plannotator-graphite.css +66 -0
- package/types.ts +26 -9
- package/utils/agentSwitch.ts +41 -20
- package/utils/agentToolSetting.ts +1 -1
- package/utils/aiChatFormat.ts +23 -7
- package/utils/annotationOwnsHighlight.ts +4 -2
- package/utils/asciidocParser.ts +570 -0
- package/utils/autoUpdateNotice.ts +24 -0
- package/utils/belowTitleSlot.ts +174 -0
- package/utils/codeLineScroll.ts +100 -0
- package/utils/commentSubmitKey.ts +48 -0
- package/utils/cssFontFamily.ts +65 -0
- package/utils/decisionSpec.ts +190 -2
- package/utils/defaultNotesApp.ts +1 -1
- package/utils/diffFonts.ts +4 -1
- package/utils/documentFont.ts +68 -0
- package/utils/documentTextSize.ts +54 -0
- package/utils/fontDetect.ts +63 -0
- package/utils/inboxAnnouncement.ts +78 -0
- package/utils/lookAndFeelAnnouncement.ts +82 -5
- package/utils/markdownExtensions.ts +2 -2
- package/utils/mentions.ts +34 -5
- package/utils/mermaid.ts +20 -3
- package/utils/mermaidTheme.ts +3 -51
- package/utils/notion.ts +58 -0
- package/utils/parser.ts +28 -1
- package/utils/platform.ts +46 -0
- package/utils/refCard.ts +82 -0
- package/utils/richClipboard.ts +380 -0
- package/utils/richClipboardLists.ts +57 -0
- package/utils/richCopyPolicy.ts +22 -0
- package/utils/scrollAnchor.ts +82 -0
- package/utils/sectionRefs.ts +123 -0
- package/utils/selectionBoundary.ts +40 -0
- package/utils/sendFailure.ts +85 -0
- package/utils/sharing.ts +5 -2
- package/utils/themeRegistry.ts +10 -0
- package/utils/themeTokens.ts +74 -0
- package/utils/updateNotice.ts +30 -8
- package/utils/vscodePanel.ts +9 -0
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ configurePlannotatorUI({
|
|
|
19
19
|
storageBackend, // where settings persist
|
|
20
20
|
identityProvider, // who the current user is
|
|
21
21
|
imageSrcResolver, // how image paths resolve to URLs
|
|
22
|
+
uploadTransport, // where pasted/attached images upload to
|
|
22
23
|
docPreviewFetcher,
|
|
23
24
|
fileTreeBackend,
|
|
24
25
|
draftTransport,
|
|
@@ -34,6 +35,10 @@ configurePlannotatorUI({
|
|
|
34
35
|
identityGenerator, // sync generator behind the default "tater" name (no identityProvider)
|
|
35
36
|
alertIconRenderer, // (name) => ReactNode | null for a GitHub alert title line's <!-- icon: name --> (default: null, the type's icon)
|
|
36
37
|
fenceTheme, // (colorTheme, mode) => Shiki theme name | undefined for code fences (default: SHIKI_THEME_MAP, else pierre-dark/light)
|
|
38
|
+
componentTooltips, // true: ui's own hover text (components/HostTitle) draws as ui's Tooltip, not a native title (ui 0.53.0; default false)
|
|
39
|
+
digitHexIssueRefs, // true: digit-only #123456 / #12345678 link as issue refs (when a ref link can be built), not hex swatches (default false)
|
|
40
|
+
refCardTransport, // (href, ref, signal) => card data for an issue / PR / MR link; with one, useRefHoverCards(rootRef) draws hover cards (default: none, no card, no request). refCardRouteTransport(route) builds one over a server route
|
|
41
|
+
loadSettingsFromBackend, // true: re-hydrate settings from your (synchronous) storageBackend after install, applied last
|
|
37
42
|
});
|
|
38
43
|
```
|
|
39
44
|
|
|
@@ -59,10 +64,10 @@ Building your own tooltip and removing the built-in double-click reset are host-
|
|
|
59
64
|
The Mermaid runtime, the Graphviz engine, KaTeX and the username dictionary are off the static import graph of `Viewer`, so a host that bundles by route does not download them for a plain markdown read. Graphviz has its own slot since 0.41.0 (`utils/graphviz`, the same shape as Mermaid's: lazy `import('@viz-js/viz')` on the first dot fence, or `setGraphvizRuntime` from the host) and the block shows the source fence under a status until the SVG lands. Mermaid, Graphviz, KaTeX and the dictionary sit behind synchronous slots:
|
|
60
65
|
|
|
61
66
|
- **Math.** Without registration, a math node renders its TeX as text in the same wrapper (same `data-math-tex` / `data-math-display` / `aria-label` / class names), loads KaTeX via `import('katex')`, and re-renders typeset. To keep math typeset on the very first commit, as Plannotator does, add one line to your entry: `import "@plannotator/ui/utils/math-eager";`. To put KaTeX and its stylesheet on one lazy chunk instead, pass `mathRendererLoader`. The stylesheet remains your job either way (see "Consuming it", step 3). The default `import('katex')` is the only runtime mention of `katex` in the package and lives in `utils/math-default-loader` (0.33.0), called only while no loader is registered; a registered loader is never backfilled by it, though a default load already in flight at registration still fills the slot (pre-existing), so register the loader before the first math render. Chunk emission is static, so a bundler still emits that chunk (never requested) unless you alias the module away; see HANDOFF.md "Lazy renderers and eager entries" for the two-line alias. The Mermaid runtime has its own `import("katex")` for `$$` labels, which leaves a second, shared KaTeX chunk in a host build even with the alias; since 0.34.0 a host redirects that one import (for importers inside the `mermaid` package only) to `@plannotator/ui/utils/mermaid-math-slot`, which typesets the labels through your registered renderer, so one KaTeX chunk remains and it is yours. Recipe and measurement in HANDOFF.md, same section. `resetMathRenderer()` empties the slot only and keeps a registered loader (0.34.0); `setMathRendererLoader(null)` is the explicit way back to the package default.
|
|
62
|
-
- **Mermaid.** Without registration, the first diagram on a page fetches the runtime through `import('mermaid')` and the block shows the source fence under a "Rendering diagram" status until the SVG lands; a failed import is dropped from the memo, re-attempted once after a short delay, and the error panel (with the source) offers Retry, which issues another fresh attempt. **Since 0.40.0 (Mermaid 12.0.0, ELK layout by default, Safari 17.4+) this lazy path is Plannotator's own**: the plan editor no longer imports the eager entry, so a document with no diagram never downloads the runtime in a chunked build. `import "@plannotator/ui/utils/mermaid-eager";` in your entry registers and initializes the runtime at startup instead, if you would rather it never fail separately from the app. Mermaid 12 lays flowchart, state, class, ER and requirement diagrams out with ELK (orthogonal edges, different packing); every generated SVG `id` keeps its 11.x shape, but children of `g.edgePaths` are now in declaration order, so select edges by id (`{renderId}-L_{from}_{to}_{n}`, `{renderId}-edge{n}`), never by DOM position. Details in HANDOFF.md "Mermaid 12 (0.40.0)". **Diagrams follow the colour theme.** Before every render `MermaidBlock` calls `applyMermaidTheme` (`utils/mermaidTheme`), which reads the theme tokens off the document (`--background`, `--foreground`, `--card`, `--border`, `--muted`, `--muted-foreground`, `--primary`, the accent tokens and `--font-sans`; `readThemeTokens`), derives a complete `themeVariables` set for every diagram family from them (`buildMermaidThemeVariables(tokens, mode)`, pure; base theme `dark` under a dark resolved mode, `default` under light; every text-on-fill pair guarded to WCAG 4.5:1 and every line 3:1, rule in the module doc) and runs the global `mermaid.initialize` once per `(palette, mode, shadow amount)` key, re-rendering mounted diagrams when the key changes. The key comes from `useTheme()`, so a host that mounts `ThemeProvider` and ships `theme.css` gets diagrams in its palette with nothing to configure. **The node shadow follows the palette too.** Mermaid 12's neo look shadows every node from a fixed `rgba(185,185,185,1)` grey; the mapping keeps the look and replaces that filter, setting `themeVariables.dropShadow` from the page's own ground at `options.shadowAmount` (0..1, default `DEFAULT_MERMAID_SHADOW_AMOUNT` = **0.7** of Mermaid's geometry; 1 reproduces its `1px 2px 2px` exactly, 0 publishes `dropShadow: false` and the neo rules render `filter: none`). Its polarity follows the page — lighter than the ground on a dark page, darker on a light one, which is what Mermaid's own `insertLookDefs` does and what keeps a shadow visible at all. A host that wants Mermaid's grey passes its own `themeVariables.dropShadow` after ours; one that wants none passes `{ shadowAmount: 0 }`. `mermaidThemeKey(palette, mode, amount?)` appends the amount only when it is not the default, so a host that names none keeps the key it had. **Fallback contract:** with no tokens on the document (no `ThemeProvider`, no `theme.css`) `readThemeTokens` returns `undefined`, nothing is re-initialized, and the runtime keeps the static `MERMAID_CONFIG` it was initialized with, so such a host renders byte-identically to 0.39.0. `MERMAID_CONFIG` keeps its value and meaning (`securityLevel: 'strict'` pinned); the new exports are additive. Honest limit of any in-page retry: a browser records a failed module fetch in its module map for the page lifetime, so a fresh `import()` of the same chunk URL rejects without a request; the retry recovers failures after the fetch (engine instantiation, initialize) and hosts that version chunk URLs. A host that needs recovery from a failed first fetch uses versioned chunk URLs or a `vite:preloadError` reload at app level.
|
|
67
|
+
- **Mermaid.** Without registration, the first diagram on a page fetches the runtime through `import('mermaid')` and the block shows the source fence under a "Rendering diagram" status until the SVG lands; a failed import is dropped from the memo, re-attempted once after a short delay, and the error panel (with the source) offers Retry, which issues another fresh attempt. **Since 0.40.0 (Mermaid 12.0.0, ELK layout by default, Safari 17.4+) this lazy path is Plannotator's own**: the plan editor no longer imports the eager entry, so a document with no diagram never downloads the runtime in a chunked build. `import "@plannotator/ui/utils/mermaid-eager";` in your entry registers and initializes the runtime at startup instead, if you would rather it never fail separately from the app. Mermaid 12 lays flowchart, state, class, ER and requirement diagrams out with ELK (orthogonal edges, different packing); every generated SVG `id` keeps its 11.x shape, but children of `g.edgePaths` are now in declaration order, so select edges by id (`{renderId}-L_{from}_{to}_{n}`, `{renderId}-edge{n}`), never by DOM position. Details in HANDOFF.md "Mermaid 12 (0.40.0)". **Diagrams follow the colour theme.** Before every render `MermaidBlock` calls `applyMermaidTheme` (`utils/mermaidTheme`), which reads the theme tokens off the document (`--background`, `--foreground`, `--card`, `--border`, `--muted`, `--muted-foreground`, `--primary`, the accent tokens and `--font-sans`; `readThemeTokens`), derives a complete `themeVariables` set for every diagram family from them (`buildMermaidThemeVariables(tokens, mode)`, pure; base theme `dark` under a dark resolved mode, `default` under light; every text-on-fill pair guarded to WCAG 4.5:1 and every line 3:1, rule in the module doc) and runs the global `mermaid.initialize` once per `(palette, mode, shadow amount)` key, re-rendering mounted diagrams when the key changes. The key comes from `useTheme()`, so a host that mounts `ThemeProvider` and ships `theme.css` gets diagrams in its palette with nothing to configure. **The node shadow follows the palette too.** Mermaid 12's neo look shadows every node from a fixed `rgba(185,185,185,1)` grey; the mapping keeps the look and replaces that filter, setting `themeVariables.dropShadow` from the page's own ground at `options.shadowAmount` (0..1, default `DEFAULT_MERMAID_SHADOW_AMOUNT` = **0.7** of Mermaid's geometry; 1 reproduces its `1px 2px 2px` exactly, 0 publishes `dropShadow: false` and the neo rules render `filter: none`). Its polarity follows the page — lighter than the ground on a dark page, darker on a light one, which is what Mermaid's own `insertLookDefs` does and what keeps a shadow visible at all. A host that wants Mermaid's grey passes its own `themeVariables.dropShadow` after ours; one that wants none passes `{ shadowAmount: 0 }`. `mermaidThemeKey(palette, mode, amount?)` appends the amount only when it is not the default, so a host that names none keeps the key it had. **Fallback contract:** with no tokens on the document (no `ThemeProvider`, no `theme.css`) `readThemeTokens` returns `undefined`, nothing is re-initialized, and the runtime keeps the static `MERMAID_CONFIG` it was initialized with, so such a host renders byte-identically to 0.39.0. `MERMAID_CONFIG` keeps its value and meaning (`securityLevel: 'strict'` pinned); the new exports are additive. Honest limit of any in-page retry: a browser records a failed module fetch in its module map for the page lifetime, so a fresh `import()` of the same chunk URL rejects without a request; the retry recovers failures after the fetch (engine instantiation, initialize) and hosts that version chunk URLs. A host that needs recovery from a failed first fetch uses versioned chunk URLs or a `vite:preloadError` reload at app level. The `Viewer`'s lazy blocks (Mermaid, Graphviz, HTML and SVG embeds), the diagram popout and the Source pane each sit in `components/LazyBlockBoundary`: when a chunk cannot load, that one block shows its source under a quiet "Couldn't load this view. Reload" line ("This page was updated. Reload to see this" when the chunk answers 404), and the rest of the page stays mounted.
|
|
63
68
|
- **Identity.** With an `identityProvider` the generator is never called and the word lists stay out of your bundle. Without one, default names come from a small built-in pool of the same `adjective-noun-tater` shape; `import "@plannotator/ui/utils/identity-tater";` registers the full dictionary, or pass your own `identityGenerator`.
|
|
64
69
|
|
|
65
|
-
Plannotator's own entries import the eager math and identity modules (`math-eager` and `identity-tater` in both `packages/editor/App.tsx` and `packages/review-editor/App.tsx`), which is what keeps math typeset on the first commit and names minted from the full dictionary; neither app imports `mermaid-eager` since 0.40.0, so the Mermaid runtime rides a lazy chunk on the share portal and
|
|
70
|
+
Plannotator's own entries import the eager math and identity modules (`math-eager` and `identity-tater` in both `packages/editor/App.tsx` and `packages/review-editor/App.tsx`), which is what keeps math typeset on the first commit and names minted from the full dictionary; neither app imports `mermaid-eager` since 0.40.0, so the Mermaid runtime rides a lazy chunk, both on the share portal and in the plan and review apps (which since #1618 ship as one packed file whose server answers each chunk at `/assets/*`, instead of inlining them). `tests/entry-assets.test.ts` fails if an eager math/identity import is dropped or an eager Mermaid import creeps back. See HANDOFF.md "Lazy renderers and eager entries" and "Mermaid 12 (0.40.0)".
|
|
66
71
|
|
|
67
72
|
### Markdown editor extensions + wiki links (`MarkdownEditor` / `InlineMarkdown`)
|
|
68
73
|
|
|
@@ -234,9 +239,40 @@ exactly. The standalone `StickyHeaderLane` remains supported for Plannotator's
|
|
|
234
239
|
hidden-at-rest ghost lane, but its always-visible mode is an overlay and is not
|
|
235
240
|
the in-flow host integration.
|
|
236
241
|
|
|
242
|
+
#### Content under the title (`Viewer.belowTitle`; ui 0.54.0)
|
|
243
|
+
|
|
244
|
+
`belowTitle?: ReactNode` draws a host's node right under the document's
|
|
245
|
+
title, e.g. a references block (pull requests, depends on, links, referenced
|
|
246
|
+
by). Use it instead of inserting into the Viewer's DOM.
|
|
247
|
+
|
|
248
|
+
- **Where.** When the body's first block is a level-1 heading, right after
|
|
249
|
+
that heading (`data-placement="below-title"`). Otherwise (a body that opens
|
|
250
|
+
with a paragraph, a `## heading`, or a `# title` further down) at the top of
|
|
251
|
+
the body, before the first block and after the frontmatter card
|
|
252
|
+
(`data-placement="top"`). The heading itself is unchanged.
|
|
253
|
+
- **Outside the document text.** The wrapper `[data-viewer-below-title]` is
|
|
254
|
+
`annotation-exclude` (no comment starts in it, its text is never searched
|
|
255
|
+
or quoted), `data-pinpoint-ignore` (pinpoint and Vim skip it),
|
|
256
|
+
`data-print-hide`, and carries no `data-block-id`; heading slugs, the TOC,
|
|
257
|
+
exports and share links come from `blocks` and never see it.
|
|
258
|
+
- **Comment positions ignore it.** web-highlighter's `startMeta`/`endMeta`
|
|
259
|
+
count elements by tag across the whole article, so a `<p>` or `<a>` in the
|
|
260
|
+
slot would renumber later comments. The highlighter stores and restores
|
|
261
|
+
positions as if the slot were not there, so the same comment has the same
|
|
262
|
+
metas with or without the slot, and with any slot contents. A selection
|
|
263
|
+
from the title that ends in the slot is cut back to the end of the title.
|
|
264
|
+
Only the wrapper the Viewer draws counts: its attribute carries a per-load
|
|
265
|
+
value, so a `<div data-viewer-below-title>` an author writes as raw HTML is
|
|
266
|
+
never taken for it. Match the wrapper by presence
|
|
267
|
+
(`[data-viewer-below-title]`), not by value.
|
|
268
|
+
- **Absent is absent.** `undefined`, `null` and booleans draw no wrapper; the
|
|
269
|
+
DOM is exactly the one without the prop.
|
|
270
|
+
|
|
271
|
+
See HANDOFF.md § "Viewer `belowTitle` (published in ui 0.54.0)".
|
|
272
|
+
|
|
237
273
|
### Diagram engine (`components/diagram`; 0.41.0)
|
|
238
274
|
|
|
239
|
-
One renderer slot and one canvas render every Mermaid and Graphviz diagram — the fences in `Viewer` (`MermaidBlock`, `GraphvizBlock`), their popout, and whatever a host renders itself. `DiagramViewer` from `@plannotator/ui/components/diagram` takes `kind`, `source`, `theme` (`{ colorTheme, mode, shadowAmount? }`), `comments: DiagramComment[]` and `onCreateComment(anchor, text, additionalTargets)`; pass `onSave(source) => Promise<{ status: 'ok' } | { status: 'stale', currentSource }>` to get the Source pane (left of the canvas on desktop, under it on the phone; `readOnlySource` shows it without Save), and `sourceOpen` to toggle it. Zoom, pan and fit are the canvas's (wheel, drag, `+` `-` `0`, arrow keys); a click opens the composer at the part beneath while a drag pans (4 px threshold, 10 px for a finger), nothing highlights on a plain mouse-over (the ring under the pointer needs the platform modifier held), every edge carries an invisible 14 px hit path in one top layer and a click is resolved by priority over everything under the pointer (node, then edge, then cluster; an edge label is its edge), a click on no part comments on the whole diagram (kind `diagram`), sequence diagrams are addressable (actors, messages, notes, frames), a saved comment paints a ring and a numbered badge from the array order, and every comment re-resolves against each render through the engine's finder (id, then label, then unanchored — reported through `onUnanchoredChange`). The anchor is `DiagramAnchor` from `@plannotator/core/diagram-anchor` (`{ v: 1, family, kind, id | from + to, label, sourceLine }`, document lines), and Plannotator stores it as `Annotation.diagramAnchor`. Runtime slots: `utils/mermaid` (as before) and `utils/graphviz` (new, same shape, `@viz-js/viz` pinned `3.
|
|
275
|
+
One renderer slot and one canvas render every Mermaid and Graphviz diagram — the fences in `Viewer` (`MermaidBlock`, `GraphvizBlock`), their popout, and whatever a host renders itself. `DiagramViewer` from `@plannotator/ui/components/diagram` takes `kind`, `source`, `theme` (`{ colorTheme, mode, shadowAmount? }`), `comments: DiagramComment[]` and `onCreateComment(anchor, text, additionalTargets)`; pass `onSave(source) => Promise<{ status: 'ok' } | { status: 'stale', currentSource }>` to get the Source pane (left of the canvas on desktop, under it on the phone; `readOnlySource` shows it without Save), and `sourceOpen` to toggle it. Zoom, pan and fit are the canvas's (wheel, drag, `+` `-` `0`, arrow keys); a click opens the composer at the part beneath while a drag pans (4 px threshold, 10 px for a finger), nothing highlights on a plain mouse-over (the ring under the pointer needs the platform modifier held), every edge carries an invisible 14 px hit path in one top layer and a click is resolved by priority over everything under the pointer (node, then edge, then cluster; an edge label is its edge), a click on no part comments on the whole diagram (kind `diagram`), sequence diagrams are addressable (actors, messages, notes, frames), a saved comment paints a ring and a numbered badge from the array order, and every comment re-resolves against each render through the engine's finder (id, then label, then unanchored — reported through `onUnanchoredChange`). The anchor is `DiagramAnchor` from `@plannotator/core/diagram-anchor` (`{ v: 1, family, kind, id | from + to, label, sourceLine }`, document lines), and Plannotator stores it as `Annotation.diagramAnchor`. Runtime slots: `utils/mermaid` (as before) and `utils/graphviz` (new, same shape, `@viz-js/viz` pinned `3.31.0`), each lazy on first use or filled by the host. `DiagramPopout` is the full-size viewer in the `PopoutDialog` chrome. See HANDOFF.md § "Diagram engine (0.41.0)" for every export, the adapter, the sanitizer and the migration notes.
|
|
240
276
|
|
|
241
277
|
### Host toolbar seams (0.43.0)
|
|
242
278
|
|
|
@@ -294,6 +330,16 @@ in 0.42.0 (proven by diffing the mounted `outerHTML` against the base commit).
|
|
|
294
330
|
in a plain-text body, so the first person you list owns every occurrence of
|
|
295
331
|
it; and a restored draft has no chips (and reports no ids) until the author
|
|
296
332
|
picks again. No source → no overlay element at all.
|
|
333
|
+
- **Agents in the `@` picker** (ui 0.53.1; the Plannotator
|
|
334
|
+
Inbox's agent tags): `MentionSource.kinds?: ('user' | 'agent')[]` (absent →
|
|
335
|
+
users only, as before) lets a host offer agents; `MentionSource.matchDetail?:
|
|
336
|
+
boolean` (absent → true) keeps a query from matching the `detail` line;
|
|
337
|
+
`MentionPerson.group?: string` draws consecutive people of one group under a
|
|
338
|
+
heading, each run a `role="group"` labelled by it (absent on every person →
|
|
339
|
+
the flat list); `MentionSource.renderNotice?: (tagged) => ReactNode` draws a
|
|
340
|
+
host line under the composer field from the people the draft still tags
|
|
341
|
+
(`CommentPopover`). All optional; a host passing none renders exactly as
|
|
342
|
+
before.
|
|
297
343
|
- **`Viewer` / `HtmlViewer` `mentionSource`** (0.43.1): the same prop on the two
|
|
298
344
|
viewers, forwarded to every comment composer each of them mounts, so a host
|
|
299
345
|
wires mentions once per surface instead of per composer. The ids the author
|
|
@@ -350,22 +396,42 @@ Neither prop reaches `CodeAnnotation` cards (the review-editor shape), which
|
|
|
350
396
|
take no `renderCardFooter` either. See HANDOFF.md § "Annotation card header
|
|
351
397
|
slot and mentions on the edit box (0.45.0)".
|
|
352
398
|
|
|
353
|
-
- **`compareAnnotations?: (a, b) => number`** (
|
|
399
|
+
- **`compareAnnotations?: (a, b) => number`** (ui 0.53.0) — the order of the
|
|
354
400
|
comment timeline and of each All-files group, for comments with an order of
|
|
355
401
|
their own (the Inbox sorts a video's comments by the moment they are at).
|
|
356
402
|
Ties keep creation order; replies still thread under their parent. Omit it
|
|
357
403
|
and the panel sorts by creation exactly as before.
|
|
358
|
-
- **`title?`, `titleNote?`, `cardParts?: (annotation) => { quote?, images? }`**
|
|
359
|
-
(
|
|
360
|
-
at its right, and per card whether the built-in quote
|
|
361
|
-
drawn (the Inbox's video comments draw the
|
|
362
|
-
`renderCardHeader` instead).
|
|
363
|
-
|
|
404
|
+
- **`title?`, `titleNote?`, `cardParts?: (annotation) => { quote?, images?, text? }`**
|
|
405
|
+
(ui 0.53.0; `text` ui 0.54.0) — the header's title (default "Annotations")
|
|
406
|
+
and a quiet note at its right, and per card whether the built-in quote,
|
|
407
|
+
image row and comment text are drawn (the Inbox's video comments draw the
|
|
408
|
+
moment and the frame in `renderCardHeader` instead). `text: false` treats
|
|
409
|
+
the stored text as the host's stand-in, not the person's words: it is not
|
|
410
|
+
drawn and the card's edit box starts empty (the Inbox's drawing with no
|
|
411
|
+
words, whose text "See the marked-up image." is for the agent). Omit them
|
|
412
|
+
and the panel is as before.
|
|
413
|
+
- **`renderCardBody?: (annotation) => ReactNode`** (ui 0.54.0) — the host's
|
|
414
|
+
own body for a plan-annotation card, drawn where the comment text goes,
|
|
415
|
+
inside the card (a click on it selects the card). The Inbox draws a
|
|
416
|
+
drawing with no words as its picture. Omit it and nothing extra is drawn.
|
|
417
|
+
- **Export: `exportLocation`** (ui 0.53.0) — a string a host sets on the
|
|
364
418
|
COPY of an annotation it hands the exporters (`exportAnnotations`,
|
|
365
419
|
`exportLinkedDocAnnotations`): one line printed under the entry's heading
|
|
366
420
|
(the Inbox's `At 1:42 (102.4 s of 7:41)`). Never stored; an annotation
|
|
367
421
|
without it exports byte-identically.
|
|
368
422
|
|
|
423
|
+
### Image annotator seams (`components/ImageAnnotator`; ui 0.54.0)
|
|
424
|
+
|
|
425
|
+
- **`escapeCancels?: boolean`** — Escape closes the drawing screen and accepts
|
|
426
|
+
nothing; Enter and the ✓ accept; a click outside closes only an empty
|
|
427
|
+
drawing (strokes are never lost to a stray click); the ✓'s title reads
|
|
428
|
+
"Save (Enter)". The Inbox passes it (the owner's rule, 2026-10-26). Omit it
|
|
429
|
+
and Escape, Enter and a click outside all accept, under "Save (Esc)", as
|
|
430
|
+
plan review has always done.
|
|
431
|
+
- **`hint?: ReactNode`** — the line under the image in place of "Press Esc or
|
|
432
|
+
Enter or click outside to accept" (the Inbox: "Enter or ✓ adds to your
|
|
433
|
+
reply · Esc cancels"). Omit it and the line is as before.
|
|
434
|
+
|
|
369
435
|
### WebMCP provider (`@plannotator/ui/webmcp`; 0.32.0)
|
|
370
436
|
|
|
371
437
|
The engine that lets a browser-integrated agent (Chrome/Edge WebMCP, `document.modelContext`) call in-page tools on a document surface. Feature-detected once; a browser without the API sees no registration, no DOM, no network, no timers. Seam: `configurePlannotatorUI({ webmcp: { enabled, namePrefix } })`, default enabled with the `plannotator.` prefix; pass `enabled: false` to keep a host page tool-free, or your own prefix to namespace the tools beside your own. There is deliberately no confirmation seam: the catalog is read-and-comment only (no approve / submit / close tools), and the agent may only edit or remove comments stamped `source: "browser-agent"`.
|
|
@@ -374,7 +440,7 @@ The engine that lets a browser-integrated agent (Chrome/Edge WebMCP, `document.m
|
|
|
374
440
|
- `useToolset({ id, active, build, deps, hooks })` attaches a named tool set to the document registry; handlers read through refs, so re-renders never re-register, and `active: false` aborts every registration (what Plannotator's Settings opt-out drives).
|
|
375
441
|
- `AnnotationChangeTracker` / `buildNudges` are pure (no DOM): per-annotation `seq`, tombstones, a per-tab watermark with `since` override, and the nudge vocabulary every response carries.
|
|
376
442
|
- A host with its own document state builds the same adapter-driven catalog Plannotator uses (`packages/editor/webmcp/documentTools.ts`, `buildDocumentTools(adapter, state, options)`) over its own getters and actions; multi-document pages should register one set whose tools take `path` (the folder-session shape) rather than one set per viewer (duplicate names across sets are skipped with a warning, never replaced).
|
|
377
|
-
- Never register tools inside an untrusted iframe: the raw-HTML viewer's `sandbox="allow-scripts"` frame and the live-app frame carry no `allow="tools"`, and that is what keeps a framed page from impersonating the host's tools.
|
|
443
|
+
- Never register tools inside an untrusted iframe: the raw-HTML viewer's `sandbox="allow-scripts allow-modals"` frame and the live-app frame carry no `allow="tools"`, and that is what keeps a framed page from impersonating the host's tools.
|
|
378
444
|
|
|
379
445
|
The one additive data-model change that rides with it: `Annotation.inReplyTo` (threaded replies; the panel indents them under the parent, the export nests them, share links drop them).
|
|
380
446
|
|
|
@@ -418,13 +484,15 @@ See HANDOFF.md § "Question decision toggle and status tag (ui 0.52.0; …)".
|
|
|
418
484
|
|
|
419
485
|
See HANDOFF.md § "Decision toggle on any question, and an opener for the host's decision card (ui 0.52.1, …)".
|
|
420
486
|
|
|
421
|
-
**Questions without decision lines (
|
|
487
|
+
**Questions without decision lines (ui 0.53.0, core 0.25.13).** `Viewer` `questionDecisionLines?: boolean` (default `true`, today's behaviour). `false` parses every `:::question` card with core's new `{ decisionLines: false }` option (`parseQuestionBlock(kind, body, options?)`, `indexQuestionBlocks(blocks, options?)`): a `Decision:` line is shown as context, and no card gets a decision tag or row from it. The prompt, key, numbering and choices are the default parse's (the line still ends a choice right above it). For a host that records no decisions; the Plannotator Inbox passes it since Decisions were removed there (2026-10). Every decision seam above is unchanged.
|
|
422
488
|
|
|
423
489
|
**Answers without comments (ui 0.53.0).** `Viewer` `answerOnly`: annotation authoring and the document actions (Global comment, Copy) are off as under `readOnly`, while question cards and checkboxes keep their handlers, for a host that shows a message to answer, not a document to annotate. See HANDOFF.md § "Viewer `answerOnly` (ui 0.53.0)".
|
|
424
490
|
|
|
491
|
+
**Comments on an answer-only document (ui 0.53.1).** `Viewer` `textComments`, with `answerOnly`: the text-selection toolbar (comment-only) and its composer come back, question cards stay out of selection, nothing else of authoring does. See HANDOFF.md § "Viewer `textComments` (ui 0.53.1)".
|
|
492
|
+
|
|
425
493
|
**Local links decided by the host (ui 0.53.0).** `Viewer` `resolveLocalLink`: the host decides what a link to a local path does (open it itself, or draw it as inert text), markdown and raw HTML alike. See HANDOFF.md § "Viewer `resolveLocalLink` (ui 0.53.0)".
|
|
426
494
|
|
|
427
|
-
### Diff file tree (`components/DiffFileTree`;
|
|
495
|
+
### Diff file tree (`components/DiffFileTree`; ui 0.50.0)
|
|
428
496
|
|
|
429
497
|
A read-only file tree for a list of changed files, to sit beside a host's own per-file diff list. It is Plannotator's code-review tree, not a lookalike: the builder (`utils/diffFileTree`), the folder open/closed hook (`hooks/useDiffFileTreeExpansion`) and the row atoms moved here from `packages/review-editor`, and the review now renders from them.
|
|
430
498
|
|
|
@@ -464,12 +532,14 @@ npm install @plannotator/ui @plannotator/core
|
|
|
464
532
|
4. Import components: `import { Viewer } from "@plannotator/ui/components/Viewer";`
|
|
465
533
|
5. Build with a bundler that compiles TS/TSX (Vite + React 19 + Tailwind v4). The packages ship **source**, so your bundler compiles them — set `moduleResolution: "bundler"`, `allowImportingTsExtensions`, `jsx: "react-jsx"`.
|
|
466
534
|
|
|
535
|
+
**Upgrade note: the plan look defaults to Clean.** `gridEnabled` (storage key `plannotator-grid-enabled`) now defaults to `false`, and a stored value counts only with the v2 marker `plannotator-plan-look-v2 = 'resolved'`. Plannotator's chooser and its Settings switch write it. On the first settings read, a v1 marker (`plannotator-plan-look-choice-resolved = 'true'`, or the legacy `plannotator-look-feel-announcement-seen = '2'`) next to a stored `'true'` is moved to Clean once (the stored value is rewritten to `'false'` and the v2 key set to `'notice'`); a v1 marker with Clean becomes `'resolved'` silently. Plannotator's apps then show a one-time notice. That includes the share portal's root and demo pages; only a shared session puts it off. A host that renders no chooser (Workspaces) shows nothing, so its users simply open in Clean, and every user with no marker opens in Clean too. To keep stored values instead, call `markLookAndFeelChoiceResolved()` from `@plannotator/ui/utils/lookAndFeelAnnouncement` before the first settings read, or seed `plannotator-plan-look-v2 = 'resolved'` in your `storageBackend`. A host that writes `gridEnabled` from its own UI should call `markLookAndFeelChoiceResolved()` with it, or the value will not survive a reload.
|
|
536
|
+
|
|
467
537
|
## Packages & publishing
|
|
468
538
|
|
|
469
539
|
- `@plannotator/core` — pure utils + types, zero deps, browser-safe (CI enforces no `node:` imports). Published.
|
|
470
540
|
- `@plannotator/ui` — React components/hooks + theme + `configure()`. Depends on an exact published `@plannotator/core` version. Published.
|
|
471
541
|
- `@plannotator/shared`, `@plannotator/ai` — stay private to the monorepo; `shared` re-exports `core`'s modules via shims so Plannotator's internals are untouched.
|
|
472
|
-
- Currently `@plannotator/ui` 0.
|
|
542
|
+
- Currently `@plannotator/ui` 0.54.0 depends exactly on `@plannotator/core` 0.25.14. `core` is bumped only when something under `packages/core` changes, so `ui` can advance alone. Keep the published core version exact in `packages/ui/package.json`; do not use a `workspace:` protocol there, because a directly published manifest must remain installable outside this monorepo. Bun still links the matching local workspace during development. When both packages change, publish `core` first, then build and publish the UI tarball. `bun run check:sibling-deps @plannotator/ui` (from the repo root; it reads npm) fails while the pinned core isn't on npm. See HANDOFF.md "Publishing & versioning" for the verification command.
|
|
473
543
|
|
|
474
544
|
## The one rule
|
|
475
545
|
|
|
@@ -193,7 +193,7 @@ export function AgentToolAnnouncementDialog({
|
|
|
193
193
|
</p>
|
|
194
194
|
</div>
|
|
195
195
|
<p className="mt-3 text-pretty text-xs leading-relaxed text-muted-foreground" data-agent-tool-cost>
|
|
196
|
-
Adds about
|
|
196
|
+
Adds about 800 tokens to each request.{save.kind === 'saved' ? '' : ` ${startsLine(host)}`}
|
|
197
197
|
</p>
|
|
198
198
|
|
|
199
199
|
<div className="mt-5 flex flex-col gap-3">
|
package/components/AgentsTab.tsx
CHANGED
|
@@ -423,7 +423,7 @@ function JobCard({
|
|
|
423
423
|
e.stopPropagation();
|
|
424
424
|
onKill();
|
|
425
425
|
}}
|
|
426
|
-
className="absolute top-1.5 right-1.5 rounded p-1 text-muted-foreground opacity-0 transition-all hover:bg-destructive/10 hover:text-destructive group-hover:opacity-100"
|
|
426
|
+
className="absolute top-1.5 right-1.5 rounded p-1 text-muted-foreground opacity-0 transition-all hover:bg-destructive/10 hover:text-destructive group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100"
|
|
427
427
|
title="Kill agent"
|
|
428
428
|
>
|
|
429
429
|
<X size={12} />
|