comark-ui 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -54,7 +54,7 @@ Vue, React, and Svelte leave out annotate and diff, which render only to HTML.
54
54
  | `fromMarkdown`, `toMarkdown` | Convert between Markdown and the editor's JSON with Comark's own parser and writer. Front matter stays beside the document. |
55
55
  | `checkMarkdown(markdown, { plugins })` | Whether the editor can save the document without changing what readers see, judged with the plugins the page renders with. A document it can't keep opens read-only. |
56
56
  | `Upload` | Uploads pasted and dropped files. Each shows a placeholder with its progress until `upload(file, progress)` returns its URL. |
57
- | `createEditorCodec({ plugins })` | `{ extensions, fromMarkdown, toMarkdown, verify }` for a collaborative room, such as ViteHub Realtime's `editor` option. |
57
+ | `createEditorCodec({ plugins })` | `{ extensions, split, join, fromMarkdown, toMarkdown, verify }` for a collaborative room, such as ViteHub Realtime's `editor` option. A room keeps the body as the editor's document and the frontmatter as its fields, so a text edit never overwrites the metadata. |
58
58
 
59
59
  `comark-ui/editor/vue` binds the editor to a Markdown string with `useComarkEditor(markdown)` or `<ComarkEditor v-model="markdown" />`. Run `pnpm playground` in this package to try it.
60
60
 
@@ -178,8 +178,6 @@ function addStyles(name, css) {
178
178
  style.textContent = css;
179
179
  document.head.append(style);
180
180
  }
181
- //#endregion
182
- //#region src/client.ts
183
181
  /**
184
182
  * comark-pele's diagrams on the shared `--comark-*` tokens, on a card like comark-diagrams'. Pele paints with its
185
183
  * `--pele-*` variables, so setting them on the wrapper themes every diagram, light and dark, without drawing it
@@ -187,8 +185,17 @@ function addStyles(name, css) {
187
185
  */
188
186
  const mermaidCss = `@layer comark{${[
189
187
  theme(".cmm,.pele-diagram"),
190
- theme(":root", "status"),
191
- `:where(:root){--pele-bg:var(--_comark-background);--pele-fg:var(--_comark-foreground);--pele-muted:var(--_comark-muted);--pele-line:var(--_comark-muted);--pele-surface:var(--_comark-accent);--pele-surface-alt:color-mix(in srgb,var(--_comark-accent),var(--_comark-foreground) 6%);--pele-border:var(--_comark-border);--pele-accent:var(--ui-primary,var(--primary,var(--_comark-foreground)));--pele-critical:var(--_comark-danger);--pele-error:var(--_comark-danger);--pele-font:var(--_comark-font);--pele-radius:calc(var(--_comark-radius) - 4px);--pele-series-1:var(--comark-chart-1,var(--ui-chart-1,var(--chart-1,#3b82f6)));--pele-series-2:var(--comark-chart-2,var(--ui-chart-2,var(--chart-2,#f97316)));--pele-series-3:var(--comark-chart-3,var(--ui-chart-3,var(--chart-3,#10b981)));--pele-series-4:var(--comark-chart-4,var(--ui-chart-4,var(--chart-4,#a855f7)));--pele-series-5:var(--comark-chart-5,var(--ui-chart-5,var(--chart-5,#f43f5e)));--pele-series-6:var(--comark-chart-6,var(--ui-chart-6,var(--chart-6,#eab308)));--pele-series-7:var(--comark-chart-7,var(--ui-chart-7,var(--chart-7,#06b6d4)));--pele-series-8:var(--comark-chart-8,var(--ui-chart-8,var(--chart-8,#ec4899)))}`,
188
+ theme(":root", "status", "primary"),
189
+ `:where(:root){--pele-bg:var(--_comark-background);--pele-fg:var(--_comark-foreground);--pele-muted:var(--_comark-muted);--pele-line:var(--_comark-muted);--pele-surface:var(--_comark-accent);--pele-surface-alt:color-mix(in srgb,var(--_comark-accent),var(--_comark-foreground) 6%);--pele-border:var(--_comark-border);--pele-accent:var(--_comark-primary);--pele-critical:var(--_comark-danger);--pele-error:var(--_comark-danger);--pele-font:var(--_comark-font);--pele-radius:calc(var(--_comark-radius) - 4px);${[
190
+ "#3b82f6",
191
+ "#f97316",
192
+ "#10b981",
193
+ "#a855f7",
194
+ "#f43f5e",
195
+ "#eab308",
196
+ "#06b6d4",
197
+ "#ec4899"
198
+ ].map((hex, index) => `--pele-series-${index + 1}:var(--comark-chart-${index + 1},var(--ui-chart-${index + 1},var(--chart-${index + 1},${hex})))`).join(";")}}`,
192
199
  ".pele-diagram{box-sizing:border-box;margin-block:1.5rem;padding:1rem;overflow-x:auto;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-background);color:var(--_comark-foreground);text-align:center}",
193
200
  ".pele-diagram>svg{min-width:min(28rem,var(--cmm-svg,28rem))}",
194
201
  ".pele-source{margin:0;color:var(--_comark-muted);text-align:left}",
package/dist/client.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
2
- import { i as startMermaid, n as mermaidBar, r as mermaidCss, t as addMermaidStyles } from "./client-BhzMlpun.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
+ import { i as startMermaid, n as mermaidBar, r as mermaidCss, t as addMermaidStyles } from "./client-kUEiRbtp.mjs";
3
3
  export { addMermaidStyles, mermaidBar, mermaidCss, startMermaid, typesetCss };
@@ -9,7 +9,8 @@ import Image from "@tiptap/extension-image";
9
9
  import { TaskItem, TaskList } from "@tiptap/extension-list";
10
10
  import { TableKit } from "@tiptap/extension-table";
11
11
  import StarterKit from "@tiptap/starter-kit";
12
- import { renderMarkdown } from "comark/render";
12
+ import { parseFrontmatter } from "comark/parse";
13
+ import { renderFrontmatter, renderMarkdown } from "comark/render";
13
14
  import { indent, textContent } from "comark/utils";
14
15
  import { Plugin, PluginKey } from "@tiptap/pm/state";
15
16
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
@@ -1054,16 +1055,90 @@ async function fromMarkdown(markdown) {
1054
1055
  async function toMarkdown(doc, options = {}) {
1055
1056
  const markdown = await renderMarkdown({
1056
1057
  nodes: writeBlocks(doc.content),
1057
- frontmatter: options.frontmatter ?? {},
1058
+ frontmatter: {},
1058
1059
  meta: {}
1059
1060
  }, writeOptions);
1060
- return markdown.trim() ? `${markdown.trimEnd()}\n` : "";
1061
+ return withFrontmatter(markdown.trim() ? `${markdown.trimEnd()}\n` : "", options.frontmatter);
1062
+ }
1063
+ /** Finds the frontmatter block where Comark does, and says why a save wouldn't write it back. */
1064
+ function readFrontmatter(markdown) {
1065
+ if (!markdown.startsWith("---") || !markdown.includes("\n---")) return { kind: "none" };
1066
+ let read;
1067
+ try {
1068
+ read = parseFrontmatter(markdown);
1069
+ } catch (error) {
1070
+ return {
1071
+ kind: "lost",
1072
+ problem: `The frontmatter isn't valid YAML (${String(error instanceof Error && "reason" in error ? error.reason : error)}); fix it to edit the document.`,
1073
+ body: markdown.slice(markdown.indexOf("\n---") + 4).replace(/^(?:\r?\n)+/, "")
1074
+ };
1075
+ }
1076
+ const { data, frontmatterText: yaml } = read;
1077
+ const value = data;
1078
+ if (!(value instanceof Object) || Array.isArray(value)) return {
1079
+ kind: "lost",
1080
+ problem: `The frontmatter is a YAML ${Array.isArray(value) ? "list" : "scalar"}, which a save would rewrite; turn it into \`key: value\` lines to edit the document.`
1081
+ };
1082
+ const comments = hasComment(yaml);
1083
+ if (!Object.keys(data).length) return {
1084
+ kind: "lost",
1085
+ problem: comments ? "The frontmatter holds only YAML comments, which a save would drop; delete them or move them into the text to edit the document." : "The frontmatter block is empty, so a save wouldn't write it back; delete its `---` lines or add a `key: value` line to edit the document."
1086
+ };
1087
+ if (comments) return {
1088
+ kind: "lost",
1089
+ problem: "The frontmatter has YAML comments, which a save would drop; delete them or move them into the text to edit the document."
1090
+ };
1091
+ if (hasProtoKey(data)) return {
1092
+ kind: "lost",
1093
+ problem: "The frontmatter has a `__proto__` key, which the room's fields would drop on save; rename it to edit the document."
1094
+ };
1095
+ return {
1096
+ kind: "kept",
1097
+ body: read.content.replace(/^(?:\r?\n)+/, ""),
1098
+ fields: data
1099
+ };
1100
+ }
1101
+ function hasProtoKey(value, seen = /* @__PURE__ */ new Set()) {
1102
+ if (!(value instanceof Object) || seen.has(value)) return false;
1103
+ seen.add(value);
1104
+ return Object.hasOwn(value, "__proto__") || Object.values(value).some((child) => hasProtoKey(child, seen));
1105
+ }
1106
+ /**
1107
+ * Whether the YAML has a comment. A `#` after a space may sit inside a quoted value, so each candidate counts only when
1108
+ * cutting the line there leaves the same data.
1109
+ */
1110
+ function hasComment(yaml) {
1111
+ const lines = yaml.split("\n");
1112
+ const data = (text) => {
1113
+ try {
1114
+ return { value: parseFrontmatter(`---\n${text}\n---\n`).data };
1115
+ } catch {
1116
+ return;
1117
+ }
1118
+ };
1119
+ const original = data(yaml);
1120
+ return lines.some((line, index) => [...line.matchAll(/(?:^|\s)#/g)].some((match) => {
1121
+ const cut = [...lines];
1122
+ cut[index] = line.slice(0, match.index);
1123
+ const read = data(cut.join("\n"));
1124
+ return read !== void 0 && equal(read.value, original?.value);
1125
+ }));
1126
+ }
1127
+ /** Writes the frontmatter above the body. Comark's own writer leaves out the `---` lines when the body is empty. */
1128
+ function withFrontmatter(body, frontmatter = {}) {
1129
+ if (!Object.keys(frontmatter).length) return body;
1130
+ return `---\n${renderFrontmatter(frontmatter, "")}\n---\n${body ? `\n${body}` : ""}`;
1061
1131
  }
1062
1132
  /**
1063
1133
  * Whether the editor can open this Markdown and save it without changing what readers see. Pass the parse plugins the
1064
1134
  * page renders with, so syntax only they read, such as footnote definitions, counts too.
1065
1135
  */
1066
1136
  async function checkMarkdown(markdown, options = {}) {
1137
+ const frontmatter = readFrontmatter(markdown);
1138
+ if (frontmatter.kind === "lost") return {
1139
+ editable: false,
1140
+ reason: frontmatter.problem
1141
+ };
1067
1142
  const first = await fromMarkdown(markdown);
1068
1143
  if (first.lossy) return { editable: false };
1069
1144
  const written = await toMarkdown(first.doc, first);
@@ -1272,10 +1347,28 @@ function createEditorCodec(options = {}) {
1272
1347
  });
1273
1348
  return {
1274
1349
  extensions: () => [kit],
1275
- fromMarkdown: async (markdown) => (await fromMarkdown(markdown)).doc,
1350
+ async fromMarkdown(markdown) {
1351
+ const frontmatter = readFrontmatter(markdown);
1352
+ return (await fromMarkdown(frontmatter.kind === "lost" ? frontmatter.body ?? markdown : markdown)).doc;
1353
+ },
1276
1354
  toMarkdown: (doc) => toMarkdown(doc),
1355
+ split(markdown) {
1356
+ const frontmatter = readFrontmatter(markdown);
1357
+ return frontmatter.kind === "kept" ? {
1358
+ body: frontmatter.body,
1359
+ fields: frontmatter.fields
1360
+ } : {
1361
+ body: markdown,
1362
+ fields: {}
1363
+ };
1364
+ },
1365
+ join: ({ body, fields }) => withFrontmatter(body, fields),
1277
1366
  async verify(markdown) {
1278
- if (Object.keys((await fromMarkdown(markdown)).frontmatter).length) return { writable: false };
1367
+ const frontmatter = readFrontmatter(markdown);
1368
+ if (frontmatter.kind !== "none") return {
1369
+ writable: false,
1370
+ issues: [{ message: frontmatter.kind === "lost" ? frontmatter.problem : "The frontmatter is in the room's body, where a save would drop it; open the room with the codec's `split()` so it stays in the room's fields." }]
1371
+ };
1279
1372
  const check = await checkMarkdown(markdown, { plugins: options.plugins });
1280
1373
  return check.editable ? {
1281
1374
  writable: true,
@@ -1,4 +1,4 @@
1
- import { x as ComarkKitOptions } from "./editor-KD97dL_1.mjs";
1
+ import { S as ComarkKitOptions } from "./editor-yoVL7Ajq.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-ASZBrUYa.mjs";
1
+ import { c as fromMarkdown, l as toMarkdown, m as ComarkKit, s as checkMarkdown } from "./editor-9IcfELjG.mjs";
2
2
  import { Editor, EditorContent } from "@tiptap/vue-3";
3
3
  import { rowsOf } from "comark-properties";
4
4
  import { Properties } from "comark-properties/vue";
@@ -50,7 +50,7 @@ declare const ComarkKit: Extension<ComarkKitOptions, any>;
50
50
  interface EditorDocument {
51
51
  /** The document as Tiptap JSON, for `setContent` or a collaborative room. */
52
52
  doc: JSONContent;
53
- /** The front matter, which stays outside the editor: a collaborative room keeps only the body. */
53
+ /** The front matter, which stays outside the editor. A collaborative room keeps it beside the body, in its fields. */
54
54
  frontmatter: Record<string, unknown>;
55
55
  /** The Markdown holds something the editor can't keep, so writing it back would change more than the edit. */
56
56
  lossy: boolean;
@@ -61,11 +61,17 @@ declare function fromMarkdown(markdown: string): Promise<EditorDocument>;
61
61
  declare function toMarkdown(doc: JSONContent, options?: {
62
62
  frontmatter?: Record<string, unknown>;
63
63
  }): Promise<string>;
64
+ /** A Markdown file as a collaborative room holds it: the body the editor shows, and the frontmatter beside it. */
65
+ interface SplitMarkdown {
66
+ body: string;
67
+ fields: Record<string, unknown>;
68
+ }
64
69
  type MarkdownCheck = {
65
70
  editable: true;
66
71
  /** The Markdown as the editor writes it, which the first save produces. */ markdown: string;
67
72
  } | {
68
73
  editable: false;
74
+ /** Why, when it's the frontmatter the editor can't keep. */ reason?: string;
69
75
  };
70
76
  /**
71
77
  * Whether the editor can open this Markdown and save it without changing what readers see. Pass the parse plugins the
@@ -128,15 +134,33 @@ declare const Upload: Extension<UploadOptions, any>;
128
134
  * A schema and Markdown codec that a server and its browsers share, as a collaborative room needs: the server converts
129
135
  * the stored Markdown with it, and every editor that joins builds the same schema from `extensions()`. ViteHub
130
136
  * Realtime's `editor` option takes it as it is.
137
+ *
138
+ * A room holds the body as the editor's document and the frontmatter as its fields, a map of one value per key, so an
139
+ * edit to the text never writes over the metadata. `split()` and `join()` convert between a stored file and the two.
131
140
  */
132
141
  interface EditorCodec {
133
142
  extensions(): AnyExtension[];
143
+ /** Reads a room's body, without frontmatter. */
134
144
  fromMarkdown(markdown: string): Promise<JSONContent>;
145
+ /** Writes a room's body, without frontmatter. */
135
146
  toMarkdown(doc: JSONContent): Promise<string>;
136
- /** Whether the room may save this document. `canonical` is what the first save writes for it unchanged. */
147
+ /**
148
+ * Splits a stored file into the room's body and fields. Frontmatter a save couldn't write back stays in the body,
149
+ * where `verify()` says why the room can't save it.
150
+ */
151
+ split(markdown: string): SplitMarkdown;
152
+ /** Joins a room's body and fields into the file to store. */
153
+ join(document: SplitMarkdown): string;
154
+ /**
155
+ * Whether the room may save this body. `canonical` is what the first save writes for it unchanged, and `issues` says
156
+ * why a body that still holds frontmatter can't be saved. The fields need no check: every JSON value survives YAML.
157
+ */
137
158
  verify(markdown: string): Promise<{
138
159
  writable: boolean;
139
160
  canonical?: string;
161
+ issues?: {
162
+ message: string;
163
+ }[];
140
164
  }>;
141
165
  }
142
166
  interface EditorCodecOptions {
@@ -146,4 +170,4 @@ interface EditorCodecOptions {
146
170
  }
147
171
  declare function createEditorCodec(options?: EditorCodecOptions): EditorCodec;
148
172
  //#endregion
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 };
173
+ export { DefinitionTerm as A, ComarkSlot as C, CriticInsertion as D, CriticHighlight as E, FootnoteReference as M, InlineFootnote as N, DefinitionDescription as O, ComarkKitOptions as S, CriticDeletion as T, ComarkBlock as _, Upload as a, ComarkInline as b, startUploads as c, EditorDocument as d, MarkdownCheck as f, toMarkdown as g, fromMarkdown as h, PendingUpload as i, FootnoteDefinition as j, DefinitionList as k, uploadKey as l, checkMarkdown as m, EditorCodecOptions as n, UploadOptions as o, SplitMarkdown as p, createEditorCodec as r, findUpload as s, EditorCodec as t, uploadPlugin as u, ComarkComment as v, CriticComment as w, ComarkKit as x, ComarkImage as y };
package/dist/editor.d.mts CHANGED
@@ -1,2 +1,2 @@
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 };
1
+ import { A as DefinitionTerm, C as ComarkSlot, D as CriticInsertion, E as CriticHighlight, M as FootnoteReference, N as InlineFootnote, O as DefinitionDescription, S as ComarkKitOptions, T as CriticDeletion, _ as ComarkBlock, a as Upload, b as ComarkInline, c as startUploads, d as EditorDocument, f as MarkdownCheck, g as toMarkdown, h as fromMarkdown, i as PendingUpload, j as FootnoteDefinition, k as DefinitionList, l as uploadKey, m as checkMarkdown, n as EditorCodecOptions, o as UploadOptions, p as SplitMarkdown, r as createEditorCodec, s as findUpload, t as EditorCodec, u as uploadPlugin, v as ComarkComment, w as CriticComment, x as ComarkKit, y as ComarkImage } from "./editor-yoVL7Ajq.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, type SplitMarkdown, 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, 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-ASZBrUYa.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-9IcfELjG.mjs";
package/dist/html.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
2
  import { n as preset, t as criticCss } from "./src-PkmYCSNu.mjs";
3
- import { a as start, n as mermaidBar, r as mermaidCss } from "./client-BhzMlpun.mjs";
3
+ import { a as start, n as mermaidBar, r as mermaidCss } from "./client-kUEiRbtp.mjs";
4
4
  import { t as rawText } from "./text-D8j9clg3.mjs";
5
5
  import { escapeHtml } from "comark/utils";
6
6
  import * as annotate from "comark-annotate/html";
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
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.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
2
  import { n as preset, t as criticCss } from "./src-PkmYCSNu.mjs";
3
- import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-BhzMlpun.mjs";
3
+ import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-kUEiRbtp.mjs";
4
4
  import { createElement, useEffect } from "react";
5
5
  import * as badge from "comark-badge/react";
6
6
  import * as cards from "comark-cards/react";
@@ -45,6 +45,8 @@ Either way, the HTML renderer inlines the styles once per document, and the Vue,
45
45
 
46
46
  The `--comark-*` tokens theme every plugin at once; https://comark-ui.onmax.me/guide/theming/ lists them. In a Nuxt UI app the plugins follow its `--ui-*` variables with no setup. For a page rendered outside the app, such as review evidence, put the CSS from `nuxtUiTheme({ colors: appConfig.ui?.colors, css: mainCss })` (from `comark-ui/nuxt-ui`) in a `<style>`, so the page matches the app.
47
47
 
48
+ A project that renders Markdown with remark moves to Comark with `remark-to-comark`; the [remark-to-comark reference](references/remark-to-comark.md) walks through it.
49
+
48
50
  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.
49
51
 
50
52
  ## Shape the document
@@ -7,12 +7,12 @@ comark-media plays an image that points at a video or a sound file, and a GitHub
7
7
  ```md
8
8
  The spinner now stops once the build finishes:
9
9
 
10
- ![The build spinner stops and the Deploy button turns green](./recordings/build.mp4){loop muted}
10
+ ![The build spinner stops and the Deploy button turns green](./recordings/build.mp4){loop muted width=1280 height=720}
11
11
  ```
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. 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.
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. Give every video its `width` and `height`, paired whole pixel dimensions like `{width=1280 height=720}`, so the page keeps its room before the file loads; read them with `ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 clip.mp4`. 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
@@ -0,0 +1,29 @@
1
+ # Moving a project from remark
2
+
3
+ remark-to-comark moves a project that renders Markdown with remark, rehype, unified, or react-markdown to Comark. It reports what it finds as diagnostics, `RTC0101` to `RTC0303`, each with a fix and a link to https://comark-ui.onmax.me/remark-to-comark/diagnostics/.
4
+
5
+ ## Run it
6
+
7
+ ```sh
8
+ vp exec remark-to-comark check . --json
9
+ vp exec remark-to-comark content "**/*.{md,mdx}" --write
10
+ vp exec remark-to-comark rewrite . --write
11
+ vp exec remark-to-comark check . --json
12
+ ```
13
+
14
+ - `check` lists the remark setups it finds and what each plugin becomes, the unified chains `rewrite` can replace and why the others stay, the Markdown files `content` would change, and the MDX it leaves alone. `--json` prints each finding with its code, file, line, fix, and docs link.
15
+ - `rewrite` replaces a chain like `unified().use(remarkParse).use(remarkRehype).use(rehypeStringify).process(markdown)` with `renderHtml(markdown, comarkOptions({ … }))`, importing `comarkOptions` from `remark-to-comark/setup`. Install what the new code imports: `comark`, `@comark/html`, and `remark-to-comark`.
16
+ - `content` escapes `{`, `[`, and `:name` that Comark would read as attributes, spans, or components, so remark still renders every file byte for byte as before. It parses with the project's syntax plugins, so `$x_{i}$` stays math. Run it before `rewrite`, which removes the remark code it reads them from.
17
+ - Run `check` again at the end. What's left is work for you: framework configs (RTC0101), the project's own plugins (RTC0105), chains it couldn't rewrite (RTC0201), MDX (RTC0301), and blocks left as written (RTC0303).
18
+
19
+ ## What it leaves to you
20
+
21
+ - A framework config, such as Astro's `markdown.remarkPlugins` or Docusaurus' `remarkPlugins`, stays as it is: the framework calls remark. Render with `renderHtml(markdown, comarkOptions())` where the framework lets you, such as a content loader or a route.
22
+ - react-markdown becomes `<Markdown value={markdown} options={…} plugins={[…]} />` from `@comark/react`, with the plugins the findings name.
23
+ - A plugin a comark-ui package replaces is reported with that package (RTC0102); its markup differs from the remark plugin's, so check pages that style it.
24
+ - MDX stays MDX. Convert a page by hand only when its JSX just passes props: rename it to `.md`, run `content` on the prose, then write the components as Comark blocks. `content` escapes Comark syntax, so don't run it after you add them.
25
+
26
+ ## Register
27
+
28
+ - In `comark-ui`: not included. Install it beside the preset for the migration.
29
+ - Alone: `pnpm add -D remark-to-comark`. It installs the command `remark-to-comark`, the library `remark-to-comark`, the Comark setup `remark-to-comark/setup` (add it to your runtime dependencies when rewritten code imports it), the ports `remark-to-comark/plugins`, and the Vite Doctor extension `remark-to-comark/doctor`.
package/dist/skills.mjs CHANGED
@@ -42,7 +42,7 @@ const skills = [
42
42
  "files": [
43
43
  {
44
44
  "path": "SKILL.md",
45
- "text": "---\nname: comark-ui\ndescription: Writes PR evidence, explanations of how something works, and docs pages in Markdown with comark-ui. Use when a document needs an annotated screenshot, a before/after, a chart, a diagram, a code change, or another comark block, or when setting up comark-ui or a comark-* package.\n---\n\n# comark-ui\n\nYou write Markdown, and the comark plugins render it as static HTML. The reader sees only what you measured and wrote down, so measure every mark from the page, take every number from the data, and check the render before you hand it over.\n\n## Install\n\nInstall `comark-ui` by default. It holds every plugin and registers them in the order they need:\n\n```sh\npnpm add comark @comark/html comark-ui\n```\n\nRun 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.\n\n```ts\nimport { createHtmlRenderer } from \"@comark/html\";\nimport comarkUi from \"comark-ui/html\";\n\nconst render = createHtmlRenderer(comarkUi());\n```\n\nEvery plugin is on except `include`. The [comark-ui reference](references/ui.md) covers its options, the plugins that read files or use the network, and its Vue, React, and Svelte entries.\n\nTo use only some plugins, install those packages and register each plugin with its components:\n\n```sh\npnpm add comark @comark/html comark-annotate\n```\n\n```ts\nimport { createHtmlRenderer } from \"@comark/html\";\nimport annotate, { components } from \"comark-annotate/html\";\n\nconst render = createHtmlRenderer({ plugins: [annotate()], components });\n```\n\nEach reference ends with a Register section that says how its package registers alone and which options it needs.\n\nEither way, the HTML renderer inlines the styles once per document, and the Vue, React, and Svelte components add them on mount. On a server-rendered page, import each package's `style.css`, such as `comark-cards/style.css`, so the page doesn't shift.\n\nThe `--comark-*` tokens theme every plugin at once; https://comark-ui.onmax.me/guide/theming/ lists them. In a Nuxt UI app the plugins follow its `--ui-*` variables with no setup. For a page rendered outside the app, such as review evidence, put the CSS from `nuxtUiTheme({ colors: appConfig.ui?.colors, css: mainCss })` (from `comark-ui/nuxt-ui`) in a `<style>`, so the page matches the app.\n\nProblems 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.\n\n## Shape the document\n\nThis 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 it isn't installed, suggest `vp dlx skills add onmax/comark-ui` and picking it from the list.\n\n## Pick the blocks\n\nRead the reference for each block you use before you write it. Mix the blocks freely with ordinary Markdown, such as headings, tables, and lists.\n\n### Review blocks\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| One state of a screen, with marks, numbered notes, or arrows | `::screenshot` | [annotate](references/annotate.md) |\n| A change on screen, before and after | `::compare` | [annotate](references/annotate.md) |\n| A command's output, such as a test run or a build, as colored text | `::cast` | [cast](references/cast.md) |\n| Numbers, such as sizes, timings, before/after metrics, a trend, or a timeline | `::chart` | [charts](references/charts.md) |\n| A flow, a sequence, an architecture, or a schema as a diagram | a `mermaid`, `d2`, `dot`, `plantuml`, `dbml`, `goat`, or `svgbob` fence | [diagrams](references/diagrams.md) |\n| Links and `@mentions` with the site's favicon or the person's avatar | links, `@user`, `:chip` | [chips](references/chips.md) |\n| Colors, such as design tokens before and after, or a palette | a color in inline code, `::palette` | [colors](references/colors.md) |\n| A suggested change to one file, before and after | `::diff` | [diff](references/diff.md) |\n| 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) |\n| A video, a design file, or a live reproduction from its link | `::embed` | [embed](references/embed.md) |\n| A screen recording or a sound that a screenshot can't show | `![alt](clip.mp4)` | [media](references/media.md) |\n| A number before and after, a table of befores and afters, or progress | `:delta`, `::stats`, `:progress` | [metrics](references/metrics.md) |\n| Data from a CSV, TSV, or JSON fence or file, as a table that copies and sorts | `::table`, a `csv` fence | [table](references/table.md) |\n| A test run's passed, failed, and skipped counts and its failures, from a JUnit report | `::tests` | [tests](references/tests.md) |\n| A note, tip, or warning box, or long output folded away, as on GitHub | `> [!NOTE]`, `<details>` | [github](references/github.md) |\n| The review's status, pull request, author, date, and tags in a box | frontmatter, `::properties` | [properties](references/properties.md) |\n| Claims backed by links, with numbered citations | `:cite`, `::sources` | [sources](references/sources.md) |\n| An icon in a sentence or a table cell, such as a logo or a status | `:icon` | [icon](references/icon.md) |\n| A preview link a reviewer can scan with a phone | `:qr` | [qr](references/qr.md) |\n| Why a line of code is there, as numbered notes under it | `// <1>` and a numbered list | [code-notes](references/code-notes.md) |\n\n### Page behavior\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| A stable link to a section, or a link that opens a new tab | `{#id}`, `{target=\"_blank\"}` | [links](references/links.md) |\n| A table, code block, or diagram too small to read in place | `::zoom` | [zoom](references/zoom.md) |\n| A sequence to page through in one place, such as the screens of a flow | `::carousel` | [carousel](references/carousel.md) |\n| Versions of one thing where each reader needs only one, such as an OS or a package manager | `::tabs`, `::code-group`, `pm` | [tabs](references/tabs.md) |\n| Code with lines marked added, removed, or highlighted, or a pasted `git diff` | `ins={4}` after a fence's language, `diff lang=\"ts\"` | [code](references/code.md) |\n\n### Docs blocks\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| Content a reader may skip, such as a long log, behind a summary | `::details`, `::accordion`, `::collapsible` | [details](references/details.md) |\n| Folders and files, with what a change added, removed, or changed | `::file-tree` | [file-tree](references/file-tree.md) |\n| A component's props or a function's options, with types and defaults | `::field` | [fields](references/fields.md) |\n| A version, status, or deprecation tag in a heading or a sentence | `:badge` | [badge](references/badge.md) |\n| Links to the next pages as cards, or blocks side by side | `::card-group`, `::columns` | [cards](references/cards.md) |\n| Instructions the reader follows in order, such as repro or setup steps | `::steps` | [steps](references/steps.md) |\n| Code that changes over several steps, such as a refactor told one move at a time | `::magic-move` | [magic-move](references/magic-move.md) |\n| An interactive lesson question, poll, or recall card | `::question`, `::poll`, `::recall` | [quiz](references/quiz.md) |\n\n### Markdown syntax\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| Bold or italics in Chinese, Japanese, or Korean text | `**…**`, `*…*` | [cjk](references/cjk.md) |\n| An image with a caption under it | `![alt](src \"Caption\")` | [figure](references/figure.md) |\n| A highlighted phrase or a superscript | `==text==`, `^2^` | [mark](references/mark.md) |\n| A suggested edit or a comment inside the text | `{++ ++}`, `{-- --}`, `{~~ ~> ~~}`, `{== ==}`, `{>> <<}` | [critic](references/critic.md) |\n| Terms and their definitions, such as a glossary | `Term`, then `: definition` | [deflist](references/deflist.md) |\n| A numbered note at the end of the page, such as a source or a caveat | `[^label]`, `^[note]` | [footnotes](references/footnotes.md) |\n| A formula, such as a complexity bound or how a number was computed | `$…$`, `$$…$$` | [math](references/math.md) |\n| A keyboard shortcut | `++ctrl+k++` | [keys](references/keys.md) |\n\n### Authoring tools\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| A shared section, or real source lines, pasted from a file | `:include{src}` | [include](references/include.md) |\n| A table of contents of the page's headings | `::toc` | [outline](references/outline.md) |\n| Figures and tables numbered and referred to by number | `{#fig-name}`, `:ref{#fig-name}` | [xref](references/xref.md) |\n| Word count and reading time, for the page's layout | nothing; it writes `meta.readingTime` | [reading-time](references/reading-time.md) |\n\n### Chat\n\n| To show | Use | Reference |\n| --- | --- | --- |\n| An AI SDK chat message, with its reasoning, tool calls, sources, and files | `MessageParts` from `comark-chat` | [chat](references/chat.md) |\n\n## Marks\n\nScreenshots and charts draw the same marks. Each `::annotation` is one mark and its note, written in Markdown.\n\n- `mark` is `box` (default), `circle`, `underline`, `line`, `highlight`, `bracket`, `cross`, `check`, or `none`, which draws nothing, so its note needs a `side` and the arrow alone points.\n- `color` is `violet` (default) to point something out, `red` for a problem, `amber` for a warning, `green` for a fix, `blue` for a second voice, `gray` for neutral, or a 3- or 6-digit hex color.\n- `side` is `top`, `bottom`, `left`, or `right`, and puts the note in that margin with an arrow to its mark. Notes without one are numbered in a list under the figure.\n\nMark only what your text talks about. Write each note in the words the screen or table shows, saying what changed or what is wrong, so the reader can check it against the figure. Leave out a value the figure doesn't show.\n\n## Check the result\n\n1. Lint the file with `vp exec comark-lint report.md`, in a project with `comark-linter` and the plugins installed (`comark-ui` counts as every plugin). It prints one line per problem with its line, its rule, and the fix, and exits with 1 while any remain. Fix every problem and run it again until it exits with 0. The [lint reference](references/lint.md) covers the flags, the config file, the library, ESLint, and Vite Doctor.\n2. Render the document as the block's reference says, open it in a browser, and take a screenshot. Each mark must sit on its target, each number must match its note, and each chart must say what your text says. Name anything you couldn't verify when you hand the document over.\n"
45
+ "text": "---\nname: comark-ui\ndescription: Writes PR evidence, explanations of how something works, and docs pages in Markdown with comark-ui. Use when a document needs an annotated screenshot, a before/after, a chart, a diagram, a code change, or another comark block, or when setting up comark-ui or a comark-* package.\n---\n\n# comark-ui\n\nYou write Markdown, and the comark plugins render it as static HTML. The reader sees only what you measured and wrote down, so measure every mark from the page, take every number from the data, and check the render before you hand it over.\n\n## Install\n\nInstall `comark-ui` by default. It holds every plugin and registers them in the order they need:\n\n```sh\npnpm add comark @comark/html comark-ui\n```\n\nRun 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.\n\n```ts\nimport { createHtmlRenderer } from \"@comark/html\";\nimport comarkUi from \"comark-ui/html\";\n\nconst render = createHtmlRenderer(comarkUi());\n```\n\nEvery plugin is on except `include`. The [comark-ui reference](references/ui.md) covers its options, the plugins that read files or use the network, and its Vue, React, and Svelte entries.\n\nTo use only some plugins, install those packages and register each plugin with its components:\n\n```sh\npnpm add comark @comark/html comark-annotate\n```\n\n```ts\nimport { createHtmlRenderer } from \"@comark/html\";\nimport annotate, { components } from \"comark-annotate/html\";\n\nconst render = createHtmlRenderer({ plugins: [annotate()], components });\n```\n\nEach reference ends with a Register section that says how its package registers alone and which options it needs.\n\nEither way, the HTML renderer inlines the styles once per document, and the Vue, React, and Svelte components add them on mount. On a server-rendered page, import each package's `style.css`, such as `comark-cards/style.css`, so the page doesn't shift.\n\nThe `--comark-*` tokens theme every plugin at once; https://comark-ui.onmax.me/guide/theming/ lists them. In a Nuxt UI app the plugins follow its `--ui-*` variables with no setup. For a page rendered outside the app, such as review evidence, put the CSS from `nuxtUiTheme({ colors: appConfig.ui?.colors, css: mainCss })` (from `comark-ui/nuxt-ui`) in a `<style>`, so the page matches the app.\n\nA project that renders Markdown with remark moves to Comark with `remark-to-comark`; the [remark-to-comark reference](references/remark-to-comark.md) walks through it.\n\nProblems 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.\n\n## Shape the document\n\nThis 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 it isn't installed, suggest `vp dlx skills add onmax/comark-ui` and picking it from the list.\n\n## Pick the blocks\n\nRead the reference for each block you use before you write it. Mix the blocks freely with ordinary Markdown, such as headings, tables, and lists.\n\n### Review blocks\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| One state of a screen, with marks, numbered notes, or arrows | `::screenshot` | [annotate](references/annotate.md) |\n| A change on screen, before and after | `::compare` | [annotate](references/annotate.md) |\n| A command's output, such as a test run or a build, as colored text | `::cast` | [cast](references/cast.md) |\n| Numbers, such as sizes, timings, before/after metrics, a trend, or a timeline | `::chart` | [charts](references/charts.md) |\n| A flow, a sequence, an architecture, or a schema as a diagram | a `mermaid`, `d2`, `dot`, `plantuml`, `dbml`, `goat`, or `svgbob` fence | [diagrams](references/diagrams.md) |\n| Links and `@mentions` with the site's favicon or the person's avatar | links, `@user`, `:chip` | [chips](references/chips.md) |\n| Colors, such as design tokens before and after, or a palette | a color in inline code, `::palette` | [colors](references/colors.md) |\n| A suggested change to one file, before and after | `::diff` | [diff](references/diff.md) |\n| 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) |\n| A video, a design file, or a live reproduction from its link | `::embed` | [embed](references/embed.md) |\n| A screen recording or a sound that a screenshot can't show | `![alt](clip.mp4)` | [media](references/media.md) |\n| A number before and after, a table of befores and afters, or progress | `:delta`, `::stats`, `:progress` | [metrics](references/metrics.md) |\n| Data from a CSV, TSV, or JSON fence or file, as a table that copies and sorts | `::table`, a `csv` fence | [table](references/table.md) |\n| A test run's passed, failed, and skipped counts and its failures, from a JUnit report | `::tests` | [tests](references/tests.md) |\n| A note, tip, or warning box, or long output folded away, as on GitHub | `> [!NOTE]`, `<details>` | [github](references/github.md) |\n| The review's status, pull request, author, date, and tags in a box | frontmatter, `::properties` | [properties](references/properties.md) |\n| Claims backed by links, with numbered citations | `:cite`, `::sources` | [sources](references/sources.md) |\n| An icon in a sentence or a table cell, such as a logo or a status | `:icon` | [icon](references/icon.md) |\n| A preview link a reviewer can scan with a phone | `:qr` | [qr](references/qr.md) |\n| Why a line of code is there, as numbered notes under it | `// <1>` and a numbered list | [code-notes](references/code-notes.md) |\n\n### Page behavior\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| A stable link to a section, or a link that opens a new tab | `{#id}`, `{target=\"_blank\"}` | [links](references/links.md) |\n| A table, code block, or diagram too small to read in place | `::zoom` | [zoom](references/zoom.md) |\n| A sequence to page through in one place, such as the screens of a flow | `::carousel` | [carousel](references/carousel.md) |\n| Versions of one thing where each reader needs only one, such as an OS or a package manager | `::tabs`, `::code-group`, `pm` | [tabs](references/tabs.md) |\n| Code with lines marked added, removed, or highlighted, or a pasted `git diff` | `ins={4}` after a fence's language, `diff lang=\"ts\"` | [code](references/code.md) |\n\n### Docs blocks\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| Content a reader may skip, such as a long log, behind a summary | `::details`, `::accordion`, `::collapsible` | [details](references/details.md) |\n| Folders and files, with what a change added, removed, or changed | `::file-tree` | [file-tree](references/file-tree.md) |\n| A component's props or a function's options, with types and defaults | `::field` | [fields](references/fields.md) |\n| A version, status, or deprecation tag in a heading or a sentence | `:badge` | [badge](references/badge.md) |\n| Links to the next pages as cards, or blocks side by side | `::card-group`, `::columns` | [cards](references/cards.md) |\n| Instructions the reader follows in order, such as repro or setup steps | `::steps` | [steps](references/steps.md) |\n| Code that changes over several steps, such as a refactor told one move at a time | `::magic-move` | [magic-move](references/magic-move.md) |\n| An interactive lesson question, poll, or recall card | `::question`, `::poll`, `::recall` | [quiz](references/quiz.md) |\n\n### Markdown syntax\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| Bold or italics in Chinese, Japanese, or Korean text | `**…**`, `*…*` | [cjk](references/cjk.md) |\n| An image with a caption under it | `![alt](src \"Caption\")` | [figure](references/figure.md) |\n| A highlighted phrase or a superscript | `==text==`, `^2^` | [mark](references/mark.md) |\n| A suggested edit or a comment inside the text | `{++ ++}`, `{-- --}`, `{~~ ~> ~~}`, `{== ==}`, `{>> <<}` | [critic](references/critic.md) |\n| Terms and their definitions, such as a glossary | `Term`, then `: definition` | [deflist](references/deflist.md) |\n| A numbered note at the end of the page, such as a source or a caveat | `[^label]`, `^[note]` | [footnotes](references/footnotes.md) |\n| A formula, such as a complexity bound or how a number was computed | `$…$`, `$$…$$` | [math](references/math.md) |\n| A keyboard shortcut | `++ctrl+k++` | [keys](references/keys.md) |\n\n### Authoring tools\n\n| To show | Write | Reference |\n| --- | --- | --- |\n| A shared section, or real source lines, pasted from a file | `:include{src}` | [include](references/include.md) |\n| A table of contents of the page's headings | `::toc` | [outline](references/outline.md) |\n| Figures and tables numbered and referred to by number | `{#fig-name}`, `:ref{#fig-name}` | [xref](references/xref.md) |\n| Word count and reading time, for the page's layout | nothing; it writes `meta.readingTime` | [reading-time](references/reading-time.md) |\n\n### Chat\n\n| To show | Use | Reference |\n| --- | --- | --- |\n| An AI SDK chat message, with its reasoning, tool calls, sources, and files | `MessageParts` from `comark-chat` | [chat](references/chat.md) |\n\n## Marks\n\nScreenshots and charts draw the same marks. Each `::annotation` is one mark and its note, written in Markdown.\n\n- `mark` is `box` (default), `circle`, `underline`, `line`, `highlight`, `bracket`, `cross`, `check`, or `none`, which draws nothing, so its note needs a `side` and the arrow alone points.\n- `color` is `violet` (default) to point something out, `red` for a problem, `amber` for a warning, `green` for a fix, `blue` for a second voice, `gray` for neutral, or a 3- or 6-digit hex color.\n- `side` is `top`, `bottom`, `left`, or `right`, and puts the note in that margin with an arrow to its mark. Notes without one are numbered in a list under the figure.\n\nMark only what your text talks about. Write each note in the words the screen or table shows, saying what changed or what is wrong, so the reader can check it against the figure. Leave out a value the figure doesn't show.\n\n## Check the result\n\n1. Lint the file with `vp exec comark-lint report.md`, in a project with `comark-linter` and the plugins installed (`comark-ui` counts as every plugin). It prints one line per problem with its line, its rule, and the fix, and exits with 1 while any remain. Fix every problem and run it again until it exits with 0. The [lint reference](references/lint.md) covers the flags, the config file, the library, ESLint, and Vite Doctor.\n2. Render the document as the block's reference says, open it in a browser, and take a screenshot. Each mark must sit on its target, each number must match its note, and each chart must say what your text says. Name anything you couldn't verify when you hand the document over.\n"
46
46
  },
47
47
  {
48
48
  "path": "agents/openai.yaml",
@@ -178,7 +178,7 @@ const skills = [
178
178
  },
179
179
  {
180
180
  "path": "references/media.md",
181
- "text": "# Videos and audio\n\ncomark-media plays an image that points at a video or a sound file, and a GitHub attachment link alone on its line, with the alt text as the player's label. Use it for a screen recording in PR evidence: a transition, a loading state, or a sound, which a screenshot can't show.\n\n## Syntax\n\n```md\nThe spinner now stops once the build finishes:\n\n![The build spinner stops and the Deploy button turns green](./recordings/build.mp4){loop muted}\n```\n\n- 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.\n- 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.\n- `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.\n- 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.\n\n## Record the evidence\n\n- Record only the part of the screen that changes, for a few seconds.\n- Encode it as H.264 `.mp4` for every browser, or `.webm` for a smaller file. A `.mov` from macOS doesn't always play in Firefox.\n- Say what happens in the alt text, so a reader who can't play the file still learns what it shows.\n- Add `loop muted` to a clip of one motion, so it reads like an animated screenshot once played.\n- A GitHub attachment from a private repository plays only for readers signed in with access to it. For a page that others read, download the file and serve it with the page.\n\n## Register\n\n- 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()`.\n- 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.\n"
181
+ "text": "# Videos and audio\n\ncomark-media plays an image that points at a video or a sound file, and a GitHub attachment link alone on its line, with the alt text as the player's label. Use it for a screen recording in PR evidence: a transition, a loading state, or a sound, which a screenshot can't show.\n\n## Syntax\n\n```md\nThe spinner now stops once the build finishes:\n\n![The build spinner stops and the Deploy button turns green](./recordings/build.mp4){loop muted width=1280 height=720}\n```\n\n- 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.\n- 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.\n- `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. Give every video its `width` and `height`, paired whole pixel dimensions like `{width=1280 height=720}`, so the page keeps its room before the file loads; read them with `ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 clip.mp4`. Audio takes neither. Other props, such as a class, are dropped and reported.\n- 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.\n\n## Record the evidence\n\n- Record only the part of the screen that changes, for a few seconds.\n- Encode it as H.264 `.mp4` for every browser, or `.webm` for a smaller file. A `.mov` from macOS doesn't always play in Firefox.\n- Say what happens in the alt text, so a reader who can't play the file still learns what it shows.\n- Add `loop muted` to a clip of one motion, so it reads like an animated screenshot once played.\n- A GitHub attachment from a private repository plays only for readers signed in with access to it. For a page that others read, download the file and serve it with the page.\n\n## Register\n\n- 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()`.\n- 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.\n"
182
182
  },
183
183
  {
184
184
  "path": "references/metrics.md",
@@ -204,6 +204,10 @@ const skills = [
204
204
  "path": "references/reading-time.md",
205
205
  "text": "# Reading time\n\ncomark-reading-time counts a document's words while parsing and writes them to `meta.readingTime` as `{ words, minutes }`. It has no syntax and draws nothing. The host's layout shows the numbers, such as \"7 min read\" under the title.\n\n## What it counts\n\n- The text a reader reads. It leaves out code blocks, frontmatter, and comments, and counts inline code.\n- Each Chinese, Japanese, or Korean character counts as a word, since those languages put no spaces between words.\n- `minutes` is `words` over `wordsPerMinute`, rounded up, so any text takes at least a minute. It is 0 only for a document with no words.\n- `countWords(text)`, also exported, counts any string the same way, such as an excerpt.\n\n## Show it\n\nWith `@comark/html`, parse once, read the meta, and render that document:\n\n```ts\nimport { renderHtmlFromDocument } from \"@comark/html\";\nimport { parseMarkdown } from \"comark\";\nimport comarkUi from \"comark-ui/html\";\n\nconst { plugins, components } = comarkUi();\nconst document = await parseMarkdown(markdown, { plugins });\nconst body = await renderHtmlFromDocument(document, { components });\nconst html = `<p class=\"byline\">${document.meta.readingTime.minutes} min read</p>${body}`;\n```\n\nWith `@comark/vue` or `@comark/react`, read `meta.readingTime` from the parsed document you pass to `MarkdownDocument`.\n\n## Register\n\n- In `comark-ui`: on in every entry. `readingTime: { wordsPerMinute: 230 }` sets the speed; 0 or less counts at 200. The default, 200, is a cautious pace for technical prose; 230 to 250 suits light reading, and a site mostly in CJK may want more.\n- Alone: `pnpm add comark comark-reading-time`, then `parseMarkdown(markdown, { plugins: [readingTime()] })` with `readingTime` from `comark-reading-time`. It has no components, so it works with any renderer.\n"
206
206
  },
207
+ {
208
+ "path": "references/remark-to-comark.md",
209
+ "text": "# Moving a project from remark\n\nremark-to-comark moves a project that renders Markdown with remark, rehype, unified, or react-markdown to Comark. It reports what it finds as diagnostics, `RTC0101` to `RTC0303`, each with a fix and a link to https://comark-ui.onmax.me/remark-to-comark/diagnostics/.\n\n## Run it\n\n```sh\nvp exec remark-to-comark check . --json\nvp exec remark-to-comark content \"**/*.{md,mdx}\" --write\nvp exec remark-to-comark rewrite . --write\nvp exec remark-to-comark check . --json\n```\n\n- `check` lists the remark setups it finds and what each plugin becomes, the unified chains `rewrite` can replace and why the others stay, the Markdown files `content` would change, and the MDX it leaves alone. `--json` prints each finding with its code, file, line, fix, and docs link.\n- `rewrite` replaces a chain like `unified().use(remarkParse).use(remarkRehype).use(rehypeStringify).process(markdown)` with `renderHtml(markdown, comarkOptions({ … }))`, importing `comarkOptions` from `remark-to-comark/setup`. Install what the new code imports: `comark`, `@comark/html`, and `remark-to-comark`.\n- `content` escapes `{`, `[`, and `:name` that Comark would read as attributes, spans, or components, so remark still renders every file byte for byte as before. It parses with the project's syntax plugins, so `$x_{i}$` stays math. Run it before `rewrite`, which removes the remark code it reads them from.\n- Run `check` again at the end. What's left is work for you: framework configs (RTC0101), the project's own plugins (RTC0105), chains it couldn't rewrite (RTC0201), MDX (RTC0301), and blocks left as written (RTC0303).\n\n## What it leaves to you\n\n- A framework config, such as Astro's `markdown.remarkPlugins` or Docusaurus' `remarkPlugins`, stays as it is: the framework calls remark. Render with `renderHtml(markdown, comarkOptions())` where the framework lets you, such as a content loader or a route.\n- react-markdown becomes `<Markdown value={markdown} options={…} plugins={[…]} />` from `@comark/react`, with the plugins the findings name.\n- A plugin a comark-ui package replaces is reported with that package (RTC0102); its markup differs from the remark plugin's, so check pages that style it.\n- MDX stays MDX. Convert a page by hand only when its JSX just passes props: rename it to `.md`, run `content` on the prose, then write the components as Comark blocks. `content` escapes Comark syntax, so don't run it after you add them.\n\n## Register\n\n- In `comark-ui`: not included. Install it beside the preset for the migration.\n- Alone: `pnpm add -D remark-to-comark`. It installs the command `remark-to-comark`, the library `remark-to-comark`, the Comark setup `remark-to-comark/setup` (add it to your runtime dependencies when rewritten code imports it), the ports `remark-to-comark/plugins`, and the Vite Doctor extension `remark-to-comark/doctor`.\n"
210
+ },
207
211
  {
208
212
  "path": "references/sources.md",
209
213
  "text": "# Citations and sources\n\ncomark-sources turns `:cite[n]` into a numbered link to the nth source in the document's `::sources` list, and gives each source a link back to every citation of it. Use it when the claims in a document rest on links, such as the research behind a change.\n\n## Syntax\n\n```md\nPigeons find home by the sun and by the earth's magnetic field :cite[1], and some by smell too :cite[2,3].\n\n::sources\n1. [Magnetoreception in homing pigeons](https://en.wikipedia.org/wiki/Magnetoreception)\n2. [Olfactory navigation](https://en.wikipedia.org/wiki/Pigeon_intelligence), on smell\n3. [How do homing pigeons navigate?](https://www.sciencefocus.com/nature/how-do-homing-pigeons-navigate)\n::\n```\n\n- `:cite[2]` cites the second source, and `:cite[1,3]` several. Each becomes a superscript `[1]` that links to its source. Hovering or focusing it shows a card with the source's title, its site, and the icon the list shows for it, a globe when there's none. Put a space before `:cite`, which the plugin removes; `word:cite[1]` stays text, and nothing reports it.\n- `::sources` holds one numbered list with one source per item, each with its link. Sources count by their place in the list. Text after the link, such as `, on smell`, stays in the item. `title` names the list, \"Sources\" by default. The plugin drops anything beside the list and reports it.\n- A document has one `::sources`, anywhere in it, and citations before and after it both count. A citation of a number the list doesn't have, a source with no link, and a second list are reported.\n\n## Register\n\n- In `comark-ui`: on. It takes no options.\n- Alone: `pnpm add comark @comark/html comark-sources`, then `createHtmlRenderer({ plugins: [sources()], components })` with both from `comark-sources/html`. `comark-sources/vue`, `/react`, and `/svelte` render it too. Citations are plain `sup` links that work in any renderer, and the `sources` component draws the list and carries the styles for both. It has no options. The HTML renderer inlines the card's script once per document, and the components start it when they mount.\n"
@@ -321,6 +325,6 @@ const skills = [
321
325
  }
322
326
  ];
323
327
  /** The comark-ui version the skills come from, for a host's cache key or a digest. */
324
- const version = "0.8.0";
328
+ const version = "0.8.1";
325
329
  //#endregion
326
330
  export { skills, version };
@@ -89,7 +89,7 @@ const rules = `
89
89
  &:where(ul > li, ol > li)::marker { color: var(--_ty-muted); }
90
90
  &:where(ul.contains-task-list) { list-style-type: none; padding-inline-start: 0.25em; }
91
91
  &:where(li.task-list-item > input[type="checkbox"]) {
92
- margin-inline-end: 0.5em; vertical-align: -0.1em; accent-color: var(--ui-primary, var(--primary, currentColor));
92
+ margin-inline-end: 0.5em; vertical-align: -0.1em; accent-color: var(--_comark-primary);
93
93
  }
94
94
 
95
95
  &:where(details) { margin-block: var(--_ty-flow) 0; }
@@ -211,6 +211,6 @@ const rules = `
211
211
  * page alone. The `typeset` layer sits inside `comark`, so every block's own rules win over it, and any plain rule of
212
212
  * the page wins over both.
213
213
  */
214
- const typesetCss = `@layer comark{@layer typeset{@scope{${theme(":scope")}${rules}}}}`.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/ ?([{};,>(]) ?/g, "$1").replace(/: /g, ":").trim();
214
+ const typesetCss = `@layer comark{@layer typeset{@scope{${theme(":scope", "primary")}${rules}}}}`.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/ ?([{};,>(]) ?/g, "$1").replace(/: /g, ":").trim();
215
215
  //#endregion
216
216
  export { typesetCss as t };
package/dist/vue.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { t as typesetCss } from "./typeset-css-5aJ7cSmh.mjs";
1
+ import { t as typesetCss } from "./typeset-css-blSNpFNK.mjs";
2
2
  import { n as preset, t as criticCss } from "./src-PkmYCSNu.mjs";
3
- import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-BhzMlpun.mjs";
3
+ import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-kUEiRbtp.mjs";
4
4
  import * as properties from "comark-properties/vue";
5
5
  import { defineComponent, h, onBeforeUnmount, onMounted } from "vue";
6
6
  import * as badge from "comark-badge/vue";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comark-ui",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Every comark-ui block in one install: annotated screenshots, charts, diffs, tabs, zoom, and more, registered in the order they need with shared icons. Turn any of them off or pass its options.",
5
5
  "keywords": [
6
6
  "comark",
@@ -48,50 +48,50 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "comark-pele": "^1.1.0",
51
- "comark-annotate": "^0.8.0",
52
- "comark-cards": "^0.8.0",
53
- "comark-carousel": "^0.8.0",
54
- "comark-cast": "^0.8.0",
55
- "comark-charts": "^0.8.0",
56
- "comark-badge": "^0.8.0",
57
- "comark-chips": "^0.8.0",
58
- "comark-code-notes": "^0.8.0",
59
- "comark-cjk": "^0.8.0",
60
- "comark-colors": "^0.8.0",
61
- "comark-code": "^0.8.0",
62
- "comark-comments": "^0.8.0",
63
- "comark-deflist": "^0.8.0",
64
- "comark-details": "^0.8.0",
65
- "comark-critic": "^0.8.0",
66
- "comark-diagrams": "^0.8.0",
67
- "comark-embed": "^0.8.0",
68
- "comark-fields": "^0.8.0",
69
- "comark-file-tree": "^0.8.0",
70
- "comark-footnotes": "^0.8.0",
71
- "comark-icon": "^0.8.0",
72
- "comark-github": "^0.8.0",
73
- "comark-include": "^0.8.0",
74
- "comark-links": "^0.8.0",
75
- "comark-keys": "^0.8.0",
76
- "comark-magic-move": "^0.8.0",
77
- "comark-figure": "^0.8.0",
78
- "comark-diff": "^0.8.0",
79
- "comark-mathml": "^0.8.0",
80
- "comark-media": "^0.8.0",
81
- "comark-outline": "^0.8.0",
82
- "comark-properties": "^0.8.0",
83
- "comark-mark": "^0.8.0",
84
- "comark-metrics": "^0.8.0",
85
- "comark-qr": "^0.8.0",
86
- "comark-quiz": "^0.8.0",
87
- "comark-sources": "^0.8.0",
88
- "comark-steps": "^0.8.0",
89
- "comark-table": "^0.8.0",
90
- "comark-tabs": "^0.8.0",
91
- "comark-tests": "^0.8.0",
92
- "comark-xref": "^0.8.0",
93
- "comark-reading-time": "^0.8.0",
94
- "comark-zoom": "^0.8.0"
51
+ "comark-annotate": "^0.8.1",
52
+ "comark-carousel": "^0.8.1",
53
+ "comark-cast": "^0.8.1",
54
+ "comark-badge": "^0.8.1",
55
+ "comark-charts": "^0.8.1",
56
+ "comark-cards": "^0.8.1",
57
+ "comark-chips": "^0.8.1",
58
+ "comark-cjk": "^0.8.1",
59
+ "comark-code-notes": "^0.8.1",
60
+ "comark-comments": "^0.8.1",
61
+ "comark-code": "^0.8.1",
62
+ "comark-colors": "^0.8.1",
63
+ "comark-critic": "^0.8.1",
64
+ "comark-deflist": "^0.8.1",
65
+ "comark-details": "^0.8.1",
66
+ "comark-diagrams": "^0.8.1",
67
+ "comark-diff": "^0.8.1",
68
+ "comark-embed": "^0.8.1",
69
+ "comark-figure": "^0.8.1",
70
+ "comark-footnotes": "^0.8.1",
71
+ "comark-file-tree": "^0.8.1",
72
+ "comark-fields": "^0.8.1",
73
+ "comark-keys": "^0.8.1",
74
+ "comark-include": "^0.8.1",
75
+ "comark-icon": "^0.8.1",
76
+ "comark-github": "^0.8.1",
77
+ "comark-magic-move": "^0.8.1",
78
+ "comark-mark": "^0.8.1",
79
+ "comark-links": "^0.8.1",
80
+ "comark-media": "^0.8.1",
81
+ "comark-mathml": "^0.8.1",
82
+ "comark-metrics": "^0.8.1",
83
+ "comark-properties": "^0.8.1",
84
+ "comark-outline": "^0.8.1",
85
+ "comark-qr": "^0.8.1",
86
+ "comark-quiz": "^0.8.1",
87
+ "comark-steps": "^0.8.1",
88
+ "comark-table": "^0.8.1",
89
+ "comark-sources": "^0.8.1",
90
+ "comark-reading-time": "^0.8.1",
91
+ "comark-tabs": "^0.8.1",
92
+ "comark-tests": "^0.8.1",
93
+ "comark-xref": "^0.8.1",
94
+ "comark-zoom": "^0.8.1"
95
95
  },
96
96
  "devDependencies": {
97
97
  "@comark/html": "^0.8.1",