@plannotator/ui 0.54.0 → 0.56.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 +102 -6
- package/README.md +128 -3
- package/components/AnnotationPanel.tsx +2 -1
- package/components/AttachmentsButton.tsx +1 -1
- package/components/BlockRenderer.tsx +4 -1
- package/components/CodeFilePopout.tsx +6 -4
- package/components/CodeSnippetPreview.tsx +174 -0
- package/components/ComposerTokenTextarea.tsx +43 -2
- package/components/DeferredMarkdownContentsRail.tsx +38 -0
- package/components/DiffFileTree.tsx +7 -6
- package/components/DocBadges.tsx +47 -35
- package/components/DocumentContents.tsx +74 -0
- package/components/ImageLightbox.tsx +121 -63
- package/components/ImageZoomStage.tsx +32 -4
- package/components/InlineMarkdown.tsx +15 -150
- package/components/MarkdownContentsRail.tsx +777 -0
- package/components/Settings.tsx +2 -1
- package/components/TableOfContents.tsx +22 -7
- package/components/Viewer.tsx +51 -19
- package/components/blocks/QuestionBlock.tsx +14 -4
- package/components/chat/ChatComposer.tsx +192 -0
- package/components/chat/ChatFrame.tsx +524 -0
- package/components/chat/ChatTranscript.tsx +422 -0
- package/components/chat/chatMarkdown.ts +436 -0
- package/components/chat/chatRecipes.tsx +39 -0
- package/components/chat/chatWindowStore.ts +196 -0
- package/components/chat/index.ts +72 -0
- package/components/diagram/DiagramCanvas.tsx +7 -0
- package/components/diagram/DiagramComposer.tsx +15 -3
- package/components/diagram/DiagramViewer.tsx +1 -0
- package/components/docBadgesWidth.ts +29 -0
- package/components/html-viewer/HtmlViewer.tsx +36 -1
- package/components/imageZoom.ts +21 -0
- package/components/inbox-thread/FileName.tsx +118 -0
- package/components/inbox-thread/GuideCard.tsx +39 -0
- package/components/inbox-thread/ReaskNote.tsx +25 -0
- package/components/inbox-thread/SkillsLine.tsx +40 -0
- package/components/inbox-thread/Tip.tsx +105 -0
- package/components/inbox-thread/icons.tsx +262 -0
- package/components/inbox-thread/reask.ts +222 -0
- package/contents-rail.css +337 -0
- package/hooks/useActiveSection.ts +46 -63
- package/hooks/useAnnotationHighlighter.ts +40 -1
- package/hooks/useFileBrowser.ts +4 -3
- package/hooks/useHeadingScrollSpy.ts +77 -0
- package/hooks/useHoverPreview.ts +94 -0
- package/hooks/usePrefersReducedMotion.ts +23 -0
- package/hooks/useSwipeStep.ts +597 -0
- package/inbox-thread.css +4542 -0
- package/package.json +13 -4
- package/styles.css +1 -1
- package/theme.css +1 -0
- package/themes/tokyo-night-moon.css +34 -0
- package/utils/aiChatFormat.ts +32 -11
- package/utils/annotationHelpers.ts +32 -4
- package/utils/blockParse.ts +45 -0
- package/utils/headingScrollSpy.ts +105 -0
- package/utils/syntaxTheme.ts +1 -0
- package/utils/themeRegistry.ts +11 -0
package/HANDOFF.md
CHANGED
|
@@ -220,6 +220,7 @@ We deliberately did **not** restructure the exports map in this PR (move-don't-r
|
|
|
220
220
|
| `components/TableOfContents` | Pure — renders from `blocks`; pair with `useActiveSection` for scroll-spy. *(Blessed in 0.24.0.)* |
|
|
221
221
|
| `components/ResizeHandle` + `hooks/useResizablePanel` | Layout pair for draggable panel widths; persists the width through the `storageBackend` seam. *(Blessed in 0.24.0.)* |
|
|
222
222
|
| `hooks/useActiveSection` | Scroll-spy over rendered headings; no backend. *(Blessed in 0.24.0.)* |
|
|
223
|
+
| `components/MarkdownContentsRail` + `components/DeferredMarkdownContentsRail` | Workspaces' document contents rail (ticks in the right gutter, the Contents panel on click), moved here; no backend. Its styles are their own sheet, `@plannotator/ui/contents-rail.css`: import it outside any CSS layer. Its scroll math is `utils/headingScrollSpy` (+ `hooks/useHeadingScrollSpy` for a list, `components/DocumentContents`). See README "Document contents rail" and "Scroll-spies: one per surface". |
|
|
223
224
|
| `hooks/useScrollViewport` | Resolves the scrolling element for viewport-aware UI; no backend. *(Blessed in 0.24.0.)* |
|
|
224
225
|
| `utils/annotationHelpers` | Pure annotation utilities (`getAnnotationCountBySection`, `buildTocHierarchy` + `TocItem`). *(Blessed in 0.24.0.)* |
|
|
225
226
|
| `components/html-viewer` (`HtmlViewer`, `projectHostThreads`, `buildPersistedHtmlAnchor`) | The raw-HTML annotation viewer: overlay-projected placed markers, pinpoint anchors, multi-target comments. Props + validated bridge protocol; no backend of its own. See "Raw-HTML annotation viewer + syntax-highlighting migration (0.29.0)" and "HTML annotation parity seams". *(Blessed in 0.29.0.)* |
|
|
@@ -233,6 +234,7 @@ We deliberately did **not** restructure the exports map in this PR (move-don't-r
|
|
|
233
234
|
| `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.)* |
|
|
234
235
|
| `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". |
|
|
235
236
|
| `utils/mermaid-math-slot` | Alias target only: what a host redirects Mermaid's own `katex` import to, so `$$` labels in diagrams typeset through the math slot and the host build carries one KaTeX chunk. Never import it yourself. See "Lazy renderers and eager entries", item 2. |
|
|
237
|
+
| `components/chat` (`ChatFrame`, `ChatFrameLauncher`, `ChatWindowStore`, `ChatTranscript`, `ChatMarkdown`, `ChatComposer` and the hooks and types beside them) | The shared floating chat window: screen code only. The host owns the conversation, the transport and anything saved. Add the folder and `components/ui/{button,textarea}.tsx` to your `@source`, and define the theme tokens listed in `components/chat/index.ts`. See "Floating chat window" in `README.md`. *(Published in ui 0.56.0.)* |
|
|
236
238
|
| `utils/identity-tater` | Side-effect entry that registers the full username dictionary into the identity generator slot. Import it only if you rely on the default tater names and want the full dictionary; a host with `identityProvider` should not. |
|
|
237
239
|
| `utils/mermaid` (`loadMermaidRuntime`, `getMermaidRuntime`, `getMermaidRuntimeSource`, `setMermaidRuntime`, `MERMAID_CONFIG`) and `utils/mermaid-eager` | The Mermaid runtime slot and its eager registration. Omit `utils/mermaid-eager` for the lazy path with retry, which is what Plannotator itself does since 0.40.0 (Mermaid 12); import it to register the runtime in your entry chunk at startup. See "Lazy renderers and eager entries" and "Mermaid 12 (0.40.0)". |
|
|
238
240
|
| `utils/mermaidTheme` (`buildMermaidThemeVariables`, `readThemeTokens`, `applyMermaidTheme`, `mermaidThemeKey`, `buildMermaidConfig`, `ensureContrast`, `buildMermaidShadow`, `DEFAULT_MERMAID_SHADOW_AMOUNT`), `utils/diagramShadow` and `utils/cssColor` | Theme-aware diagram configuration: the pure token-to-`themeVariables` mapping with its contrast guard, the palette-derived node shadow (`options.shadowAmount`, default 0.7 of Mermaid's own), the document token reader, and the cached per-`(palette, mode, shadow amount)` `initialize` step `MermaidBlock` runs before each render. Additive; with no theme tokens on the document the static `MERMAID_CONFIG` stays in force. See "Theme-aware Mermaid diagrams (0.40.0)". |
|
|
@@ -264,7 +266,7 @@ If Workspaces ever wants one of these surfaces, the path is the same as everythi
|
|
|
264
266
|
The renderer's `MathBlock` (and inline math) uses KaTeX. **KaTeX's stylesheet and its ~1.1MB of math fonts are deliberately NOT in the published `styles.css`** — bundling them would 9x the CSS for every page load, math or not. This is app-developer setup, done once; end users never touch it. Pick one:
|
|
265
267
|
|
|
266
268
|
1. **Self-hosted (recommended for production):** copy `katex/dist/katex.min.css` + `katex/dist/fonts/` to your own asset origin and add one `<link rel="stylesheet">`. No third-party dependency in your serving path; fonts download lazily, only on pages that actually render math.
|
|
267
|
-
2. **CDN tag:** `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@<version>/dist/katex.min.css">` in your HTML — pin `<version>` to the `katex` version in `@plannotator/ui`'s package.json (`^0.18.11`
|
|
269
|
+
2. **CDN tag:** `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@<version>/dist/katex.min.css">` in your HTML — pin `<version>` to the `katex` version in `@plannotator/ui`'s package.json (`^0.19.0` from ui 0.55.0, `^0.18.11` in ui 0.54.0, `^0.16.47` before) so CSS and the bundled KaTeX JS stay in step. A stylesheet from another KaTeX minor does not match the markup: 0.18 renamed 21 internal classes (see "Dependency bumps: KaTeX 0.18, @pierre/diffs 1.5.2, Mermaid 12.2 (published in ui 0.54.0)"). The same goes for a self-hosted copy (option 1). Same lazy-font behavior; adds a third-party origin.
|
|
268
270
|
3. **Bundler import:** `import 'katex/dist/katex.min.css';` next to your `styles.css` import — your bundler ships the fonts as separate lazy-loaded files. With npm/bun this resolves out of the box (`katex` is a dependency of `@plannotator/ui` and gets hoisted); under pnpm's strict `node_modules`, add `katex` to your own dependencies to import it directly.
|
|
269
271
|
|
|
270
272
|
If you skip all three and render math, equations appear as broken-looking raw HTML — that's the symptom to recognize. If you never render math, do nothing.
|
|
@@ -1769,6 +1771,7 @@ Additive, one optional `Viewer` prop; Plannotator passes none, so its render is
|
|
|
1769
1771
|
Additive, one optional `Viewer` prop; Plannotator's plan review and annotate pass none, so their render is unchanged. Added for the Plannotator Inbox (approval "inbox-message-comments", 2026-10-19), where the person comments on the words of an agent's message.
|
|
1770
1772
|
|
|
1771
1773
|
- **`textComments?: boolean`** on `Viewer`, with `answerOnly`: a text selection opens the selection toolbar again, comment-only (the toolbar's `commentOnly`: Comment and the one-click 👍, no Delete, no label picker), with its comment composer, and `annotations` are drawn as highlights (the highlighter is enabled). Question cards (and a host's `renderQuestionCard`) are wrapped `annotation-exclude`, so their words are never commented on. Nothing else of authoring comes back: no pinpoint, code-block toolbar, Global comment, Copy or Vim. Ignored without `answerOnly` and under `readOnly`. Inside, `selectionCommentsOn = !authoringOff || (answerOnly && textComments && !readOnly)` replaces `!authoringOff` for the highlighter and the selection toolbar, its composer and its label picker only.
|
|
1774
|
+
- **Diagrams follow it (ui 0.56.0; no API change).** Under the same condition a Mermaid or Graphviz fence takes comments on its parts, inline and in the popout, and a host's `onAskAI` reaches its composer as it reaches the text composer: `onAddAnnotation` gets the row `DiagramBlock` has always made (`diagramAnchor`, the fence's `blockId`, the part's label as `originalText`). A host that passes `textComments` must keep that row and pass it back in `annotations`; plain `answerOnly` and `readOnly` documents stay read-only, unchanged. Two touch fixes reach every host that comments on diagrams: a second finger cancels the press (it opened a composer), and the composer lifts to stay inside a canvas that would clip it.
|
|
1772
1775
|
|
|
1773
1776
|
## Dependency bumps: KaTeX 0.18, @pierre/diffs 1.5.2, Mermaid 12.2 (published in ui 0.54.0)
|
|
1774
1777
|
|
|
@@ -1844,6 +1847,16 @@ Everything below is opt-in and byte-identical when a host passes none of it.
|
|
|
1844
1847
|
|
|
1845
1848
|
## Publishing & versioning
|
|
1846
1849
|
|
|
1850
|
+
- **ui 0.56.0: ui only. `@plannotator/core` stays at 0.25.14, and ui keeps pinning it exactly.** Additive: no `exports` subpath is removed, no dependency or peer range changed (`postcss` is a devDependency for the sheet generator). Core files under `packages/core` did change since v0.28.37 (Inbox voice, projects, `shortAgentName`), but every `@plannotator/core` subpath ui imports exists in the tag, and ui imports none of the names those changes added. Cut from `main` at `a5707a547` plus the version bump. What is new, and what a host does:
|
|
1851
|
+
1. **Floating chat window** (#2452), `@plannotator/ui/components/chat`: `ChatFrame`, `ChatFrameLauncher`, `ChatWindowStore`, `ChatTranscript`, `ChatMarkdown`, `ChatComposer`, `chatMarkdownHtml`, `personMarkdownHtml`, and the hooks and types beside them. Required of a host that uses it: define the nine theme tokens listed in `components/chat/index.ts`, and add `components/chat` and `components/ui/{button,textarea}.tsx` to Tailwind `@source`. See README "Floating chat window".
|
|
1852
|
+
2. **Inbox thread parts** (`components/inbox-thread/*`: `GuideCard`, `ReaskNote`, `SkillsLine`, `FileName`, `Tip`, `icons`, `reask`) and the generated **`@plannotator/ui/inbox-thread.css`**. Import the sheet outside any cascade layer, after the base reset, and wrap the parts in `<div className="pn-inbox" data-pn-tooltip-root="">`. See "Inbox thread stylesheet". The parts are unstable until the set is complete.
|
|
1853
|
+
3. **Contents rail** (`components/MarkdownContentsRail`, `DeferredMarkdownContentsRail`, `DocumentContents`, `hooks/useHeadingScrollSpy`) and **`@plannotator/ui/contents-rail.css`**, moved from Workspaces. A host that has its own copy of the rail switches to these and imports the sheet outside any layer.
|
|
1854
|
+
4. **Diagram comments** (#2469, no API change): with `Viewer` `textComments`, a Mermaid or Graphviz fence takes comments on its parts. A host that passes `textComments` must keep the `DiagramBlock` annotation row and pass it back in `annotations`.
|
|
1855
|
+
5. **Smaller, no action:** `Viewer` `#anchor` and badge fixes, Windows path names, Tokyo Night Moon palette, Ask AI output sanitizing, `CodeSnippetPreview` and `useHoverPreview` extracted from `InlineMarkdown`.
|
|
1856
|
+
- **ui 0.55.0: ui only. `@plannotator/core` is unchanged at 0.25.14** (nothing under `packages/core` moved since v0.28.37), so ui publishes alone and keeps pinning core `0.25.14` exactly. No guides.show deploy is needed. It is cut from `main` at `f103b6703` plus the version bump, and packed with `bun pm pack` from a clean `bun install --frozen-lockfile` at Bun 1.4.4. **No export name is removed** (checked name by name against ui 0.54.0 on npm), and peer ranges are unchanged. Hosts must act on these:
|
|
1857
|
+
1. **KaTeX 0.19.** `katex` is `^0.19.0` (was `^0.18.11`, which npm marks deprecated). Rendering is unchanged, but a host that serves its own `katex.min.css` should move it to 0.19. Mermaid 12.2.0 still declares `katex ^0.18.0`, so without action the install holds two KaTeX copies. Add a root override `"katex": "^0.19.0"` (`pnpm.overrides`, `overrides` or `resolutions`), or point Mermaid's `katex` at `utils/mermaid-math-slot`. See "KaTeX 0.19".
|
|
1858
|
+
2. **`HtmlViewer` `sandbox?: string`, for Workspaces.** The srcdoc frame's whole `sandbox` attribute. Workspaces passes `sandbox="allow-scripts"` so a shared document's `alert` / `confirm` / `prompt` cannot draw over its origin, and can drop its pnpm patch. Absent, the default stays `"allow-scripts allow-modals"`. See "`HtmlViewer` `sandbox`".
|
|
1859
|
+
3. **Smaller changes with no action.** A `Viewer`'s `#anchor` link looks the id up inside its own container, so a matching id elsewhere on the page no longer hides the target (#2316). Additive and inert until a host passes it (#2315): `Viewer` `onQuestionPictureZoom` (`BlockRenderer` `onQuestionPictureZoom`, `QuestionBlock` `onZoomChoice`), `ImageLightbox` `swipe` and `LightboxImage.name`, `imageZoom`'s `canPanToward`, and the new `hooks/useSwipeStep`.
|
|
1847
1860
|
- **core 0.25.14 / ui 0.54.0: both change. Order: `core` 0.25.14, then `ui` 0.54.0. guides.show is already deployed.** Both are cut from the v0.28.37 release tag, `1722a14de`, and packed with `bun pm pack` from a clean `bun install --frozen-lockfile` at Bun 1.4.4. The previous pairs were ui 0.53.0 and ui 0.53.1 on core 0.25.13 (0.53.1 was published from `b68f0be02`). ui pins core `0.25.14` exactly and imports four core subpaths that 0.25.13 lacks: `agent-switch`, `forge-ref-card`, `html-embed` and `image-region`. So core goes first. core 0.25.14 moves the guides.show viewer pin from `viewer.DERExLls.js` + `viewer.BjyNeslq.css` (0.25.13) to `viewer.SgPt8W-S.js` + `viewer.DejL7_DK.css`. guides.show deployed on the v0.28.37 tag: both files and all 59 `langs` chunks answer 200 on `https://guides.show/v1/`, and the two viewer files match the manifest's SRI hashes, so no manual deploy is needed. **No export name is removed from either package** (checked name by name against core 0.25.13 and ui 0.53.1). Peer ranges are unchanged. Some defaults and outputs change, so Workspaces should check these before it upgrades:
|
|
1848
1861
|
1. **KaTeX 0.18 (ui dependency).** A host that loads `katex.min.css` itself must move it to 0.18, or equations lay out wrong (21 classes were renamed). `@pierre/diffs` 1.5.2 now logs `console.error` for a miscounted hunk. `mermaid` is 12.2.0 and `motion` is `^14.4.0` (was `^12.38.0`). See "Dependency bumps: KaTeX 0.18, @pierre/diffs 1.5.2, Mermaid 12.2".
|
|
1849
1862
|
2. **Plans open in the Clean look.** `gridEnabled` now defaults to `false`, and a stored value counts only with the v2 marker `plannotator-plan-look-v2 = 'resolved'`. A host that renders no chooser (Workspaces) opens every user in Clean. To keep stored values, call `markLookAndFeelChoiceResolved()` before the first settings read. A host that writes `gridEnabled` itself must also call it, or the value is lost on reload. See the README's "Upgrade note: the plan look defaults to Clean".
|
|
@@ -1881,12 +1894,12 @@ Everything below is opt-in and byte-identical when a host passes none of it.
|
|
|
1881
1894
|
- The previous pair was `@plannotator/ui` `0.40.0` on `@plannotator/core` `0.25.3` (publish order the same). Three things shipped in 0.40.0: (1) **Mermaid 12.0.0**, pinned exactly (was `^11.17.2`): ELK layout by default for flowchart/state/class/ER/requirement, Safari 17.4+ / ES2024 floor, SVG ids byte-identical to 11 but `g.edgePaths` children now in declaration order, and the plan editor no longer imports `utils/mermaid-eager` (the lazy path is the default for everyone; hosts that want startup registration import the eager entry themselves) — see "Mermaid 12 (0.40.0)"; (2) **theme-aware Mermaid diagrams**: New additive exports `utils/mermaidTheme` (`buildMermaidThemeVariables`, `readThemeTokens`, `applyMermaidTheme`, `mermaidThemeKey`, `buildMermaidConfig`, `ensureContrast`, `isDarkBackground`, `MERMAID_THEME_TOKEN_NAMES`) and `utils/cssColor` (parser + OKLab/contrast toolkit). `MermaidBlock` now calls `useTheme()` and `applyMermaidTheme` before each render; `MERMAID_CONFIG`, `loadMermaidRuntime`, `mermaid-eager` and `securityLevel: 'strict'` are unchanged. A host whose document carries no theme tokens renders diagrams byte-identically to 0.39.0; a host that mounts `ThemeProvider` with `theme.css` gets diagrams in its palette and mode with no configuration. No new peer dependencies; core unchanged. See "Theme-aware Mermaid diagrams (0.40.0)".; (3) **element context through the host seam (#1521, #1549), which is what moves `core` to 0.25.3:** `@plannotator/core/html-anchor` gains `parseHtmlElementContext`, `MAX_ELEMENT_CONTEXT_BYTES` and `MAX_PAGE_URL_LENGTH`; `PersistedHtmlAnchor.elementContext?` and `HtmlAnnotationTarget.context?` now round-trip through `buildPersistedHtmlAnchor` and `projectHostThreads`. **Core changes here, so bump and publish `core` first** and update UI's exact core dependency before packing ui — a ui build that imports these from an older published core fails to compile in a consumer, the 0.38.0 failure mode. `@plannotator/ui/components/html-viewer` re-exports the validator, so 0.39.0's import site is unchanged, and rows without context stay byte-identical on the wire. `utils/parser` gains `includeOutline` on `elementContextExportBlock` / `exportAnnotationEntry`, and `exportAnnotationEntry`'s `includeRoute` now defaults to true per field. See "Element context through the host seam".
|
|
1882
1895
|
- The previous pair was `@plannotator/ui` `0.39.0` on `@plannotator/core` `0.25.2` (core unchanged; nothing under `packages/core` moved). UI 0.39.0 adds **element context** to raw-HTML and live-app pinpoint annotations (#1517, #1520): a new optional `Annotation.elementContext` (`HtmlElementContext` in `@plannotator/ui/types`) and `HtmlAnnotationTarget.context`, captured by the bridge at click time (tag, id, author classes, ancestor `path`, `role`, accessible `name`, an allowlisted `attrs` set with href/src scrubbed of query and fragment, rendered `text`, an adaptive collapsed HTML `outline`, child count, viewport `rect`, nearest `landmark` and `heading`, a `component` hint, and in live-app sessions `page`), hard-capped at 2 KiB serialized per primary and 1 KiB per extra target, and re-validated at the parent trust boundary by the new `parseHtmlElementContext` export of `@plannotator/ui/components/html-viewer`. New helpers on `@plannotator/ui/utils/parser`: `elementContextExportBlock(ann, { includeRoute })` (the fenced skeleton plus selector/path/role/name/attrs/text/box/near lines the full export now prints under a context-bearing comment) and `exportAnnotationEntry(ann, { includeRoute })` (one annotation as a standalone feedback entry, a pure helper for hosts; `AnnotationPanel`'s card chrome is unchanged from 0.38.2). The field is purely descriptive: `HtmlElementAnchor` and restore are untouched, no `BRIDGE_PROTOCOL_VERSION` bump, share links drop it like anchors, annotations without it export byte-identically, and the repaint path posts only anchors to the bridge. **Host persistence gap in 0.39.0 itself, closed in the next publish (#1521, #1549)**: as shipped, 0.39.0's `@plannotator/core/html-anchor` (`buildPersistedHtmlAnchor`, `projectHostThreads`) does not carry `elementContext`, so a host pinned to 0.39.0 that persists through those helpers drops it on save and must persist and project the field itself. The next publish carries it end to end — see "Element context through the host seam". Peer ranges are unchanged from 0.38.2: `react` / `react-dom` `^19.2.3`, `tailwindcss` as before, and `@codemirror/state ^6.7.2` beside `@codemirror/view ^6.43.10`. Decision-control change in the same window (#1516): the header primary reads `Send Feedback` / `Post Comments` with no inline count (`DecisionPrimary.count` removed; internal, not host-supported surface).
|
|
1883
1896
|
- Before that, `@plannotator/ui` `0.38.2` on `@plannotator/core` `0.25.2`. UI 0.38.2 keeps the type word in a titled alert's accessible name through a visually hidden `sr-only` span before the title instead of an `aria-label` on the title row (naming a generic `div` is prohibited by ARIA and WebKit drops it, so VoiceOver on Safari read only the bold title in 0.38.1), and loosens the React peer back to `^19.2.3` (0.38.1 declared `^19.2.8` only because the dependency batch moved it; nothing in the package needs a newer API). **Do not consume ui 0.38.0**: it imports `@plannotator/core/token-hover` (the hover-card trigger settings, #1462) but pins core 0.25.1, which never exported that subpath, so it fails to compile in any consumer; 0.38.1 is the same UI pinning core 0.25.2, which publishes `./token-hover`, the rotated `guide-viewer-manifest` pin, and the `config-types` hover fields (core 0.25.2 is the first core publish since 0.25.1 even though those changes landed over several releases; the package smoke now diffs the UI's core imports against the registry so an unpublished core subpath fails preflight instead of the consumer). UI 0.38.1 also aligns `@codemirror/state` to `^6.7.2` beside `@codemirror/view ^6.43.10`, so a consumer can no longer resolve two state copies. UI 0.38.0 also renders a GitHub alert's bold-only first body line as its title on the icon row (an emoji on that line becomes the icon; `<!-- icon: name -->` is stripped and resolved through the new `alertIconRenderer` seam, null by default; grammar in `utils/alertTitle`, importable by a host editor so it writes the bytes the reader parses; a fenced code block inside an alert body still renders as text, deferred because nesting a `CodeBlock` inside a block interacts with the positional annotation anchors and needs its own design). UI 0.38.0 carries the whole unified decision-control stack: the internal primitives (`DecisionControl`, `utils/decisionSpec`, `hooks/useDismissablePopover` — not host-supported surface, see the unsupported list; `useDismissablePopover` also replaced the hand-rolled dismissal inside `ActionMenu`/`ApproveDropdown`, both likewise unsupported) plus one blessed-barrel addition: `decisionControlShortcuts` on `@plannotator/ui/shortcuts` (pure scope data, fetch-free, same contract as the other scopes). The removal of `ToolbarButtons`' platform-mode `muted` prop is internal — `ToolbarButtons` is not host-supported surface. UI 0.37.0 added the Viewer-owned document-header seam (a new public API, hence the minor bump; 0.36.1 was reserved for it but never published) while retaining the `hideQuickLabel` and `StickyHeaderLane` seams from the 0.35.x and 0.36.0 releases; core 0.25.1 publishes the `annotation-threads` subpath already used by `AnnotationPanel` and `utils/parser`, and UI pins that corrected core exactly.
|
|
1884
|
-
- Recent pairs, for the consumer's install matrix: ui 0.32.0 on core 0.25.0 (lockstep, `html-anchor`), ui 0.33.0 and ui 0.34.0 on core 0.25.0 (ui only), and ui 0.35.2, ui 0.36.0, and ui 0.37.0 on core 0.25.1 (0.36.1 was never published), ui 0.38.1, ui 0.38.2, and ui 0.39.0 on core 0.25.2, and ui 0.40.0 on core 0.25.3 (lockstep, `html-anchor` element context). Since then, from the registry: ui 0.41.0, 0.41.1 and 0.41.2 on core 0.25.4; ui 0.42.0, 0.43.0 to 0.43.2, 0.44.0, 0.45.0 and 0.45.1 on core 0.25.5; ui 0.46.0 and 0.46.1 on core 0.25.6; ui 0.47.0 on core 0.25.7; ui 0.49.0 on core 0.25.9 (core 0.25.8 and ui 0.48.0 were never published; their changes first shipped in ui 0.49.0 / core 0.25.9); ui 0.50.0 on core 0.25.10; ui 0.51.0 on core 0.25.11; ui 0.52.0 and 0.52.1 on core 0.25.12; ui 0.53.0 and 0.53.1 on core 0.25.13; ui 0.54.0 on core 0.25.14 (the current `latest`
|
|
1897
|
+
- Recent pairs, for the consumer's install matrix: ui 0.32.0 on core 0.25.0 (lockstep, `html-anchor`), ui 0.33.0 and ui 0.34.0 on core 0.25.0 (ui only), and ui 0.35.2, ui 0.36.0, and ui 0.37.0 on core 0.25.1 (0.36.1 was never published), ui 0.38.1, ui 0.38.2, and ui 0.39.0 on core 0.25.2, and ui 0.40.0 on core 0.25.3 (lockstep, `html-anchor` element context). Since then, from the registry: ui 0.41.0, 0.41.1 and 0.41.2 on core 0.25.4; ui 0.42.0, 0.43.0 to 0.43.2, 0.44.0, 0.45.0 and 0.45.1 on core 0.25.5; ui 0.46.0 and 0.46.1 on core 0.25.6; ui 0.47.0 on core 0.25.7; ui 0.49.0 on core 0.25.9 (core 0.25.8 and ui 0.48.0 were never published; their changes first shipped in ui 0.49.0 / core 0.25.9); ui 0.50.0 on core 0.25.10; ui 0.51.0 on core 0.25.11; ui 0.52.0 and 0.52.1 on core 0.25.12; ui 0.53.0 and 0.53.1 on core 0.25.13; ui 0.54.0 and 0.55.0 on core 0.25.14 (ui 0.55.0 and core 0.25.14 are the current `latest` once published). Do not consume ui 0.35.0 externally because its published manifest contains `workspace:*`; do not consume ui 0.35.1 because its exact core 0.25.0 dependency lacks the `annotation-threads` export. Do not consume ui 0.38.0 because its exact core 0.25.1 dependency lacks the `token-hover` export.
|
|
1885
1898
|
- When both packages change, **publish `core` first**: ui 0.32.0 imports the `@plannotator/core/html-anchor` subpath, which no earlier published core (0.24.0 and before) has, just as ui 0.29.0 needed core 0.23.0 for `@plannotator/core/annotatable`. Bump core, update UI's exact core dependency to the same new version, and run `bun install` so `bun.lock` records the new workspace versions before packing either package.
|
|
1886
1899
|
- The HTML annotation seams also changed the guides.show viewer **stylesheet** (five utility rules from `HtmlSurfaceControls`; the viewer JS is unchanged), so `packages/core/guide-viewer-manifest.ts` now pins a CSS hash that exists on guides.show only after the deploy workflow has published this build's `/v1/` assets. A guide exported from this build before that deploy would pin a stylesheet the host does not serve yet: **deploy guides.show before any release that ships this manifest.**
|
|
1887
1900
|
- UI declares the already published core version exactly in its source manifest. Do not replace it with `workspace:*`: direct publication can preserve that protocol and make the package impossible to install outside this repository. Bun links the local core workspace whenever its version matches the exact dependency. Before publishing, run `bun run --cwd packages/ui smoke:package`; it checks the source and packed manifests, required tarball subpaths, local Bun linking, and a real pnpm install in an external temporary consumer. When both packages change, publish **`core` first, then `ui`**.
|
|
1888
1901
|
- **`--provenance` only works from a supported CI environment (GitHub Actions OIDC)** — a local publish fails with `Automatic provenance generation not supported for provider: null`. Until a CI publish job exists for these two packages, local publishes drop the flag. Publishing under `--tag next` first lets the consumer preflight before `npm dist-tag add <pkg>@<version> latest` promotes it.
|
|
1889
|
-
- `styles.css` is built by the `prepack` script (`bun run build:css`) so the published tarball always carries fresh precompiled CSS; since 0.33.0 `prepack` also runs `build:bridge-assets`, which generates the gitignored `bridge-script.asset.js` and `bridge-script.lite.ts` beside their source.
|
|
1902
|
+
- `styles.css` is built by the `prepack` script (`bun run build:css`) so the published tarball always carries fresh precompiled CSS; since 0.33.0 `prepack` also runs `build:bridge-assets`, which generates the gitignored `bridge-script.asset.js` and `bridge-script.lite.ts` beside their source, and it now runs `build:inbox-thread-css`, which generates the gitignored `inbox-thread.css` from `packages/inbox/inbox.css`. All of them are in `files`, so a tarball built without `prepack` (a hand-rolled `npm pack --ignore-scripts`) would ship export subpaths that resolve to nothing; always build with `bun pm pack`. `smoke:package` fails if the sheet is missing from the tarball or a consumer cannot import it.
|
|
1890
1903
|
- There is **no CI publish job for these two packages yet** — first publish is manual from `main` after merge. (Wiring a CI publish job is a follow-up.)
|
|
1891
1904
|
|
|
1892
1905
|
---
|
|
@@ -1930,8 +1943,18 @@ was rebuilt.
|
|
|
1930
1943
|
The `file-tree-item` CSS was already in `theme.css`. The new pieces are the
|
|
1931
1944
|
`DiffFileTree` component itself and its ARIA/keyboard handling.
|
|
1932
1945
|
|
|
1946
|
+
**Since the aligned sidebar (approved design record "review-sidebar-aligned"),
|
|
1947
|
+
the review draws its own rows.** Its Changes tree has its own folder row and
|
|
1948
|
+
file row (`FileTreeNode.tsx`: 12 px indent steps on the panel's edges, `+`/`−`
|
|
1949
|
+
in two aligned columns, `CountColumns` in `FileRowBits.tsx`); it no longer
|
|
1950
|
+
renders `DiffFileTreeFolderRow`, `DiffFileTreeFileRowContent` or `DiffCounts`.
|
|
1951
|
+
It still shares `buildDiffFileTree`, `useDiffFileTreeExpansion`,
|
|
1952
|
+
`ChangeTypeLetter`, and `diffFileTreeIndent` (All files keeps that indent).
|
|
1953
|
+
The atoms below are unchanged and stay the host's starting point for a custom
|
|
1954
|
+
row; they no longer show what the review's rows look like.
|
|
1955
|
+
|
|
1933
1956
|
**Review-only, and why.** The review's `FileTree` keeps its own wrapper and
|
|
1934
|
-
|
|
1957
|
+
rows, adding: the viewed checkbox and the
|
|
1935
1958
|
hide-viewed filter, stage controls and the staged/committed/untracked marks,
|
|
1936
1959
|
the annotation badge and `has-annotations` tint, the scroll-highlight row, the
|
|
1937
1960
|
"Copy path" context menu, the double-click handler, the panel header, view toggle,
|
|
@@ -1939,8 +1962,8 @@ diff-type / base / worktree / evolog pickers, search results, the nav rows
|
|
|
1939
1962
|
(PR overview, artifacts, call flow, semantic diff, all files), and its
|
|
1940
1963
|
**window-wide** j/k/arrow/Home/End shortcut (an app shortcut, gated on inputs,
|
|
1941
1964
|
menus and dialogs). None of those is a host concern, so `DiffFileTree` takes no
|
|
1942
|
-
props for them; a host that needs any of them builds its own row from the atoms
|
|
1943
|
-
|
|
1965
|
+
props for them; a host that needs any of them builds its own row from the atoms
|
|
1966
|
+
(the review now goes further and draws its own rows, above). The review rows also keep their current DOM: no ARIA tree
|
|
1944
1967
|
roles were added there (that would be a behavior change to Plannotator's app,
|
|
1945
1968
|
out of scope here).
|
|
1946
1969
|
|
|
@@ -2546,6 +2569,40 @@ Slice 1 of the Inbox thread extract (plan record, not in this repo). These pure
|
|
|
2546
2569
|
|
|
2547
2570
|
Publish core first, then ui, as always. These subpaths need no ui bump until ui imports them.
|
|
2548
2571
|
|
|
2572
|
+
## Inbox thread stylesheet: `@plannotator/ui/inbox-thread.css` (ui 0.56.0; additive, no core change)
|
|
2573
|
+
|
|
2574
|
+
The parts in `components/inbox-thread/` draw with the Inbox's `ib-` classes. `packages/inbox/inbox.css` stays their only source. `scripts/build-inbox-thread-css.ts` generates `inbox-thread.css` from it at `prepack`, the pattern of `bridge-script.asset.js`: gitignored, in `files`, exported as `./inbox-thread.css`. It is the whole sheet in the source's order, about 560 KB raw and 79 KB gzipped (511 KB and 78 KB after a host's bundler minifies it). A thread-only subset can come later.
|
|
2575
|
+
|
|
2576
|
+
**What generation changes.** The comments go, and whatever would reach a host page outside `.pn-inbox`. `tests/inbox-thread-css.test.ts` runs the generator over the real `inbox.css` and checks each of these from the outside:
|
|
2577
|
+
|
|
2578
|
+
| In `inbox.css` | Reach | In the sheet |
|
|
2579
|
+
|---|---|---|
|
|
2580
|
+
| `html, body, #root` (`@layer base`) | height and margin of the page | dropped |
|
|
2581
|
+
| `html:has(.pn-inbox), body:has(.pn-inbox)` (phone block) | the page's background | dropped |
|
|
2582
|
+
| `:root[data-ib-keyboard-pin]:has(.pn-inbox)` (phone block) | `overflow: hidden` on the page while the Inbox pins the keyboard | dropped |
|
|
2583
|
+
| `html.ib-theme-instant` | transitions on `html` | dropped; its `*` forms are scoped |
|
|
2584
|
+
| `body:has(.pn-inbox) :is(input…, textarea, select)` (phone block) | every field on the page at `16px !important` | scoped |
|
|
2585
|
+
| `pre code.pn-code`, `[data-local-link="unavailable"]`, `kbd[data-keycap]::after`, `[data-open-in-menu] …`, `[data-comment-popover] …`, `[data-full-view-menu] …`, `[data-thread-owner] b` | ui's own elements and attributes, anywhere on the page | scoped |
|
|
2586
|
+
| `.tp`, `.tp-*`, `.pm-*` | unprefixed classes a host may also use | scoped |
|
|
2587
|
+
| `.ib-*`, `dialog.ib-*`, `::highlight(ib-*)` and the rest | prefixed, but matching outside the root | scoped |
|
|
2588
|
+
| `@keyframes tp-draw` | a global name without the prefix | `ib-tp-draw`, with its use |
|
|
2589
|
+
| `:root` and `:root.light` token blocks | `--ib-*` only, checked rule by rule | kept |
|
|
2590
|
+
|
|
2591
|
+
Scoping prefixes `:where(.pn-inbox)`, after any leading page element (`:root.light :where(.pn-inbox) .ib-linknote`). It adds no specificity, so the sheet's own cascade is the source's, rule for rule. The sheet has no `@font-face`, no `@import` and no `url()` but one inline `data:` mask. It names two layers, Tailwind's `base` and its own `ib-type`. A new at-rule in `inbox.css` that the generator does not know fails `prepack`, so someone decides what it does in a host first. So do nested CSS, `body > x`, and a renamed keyframe name held in a custom property, which the rename cannot follow. The generator reads the sheet with postcss, a devDependency of this package only: it runs at `prepack` and nothing at runtime needs it.
|
|
2592
|
+
|
|
2593
|
+
**What a host does.**
|
|
2594
|
+
|
|
2595
|
+
1. Import the sheet **outside any cascade layer, after the host's base reset**, as with `contents-rail.css`. In Workspaces that is next to `contents-rail.css` in `src/styles.css`, not in `src/plannotator/plannotator.css`: inside `layer(plannotator)` the sheet would lose to Tailwind's preflight in `base`.
|
|
2596
|
+
2. Keep `styles.css`. The sheet reads the theme tokens (`--background`, `--card`, `--muted`, `--border`, `--primary`, `--ring`, `--font-sans`, `--font-mono` and the rest) and defines none of them.
|
|
2597
|
+
3. Wrap the parts in one root: `<div className="pn-inbox" data-pn-tooltip-root="">`. Both on the same element, or a `Tip` portals outside the root and loses its look. The root sets `height: 100%`, `position: relative`, the type and `background: var(--ib-ground)`. The Inbox's pane paints `--card` over that, so a host matches it with `background: var(--card)` on its root, or uses its panel's own surface. In a scrolling panel `height: 100%` stops the background short of tall content: set `height: auto` on the root, or make the root the scroller.
|
|
2598
|
+
4. Load the fonts, as for `styles.css`: Inter and Geist Mono, or your own under `--font-sans` and `--font-mono`. A Tailwind host sets both on its root: Tailwind's `theme` layer defines them too, and it beats `styles.css` in `layer(plannotator)`, so without this the parts draw in the system font.
|
|
2599
|
+
5. Set the mode on `<html>` the way `ThemeProvider` does: the light half of the tokens reads `:root.light`. `styles.css` also holds every transition at 0 s until `<html>` has `.transitions-ready`, which `ThemeProvider` adds after mount.
|
|
2600
|
+
6. Portal into the root. The `ib-` rules match only inside `.pn-inbox`, so a popup a part sends to `document.body` has no look.
|
|
2601
|
+
|
|
2602
|
+
**What the Inbox's window owns and a host does not get.** `--ib-vvh`, `--inbox-text-size`, `--sidebar-width`, `data-view`, `data-ib-keyboard` and `.ib-phone` come from the Inbox's own code. Their rules ship, inert, with fallbacks where the source has them. `@container ib-thread` needs the Inbox's `.ib-pbody` around the parts, and `@container ib-ask` its `.ib-askline`; without them those rules do not apply.
|
|
2603
|
+
|
|
2604
|
+
**Viewport queries.** The sheet keeps the Inbox's media queries, and they measure the window, not the panel. For the parts in the folder today the one that matters is the phone block, `@media (max-width: 767px), (max-height: 500px) and (max-width: 1023px) and (pointer: coarse)`. Under it `.ib-att-t` takes the full width and 56 px of height, and `.pn-inbox` paints `--card`. Most of its 712 selectors belong to the Inbox's window or to parts that have not moved yet.
|
|
2605
|
+
|
|
2549
2606
|
## Document font (#1657; published in ui 0.54.0; additive, no core change)
|
|
2550
2607
|
|
|
2551
2608
|
Settings → Display can show a **Document font** row beside Text size. Inert until a host opts in, like Text size.
|
|
@@ -2586,3 +2643,42 @@ See `packages/ui/README.md` and `packages/ui/AGENTS.md` (CLAUDE.md symlink) for
|
|
|
2586
2643
|
- **`onTokenNavigate?: (target: CodeFileTokenTarget) => void`** is a Cmd/Ctrl-click (or Alt-click) on a token.
|
|
2587
2644
|
|
|
2588
2645
|
`CodeFileTokenTarget` is `{ tokenText, tokenElement, lineNumber, lineCharStart }`. Absent: today's view, byte for byte (`CodeFilePopout` and the Inbox pass none).
|
|
2646
|
+
|
|
2647
|
+
## `HtmlViewer` `sandbox` (published in ui 0.55.0; additive, no core change)
|
|
2648
|
+
|
|
2649
|
+
**`HtmlViewer`** (`components/html-viewer`) gains **`sandbox?: string`**, the srcdoc frame's whole `sandbox` attribute. Added for Workspaces: #2063 changed the default from `allow-scripts` to `allow-scripts allow-modals` with no way to opt out, and in Workspaces a `prompt()` or `confirm()` from a shared, user-written document would draw a dialog over its origin.
|
|
2650
|
+
|
|
2651
|
+
- **Absent:** `HTML_VIEWER_SANDBOX`, `"allow-scripts allow-modals"`, as before. Plannotator passes none, so the Inbox, annotate and review frames are unchanged.
|
|
2652
|
+
- **Set:** the value is the attribute as given. Workspaces passes `sandbox="allow-scripts"` and can drop its pnpm patch.
|
|
2653
|
+
- **Not a string** (`null` from an unset config field, for example): the default, never a frame without a sandbox. React drops a `null` attribute, and a destructuring default does not catch `null`, so `HtmlViewer` checks the type itself.
|
|
2654
|
+
- **`""`** (or only spaces): kept as given. That is the strictest frame, with no scripts, so the annotation bridge never starts and the page cannot be annotated. It never widens to the default. With `hostNavigates` it becomes `allow-popups` alone.
|
|
2655
|
+
- **A changed value** applies when the frame next loads a document (a new `rawHtml`, for example): browsers read `sandbox` at navigation. To apply it to the document already shown, remount the viewer (for example, key it on the value).
|
|
2656
|
+
- **With `hostNavigates`:** `allow-popups` is still added when the value lacks it, because that mode opens links with `window.open`.
|
|
2657
|
+
- **Never add `allow-same-origin`** beside `allow-scripts`: the page could then remove its own sandbox and reach the host's origin.
|
|
2658
|
+
- Live-app mode (`src`) has no sandbox and ignores the prop. `HtmlViewer` draws one frame, so the prop covers every frame of the document.
|
|
2659
|
+
- Pinned by `components/html-viewer/HtmlViewer.sandbox.test.tsx`: no prop gives `allow-scripts allow-modals`, `sandbox="allow-scripts"` gives exactly `allow-scripts`, `hostNavigates` adds `allow-popups` to either, `null` gives the default (with and without `hostNavigates`), `""` gives an empty attribute, and `src` mode has no sandbox.
|
|
2660
|
+
|
|
2661
|
+
**Note for hosts that pin CodeMirror (from ui 0.54.0).** ui 0.54.0 raised its CodeMirror and Lezer minimums. A host that pins these packages lower must raise its pins to at least these, or its build ships two copies (two `@codemirror/state` copies break editor extensions at runtime):
|
|
2662
|
+
|
|
2663
|
+
| Package | ui 0.54.0 range | ui 0.53.1 range |
|
|
2664
|
+
|---|---|---|
|
|
2665
|
+
| `@codemirror/commands` | `^6.11.1` | `^6.11.0` |
|
|
2666
|
+
| `@codemirror/language` | `^6.13.1` | `^6.12.4` |
|
|
2667
|
+
| `@codemirror/legacy-modes` | `^6.5.5` | `^6.5.3` |
|
|
2668
|
+
| `@codemirror/state` | `^6.7.6` | `^6.7.2` |
|
|
2669
|
+
| `@codemirror/view` | `^6.43.14` | `^6.43.10` |
|
|
2670
|
+
| `@lezer/common` | `^1.5.3` | `^1.5.2` |
|
|
2671
|
+
| `@lezer/highlight` | `^1.2.5` | `^1.2.3` |
|
|
2672
|
+
|
|
2673
|
+
These did not change: `@codemirror/autocomplete` `^6.20.3`, `lang-javascript` `^6.2.5`, `lang-json` `^6.0.2`, `lang-markdown` `^6.5.2`, `lang-python` `^6.2.1`, `lang-yaml` `^6.1.3`, `merge` `^6.12.2`, `search` `^6.7.2`.
|
|
2674
|
+
|
|
2675
|
+
## KaTeX 0.19 (published in ui 0.55.0; dependency bump, no API change, no core change)
|
|
2676
|
+
|
|
2677
|
+
**`katex` is now `^0.19.0`** (was `^0.18.11`). npm marks 0.18.11 deprecated ("Accidentally published with breaking changes. Use 0.19.0 instead."), and `^0.18.11` resolves only to 0.18.x.
|
|
2678
|
+
|
|
2679
|
+
- **Rendering is unchanged.** The 0.19.0 tarball matches 0.18.11 file for file, except for the version string (`katex.version` and the `.katex-version::after` content) and the READMEs. The fonts are identical. The breaking change named in the 0.19.0 notes shipped in 0.18.11: a missing character metric is reported through `strict` (code `symbolNotInFont`), and a `strict` callback that returns `undefined` or `null` now warns. ui calls KaTeX with `strict: 'warn'` and no callback, so ui is not affected. Plan review screenshots of a math fixture (inline, display, `aligned`, `\text`, `\def` / `\newcommand`, `\tag`, `cases`, and errors) show 0 changed pixels between 0.18.11 and 0.19.0, in dark and light.
|
|
2680
|
+
- **Hosts that serve their own KaTeX stylesheet should move it to 0.19** (CDN or self-hosted; see "Math rendering (KaTeX)" above). A 0.18.11 stylesheet still works, because its rules are the same as 0.19's. But 0.18.x is deprecated, and the stylesheet and the bundled JS should stay on one version.
|
|
2681
|
+
- **Mermaid 12.2.0 still declares `katex ^0.18.0`.** ^0.18.0 does not accept 0.19, so without action the install holds two KaTeX copies: 0.19 for ui and 0.18.11 (deprecated, with an npm warning) for Mermaid's `$$` labels. A bundle that imports Mermaid with no `katex` redirect then carries both copies. In Plannotator these are the plan page, the Inbox and the phone surface: without the override, each carried Mermaid's 0.18.11 too, about 262 KB more on the plan page. Code review's diagram engine is not affected, because `apps/review/diagram-engine/build.ts` (`mermaidKatexFromPage`) already sends Mermaid's `katex` import to the page's copy. Plannotator's root `package.json` has the override `"katex": "^0.19.0"`, so the install and those bundles hold one copy. **Remove that override when ui moves to a Mermaid that declares `katex ^0.19`** (package.json cannot hold a comment, so the note is here). A host has two ways to keep one copy:
|
|
2682
|
+
- Add the same override: `pnpm.overrides` for pnpm, `overrides` for npm, or `resolutions` for Yarn.
|
|
2683
|
+
- Point Mermaid's `katex` import at `utils/mermaid-math-slot` (see "Lazy renderers and eager entries", item 2).
|
|
2684
|
+
- **0.19.1** (2026-10-23) fixes a `%` inside a raw argument. `^0.19.0` accepts it. This repo's lockfile records 0.19.0, because `bunfig.toml`'s seven-day `minimumReleaseAge` holds 0.19.1 back until 2026-10-30.
|
package/README.md
CHANGED
|
@@ -114,6 +114,8 @@ Requires `@plannotator/markdown-editor ^0.4.0` and `@plannotator/atomic-editor ^
|
|
|
114
114
|
|
|
115
115
|
`components/html-viewer` is supported host surface as of 0.29.0: the overlay-projection annotation viewer for raw HTML (placed comment markers, pinpoint element anchors, shift-click multi-target comments). Its contract is **props plus the validated iframe message protocol** — not `configurePlannotatorUI()`, which only governs the backend surfaces around it. Drive the `annotations` prop (marker numbering derives from its array order); `readOnly` keeps markers painted and clickable while disabling all authoring. **0.29.0 also carries a breaking migration:** highlight.js is gone and `.hljs` selectors are inert — style code via the exported `pn-code` class (`CODE_BLOCK_CLASS` in `utils/codeHighlight`). See HANDOFF.md § "Raw-HTML annotation viewer + syntax-highlighting migration (0.29.0)" before upgrading from 0.28.0.
|
|
116
116
|
|
|
117
|
+
**`sandbox`** (published in ui 0.55.0): the srcdoc frame's whole `sandbox` attribute. The default is `"allow-scripts allow-modals"`, so a page's `alert`, `confirm` and `prompt` open. A host that shows documents other people wrote passes `sandbox="allow-scripts"`, so no dialog draws over its origin. `hostNavigates` still adds `allow-popups`. A non-string such as `null` gets the default. `""` is the strictest frame: no scripts, so nothing on the page can be annotated. A changed value applies when the frame next loads a document. Do not add `allow-same-origin`. See HANDOFF.md § "`HtmlViewer` `sandbox`".
|
|
118
|
+
|
|
117
119
|
#### HTML annotation parity seams (0.32.0)
|
|
118
120
|
|
|
119
121
|
Everything a host needs around `HtmlViewer` to match Plannotator's HTML annotation experience, all additive and all defaulting to today's behavior. Requires `@plannotator/core` 0.25.0 (the `html-anchor` subpath), so install and publish core before ui:
|
|
@@ -488,7 +490,7 @@ See HANDOFF.md § "Decision toggle on any question, and an opener for the host's
|
|
|
488
490
|
|
|
489
491
|
**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)".
|
|
490
492
|
|
|
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)".
|
|
493
|
+
**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, a diagram's parts take comments too (ui 0.56.0), question cards stay out of selection, nothing else of authoring does. See HANDOFF.md § "Viewer `textComments` (ui 0.53.1)".
|
|
492
494
|
|
|
493
495
|
**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)".
|
|
494
496
|
|
|
@@ -512,7 +514,123 @@ import { DiffFileTree } from "@plannotator/ui/components/DiffFileTree";
|
|
|
512
514
|
- `role="tree"` with `treeitem` rows (`aria-level`, `aria-expanded`, `aria-selected`) and one tab stop. While focus is in the tree: ArrowDown/`j`, ArrowUp/`k`, Home, End select files in tree order (the review's keys, opening folders as needed); ArrowRight/ArrowLeft open/close a focused folder, ArrowLeft from a file focuses its folder. Unlike the review, nothing listens on `window`.
|
|
513
515
|
- Styling is the `file-tree-item` rules in `styles.css` plus ThemeProvider tokens; no fetches, no seams.
|
|
514
516
|
|
|
515
|
-
For a custom row, use the atoms (`DiffFileTreeFolderRow`, `DiffFileTreeFileRowContent`, `ChangeTypeLetter`, `DiffCounts`, `diffFileTreeIndent`) with `buildDiffFileTree` and `useDiffFileTreeExpansion
|
|
517
|
+
For a custom row, use the atoms (`DiffFileTreeFolderRow`, `DiffFileTreeFileRowContent`, `ChangeTypeLetter`, `DiffCounts`, `diffFileTreeIndent`) with `buildDiffFileTree` and `useDiffFileTreeExpansion`. Plannotator's review shares the tree builder, the expansion hook and `ChangeTypeLetter`, but since its aligned sidebar it draws its own folder and file rows, so it is no longer an example of these atoms. See HANDOFF.md § "Diff file tree".
|
|
518
|
+
|
|
519
|
+
### Document contents rail (`components/MarkdownContentsRail`; ui 0.56.0)
|
|
520
|
+
|
|
521
|
+
Workspaces' contents rail, moved here (approved 2026-10-03 in Workspaces; the move approved 2026-10-30) so Workspaces and the Plannotator Inbox draw the same one. Plan review does not use it.
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
import { DeferredMarkdownContentsRail } from "@plannotator/ui/components/DeferredMarkdownContentsRail";
|
|
525
|
+
|
|
526
|
+
// In the document pane, beside the scroll element; it positions itself (fixed) from that element.
|
|
527
|
+
<DeferredMarkdownContentsRail markdown={markdown} scrollViewport={scrollEl} />
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
```css
|
|
531
|
+
/* Its own stylesheet: import it OUTSIDE any cascade layer, after the host's base reset. */
|
|
532
|
+
@import "@plannotator/ui/contents-rail.css";
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
- **Shape.** A 36 px dock of tick marks 24 px right of the reading column, vertically centred on the scroll element; it never changes the document's width. At most 10 ticks: a longer document is condensed into 10 spans, each shown by its shallowest heading nearest the span's middle. The current tick has `aria-current="location"`. A tick (or ↑ ↓ ← → Home End, Enter, while a tick has focus) opens a 256 px Contents panel to the LEFT of the dock, over the prose: every heading of levels 1 to 4, its breadcrumb its accessible name (`buildMarkdownContentsModel` also gives each heading a one-line excerpt, the first text after it cut at 180 characters). A pick is marked at once, smooth-scrolls the heading to the 80 px activation line (instantly under reduced motion) and flashes it. Escape on a tick closes the panel; Escape anywhere else closes a panel opened by pointer too (focus stays where it was), except while focus is in an editable control, a dialog, a menu or a listbox, whose Escape it is. It is `preventDefault`-ed and never stopped, so a host's own Escape handler sees it was used. An outside press, or focus leaving the panel, closes it too. No hover reveal, no left/right setting.
|
|
536
|
+
- **When it shows.** At least 4 headings of levels 1 to 4 (`hasContentsRailHeadings` in `utils/annotationHelpers`, over `tocHeadings(blocks, 4)`; a host can ask without loading the rail), a scroll element at least 480 px wide, and 68 px free between the reading column's right edge and the scroll element's right edge (its scrollbar excluded): `canShowContentsRail`, `resolveContentsRailGeometry`. A docked column outside the scroll element (comments) therefore makes it yield, never overlap.
|
|
537
|
+
- **Contract with the page.** Positions come from the rendered headings (`[data-block-type="heading"][data-block-id]`, `BlockRenderer`'s); only the rail's own heading ids are read, so an embedded document's headings (namespaced ids) never move the mark. The reading column is `[data-document-read-content]` inside the scroll element unless `contentSelector` names another (the Inbox passes the Viewer's `[data-doc-text-root]`). Any change to the scroll element's DOM re-binds the headings (a host that remounts its reading column, the same headings at new places, is followed).
|
|
538
|
+
- **Which heading is current.** The rail's own scroll math, moved with it into `utils/headingScrollSpy`: each heading's top is cached when the rail measures; every scroll frame a binary search finds the last heading at or above the activation line (`CONTENTS_ACTIVATION_OFFSET`, 80 px below the scroll element's top; Workspaces' was 52), and at the document's end the last heading is current. It needs no band, so a short pane, a jump (scrollbar drag, scroll restore, Home) and scrolling up all mark the right heading.
|
|
539
|
+
- **Loading.** `DeferredMarkdownContentsRail` mounts it after the page is interactive (`requestIdleCallback`, 2 s cap; a zero-delay timer where that is missing) from a lazy chunk.
|
|
540
|
+
- **Styles.** `pn-contents-rail-*` rules in their own stylesheet, `@plannotator/ui/contents-rail.css` (not in `theme.css` or `styles.css`). Import it outside any cascade layer: a later unlayered base reset beats layered rules and puts the ticks over the prose. Plain CSS on the host's tokens, each with a fallback: `--popover`, `--popover-foreground`, `--shadow-menu`, `--geometry-floating-radius`, `--geometry-structure-radius`, `--surface-hover`, `--motion-duration-fast|basic|relaxed`, `--ease-enter|exit`, `--primary`, `--foreground`, `--muted-foreground`. The labels inherit the font size.
|
|
541
|
+
- **Optional host props** (defaults reproduce Workspaces' rail): `blocks` (the host's parsed blocks instead of `markdown`; otherwise one cached parse per body, `utils/blockParse` `parseBlocksShared`), `contentSelector`, `hidden` (a host's own hide choice: draws nothing, still measures), `onAvailableChange(fits)` (whether it can show, after each measure, so a host can offer another way to Contents where it yields), `panelAction` (one control at the right of the open panel's title; the Inbox's Hide).
|
|
542
|
+
- Also moved: `hooks/usePrefersReducedMotion`.
|
|
543
|
+
|
|
544
|
+
### Scroll-spies: one per surface
|
|
545
|
+
|
|
546
|
+
Two, both in the package, never two on one surface:
|
|
547
|
+
|
|
548
|
+
- **Plan review** keeps `hooks/useActiveSection`, unchanged: an IntersectionObserver band from 80 px below the scroll element's top to 20% of its height. `useActiveSection.positions.test.tsx` pins its answers at scroll positions that tell 80 from 52 or 100 px and 20% from 30%.
|
|
549
|
+
- **The contents rail's surface** (the rail, and `DocumentContents` where the rail yields) uses the rail's scroll math, `utils/headingScrollSpy` (above); `hooks/useHeadingScrollSpy(scrollViewport, blockIds)` is it for a list. Both scroll a pick to 80 px, so a pick lands where plan review's does.
|
|
550
|
+
|
|
551
|
+
### Document contents (`components/DocumentContents`; ui 0.56.0)
|
|
552
|
+
|
|
553
|
+
A Contents list on the contents rail's surface, for where the rail yields: `TableOfContents` bound to one scroll element, with the rail's headings (levels 1 to 4) and the rail's scroll math (`useHeadingScrollSpy`) marking the heading being read, so the list and the rail never disagree. It reads the current heading at once on mount (a sheet opened mid-document). It draws no chrome; the host titles and places it. It keeps the marked entry in view inside the list's own scroll box.
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
import { DocumentContents } from "@plannotator/ui/components/DocumentContents";
|
|
557
|
+
|
|
558
|
+
<DocumentContents blocks={blocks} annotations={annotations} scrollViewport={scrollEl} onNavigate={closeSheet} />
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
- The headings come from `tocHeadings(blocks, 4)` (`utils/annotationHelpers`), the function `buildTocHierarchy`, plan review's "has entries" check (levels 1 to 3, the default) and the contents rail use.
|
|
562
|
+
- `TableOfContents` takes two optional seams: `scrollViewport`, the element a click scrolls (default: the `ScrollViewportProvider` context, as before), and `maxLevel` (default 3). Plan review passes neither and is unchanged.
|
|
563
|
+
- Used by the Inbox's sheet (a phone, or where the rail yields) and the phone surface; plan review keeps `SidebarContainer`.
|
|
564
|
+
|
|
565
|
+
### Floating chat window (`components/chat`; ui 0.56.0)
|
|
566
|
+
|
|
567
|
+
A floating window, a transcript and a composer, as screen code only: the host owns the conversation, the transport and anything saved. It imports nothing from a backend and has no seam in `configurePlannotatorUI`; everything comes in as props.
|
|
568
|
+
|
|
569
|
+
```tsx
|
|
570
|
+
import { ChatComposer, ChatFrame, ChatTranscript, ChatWindowStore } from "@plannotator/ui/components/chat";
|
|
571
|
+
|
|
572
|
+
const store = new ChatWindowStore({ initialGeometry: saved, onGeometryChange: save, onOpen: load });
|
|
573
|
+
|
|
574
|
+
<div className="relative isolate">
|
|
575
|
+
<ChatFrame store={store} label="Chat" header={() => <span>Chat</span>} onClose={close}>
|
|
576
|
+
{() => (
|
|
577
|
+
<>
|
|
578
|
+
<ChatTranscript messages={messages} status={status} agentName="Agent" />
|
|
579
|
+
<ChatComposer value={value} onChange={setValue} onSend={send} below={<span>docs/plan.md</span>} />
|
|
580
|
+
</>
|
|
581
|
+
)}
|
|
582
|
+
</ChatFrame>
|
|
583
|
+
</div>
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
- **Frame.** `ChatFrame` is `absolute`, so its parent is the frame it floats in and is fitted to. Give that parent its own stacking context (`isolate`): the window is `z-40`. Under 392 px of width the window is forced fullscreen, because it cannot float there.
|
|
587
|
+
- **Geometry.** The store holds what the person chose. What is drawn is fitted to the frame at render, so a frame that narrows for a moment changes neither the store nor what you save. `onGeometryChange` runs for each step of a person's drag or resize, so debounce it and flush on unload. It does not run for `initialGeometry`, for `restore`, or when the frame changes size. The store never touches storage.
|
|
588
|
+
- **Keys.** The frame listens to no key. `useChatFrameKeys` is the opt-in map (Mod+J, Shift+Mod+J, Escape). `useChatFrameFocusReturn` returns focus to the element wearing `data-chat-launcher` when focus was in the window.
|
|
589
|
+
- **Composer.** It is controlled and set `onChange` synchronously, or typing and IME input are lost. `onSend` runs while `busy` and with an empty box, so guard those. Enter does not bubble past the React root, so a bubble listener on `window` or `document` does not hear it; capture listeners and native listeners on ancestors still do. Other keys bubble as usual, so an app's own Escape handling still sees them. On a touch keyboard pass `sendOnEnter={false}`: it has no Shift, so otherwise no message can have a second line.
|
|
590
|
+
- **Text.** `ChatTranscript` draws text parts as sanitized markdown. Nothing in message text can carry a class, a `data-*` or `aria-*` attribute, an image or a script, and links open in a new tab with `noopener`. Raw HTML in an agent's text shows as text; the one exception is `<br>`, which stays a line break. Where the sanitizer fails its check on first use, or the text is past a work limit (length, parse time, the size of the HTML it makes, or thousands of characters past ASCII in one paragraph or code block, which is what makes layout slow), the message goes out as plain text. The time limit is a check, not a wall, and it is wall-clock, so a text near it can draw either way. A run of text too costly to parse, such as one holding a very long word, is local: it goes out as literal text and the rest stays formatted, and so does a table with too many cells. Long lines of code in a code block cost nothing. Mark a part `state: 'streaming'` while it grows: a text too costly to draw on every delta then stays plain, and redraws only as it grows by an eighth, so its last words can lag. It renders in full once the part stops streaming or the turn ends. A person can send while an answer streams: keep `status` at `streaming` and the answer above their message keeps streaming, and put a turn that is over in `stopped`. Rendering never throws.
|
|
591
|
+
- **Parts.** `renderPart(part, { message, index })` draws a part; return `undefined` to fall through, or `null` to draw nothing. To paint into a text part, return your own `ChatMarkdown`, with the props the transcript gives its own plus yours. Leave out `literalHtml` and a person's `<b>` draws bold:
|
|
592
|
+
|
|
593
|
+
```tsx
|
|
594
|
+
<ChatMarkdown
|
|
595
|
+
text={part.text}
|
|
596
|
+
streaming={part.state === "streaming"}
|
|
597
|
+
literalHtml={message.role === "user"}
|
|
598
|
+
messageId={message.id}
|
|
599
|
+
role={message.role}
|
|
600
|
+
partIndex={index}
|
|
601
|
+
annotatable
|
|
602
|
+
rootRef={ref}
|
|
603
|
+
/>
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
`data-text-part`, `data-message-id`, `data-message-role`, `data-part-index` and `data-annotatable` on its root are written by this code alone.
|
|
607
|
+
- **Styles.** Nine tokens are not in `theme.css`; `components/chat/index.ts` lists them, and a host defines them in its own `@theme`. Add this folder and `components/ui/{button,textarea}.tsx` to your `@source`. Recipes live in `chatRecipes.tsx` so a `*.tsx` glob reaches them.
|
|
608
|
+
|
|
609
|
+
### Inbox thread parts (`components/inbox-thread`; ui 0.56.0, in progress)
|
|
610
|
+
|
|
611
|
+
The Plannotator Inbox's thread display parts, moved here unchanged so the Inbox and Workspaces draw the same thread. They move in small steps, so treat these imports as unstable until the set is complete.
|
|
612
|
+
|
|
613
|
+
```tsx
|
|
614
|
+
import { Icon, HostMark } from "@plannotator/ui/components/inbox-thread/icons";
|
|
615
|
+
import { GuideCard } from "@plannotator/ui/components/inbox-thread/GuideCard";
|
|
616
|
+
|
|
617
|
+
<div className="pn-inbox" data-pn-tooltip-root="">
|
|
618
|
+
<GuideCard guide={guide} onOpen={open} />
|
|
619
|
+
</div>
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
```css
|
|
623
|
+
/* Its own stylesheet: import it OUTSIDE any cascade layer, after the host's base reset. */
|
|
624
|
+
@import "@plannotator/ui/inbox-thread.css";
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
- **In the folder so far.** `icons` (`Icon`, `HostMark`, `AuthorMark`, `TabGlyph`, `TailscaleMark`), `Tip` (`Tip`, `isTextCut`), `FileName`, `GuideCard`, `SkillsLine`, `ReaskNote`, and `reask` (the pure Re-ask rules).
|
|
628
|
+
- **Host-neutral.** A part takes what it shows as props, over core's `InboxMessageWire`, `InboxAttachmentState` and related types. It makes no request and reads no route, hash, cookie or storage. `tests/inbox-thread-parts-host-neutral.test.ts` fails if one does.
|
|
629
|
+
- **Tooltips.** `Tip` portals into the nearest `[data-pn-tooltip-root]`, so put that attribute on your `.pn-inbox` root, the same element: the sheet styles nothing outside that root. Without a `TooltipProvider`, each `Tip` keeps its own 150 ms delay.
|
|
630
|
+
- **Styles.** The parts draw with the Inbox's `ib-` classes, and those rules stay in `packages/inbox/inbox.css`, their only source. `@plannotator/ui/inbox-thread.css` is generated from that file at `prepack` (the pattern of `bridge-script.asset.js`; gitignored, in `files`). It is the whole sheet, about 560 KB raw and 79 KB gzipped, not a subset. What a host has to do is under "Consuming it".
|
|
631
|
+
- **Nothing reaches outside `.pn-inbox`.** The generator scopes every selector to the root with `:where(.pn-inbox)`, which adds no specificity, so the cascade is the Inbox's. It drops the rules that style the page itself (`html`, `body`, `#root`, `html:has(.pn-inbox)`, `:root[data-ib-keyboard-pin]:has(.pn-inbox)`, `html.ib-theme-instant`). What stays on `:root` is `--ib-*` tokens only, and every global name (`@keyframes`, `@property`, `::highlight()`, container names) starts `ib-`. The cost: a part that portals outside the root has no look, so portal into the root. `tests/inbox-thread-css.test.ts` checks all of this over the real `inbox.css`, so a new rule there cannot leak unnoticed. A new at-rule the generator does not know, such as `@font-face`, fails `prepack`.
|
|
632
|
+
- **Width.** The sheet carries the Inbox's viewport media queries as they are. In a panel narrower or wider than the Inbox's pane they answer for the window, not the panel. See HANDOFF.md § "Inbox thread stylesheet".
|
|
633
|
+
- **Plannotator.** The parts' old paths in `packages/inbox` re-export them, and the Inbox passes nothing new and does not load the generated sheet. These parts add no Tailwind output: the built CSS of plan review, code review, the Inbox and `styles.css` does not change.
|
|
516
634
|
|
|
517
635
|
## Consuming it (e.g. from Workspaces)
|
|
518
636
|
|
|
@@ -531,6 +649,13 @@ npm install @plannotator/ui @plannotator/core
|
|
|
531
649
|
The same policy covers math: KaTeX's stylesheet + fonts are deliberately not in `styles.css` — if you render math, load `katex/dist/katex.min.css` yourself (import, CDN tag, or self-hosted copy; see HANDOFF.md "Math rendering").
|
|
532
650
|
4. Import components: `import { Viewer } from "@plannotator/ui/components/Viewer";`
|
|
533
651
|
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"`.
|
|
652
|
+
6. **If you render the Inbox thread parts** (`components/inbox-thread`):
|
|
653
|
+
- Import `@plannotator/ui/inbox-thread.css` **outside any cascade layer, after your base reset**, as with `contents-rail.css`. Inside a layer below Tailwind's `base`, Tailwind's preflight beats it.
|
|
654
|
+
- Keep `styles.css` from step 2: the sheet reads its theme tokens and defines none of them.
|
|
655
|
+
- Wrap the parts in one root with both markers, `<div className="pn-inbox" data-pn-tooltip-root="">`. The root sets the type, `height: 100%`, `position: relative` and `background: var(--ib-ground)`. The Inbox's pane paints `--card`, so set `background: var(--card)` on your root to match it, or use your panel's own surface. In a panel that scrolls, `height: 100%` stops the background short of tall content, so set `height: auto` on the root too, or make the root the scroller.
|
|
656
|
+
- Fonts are step 3's. The sheet ships none. With Tailwind, its theme defines `--font-sans` and `--font-mono`, and that beats `styles.css` in a lower layer, so the parts draw in the system font. Set both on your root.
|
|
657
|
+
- Set the mode on `<html>` as `ThemeProvider` does: the sheet reads `:root.light`. `styles.css` also holds every transition at 0 s until `<html>` has `.transitions-ready`, which `ThemeProvider` adds after mount.
|
|
658
|
+
- Portal into the root: the sheet styles nothing outside it.
|
|
534
659
|
|
|
535
660
|
**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
661
|
|
|
@@ -539,7 +664,7 @@ npm install @plannotator/ui @plannotator/core
|
|
|
539
664
|
- `@plannotator/core` — pure utils + types, zero deps, browser-safe (CI enforces no `node:` imports). Published.
|
|
540
665
|
- `@plannotator/ui` — React components/hooks + theme + `configure()`. Depends on an exact published `@plannotator/core` version. Published.
|
|
541
666
|
- `@plannotator/shared`, `@plannotator/ai` — stay private to the monorepo; `shared` re-exports `core`'s modules via shims so Plannotator's internals are untouched.
|
|
542
|
-
- Currently `@plannotator/ui` 0.
|
|
667
|
+
- Currently `@plannotator/ui` 0.56.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.
|
|
543
668
|
|
|
544
669
|
## The one rule
|
|
545
670
|
|
|
@@ -12,6 +12,7 @@ import { OverlayScrollArea } from './OverlayScrollArea';
|
|
|
12
12
|
import { Button } from './ui/button';
|
|
13
13
|
import { cn } from '../lib/utils';
|
|
14
14
|
import { resolveReplyParents, resolveThreadRootTimestamps } from '@plannotator/core/annotation-threads';
|
|
15
|
+
import { basenameFromPath } from '@plannotator/core/source-save';
|
|
15
16
|
import { ROOT_DOCUMENT_GROUP_KEY } from '../utils/annotationScope';
|
|
16
17
|
import type { AnnotationScope, AnnotationDocumentGroup } from '../utils/annotationScope';
|
|
17
18
|
import type { QuestionPanelRow } from '../utils/questionAnswers';
|
|
@@ -1342,7 +1343,7 @@ const CodeAnnotationCard: React.FC<{
|
|
|
1342
1343
|
const lineRange = annotation.lineStart === annotation.lineEnd
|
|
1343
1344
|
? `line ${annotation.lineStart}`
|
|
1344
1345
|
: `lines ${annotation.lineStart}-${annotation.lineEnd}`;
|
|
1345
|
-
const fileName = annotation.filePath
|
|
1346
|
+
const fileName = basenameFromPath(annotation.filePath);
|
|
1346
1347
|
|
|
1347
1348
|
const handleCancelEdit = () => {
|
|
1348
1349
|
setIsEditing(false);
|
|
@@ -189,7 +189,7 @@ export const AttachmentsButton: React.FC<AttachmentsButtonProps> = ({
|
|
|
189
189
|
const trimmed = manualPath.trim();
|
|
190
190
|
if (trimmed) {
|
|
191
191
|
const name = deriveImageName(
|
|
192
|
-
trimmed.split('/').pop() || 'image',
|
|
192
|
+
trimmed.replace(/\\/g, '/').split('/').pop() || 'image',
|
|
193
193
|
images.map(i => i.name)
|
|
194
194
|
);
|
|
195
195
|
onAdd({ path: trimmed, name });
|
|
@@ -74,7 +74,9 @@ export const BlockRenderer: React.FC<{
|
|
|
74
74
|
questionPictureChoices?: QuestionBlockProps['pictureChoices'];
|
|
75
75
|
/** A label over each picture tile (see `QuestionBlock` `pictureBadge`). */
|
|
76
76
|
questionPictureBadge?: QuestionBlockProps['pictureBadge'];
|
|
77
|
-
|
|
77
|
+
/** A picture tile's zoom, with its question and choice (see `QuestionBlock` `onZoomChoice`). */
|
|
78
|
+
onQuestionPictureZoom?: QuestionBlockProps['onZoomChoice'];
|
|
79
|
+
}> = ({ block, onOpenLinkedDoc, onOpenCodeFile, imageBaseDir, onImageClick, onToggleCheckbox, checkboxOverrides, orderedIndex, githubRepo, repoHost, headingAnchorId, onNavigateAnchor, question, questionTotal, questionAnswer, onAnswerQuestion, onSaveQuestionAnswer, saveQuestionAnswerLabel, renderQuestionFooter, questionDecisionRecording, questionDecisionScope, onToggleQuestionDecisionRecording, onOpenQuestionDecision, questionStatusTag, questionStatusLabel, renderQuestionBelowPrompt, questionContractLines = false, questionPictureChoices, questionPictureBadge, onQuestionPictureZoom }) => {
|
|
78
80
|
switch (block.type) {
|
|
79
81
|
case 'heading': {
|
|
80
82
|
const Tag = `h${block.level || 1}` as React.ElementType;
|
|
@@ -213,6 +215,7 @@ export const BlockRenderer: React.FC<{
|
|
|
213
215
|
renderBelowPrompt={renderQuestionBelowPrompt}
|
|
214
216
|
pictureChoices={questionPictureChoices}
|
|
215
217
|
pictureBadge={questionPictureBadge}
|
|
218
|
+
onZoomChoice={onQuestionPictureZoom}
|
|
216
219
|
onOpenLinkedDoc={onOpenLinkedDoc}
|
|
217
220
|
onOpenCodeFile={onOpenCodeFile}
|
|
218
221
|
imageBaseDir={imageBaseDir}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
2
2
|
import { File, type LineAnnotation } from '@pierre/diffs/react';
|
|
3
3
|
import type { SelectedLineRange as PierreSelectedLineRange, LineEventBaseProps } from '@pierre/diffs';
|
|
4
|
+
import { normalizeBrowserPath } from '@plannotator/core/browser-paths';
|
|
5
|
+
import { basenameFromPath } from '@plannotator/core/source-save';
|
|
4
6
|
import { PopoutDialog } from './PopoutDialog';
|
|
5
7
|
import { isCommentSubmitKey } from '../utils/commentSubmitKey';
|
|
6
8
|
import { useConfigValue } from '../config/useConfig';
|
|
@@ -399,8 +401,8 @@ export const CodeFileView: React.FC<CodeFileViewProps> = ({
|
|
|
399
401
|
setPendingComment(null);
|
|
400
402
|
}, [filepath]);
|
|
401
403
|
|
|
402
|
-
const displayName = filepath
|
|
403
|
-
const relativePath = filepath.replace(/.*\/(?=.*\/)/, '');
|
|
404
|
+
const displayName = basenameFromPath(filepath);
|
|
405
|
+
const relativePath = normalizeBrowserPath(filepath).replace(/.*\/(?=.*\/)/, '');
|
|
404
406
|
const selectedCodeAnnotation = useMemo(
|
|
405
407
|
() => annotations.find((ann) => ann.id === selectedAnnotationId),
|
|
406
408
|
[annotations, selectedAnnotationId],
|
|
@@ -696,8 +698,8 @@ export const CodeFilePopout: React.FC<CodeFilePopoutProps> = ({
|
|
|
696
698
|
}) => {
|
|
697
699
|
const [copied, setCopied] = useState(false);
|
|
698
700
|
|
|
699
|
-
const displayName = filepath
|
|
700
|
-
const relativePath = filepath.replace(/.*\/(?=.*\/)/, '');
|
|
701
|
+
const displayName = basenameFromPath(filepath);
|
|
702
|
+
const relativePath = normalizeBrowserPath(filepath).replace(/.*\/(?=.*\/)/, '');
|
|
701
703
|
const lineCount = useMemo(() => contents.split('\n').length, [contents]);
|
|
702
704
|
|
|
703
705
|
const handleCopy = async () => {
|