comark-ui 0.7.2 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +2 -2
  2. package/dist/{editor-BAdok66p.mjs → editor-DmxNPK7K.mjs} +522 -68
  3. package/dist/{editor-CNivEuMH.d.mts → editor-KD97dL_1.d.mts} +21 -3
  4. package/dist/editor-vue.d.mts +1 -1
  5. package/dist/editor-vue.mjs +1 -1
  6. package/dist/editor.d.mts +2 -2
  7. package/dist/editor.mjs +1 -1
  8. package/dist/html.d.mts +1 -1
  9. package/dist/html.mjs +3 -1
  10. package/dist/{index-jkwx6SyG.d.mts → index-Dbqxt4Qg.d.mts} +10 -2
  11. package/dist/index.d.mts +1 -1
  12. package/dist/index.mjs +1 -1
  13. package/dist/react.d.mts +2 -2
  14. package/dist/react.mjs +2 -2
  15. package/dist/skills/before-after-evidence/SKILL.md +72 -0
  16. package/dist/skills/before-after-evidence/agents/openai.yaml +3 -0
  17. package/dist/skills/before-after-evidence/references/blocks.md +54 -0
  18. package/dist/skills/before-after-evidence/references/example.md +37 -0
  19. package/dist/skills/before-after-evidence/references/measuring.md +50 -0
  20. package/dist/skills/comark-ui/SKILL.md +8 -1
  21. package/dist/skills/comark-ui/references/annotate.md +5 -3
  22. package/dist/skills/comark-ui/references/comments.md +43 -0
  23. package/dist/skills/comark-ui/references/lint.md +1 -1
  24. package/dist/skills/comark-ui/references/media.md +2 -2
  25. package/dist/skills/comark-ui/references/quiz.md +1 -1
  26. package/dist/skills/comark-ui/references/ui.md +3 -2
  27. package/dist/skills/comark-ui/references/zoom.md +1 -1
  28. package/dist/skills/review-document/SKILL.md +64 -0
  29. package/dist/skills/review-document/agents/openai.yaml +3 -0
  30. package/dist/skills/review-document/references/blocks.md +69 -0
  31. package/dist/skills/review-document/references/example.md +51 -0
  32. package/dist/skills/teach-lesson/SKILL.md +78 -0
  33. package/dist/skills/teach-lesson/agents/openai.yaml +3 -0
  34. package/dist/skills/teach-lesson/references/blocks.md +91 -0
  35. package/dist/skills/teach-lesson/references/example.md +70 -0
  36. package/dist/skills/visual-explanation/SKILL.md +67 -0
  37. package/dist/skills/visual-explanation/agents/openai.yaml +3 -0
  38. package/dist/skills/visual-explanation/references/blocks.md +89 -0
  39. package/dist/skills/visual-explanation/references/example.md +25 -0
  40. package/dist/skills.d.mts +18 -0
  41. package/dist/skills.mjs +326 -0
  42. package/dist/{src-DROB05U6.mjs → src-PkmYCSNu.mjs} +1 -0
  43. package/dist/svelte/index.d.ts +1 -1
  44. package/dist/svelte/index.js +1 -1
  45. package/dist/vue.d.mts +2 -2
  46. package/dist/vue.mjs +2 -2
  47. package/package.json +46 -44
@@ -1,5 +1,5 @@
1
1
  import { ComarkPlugin } from "comark";
2
- import { AnyExtension, Extension, JSONContent, Node } from "@tiptap/core";
2
+ import { AnyExtension, Extension, JSONContent, Mark, Node } from "@tiptap/core";
3
3
  import { StarterKitOptions } from "@tiptap/starter-kit";
4
4
  import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
5
5
  import { DecorationSet } from "@tiptap/pm/view";
@@ -21,9 +21,27 @@ declare const ComarkSlot: Node<any, any>;
21
21
  declare const ComarkInline: Node<any, any>;
22
22
  /** An HTML comment between blocks. */
23
23
  declare const ComarkComment: Node<any, any>;
24
+ /** A footnote reference, `[^label]`. */
25
+ declare const FootnoteReference: Node<any, any>;
26
+ /** A note written where it applies, `^[text]`. */
27
+ declare const InlineFootnote: Node<any, any>;
28
+ /** A footnote's text, `[^label]: text`, with the blocks indented under it. */
29
+ declare const FootnoteDefinition: Node<any, any>;
30
+ /** A definition list, `Term` then `: Definition`. */
31
+ declare const DefinitionList: Node<any, any>;
32
+ declare const DefinitionTerm: Node<any, any>;
33
+ declare const DefinitionDescription: Node<any, any>;
34
+ /** CriticMarkup's suggested insertion, `{++text++}`. */
35
+ declare const CriticInsertion: Mark<any, any>;
36
+ /** CriticMarkup's suggested deletion, `{--text--}`. */
37
+ declare const CriticDeletion: Mark<any, any>;
38
+ /** CriticMarkup's highlight, `{==text==}`. */
39
+ declare const CriticHighlight: Mark<any, any>;
40
+ /** CriticMarkup's comment, `{>>text<<}`. */
41
+ declare const CriticComment: Node<any, any>;
24
42
  /**
25
43
  * The schema `fromMarkdown` and `toMarkdown` read and write: StarterKit's nodes and marks, task lists, inline images,
26
- * tables, and Comark's components, slots, and comments. It needs no DOM, so a server can load it to convert a document
44
+ * tables, footnotes, definition lists, CriticMarkup, and Comark's components, slots, and comments. It needs no DOM, so a server can load it to convert a document
27
45
  * for a collaborative room.
28
46
  */
29
47
  declare const ComarkKit: Extension<ComarkKitOptions, any>;
@@ -128,4 +146,4 @@ interface EditorCodecOptions {
128
146
  }
129
147
  declare function createEditorCodec(options?: EditorCodecOptions): EditorCodec;
130
148
  //#endregion
131
- export { ComarkSlot as S, ComarkComment as _, Upload as a, ComarkKit as b, startUploads as c, EditorDocument as d, MarkdownCheck as f, ComarkBlock as g, toMarkdown as h, PendingUpload as i, uploadKey as l, fromMarkdown as m, EditorCodecOptions as n, UploadOptions as o, checkMarkdown as p, createEditorCodec as r, findUpload as s, EditorCodec as t, uploadPlugin as u, ComarkImage as v, ComarkKitOptions as x, ComarkInline as y };
149
+ export { FootnoteDefinition as A, CriticComment as C, DefinitionDescription as D, CriticInsertion as E, InlineFootnote as M, DefinitionList as O, ComarkSlot as S, CriticHighlight as T, ComarkComment as _, Upload as a, ComarkKit as b, startUploads as c, EditorDocument as d, MarkdownCheck as f, ComarkBlock as g, toMarkdown as h, PendingUpload as i, FootnoteReference as j, DefinitionTerm as k, uploadKey as l, fromMarkdown as m, EditorCodecOptions as n, UploadOptions as o, checkMarkdown as p, createEditorCodec as r, findUpload as s, EditorCodec as t, uploadPlugin as u, ComarkImage as v, CriticDeletion as w, ComarkKitOptions as x, ComarkInline as y };
@@ -1,4 +1,4 @@
1
- import { x as ComarkKitOptions } from "./editor-CNivEuMH.mjs";
1
+ import { x as ComarkKitOptions } from "./editor-KD97dL_1.mjs";
2
2
  import { ComarkPlugin } from "comark";
3
3
  import { AnyExtension } from "@tiptap/core";
4
4
  import { Editor } from "@tiptap/vue-3";
@@ -1,4 +1,4 @@
1
- import { c as fromMarkdown, l as toMarkdown, m as ComarkKit, s as checkMarkdown } from "./editor-BAdok66p.mjs";
1
+ import { c as fromMarkdown, l as toMarkdown, m as ComarkKit, s as checkMarkdown } from "./editor-DmxNPK7K.mjs";
2
2
  import { Editor, EditorContent } from "@tiptap/vue-3";
3
3
  import { defineComponent, h, onScopeDispose, ref, shallowRef, watch } from "vue";
4
4
  //#region src/editor-vue.ts
package/dist/editor.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { S as ComarkSlot, _ as ComarkComment, a as Upload, b as ComarkKit, c as startUploads, d as EditorDocument, f as MarkdownCheck, g as ComarkBlock, h as toMarkdown, i as PendingUpload, l as uploadKey, m as fromMarkdown, n as EditorCodecOptions, o as UploadOptions, p as checkMarkdown, r as createEditorCodec, s as findUpload, t as EditorCodec, u as uploadPlugin, v as ComarkImage, x as ComarkKitOptions, y as ComarkInline } from "./editor-CNivEuMH.mjs";
2
- export { ComarkBlock, ComarkComment, ComarkImage, ComarkInline, ComarkKit, type ComarkKitOptions, ComarkSlot, type EditorCodec, type EditorCodecOptions, type EditorDocument, type MarkdownCheck, type PendingUpload, Upload, type UploadOptions, checkMarkdown, createEditorCodec, findUpload, fromMarkdown, startUploads, toMarkdown, uploadKey, uploadPlugin };
1
+ import { A as FootnoteDefinition, C as CriticComment, D as DefinitionDescription, E as CriticInsertion, M as InlineFootnote, O as DefinitionList, S as ComarkSlot, T as CriticHighlight, _ as ComarkComment, a as Upload, b as ComarkKit, c as startUploads, d as EditorDocument, f as MarkdownCheck, g as ComarkBlock, h as toMarkdown, i as PendingUpload, j as FootnoteReference, k as DefinitionTerm, l as uploadKey, m as fromMarkdown, n as EditorCodecOptions, o as UploadOptions, p as checkMarkdown, r as createEditorCodec, s as findUpload, t as EditorCodec, u as uploadPlugin, v as ComarkImage, w as CriticDeletion, x as ComarkKitOptions, y as ComarkInline } from "./editor-KD97dL_1.mjs";
2
+ export { ComarkBlock, ComarkComment, ComarkImage, ComarkInline, ComarkKit, type ComarkKitOptions, ComarkSlot, CriticComment, CriticDeletion, CriticHighlight, CriticInsertion, DefinitionDescription, DefinitionList, DefinitionTerm, type EditorCodec, type EditorCodecOptions, type EditorDocument, FootnoteDefinition, FootnoteReference, InlineFootnote, type MarkdownCheck, type PendingUpload, Upload, type UploadOptions, checkMarkdown, createEditorCodec, findUpload, fromMarkdown, startUploads, toMarkdown, uploadKey, uploadPlugin };
package/dist/editor.mjs CHANGED
@@ -1 +1 @@
1
- export { u as ComarkBlock, d as ComarkComment, f as ComarkImage, p as ComarkInline, m as ComarkKit, h as ComarkSlot, n as Upload, s as checkMarkdown, t as createEditorCodec, r as findUpload, c as fromMarkdown, i as startUploads, l as toMarkdown, a as uploadKey, o as uploadPlugin } from "./editor-BAdok66p.mjs";
1
+ export { u as ComarkBlock, d as ComarkComment, f as ComarkImage, p as ComarkInline, m as ComarkKit, h as ComarkSlot, g as CriticComment, _ as CriticDeletion, v as CriticHighlight, y as CriticInsertion, b as DefinitionDescription, x as DefinitionList, S as DefinitionTerm, C as FootnoteDefinition, w as FootnoteReference, T as InlineFootnote, n as Upload, s as checkMarkdown, t as createEditorCodec, r as findUpload, c as fromMarkdown, i as startUploads, l as toMarkdown, a as uploadKey, o as uploadPlugin } from "./editor-DmxNPK7K.mjs";
package/dist/html.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as criticCss, n as Preset, r as PresetOptions } from "./index-jkwx6SyG.mjs";
1
+ import { a as criticCss, n as Preset, r as PresetOptions } from "./index-Dbqxt4Qg.mjs";
2
2
  import { NodeHandler } from "comark";
3
3
  //#region src/html.d.ts
4
4
  /**
package/dist/html.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
2
- import { n as preset, t as criticCss } from "./src-DROB05U6.mjs";
2
+ import { n as preset, t as criticCss } from "./src-PkmYCSNu.mjs";
3
3
  import { a as start, n as mermaidBar, r as mermaidCss } from "./client-Bjd6VcGQ.mjs";
4
4
  import { escapeHtml } from "comark/utils";
5
5
  import * as annotate from "comark-annotate/html";
@@ -12,6 +12,7 @@ import * as chips from "comark-chips/html";
12
12
  import * as code from "comark-code/html";
13
13
  import * as codeNotes from "comark-code-notes/html";
14
14
  import * as colors from "comark-colors/html";
15
+ import * as comments from "comark-comments/html";
15
16
  import * as details from "comark-details/html";
16
17
  import * as diagrams from "comark-diagrams/html";
17
18
  import * as diff from "comark-diff/html";
@@ -77,6 +78,7 @@ var html_default = (options) => preset({
77
78
  code,
78
79
  codeNotes,
79
80
  colors,
81
+ comments,
80
82
  details,
81
83
  diagrams,
82
84
  diff,
@@ -8,10 +8,12 @@ import { ReadingTimeOptions } from "comark-reading-time";
8
8
  import { XrefOptions } from "comark-xref";
9
9
  import { ComarkPlugin } from "comark";
10
10
  import { BadgeOptions } from "comark-badge";
11
+ import { AnnotateOptions } from "comark-annotate";
11
12
  import { CastOptions } from "comark-cast";
12
13
  import { ChartsOptions } from "comark-charts";
13
14
  import { ChipsOptions } from "comark-chips";
14
15
  import { CodeOptions } from "comark-code";
16
+ import { CommentsOptions } from "comark-comments";
15
17
  import { DetailsOptions } from "comark-details";
16
18
  import { DiagramsOptions } from "comark-diagrams";
17
19
  import { FileTreeOptions } from "comark-file-tree";
@@ -42,7 +44,7 @@ type PresetOptions = {
42
44
  /** Pastes files into the Markdown before the other plugins read it. Off, since it reads from disk by default. */
43
45
  include?: IncludeOptions | boolean;
44
46
  /** HTML only: Vue, React, and Svelte have no annotate components. */
45
- annotate?: boolean;
47
+ annotate?: AnnotateOptions | boolean;
46
48
  carousel?: boolean;
47
49
  /** Reads no cast files unless you pass `{ root }` or `{ read }`; inline casts render either way. */
48
50
  cast?: CastOptions | boolean;
@@ -54,6 +56,11 @@ type PresetOptions = {
54
56
  /** Pass `{ download: false }` when parsing in a browser, which may not download another site's images. */
55
57
  chips?: ChipsOptions | boolean;
56
58
  colors?: boolean;
59
+ /**
60
+ * HTML only: Vue, React, and Svelte have no comments components. `{ mark: "pen" }` draws suggestions with a
61
+ * hand-drawn pen unless one sets its own `mark`.
62
+ */
63
+ comments?: CommentsOptions | boolean;
57
64
  /** Sends each diagram's source to `https://kroki.io` unless you pass your own `server`, or `{ draw: false }`. */
58
65
  diagrams?: DiagramsOptions | boolean;
59
66
  /** Draws `mermaid` fences with comark-pele, on the server and offline. */
@@ -116,12 +123,13 @@ type Part<C, O = void> = {
116
123
  };
117
124
  /** One renderer's entry of each package with components, by the option that turns it on. */
118
125
  type Renderer<C> = {
119
- annotate?: Part<C>;
126
+ annotate?: Part<C, AnnotateOptions>;
120
127
  carousel: Part<C>;
121
128
  cast: Part<C, CastOptions>;
122
129
  charts: Part<C, ChartsOptions>;
123
130
  chips: Part<C, ChipsOptions>;
124
131
  colors: Part<C>;
132
+ comments?: Part<C, CommentsOptions>;
125
133
  diagrams: Part<C, DiagramsOptions>;
126
134
  mermaid: Part<C, PeleConfig>;
127
135
  diff?: Part<C>;
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { t as typesetCss } from "./typeset-css-DdOGL7WF.mjs";
2
- import { a as criticCss, c as typeset, i as Renderer, n as Preset, o as preset, r as PresetOptions, s as TYPESET_TAG, t as AnyPlugin } from "./index-jkwx6SyG.mjs";
2
+ import { a as criticCss, c as typeset, i as Renderer, n as Preset, o as preset, r as PresetOptions, s as TYPESET_TAG, t as AnyPlugin } from "./index-Dbqxt4Qg.mjs";
3
3
  export { AnyPlugin, Preset, PresetOptions, Renderer, TYPESET_TAG, criticCss, preset, typeset, typesetCss };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
2
- import { i as typeset, n as preset, r as TYPESET_TAG, t as criticCss } from "./src-DROB05U6.mjs";
2
+ import { i as typeset, n as preset, r as TYPESET_TAG, t as criticCss } from "./src-PkmYCSNu.mjs";
3
3
  export { TYPESET_TAG, criticCss, preset, typeset, typesetCss };
package/dist/react.d.mts CHANGED
@@ -1,8 +1,8 @@
1
- import { a as criticCss, n as Preset, r as PresetOptions } from "./index-jkwx6SyG.mjs";
1
+ import { a as criticCss, n as Preset, r as PresetOptions } from "./index-Dbqxt4Qg.mjs";
2
2
  import { ComponentType } from "react";
3
3
  //#region src/react.d.ts
4
4
  type AnyComponent = ComponentType<any>;
5
- /** Every plugin with components for `@comark/react`, which leaves out annotate, diff, and quiz. */
5
+ /** Every plugin with components for `@comark/react`, which leaves out annotate, comments, diff, and quiz. */
6
6
  declare const _default: (options?: PresetOptions) => Preset<AnyComponent>;
7
7
  //#endregion
8
8
  export { type PresetOptions, criticCss, _default as default };
package/dist/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
2
- import { n as preset, t as criticCss } from "./src-DROB05U6.mjs";
2
+ import { n as preset, t as criticCss } from "./src-PkmYCSNu.mjs";
3
3
  import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-Bjd6VcGQ.mjs";
4
4
  import { createElement, useEffect } from "react";
5
5
  import * as badge from "comark-badge/react";
@@ -57,7 +57,7 @@ const typeset = () => createElement("style", {
57
57
  "data-comark-typeset": "",
58
58
  dangerouslySetInnerHTML: { __html: typesetCss }
59
59
  });
60
- /** Every plugin with components for `@comark/react`, which leaves out annotate, diff, and quiz. */
60
+ /** Every plugin with components for `@comark/react`, which leaves out annotate, comments, diff, and quiz. */
61
61
  var react_default = (options) => preset({
62
62
  badge,
63
63
  cards,
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: before-after-evidence
3
+ description: Shows a visible change as two comparable screenshots, before and after, with marks measured from the source and checked in the rendered page. Use when a pull request, bug fix, or design change alters what people see, or when someone asks for before/after screenshots or visual evidence.
4
+ license: MIT
5
+ ---
6
+
7
+ # Before/after evidence
8
+
9
+ A before/after is evidence when a reviewer can check every claim against the two images and nothing else. Three things make it so, and the reader can't repair any of them: two captures of the same view, marks that sit on what changed, and notes in the words the screen shows.
10
+
11
+ This skill writes the document. Where it's published, and who can open it, is up to your host.
12
+
13
+ ## 1. Capture a comparable pair
14
+
15
+ Take **after** from the branch or its preview, and **before** from a deployment or commit that still behaves the old way. Capture both with the same viewport, zoom, theme, data, scroll position, and open menus, so the change is the only difference.
16
+
17
+ - Set up the state that shows the change before you capture: the long name that wrapped, the error that stayed hidden, the empty list.
18
+ - Clip both captures to one region around the change, the same rectangle on each side. A `::compare` gives each image under half the page width, where a full-page capture's text is too small to read.
19
+ - Pin anything that moves the layout between the two states, such as a dialog centered vertically that grows by one line, so the same element sits at the same place in both.
20
+
21
+ If one side can't be captured comparably, stop and say what blocked you. A description in place of a screenshot, or one side alone, isn't before/after evidence.
22
+
23
+ Done when both images exist, have the same size, and differ only where the change is.
24
+
25
+ ## 2. Measure every mark
26
+
27
+ Marks take `x`, `y`, `w`, and `h` as percentages of the image. Measure them: an estimate drifts a few percent, and on a half-width image that's the neighbouring button.
28
+
29
+ - **A page you captured:** read the element's box in the same browser, viewport, and state, minus the clip's offset. [references/measuring.md](references/measuring.md) has the snippet.
30
+ - **A still you didn't capture**, such as a frame from a screen recording, a design export, or an image attached to a bug report: there is no DOM to ask. Measure in image pixels and divide by the image's natural width and height. Confirm each box by cropping the image to it and looking at the crop, as the same reference shows.
31
+
32
+ Mark the change at its own size: the field, the label, the button. Use `red` on the before side for the problem and `green` on the after side for the fix, and leave what stayed the same unmarked. Two or three marks a side usually tell the whole story.
33
+
34
+ Done when every mark has numbers you measured, and you can name the element or the pixel rectangle each came from.
35
+
36
+ ## 3. Write the document
37
+
38
+ Start from [references/example.md](references/example.md) and keep its shape:
39
+
40
+ 1. One sentence: what changed, and for whom.
41
+ 2. One line of steps to reach the screen, so a reviewer can reproduce it.
42
+ 3. The `::compare`, each note a few words in the screen's own words. A value the screenshot doesn't show stays out of its note.
43
+ 4. A `::diff` of the line that caused it, only when the reader would otherwise ask.
44
+
45
+ The images carry the evidence, so the text only points into them. How the work went (the checks you ran, retries, CI) goes in your report to the user, not in the document. [references/blocks.md](references/blocks.md) has the syntax of each block.
46
+
47
+ Image `src` must be a URL the renderer can reach: a path next to the document, or an https URL your host gives you for the upload.
48
+
49
+ ## 4. Check the render
50
+
51
+ 1. Lint it, and fix until it reports no problems:
52
+
53
+ ```sh
54
+ vp dlx --package comark-linter --package comark-ui lint-comark evidence.md
55
+ ```
56
+
57
+ These commands use the [global Vite+ CLI](https://viteplus.dev/guide/global-cli). Without it, `pnpm dlx` takes the same arguments.
58
+
59
+ 2. Open the page where it will be read and take a screenshot of it. For a local screenshot-only check, copy the complete `::compare` block, including its nested screenshots and annotations, into `evidence-screenshots.md` beside `evidence.md`.
60
+
61
+ Ensure comark-annotate is a direct dependency of this workspace package. If it is only transitive, read the installed Comark UI version and add that exact version with the project's package manager: for pnpm, `pnpm add -D comark-annotate@<version>`, substituting that version. Add `-w` when installing at the workspace root. With no Comark UI packages installed, omit `@<version>`. This links the executable without fetching a different renderer release via `@latest`. Then run:
62
+
63
+ ```sh
64
+ vp exec comark-annotate render evidence-screenshots.md -o evidence-screenshots.html
65
+ ```
66
+
67
+ Without Vite+, use `pnpm exec` with the same arguments.
68
+
69
+ Open `evidence-screenshots.html` and take a screenshot of it. This CLI renders annotate, carousel, and zoom only; check any `::diff` or other blocks in the host's full Comark UI preview.
70
+ 3. Check that each mark sits on its target and each note matches what its side shows. Measure a mark that misses again, from the source, since a box moved by eye stays a guess.
71
+
72
+ Done when you've looked at the rendered page and every mark lands. Name any mark or block you couldn't check in your report.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Before/after evidence"
3
+ short_description: "Show a visible change with comparable, measured screenshots"
@@ -0,0 +1,54 @@
1
+ # Blocks for before/after evidence
2
+
3
+ These blocks come with `comark-ui`, or alone with `comark-annotate` and `comark-diff`. The comark-ui skill (`vp dlx skills add onmax/comark-ui`) documents every block in full.
4
+
5
+ ## `::compare`
6
+
7
+ Two `:::screenshot` blocks inside, before then after. They're titled Before and After unless they set `title`, and their notes are numbered across both.
8
+
9
+ | Prop | What it does |
10
+ | --- | --- |
11
+ | `title` | Names the comparison for screen readers. |
12
+ | `mode` | `columns` (default) side by side, or `slider`: one full-width image with a divider to drag. Use `slider` when the change is small and in the same spot, such as one label. |
13
+
14
+ ## `:::screenshot`
15
+
16
+ | Prop | What it does |
17
+ | --- | --- |
18
+ | `src` | Required. The image URL. |
19
+ | `alt` | Required. What the image shows, in one phrase. |
20
+ | `viewport` | The capture's size, like `640x420`. Set the same on both sides; a compare reports a mismatch. |
21
+ | `href` | The page you captured, such as the preview deployment, linked by its host above the image. |
22
+ | `title` | A heading above the image, in place of Before or After. |
23
+
24
+ Other Markdown inside the block, after its annotations, becomes the caption.
25
+
26
+ ## `::::annotation`
27
+
28
+ A direct child of a screenshot. Its content is the note, in Markdown.
29
+
30
+ | Prop | What it does |
31
+ | --- | --- |
32
+ | `x` `y` `w` `h` | Required. The marked rectangle as percentages of the image, without `%`. |
33
+ | `mark` | `box` (default), `circle`, `underline`, `highlight`, `bracket`, `cross`, `check`, or `none`. |
34
+ | `color` | `red` for a problem, `green` for the fix, `amber` for a warning, `violet` (default) to point something out, `gray`, or a hex color. |
35
+ | `side` | `top` or `bottom` moves the note into the margin with an arrow; in a side-by-side compare, `left` and `right` notes join the numbered list. |
36
+
37
+ Notes without a `side` are numbered under their image, which is the clearest default in a compare.
38
+
39
+ ## `::diff`
40
+
41
+ Two fenced blocks inside, the file before and after, starting at the same line.
42
+
43
+ ````md
44
+ ::diff{line=42 label="The change"}
45
+ ```tsx [src/members/InviteDialog.tsx]
46
+ <button onClick={send}>Send invite</button>
47
+ ```
48
+ ```tsx
49
+ <button onClick={send} disabled={invalid}>Send invite</button>
50
+ ```
51
+ ::
52
+ ````
53
+
54
+ `line` is the file's line number of the first line in both blocks. Keep a diff to the few lines that caused the change; the pull request has the rest.
@@ -0,0 +1,37 @@
1
+ The invite dialog now says what's wrong with an address, and waits for a valid one before it sends.
2
+
3
+ Open **Settings › Members**, select **Invite**, type `maria@acme`, and press Tab.
4
+
5
+ ::compare{title="Invite dialog with an incomplete address"}
6
+ :::screenshot{src="https://comark-ui.onmax.me/skill-invite-before.png" alt="Invite dialog with a red email field and no message" viewport="640x420"}
7
+ ::::annotation{x=8.3 y=42.9 w=60.6 h=12.8 color="red"}
8
+ Only the border turns red. Nothing says what to fix.
9
+ ::::
10
+
11
+ ::::annotation{x=72 y=63.5 w=19.7 h=11.5 color="red"}
12
+ **Send invite** still sends to `maria@acme`.
13
+ ::::
14
+ :::
15
+
16
+ :::screenshot{src="https://comark-ui.onmax.me/skill-invite-after.png" alt="Invite dialog with an error under the email field and a disabled Send invite button" viewport="640x420" href="https://pr-218.preview.acme.example/settings/members"}
17
+ ::::annotation{x=8.3 y=58.3 w=49 h=5.8 mark="underline" color="green"}
18
+ "Add the domain, like maria@acme.com"
19
+ ::::
20
+
21
+ ::::annotation{x=72 y=71.8 w=19.7 h=11.5 color="green"}
22
+ **Send invite** waits for a valid address.
23
+ ::::
24
+ :::
25
+ ::
26
+
27
+ ::diff{line=42 label="The change"}
28
+ ```tsx [src/members/InviteDialog.tsx]
29
+ <input id="email" aria-invalid={invalid} value={email} onChange={edit} />
30
+ <button className="primary" onClick={send}>Send invite</button>
31
+ ```
32
+ ```tsx
33
+ <input id="email" aria-invalid={invalid} aria-describedby="email-error" value={email} onChange={edit} />
34
+ {invalid && <p id="email-error" className="error">Add the domain, like {suggestion}</p>}
35
+ <button className="primary" onClick={send} disabled={invalid}>Send invite</button>
36
+ ```
37
+ ::
@@ -0,0 +1,50 @@
1
+ # Measuring marks
2
+
3
+ Every mark is `x y w h`, each a percentage of the image from 0 to 100. Measure in the same coordinates the image was captured in.
4
+
5
+ ## A page you captured
6
+
7
+ Capture both states with one clip, then read each element's box in the same page, before anything moves:
8
+
9
+ ```js
10
+ // Playwright: one clip for both sides, so the same element sits at the same place.
11
+ const clip = { x: 56, y: 12, width: 528, height: 312 };
12
+ await page.screenshot({ path: "after.png", clip });
13
+
14
+ const box = await page.$eval("#email", (el, clip) => {
15
+ const r = el.getBoundingClientRect();
16
+ return [
17
+ ((r.x - clip.x) / clip.width) * 100,
18
+ ((r.y - clip.y) / clip.height) * 100,
19
+ (r.width / clip.width) * 100,
20
+ (r.height / clip.height) * 100,
21
+ ].map((v) => +v.toFixed(1));
22
+ }, clip);
23
+ // box is [x, y, w, h] for the annotation.
24
+ ```
25
+
26
+ - Without a clip, divide by `innerWidth` and `innerHeight` and subtract nothing.
27
+ - For a full-page capture, add `scrollX` and `scrollY` to the box and divide by `document.documentElement.scrollWidth` and `scrollHeight`.
28
+ - An element that spans its container, such as a block paragraph, measures as wide as the container. Measure the text instead: give it `width: fit-content` while you capture, or measure a `Range` around its text.
29
+ - If an element crosses the clip's edge, cut its box at the edge, so every value stays between 0 and 100.
30
+
31
+ ## A still you didn't capture
32
+
33
+ A frame from a screen recording, a design export, or an image attached to an issue has no DOM. Measure in pixels:
34
+
35
+ 1. Read the image's natural size, such as with `magick identify frame.png` or `file frame.png`.
36
+ 2. Find the target's rectangle in pixels in an image viewer that shows the pointer's coordinates.
37
+ 3. Divide: `x = left / width * 100`, `y = top / height * 100`, `w = boxWidth / width * 100`, `h = boxHeight / height * 100`.
38
+ 4. Crop the image to that rectangle and look at the crop. It should show the target and little else:
39
+
40
+ ```sh
41
+ magick frame.png -crop 320x38+44+134 +repage crop.png
42
+ ```
43
+
44
+ With Python and Pillow, `Image.open("frame.png").crop((44, 134, 364, 172)).save("crop.png")` does the same.
45
+
46
+ For a pair of video frames, take both at the same playback size and use the same crop for both, as you would clip two page captures.
47
+
48
+ ## In the render
49
+
50
+ The rendered page is the last check. A mark that misses there has the wrong numbers: measure it again from the source.
@@ -15,6 +15,8 @@ Prefer `comark-ui`, every plugin in one install, registered in the order they ne
15
15
  pnpm add comark @comark/html comark-ui
16
16
  ```
17
17
 
18
+ Run the comark CLIs with the [global Vite+ CLI](https://viteplus.dev/guide/global-cli): `vp dlx` for a package the project doesn't install, `vp exec` for one it does. Without Vite+, `pnpm dlx` and `pnpm exec` take the same arguments.
19
+
18
20
  ```ts
19
21
  import { createHtmlRenderer } from "@comark/html";
20
22
  import comarkUi from "comark-ui/html";
@@ -43,6 +45,10 @@ Either way, the HTML renderer inlines the styles once per document, and the Vue,
43
45
 
44
46
  Problems land in `meta.issues` after `parseMarkdown`, one sentence each, and a host can refuse a document while any remain. For untrusted input, add Comark's `security` plugin with `blockedTags` such as `script`, `style`, and `iframe`; it cleans the Markdown inside blocks too.
45
47
 
48
+ ## Shape the document
49
+
50
+ This skill teaches the blocks. Four sibling skills teach a whole document, what goes in it and in what order: `before-after-evidence`, `review-document`, `visual-explanation`, and `teach-lesson`. When the user asks for one of those and the skill isn't installed, suggest `vp dlx skills add onmax/comark-ui` and picking it from the list.
51
+
46
52
  ## Pick the blocks
47
53
 
48
54
  Read the reference for each block you use before you write it. Mix the blocks freely with ordinary Markdown, such as headings, tables, and lists.
@@ -59,6 +65,7 @@ Read the reference for each block you use before you write it. Mix the blocks fr
59
65
  | Links and `@mentions` with the site's favicon or the person's avatar | links, `@user`, `:chip` | [chips](references/chips.md) |
60
66
  | Colors, such as design tokens before and after, or a palette | a color in inline code, `::palette` | [colors](references/colors.md) |
61
67
  | A suggested change to one file, before and after | `::diff` | [diff](references/diff.md) |
68
+ | Suggested edits and comment threads on prose, such as a copy review, each note in a card in the margin | `:suggestion`, `:comment` | [comments](references/comments.md) |
62
69
  | A video, a design file, or a live reproduction from its link | `::embed` | [embed](references/embed.md) |
63
70
  | A screen recording or a sound that a screenshot can't show | `![alt](clip.mp4)` | [media](references/media.md) |
64
71
  | A number before and after, a table of befores and afters, or progress | `:delta`, `::stats`, `:progress` | [metrics](references/metrics.md) |
@@ -134,5 +141,5 @@ Mark only what your text talks about. Each note says what changed or what is wro
134
141
 
135
142
  ## Check the result
136
143
 
137
- 1. Lint the file: `npx lint-comark report.md`, in a project with `comark-linter` and the plugins installed, where `comark-ui` counts as every plugin. It prints one line per problem with its line, its rule, and how to fix it, and exits with 1 while any remain. Fix every one, and run it again until it exits with 0. The [lint reference](references/lint.md) covers its flags, its config file, the library, ESLint, and Vite Doctor.
144
+ 1. Lint the file: `vp exec lint-comark report.md`, in a project with `comark-linter` and the plugins installed, where `comark-ui` counts as every plugin. It prints one line per problem with its line, its rule, and how to fix it, and exits with 1 while any remain. Fix every one, and run it again until it exits with 0. The [lint reference](references/lint.md) covers its flags, its config file, the library, ESLint, and Vite Doctor.
138
145
  2. Look at the render, made as the block's reference says. Open it in a browser and take a screenshot: each mark sits on its target, each number matches its note, and each chart says what your text says. If you can't verify something, say so when you hand the document over instead of guessing.
@@ -53,13 +53,15 @@ For a clipped capture, `getBoundingClientRect()` and Playwright's main-frame `cl
53
53
 
54
54
  ## Preview
55
55
 
56
+ Ensure comark-annotate is a direct dependency of the workspace package where you run the CLI. If it is only transitive, read the installed Comark UI version and add that exact version with the project's package manager: for pnpm, `pnpm add -D comark-annotate@<version>`, substituting that version. Add `-w` when installing at the workspace root. With no Comark UI packages installed, omit `@<version>`. This links the executable without fetching a different renderer release via `@latest`.
57
+
56
58
  ```sh
57
- npx comark-annotate render report.md -o report.html
59
+ vp exec comark-annotate render report.md -o report.html
58
60
  ```
59
61
 
60
- `render` writes a standalone page of the screenshots and compares, and relative `src` paths resolve next to it. Without comark-linter, `npx comark-annotate check report.md` lists these blocks' problems on its own. It names each one like `screenshot "after.png", annotation 2`, counting annotations within their screenshot, and exits with 1 while any remain.
62
+ `render` writes a standalone page of the screenshots and compares, and relative `src` paths resolve next to it. Without comark-linter, `vp exec comark-annotate check report.md` lists these blocks' problems on its own. It names each one like `screenshot "after.png", annotation 2`, counting annotations within their screenshot, and exits with 1 while any remain.
61
63
 
62
64
  ## Register
63
65
 
64
66
  - In `comark-ui`: on, in `comark-ui/html` only.
65
- - Alone: `pnpm add comark @comark/html comark-annotate`, then `createHtmlRenderer({ plugins: [annotate()], components })` with both from `comark-annotate/html`. It renders only to HTML and takes no options. Each document inlines the styles once, with style attributes for positions, so a strict Content Security Policy needs `style-src 'unsafe-inline'`.
67
+ - Alone: `pnpm add comark @comark/html comark-annotate`, then `createHtmlRenderer({ plugins: [annotate()], components })` with both from `comark-annotate/html`. It renders only to HTML. Screenshots reserve their `viewport`, or dimensions from a data URI. `annotate({ size: true })` also fetches remote screenshots at render time; enable it only for trusted documents, since requests follow redirects and can reach private networks. `annotate({ size: false })` disables measurement while retaining authored viewports. In `comark-ui`, put these options under `annotate`. Each document inlines the styles once, with style attributes for positions, so a strict Content Security Policy needs `style-src 'unsafe-inline'`.
@@ -0,0 +1,43 @@
1
+ # Suggestions and comments
2
+
3
+ comark-comments draws suggested edits and comments on a span of prose, each with its note in a card in the margin beside its line, as in a Google Docs or Word review. Use it to review copy or a docs page, where each change needs a reason and a comment can get a reply. For marks with no notes or margin, which a host can also accept or reject, use [critic](critic.md). For a change to code, write a `::diff`.
4
+
5
+ ## Syntax
6
+
7
+ ```md
8
+ To :suggestion[sign in]{from="log in" by="Claude" note="The button says Sign in."}, enter your email.
9
+
10
+ :suggestion[]{from="simply" by="Claude" note="“Simply” tells a stuck reader it should have been easy."} Enter the email you signed up with.
11
+
12
+ The link expires after :comment[15 minutes]{#ttl by="@onmax" note="Is 15 minutes enough when email is slow?"}.
13
+
14
+ :comment{reply="ttl" by="Claude" note="Keeping 15. The email has a resend button."}
15
+ ```
16
+
17
+ | Write | Is |
18
+ | --- | --- |
19
+ | `:suggestion[new]{from="old"}` | A replacement: `old` struck, `new` inserted |
20
+ | `:suggestion[]{from="old"}` or `:suggestion{from="old"}` | A deletion |
21
+ | `:suggestion[new]` | An insertion |
22
+ | `:comment[words]{note="…"}` | A comment on `words` |
23
+ | `:comment{reply="id" note="…"}` | A reply to the comment or suggestion with `#id` |
24
+
25
+ | Prop | On | What it does |
26
+ | --- | --- | --- |
27
+ | `from` | suggestion | The text it replaces or deletes. Plain text. |
28
+ | `by` | both, reply | The author, shown as written; a leading `@` is dropped. The avatar is the name's initial. |
29
+ | `note` | both, reply | The card's text. Plain text. A suggestion with no note and no replies is only a redline, with no card. |
30
+ | `mark` | suggestion | `track` (default): red strike, green insert. `pen`: a hand-drawn strike with the new words handwritten above. |
31
+ | `#id` | both | The id a reply points at. Comments and suggestions share one id space. |
32
+ | `reply` | reply | The `#id` of the thread it joins. |
33
+
34
+ - A reply can sit anywhere in the document; it moves into its thread, and the paragraph it leaves empty is dropped. Put replies after the paragraph, or at the end.
35
+ - Pen fits short changes only: both sides 24 characters or fewer, on one line, and plain text. Anything else draws as `track`, with no issue.
36
+ - Write suggestions and comments in paragraphs and list items. In a heading or a table cell, and inside another one, they draw without a card, and the linter reports them.
37
+ - On a page 50rem wide or more, each card sits in the right margin level with its line. On a narrow page the reader clicks a bubble to open its card under the line.
38
+ - Don't overlap two anchors. Mark the words once, and reply to the thread.
39
+
40
+ ## Register
41
+
42
+ - In `comark-ui`: on, as `comments`, in `comark-ui/html` only. `comments: { mark: "pen" }` draws every suggestion with the pen unless it sets its own `mark`.
43
+ - Alone: `pnpm add comark @comark/html comark-comments`, then `createHtmlRenderer({ plugins: [comments()], components })` with `comments, { components }` from `comark-comments/html`. There are no Vue, React, or Svelte entries.
@@ -5,7 +5,7 @@ comark-linter reports what Comark forgives, such as a `::` block never closed, a
5
5
  ## Run the command
6
6
 
7
7
  ```sh
8
- npx lint-comark "docs/**/*.md" --format json --check-urls
8
+ vp exec lint-comark "docs/**/*.md" --format json --check-urls
9
9
  ```
10
10
 
11
11
  - Pass files or globs. Quote a glob so the shell passes it as written; lint-comark expands it and skips `node_modules` and `dist`.
@@ -12,7 +12,7 @@ The spinner now stops once the build finishes:
12
12
 
13
13
  - A `src` that ends in `.mp4`, `.webm`, `.mov`, or `.m4v`, before any `?` or `#`, plays as a video, and one in `.mp3`, `.wav`, `.ogg`, or `.m4a` as audio. Every other image stays an image.
14
14
  - The alt text is required. It names the player for screen readers, and it's the text of a link to the file where the browser can't play it.
15
- - `autoplay`, `loop`, and `muted` are off by default. Browsers start a file by itself only when it's muted, so `autoplay` needs `muted`. `poster="frame.png"` shows an image before a video plays; audio takes none. `{loop=false}` turns a prop off. Other props, such as `width` or a class, are dropped and reported.
15
+ - `autoplay`, `loop`, and `muted` are off by default. Browsers start a file by itself only when it's muted, so `autoplay` needs `muted`. `poster="frame.png"` shows an image before a video plays; audio takes none. `{loop=false}` turns a prop off. A video's `width` and `height` must be paired whole pixel dimensions, like `{width=1280 height=720}`, to reserve its room before loading. Audio takes neither. Other props, such as a class, are dropped and reported.
16
16
  - A GitHub attachment link, `https://github.com/user-attachments/assets/<id>`, plays as a video when the bare URL sits alone in its paragraph, as on GitHub. With text of its own, or inside a sentence, it stays a link.
17
17
 
18
18
  ## Record the evidence
@@ -25,5 +25,5 @@ The spinner now stops once the build finishes:
25
25
 
26
26
  ## Register
27
27
 
28
- - In `comark-ui`: on. `media: { controls: "native" }` keeps the browser's own controls and leaves the script out.
28
+ - In `comark-ui`: on. `media: { controls: "native" }` keeps the browser's own controls and leaves the script out. Measurement reads only data URIs by default. `media: { size: true }` also measures remote videos while parsing; use it only for trusted documents, since requests follow redirects and can reach private networks. `media: { size: false }` disables measurement. Authored dimensions work with any setting. Alone, pass the same options to `media()`.
29
29
  - Alone: `pnpm add comark @comark/html comark-media`, then `createHtmlRenderer({ plugins: [media()], components })` with both from `comark-media/html`. `comark-media/vue`, `/react`, and `/svelte` render it too. Its place in `plugins` doesn't matter. It reads the tokens before any plugin sees the tree, so comark-zoom never zooms a video and comark-chips never turns an attachment link into a chip. The HTML renderer inlines the players' script once per document, and the browser's own controls show until it runs and whenever a file can't play.
@@ -37,7 +37,7 @@ Promise callbacks are microtasks, and a timer's callback is a task.
37
37
 
38
38
  A question uses radios when one option is marked `[x]`; `multiple` makes it a checkbox question. Options are shuffled deterministically unless `shuffle="false"`. Questions and recall cards share numbering, while polls are not numbered. A missing id is derived from the block content. Use `#hint` and `#explain` on questions, and `#answer` on recall cards. Polls accept `multiple` and a date in `closes`.
39
39
 
40
- The HTML renderer inlines the quiz script and styles. Hosts can listen for `comark-quiz:answer` and `comark-quiz:ready`, and restore answers or results with `comark-quiz:state`; see the package [getting started guide](../../packages/quiz/docs/getting-started.md) for the event shapes and storage behavior.
40
+ The HTML renderer inlines the quiz script and styles. Hosts can listen for `comark-quiz:answer` and `comark-quiz:ready`, and restore answers or results with `comark-quiz:state`; see [Saving answers](https://comark-ui.onmax.me/quiz/#saving-answers) on its page for the event shapes and storage behavior.
41
41
 
42
42
  ## Register
43
43
 
@@ -22,7 +22,8 @@ comarkUi({
22
22
 
23
23
  - Every plugin except `include`, which reads files from disk. `include: { root, base }` turns it on, or `{ read }` in a browser or an edge runtime. `include: true` reads under the working directory.
24
24
  - `cast`, `table`, and `tests` get a reader that finds no file. Inline casts, CSV fences, and pasted JUnit reports render, and a `src` file doesn't load until you pass `{ root }` or `{ read }`. Any other options object replaces that reader too, so the plugin reads under the working directory.
25
- - Three plugins use the network while parsing: `chips` downloads favicons and avatars, `icon` fetches icon sets from the Iconify API, and `diagrams` sends each diagram's source to `https://kroki.io`. In a browser, pass `chips: { download: false }`, since a browser may not download another site's images. To parse offline, also pass `icon: { fetch: false }` with your icon sets in `collections`, and `diagrams: { draw: false }`. To keep diagram sources private, pass your own Kroki `server`.
25
+ - By default, three plugins use the network while parsing: `chips` downloads favicons and avatars, `icon` fetches icon sets from the Iconify API, and `diagrams` sends each diagram's source to `https://kroki.io`. In a browser, pass `chips: { download: false }`, since a browser may not download another site's images. To parse offline, also pass `icon: { fetch: false }` with your icon sets in `collections`, and `diagrams: { draw: false }`. To keep diagram sources private, pass your own Kroki `server`.
26
+ - Media sizing reads only data URIs by default. `zoom: { size: true }` and `media: { size: true }` also fetch remote files while parsing; `annotate: { size: true }` does so at HTML render time. Enable these only for trusted documents, since requests follow redirects and can reach private networks. `{ size: false }` disables measurement for each plugin; authored dimensions and screenshot viewports still work.
26
27
  - `icon`'s options apply to every icon the preset draws, in cards, details, file trees, tabs, and code actions too.
27
28
  - comark-critic has no component to add its styles, so put `criticCss`, which every entry exports, in the page.
28
29
  - The text around the blocks is styled by default, scoped to the element the document renders into, on the same `--comark-*` tokens. Pass `typeset: false` when the site already styles Markdown, such as with Tailwind Typography's `prose`.
@@ -36,7 +37,7 @@ comarkUi({
36
37
  | `@comark/react` | `comark-ui/react` |
37
38
  | `@comark/svelte` | `comark-ui/svelte` |
38
39
 
39
- The Vue, React, and Svelte entries leave out annotate and diff, which render only to HTML. Screenshots, compares, diffs, and the marks on charts need `comark-ui/html`.
40
+ The Vue, React, and Svelte entries leave out annotate, comments, diff, and quiz, which render only to HTML. Screenshots, compares, diffs, suggestions and comments, and the marks on charts need `comark-ui/html`.
40
41
 
41
42
  To use another renderer, install it in place of `@comark/html`, such as `pnpm add comark @comark/vue comark-ui`, and pass it `plugins` and `components` as you would one package's. The preset needs Vue 3.5, React 18, or Svelte 5.20 or later. Every entry exports the `PresetOptions` type, and `comark-ui` itself exports `preset(renderer, options)`, which builds the same pair from renderer entries you pass.
42
43
 
@@ -31,5 +31,5 @@ The zoomed view fires `comark-zoom:open`, `comark-zoom:change`, and `comark-zoom
31
31
 
32
32
  ## Register
33
33
 
34
- - In `comark-ui`: on, as `zoom`. `zoom: { images: false }` leaves images alone, so only `::zoom` blocks and elements marked `{data-zoom}` zoom.
34
+ - In `comark-ui`: on, as `zoom`. `zoom: { images: false }` leaves images alone, so only `::zoom` blocks and elements marked `{data-zoom}` zoom. Data URI images get dimensions by default; one authored pixel side gets the other at the file's ratio. Images with both sides or a side such as `50%` keep them. `zoom: { size: true }` also measures remote images while parsing; use it only for trusted documents, since requests follow redirects and can reach private networks. `zoom: { size: false }` disables measurement. Alone, pass the same options to `zoom()`.
35
35
  - Alone: `pnpm add comark @comark/html comark-zoom`, then `createHtmlRenderer({ plugins: [zoom()], components })` with both from `comark-zoom/html`. `comark-zoom/vue`, `/react`, and `/svelte` export the same pair for those renderers. The plugin adds a node that starts the script, so pass the components even when only images zoom. A page with no renderer calls `start()` from `comark-zoom/client` once. comark-annotate and comark-carousel zoom their own blocks without it, so register `zoom()` for images and `::zoom`.