comark-ui 0.9.1 → 0.10.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 +9 -1
- package/dist/{client-kUEiRbtp.mjs → client-zWY8yhUx.mjs} +1 -14
- package/dist/client.mjs +2 -2
- package/dist/{editor-9IcfELjG.mjs → editor-CW05gHp7.mjs} +46 -6
- package/dist/{editor-yoVL7Ajq.d.mts → editor-Dk8TRweM.d.mts} +13 -2
- package/dist/editor-vue.d.mts +1 -1
- package/dist/editor-vue.mjs +1 -1
- package/dist/editor.d.mts +2 -2
- package/dist/editor.mjs +1 -1
- package/dist/html.d.mts +1 -1
- package/dist/html.mjs +6 -4
- package/dist/{index-Cq3zytGw.d.mts → index-7SBL13EQ.d.mts} +5 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/react.d.mts +1 -1
- package/dist/react.mjs +6 -3
- package/dist/skills/before-after-evidence/SKILL.md +1 -1
- package/dist/skills/comark-ui/SKILL.md +4 -1
- package/dist/skills/comark-ui/references/annotate.md +1 -1
- package/dist/skills/comark-ui/references/time.md +28 -0
- package/dist/skills/report/SKILL.md +81 -0
- package/dist/skills/report/agents/openai.yaml +3 -0
- package/dist/skills/report/references/blocks.md +199 -0
- package/dist/skills/report/references/example.md +305 -0
- package/dist/skills/report/scripts/render.mjs +74 -0
- package/dist/skills/review-document/SKILL.md +1 -1
- package/dist/skills.mjs +42 -7
- package/dist/{src-MvpWiv9v.mjs → src-C3nMECXA.mjs} +2 -0
- package/dist/styles-vZMqDkIY.mjs +15 -0
- package/dist/svelte/Mermaid.svelte +1 -1
- package/dist/svelte/index.js +2 -0
- package/dist/{typeset-css-BOtdXbMg.mjs → typeset-css-BqBoCTMG.mjs} +5 -16
- package/dist/typeset.css +1 -1
- package/dist/vue.d.mts +610 -1
- package/dist/vue.mjs +44 -4
- package/package.json +52 -51
package/README.md
CHANGED
|
@@ -33,6 +33,14 @@ comarkUi({
|
|
|
33
33
|
|
|
34
34
|
It also returns `tags` and `blocks`. `tags` is every element a parsed document can hold besides the components; with the components' names, it's the allowlist for Comark's `security` plugin. `blocks` are the blocks the plugins replace while parsing, such as `::code-group`, which a linter needs beside the components' names.
|
|
35
35
|
|
|
36
|
+
If a server already parsed your document, import Vue's `components` directly. This keeps the chart parsers and the rest of the parse preset out of the renderer bundle:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { components } from "comark-ui/vue";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The map includes every Vue renderer and the typeset styles. Use `comarkUi(options).components` when you need the map to follow plugin options.
|
|
43
|
+
|
|
36
44
|
## Renderers
|
|
37
45
|
|
|
38
46
|
| Renderer | Import |
|
|
@@ -55,7 +63,7 @@ Vue, React, and Svelte leave out annotate and diff, which render only to HTML.
|
|
|
55
63
|
| `ComarkKit` | The schema: StarterKit, task lists, inline images (drawn as players for video and audio), tables, footnotes, definition lists, CriticMarkup, and Comark's components, slots, and comments, each keeping the attributes it has no field for. |
|
|
56
64
|
| `fromMarkdown`, `toMarkdown` | Convert between Markdown and the editor's JSON with Comark's own parser and writer. Front matter stays beside the document. |
|
|
57
65
|
| `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. |
|
|
58
|
-
| `Upload` | Uploads pasted and dropped files. Each shows a placeholder with its progress until `upload(file, progress)` returns its URL. |
|
|
66
|
+
| `Upload` | Uploads pasted and dropped files, and the files the `chooseFiles` command picks. Each shows a placeholder with its progress until `upload(file, progress)` returns its URL. |
|
|
59
67
|
| `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. |
|
|
60
68
|
|
|
61
69
|
`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.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as addStyles } from "./styles-vZMqDkIY.mjs";
|
|
1
2
|
import { theme } from "comark-zoom/theme";
|
|
2
3
|
//#region src/script.ts
|
|
3
4
|
/**
|
|
@@ -164,20 +165,6 @@ function start() {
|
|
|
164
165
|
window.__comarkMermaid = state;
|
|
165
166
|
return state.stop;
|
|
166
167
|
}
|
|
167
|
-
//#endregion
|
|
168
|
-
//#region ../../shared/styles.ts
|
|
169
|
-
/**
|
|
170
|
-
* Adds a package's styles to the page once, unless the page already carries them as a `style` or a `link` with
|
|
171
|
-
* `data-comark-<name>`, which is how the HTML renderer inlines them and how an app that imports `style.css` marks them.
|
|
172
|
-
*/
|
|
173
|
-
function addStyles(name, css) {
|
|
174
|
-
const attribute = `data-comark-${name}`;
|
|
175
|
-
if (document.querySelector(`style[${attribute}],link[${attribute}]`)) return;
|
|
176
|
-
const style = document.createElement("style");
|
|
177
|
-
style.setAttribute(attribute, "");
|
|
178
|
-
style.textContent = css;
|
|
179
|
-
document.head.append(style);
|
|
180
|
-
}
|
|
181
168
|
/**
|
|
182
169
|
* comark-pele's diagrams on the shared `--comark-*` tokens, on a card like comark-diagrams'. Pele paints with its
|
|
183
170
|
* `--pele-*` variables, so setting them on the wrapper themes every diagram, light and dark, without drawing it
|
package/dist/client.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as typesetStylesheet, t as typesetCss } from "./typeset-css-
|
|
2
|
-
import { i as startMermaid, n as mermaidBar, r as mermaidCss, t as addMermaidStyles } from "./client-
|
|
1
|
+
import { n as typesetStylesheet, t as typesetCss } from "./typeset-css-BqBoCTMG.mjs";
|
|
2
|
+
import { i as startMermaid, n as mermaidBar, r as mermaidCss, t as addMermaidStyles } from "./client-zWY8yhUx.mjs";
|
|
3
3
|
export { addMermaidStyles, mermaidBar, mermaidCss, startMermaid, typesetCss, typesetStylesheet };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { t as addStyles } from "./styles-vZMqDkIY.mjs";
|
|
1
2
|
import { n as scalar } from "./text-D8j9clg3.mjs";
|
|
2
3
|
import cjk from "comark-cjk";
|
|
3
4
|
import critic from "comark-critic";
|
|
4
5
|
import deflist from "comark-deflist";
|
|
5
6
|
import footnotes from "comark-footnotes";
|
|
6
7
|
import { defineComarkPlugin, parseMarkdown } from "comark";
|
|
8
|
+
import { theme } from "comark-zoom/theme";
|
|
7
9
|
import { Extension, Mark, Node } from "@tiptap/core";
|
|
8
10
|
import Image from "@tiptap/extension-image";
|
|
9
11
|
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
|
@@ -654,6 +656,7 @@ const isTaskItem = (child) => isElement(child) && child[0] === "li" && classes(c
|
|
|
654
656
|
const isCheckbox = (child) => isElement(child) && child[0] === "input" && child[1].type === "checkbox";
|
|
655
657
|
function readList(tag, props, children, reading) {
|
|
656
658
|
const items = children.filter(isElement);
|
|
659
|
+
if (tag === "ol" && items.some(isTaskItem)) reading.lossy = true;
|
|
657
660
|
if (tag === "ul" && items.length && items.every(isTaskItem)) return node("taskList", withProps({}, rest(withoutClass(props, "contains-task-list"))), items.map((item) => readTaskItem(item, reading)));
|
|
658
661
|
const listItems = items.map((item) => {
|
|
659
662
|
const blocks = readBlocks(childrenOf(item), reading);
|
|
@@ -1167,7 +1170,18 @@ const acceptMedia = (file) => /^(?:image|video|audio)\//.test(file.type);
|
|
|
1167
1170
|
function findUpload(state, id) {
|
|
1168
1171
|
return uploadKey.getState(state)?.find(void 0, void 0, (spec) => spec.id === id)[0]?.from;
|
|
1169
1172
|
}
|
|
1173
|
+
/** The placeholder's styles, which the editor adds to the page with the first one. */
|
|
1174
|
+
const uploadCss = `@layer comark{${[
|
|
1175
|
+
theme(".cue-upload", "primary"),
|
|
1176
|
+
`.cue-upload{display:inline-grid;position:relative;min-width:12rem;max-width:15rem;overflow:hidden;vertical-align:middle;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-accent);color:var(--_comark-foreground);font:500 .75rem/1.4 var(--_comark-font)}`,
|
|
1177
|
+
`.cue-upload>img,.cue-upload>video{grid-area:1/1;width:100%;max-height:10rem;object-fit:cover;opacity:.55}`,
|
|
1178
|
+
`.cue-upload-label{grid-area:1/1;align-self:end;padding:.375rem .5rem .5rem;font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}`,
|
|
1179
|
+
`.cue-upload>img~.cue-upload-label,.cue-upload>video~.cue-upload-label{background:color-mix(in srgb,var(--_comark-background) 80%,transparent)}`,
|
|
1180
|
+
`.cue-upload::after{content:"";position:absolute;inset:auto 0 0;height:2px;width:var(--cue-upload);background:var(--_comark-primary);transition:width .2s ease-out}`,
|
|
1181
|
+
`@media (prefers-reduced-motion:reduce){.cue-upload::after{transition:none}}`
|
|
1182
|
+
].join("")}}`;
|
|
1170
1183
|
function placeholder(pending) {
|
|
1184
|
+
addStyles("editor-upload", uploadCss);
|
|
1171
1185
|
const root = document.createElement("span");
|
|
1172
1186
|
root.className = "cue-upload";
|
|
1173
1187
|
root.contentEditable = "false";
|
|
@@ -1247,6 +1261,28 @@ function send(view, file, pos, upload, options, batch) {
|
|
|
1247
1261
|
options.onError?.(error, file);
|
|
1248
1262
|
});
|
|
1249
1263
|
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Opens the file picker and uploads what's picked at the cursor, with the same placeholders as a paste. Returns false
|
|
1266
|
+
* without an `upload` callback. A "/" menu's Image item calls it through the `chooseFiles` command.
|
|
1267
|
+
*/
|
|
1268
|
+
function chooseFiles(view, options) {
|
|
1269
|
+
if (!options.upload) return false;
|
|
1270
|
+
const input = document.createElement("input");
|
|
1271
|
+
input.type = "file";
|
|
1272
|
+
input.multiple = true;
|
|
1273
|
+
input.accept = options.pick ?? "image/*,video/*,audio/*";
|
|
1274
|
+
input.style.cssText = "position:fixed;left:-9999px";
|
|
1275
|
+
input.tabIndex = -1;
|
|
1276
|
+
input.addEventListener("change", () => {
|
|
1277
|
+
const files = Array.from(input.files ?? []);
|
|
1278
|
+
input.remove();
|
|
1279
|
+
if (files.length) Promise.all(startUploads(view, files, view.state.selection.from, options));
|
|
1280
|
+
}, { once: true });
|
|
1281
|
+
input.addEventListener("cancel", () => input.remove(), { once: true });
|
|
1282
|
+
document.body.append(input);
|
|
1283
|
+
input.click();
|
|
1284
|
+
return true;
|
|
1285
|
+
}
|
|
1250
1286
|
function insertImage(tr, pos, image) {
|
|
1251
1287
|
if (tr.doc.resolve(pos).parent.inlineContent) tr.insert(pos, image);
|
|
1252
1288
|
else tr.insert(pos, tr.doc.type.schema.nodes.paragraph.create(null, image));
|
|
@@ -1321,17 +1357,21 @@ const Upload = Extension.create({
|
|
|
1321
1357
|
addOptions: () => ({
|
|
1322
1358
|
upload: void 0,
|
|
1323
1359
|
accept: acceptMedia,
|
|
1360
|
+
pick: void 0,
|
|
1324
1361
|
onError: void 0
|
|
1325
1362
|
}),
|
|
1326
1363
|
addProseMirrorPlugins() {
|
|
1327
1364
|
return [uploadPlugin(this.options)];
|
|
1328
1365
|
},
|
|
1329
1366
|
addCommands() {
|
|
1330
|
-
return {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1367
|
+
return {
|
|
1368
|
+
uploadFiles: (files) => ({ view }) => {
|
|
1369
|
+
if (!this.options.upload) return false;
|
|
1370
|
+
Promise.all(startUploads(view, files, view.state.selection.from, this.options));
|
|
1371
|
+
return true;
|
|
1372
|
+
},
|
|
1373
|
+
chooseFiles: () => ({ view, dispatch }) => dispatch ? chooseFiles(view, this.options) : Boolean(this.options.upload)
|
|
1374
|
+
};
|
|
1335
1375
|
}
|
|
1336
1376
|
});
|
|
1337
1377
|
//#endregion
|
|
@@ -1378,4 +1418,4 @@ function createEditorCodec(options = {}) {
|
|
|
1378
1418
|
};
|
|
1379
1419
|
}
|
|
1380
1420
|
//#endregion
|
|
1381
|
-
export {
|
|
1421
|
+
export { DefinitionList as C, InlineFootnote as D, FootnoteReference as E, DefinitionDescription as S, FootnoteDefinition as T, ComarkSlot as _, startUploads as a, CriticHighlight as b, uploadPlugin as c, toMarkdown as d, ComarkBlock as f, ComarkKit as g, ComarkInline as h, findUpload as i, checkMarkdown as l, ComarkImage as m, Upload as n, uploadCss as o, ComarkComment as p, chooseFiles as r, uploadKey as s, createEditorCodec as t, fromMarkdown as u, CriticComment as v, DefinitionTerm as w, CriticInsertion as x, CriticDeletion as y };
|
|
@@ -88,8 +88,10 @@ interface UploadOptions {
|
|
|
88
88
|
* bytes go; `fetch` can't report them, so use XMLHttpRequest's `upload.onprogress`. Without it, nothing uploads.
|
|
89
89
|
*/
|
|
90
90
|
upload?: (file: File, progress: (fraction: number) => void) => Promise<string>;
|
|
91
|
-
/** Which files to take from a paste or
|
|
91
|
+
/** Which files to take from a paste, a drop, or the file picker. Images, video, and audio by default. */
|
|
92
92
|
accept: (file: File) => boolean;
|
|
93
|
+
/** The file types `chooseFiles()` offers in the file picker, as an `<input accept>` value. Media by default. */
|
|
94
|
+
pick?: string;
|
|
93
95
|
/** Called when `upload` rejects. The placeholder is gone by then. */
|
|
94
96
|
onError?: (error: unknown, file: File) => void;
|
|
95
97
|
}
|
|
@@ -110,12 +112,19 @@ interface View {
|
|
|
110
112
|
declare const uploadKey: PluginKey<DecorationSet>;
|
|
111
113
|
/** Where placeholder `id` sits now, after every edit since it was added. */
|
|
112
114
|
declare function findUpload(state: EditorState, id: number): number | undefined;
|
|
115
|
+
/** The placeholder's styles, which the editor adds to the page with the first one. */
|
|
116
|
+
declare const uploadCss: string;
|
|
113
117
|
/**
|
|
114
118
|
* Uploads `files` into the document at `pos`. Each shows a placeholder there until its URL comes back, then becomes an
|
|
115
119
|
* image node, which comark-media plays when its URL ends in a video or audio extension. The placeholder is a
|
|
116
120
|
* decoration, so a collaborative room never sees a file that hasn't finished.
|
|
117
121
|
*/
|
|
118
122
|
declare function startUploads(view: View, files: File[], pos: number, options: UploadOptions): Promise<string | undefined>[];
|
|
123
|
+
/**
|
|
124
|
+
* Opens the file picker and uploads what's picked at the cursor, with the same placeholders as a paste. Returns false
|
|
125
|
+
* without an `upload` callback. A "/" menu's Image item calls it through the `chooseFiles` command.
|
|
126
|
+
*/
|
|
127
|
+
declare function chooseFiles(view: View, options: UploadOptions): boolean;
|
|
119
128
|
/** The plugin alone, for a ProseMirror editor without Tiptap. */
|
|
120
129
|
declare function uploadPlugin(options: UploadOptions): Plugin<DecorationSet>;
|
|
121
130
|
declare module "@tiptap/core" {
|
|
@@ -123,6 +132,8 @@ declare module "@tiptap/core" {
|
|
|
123
132
|
comarkUpload: {
|
|
124
133
|
/** Uploads files at the cursor, as a paste would, such as the ones a file picker returns. */
|
|
125
134
|
uploadFiles: (files: File[]) => ReturnType;
|
|
135
|
+
/** Opens the file picker and uploads the files picked at the cursor, as a "/" menu's Image item would. */
|
|
136
|
+
chooseFiles: () => ReturnType;
|
|
126
137
|
};
|
|
127
138
|
}
|
|
128
139
|
}
|
|
@@ -170,4 +181,4 @@ interface EditorCodecOptions {
|
|
|
170
181
|
}
|
|
171
182
|
declare function createEditorCodec(options?: EditorCodecOptions): EditorCodec;
|
|
172
183
|
//#endregion
|
|
173
|
-
export {
|
|
184
|
+
export { DefinitionDescription as A, ComarkKit as C, CriticDeletion as D, CriticComment as E, InlineFootnote as F, DefinitionTerm as M, FootnoteDefinition as N, CriticHighlight as O, FootnoteReference as P, ComarkInline as S, ComarkSlot as T, fromMarkdown as _, Upload as a, ComarkComment as b, findUpload as c, uploadKey as d, uploadPlugin as f, checkMarkdown as g, SplitMarkdown as h, PendingUpload as i, DefinitionList as j, CriticInsertion as k, startUploads as l, MarkdownCheck as m, EditorCodecOptions as n, UploadOptions as o, EditorDocument as p, createEditorCodec as r, chooseFiles as s, EditorCodec as t, uploadCss as u, toMarkdown as v, ComarkKitOptions as w, ComarkImage as x, ComarkBlock as y };
|
package/dist/editor-vue.d.mts
CHANGED
package/dist/editor-vue.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as isElement, t as childrenOf } from "./nodes-CwDBFXnO.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { d as toMarkdown, g as ComarkKit, l as checkMarkdown, u as fromMarkdown } from "./editor-CW05gHp7.mjs";
|
|
3
3
|
import { Editor, EditorContent } from "@tiptap/vue-3";
|
|
4
4
|
import { rowsOf } from "comark-properties";
|
|
5
5
|
import { Properties } from "comark-properties/vue";
|
package/dist/editor.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
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 };
|
|
1
|
+
import { A as DefinitionDescription, C as ComarkKit, D as CriticDeletion, E as CriticComment, F as InlineFootnote, M as DefinitionTerm, N as FootnoteDefinition, O as CriticHighlight, P as FootnoteReference, S as ComarkInline, T as ComarkSlot, _ as fromMarkdown, a as Upload, b as ComarkComment, c as findUpload, d as uploadKey, f as uploadPlugin, g as checkMarkdown, h as SplitMarkdown, i as PendingUpload, j as DefinitionList, k as CriticInsertion, l as startUploads, m as MarkdownCheck, n as EditorCodecOptions, o as UploadOptions, p as EditorDocument, r as createEditorCodec, s as chooseFiles, t as EditorCodec, u as uploadCss, v as toMarkdown, w as ComarkKitOptions, x as ComarkImage, y as ComarkBlock } from "./editor-Dk8TRweM.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, chooseFiles, createEditorCodec, findUpload, fromMarkdown, startUploads, toMarkdown, uploadCss, uploadKey, uploadPlugin };
|
package/dist/editor.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { f as ComarkBlock, p as ComarkComment, m as ComarkImage, h as ComarkInline, g as ComarkKit, _ as ComarkSlot, v as CriticComment, y as CriticDeletion, b as CriticHighlight, x as CriticInsertion, S as DefinitionDescription, C as DefinitionList, w as DefinitionTerm, T as FootnoteDefinition, E as FootnoteReference, D as InlineFootnote, n as Upload, l as checkMarkdown, r as chooseFiles, t as createEditorCodec, i as findUpload, u as fromMarkdown, a as startUploads, d as toMarkdown, o as uploadCss, s as uploadKey, c as uploadPlugin } from "./editor-CW05gHp7.mjs";
|
package/dist/html.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as commentsCss, n as Preset, o as criticCss, r as PresetOptions } from "./index-
|
|
1
|
+
import { a as commentsCss, n as Preset, o as criticCss, r as PresetOptions } from "./index-7SBL13EQ.mjs";
|
|
2
2
|
import { NodeHandler } from "comark";
|
|
3
3
|
//#region src/html.d.ts
|
|
4
4
|
/**
|
package/dist/html.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as criticCss, r as preset, t as commentsCss } from "./src-
|
|
2
|
-
import { t as typesetCss } from "./typeset-css-
|
|
3
|
-
import { a as start, n as mermaidBar, r as mermaidCss } from "./client-
|
|
1
|
+
import { n as criticCss, r as preset, t as commentsCss } from "./src-C3nMECXA.mjs";
|
|
2
|
+
import { t as typesetCss } from "./typeset-css-BqBoCTMG.mjs";
|
|
3
|
+
import { a as start, n as mermaidBar, r as mermaidCss } from "./client-zWY8yhUx.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";
|
|
@@ -38,6 +38,7 @@ import * as steps from "comark-steps/html";
|
|
|
38
38
|
import * as table from "comark-table/html";
|
|
39
39
|
import * as tabs from "comark-tabs/html";
|
|
40
40
|
import * as tests from "comark-tests/html";
|
|
41
|
+
import * as time from "comark-time/html";
|
|
41
42
|
import * as zoom from "comark-zoom/html";
|
|
42
43
|
//#region ../../shared/html.ts
|
|
43
44
|
/** A value as the inside of a double-quoted HTML attribute. */
|
|
@@ -66,7 +67,7 @@ const mermaid = (zooms) => ({
|
|
|
66
67
|
const width = /^<div[^>]*><svg[^>]* width="([\d.]+)"/.exec(card)?.[1];
|
|
67
68
|
const wide = width ? ` data-wide style="--cmm-svg:${Math.ceil(Number(width))}px"` : "";
|
|
68
69
|
const zoomed = zooms && width ? card.replace(/^<div/, "<div data-zoom tabindex=\"0\"") : card;
|
|
69
|
-
return (first ? `<style data-comark-mermaid>${mermaidCss}</style>` : "") + `<div class="cmm"${wide} data-source="${attr(source)}">${zoomed}<div class="cmm-bar">${mermaidBar()}</div>${first ? `<script>${mermaidScript}<\/script>` : ""}${zooms && width ? zoom.runtime(state) : ""}</div>`;
|
|
70
|
+
return (first ? `<style data-comark-mermaid>${mermaidCss}</style>` : "") + `<div class="cmm" data-comark-frame${wide} data-source="${attr(source)}">${zoomed}<div class="cmm-bar">${mermaidBar()}</div>${first ? `<script>${mermaidScript}<\/script>` : ""}${zooms && width ? zoom.runtime(state) : ""}</div>`;
|
|
70
71
|
}) }
|
|
71
72
|
});
|
|
72
73
|
const typeset = () => `<style data-comark-typeset>${typesetCss}</style>`;
|
|
@@ -107,6 +108,7 @@ var html_default = (options) => preset({
|
|
|
107
108
|
table,
|
|
108
109
|
tabs,
|
|
109
110
|
tests,
|
|
111
|
+
time,
|
|
110
112
|
typeset,
|
|
111
113
|
zoom
|
|
112
114
|
}, options);
|
|
@@ -28,6 +28,7 @@ import { QuizOptions } from "comark-quiz";
|
|
|
28
28
|
import { TableOptions } from "comark-table";
|
|
29
29
|
import { TabsOptions } from "comark-tabs";
|
|
30
30
|
import { TestsOptions } from "comark-tests";
|
|
31
|
+
import { TimeOptions } from "comark-time";
|
|
31
32
|
import { ZoomOptions } from "comark-zoom";
|
|
32
33
|
//#region src/mermaid-limits.d.ts
|
|
33
34
|
type MermaidOptions = PeleConfig & {
|
|
@@ -62,7 +63,7 @@ type ShikiOptions = {
|
|
|
62
63
|
* and report a limit issue without checking whether the language is supported.
|
|
63
64
|
*/
|
|
64
65
|
highlighter: (language: string) => ShikiHighlighter | undefined | Promise<ShikiHighlighter | undefined>;
|
|
65
|
-
/** Theme names your highlighter has loaded. The dark one goes in `--shiki-dark`, which
|
|
66
|
+
/** Theme names your highlighter has loaded. The dark one goes in `--shiki-dark`, which typeset and comark-code's styles follow. */
|
|
66
67
|
themes: {
|
|
67
68
|
light: string;
|
|
68
69
|
dark?: string;
|
|
@@ -154,6 +155,8 @@ type PresetOptions = {
|
|
|
154
155
|
xref?: XrefOptions | boolean;
|
|
155
156
|
/** Reads no JUnit reports unless you pass `{ root }` or `{ read }`; inline reports render either way. */
|
|
156
157
|
tests?: TestsOptions | boolean;
|
|
158
|
+
/** `:time[2026-10-12]` as relative time: "3 days ago", a date past `threshold`, and the full date in a tooltip. */
|
|
159
|
+
time?: TimeOptions | boolean;
|
|
157
160
|
zoom?: ZoomOptions | boolean;
|
|
158
161
|
/**
|
|
159
162
|
* Highlights fenced code with your own Shiki, before comark-code. Off unless you pass one, so the preset bundles no
|
|
@@ -220,6 +223,7 @@ type Renderer<C> = {
|
|
|
220
223
|
tabs: Part<C, TabsOptions>;
|
|
221
224
|
outline: Part<C, OutlineOptions>;
|
|
222
225
|
tests: Part<C, TestsOptions>;
|
|
226
|
+
time: Part<C, TimeOptions>;
|
|
223
227
|
zoom: Part<C, ZoomOptions>;
|
|
224
228
|
code: Part<C, CodeOptions>;
|
|
225
229
|
codeNotes: Part<C>;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as typesetStylesheet, t as typesetCss } from "./typeset-css-DtOLAa1Z.mjs";
|
|
2
|
-
import { a as commentsCss, c as TYPESET_TAG, d as ShikiOptions, f as MermaidOptions, i as Renderer, l as typeset, n as Preset, o as criticCss, r as PresetOptions, s as preset, t as AnyPlugin, u as ShikiHighlighter } from "./index-
|
|
2
|
+
import { a as commentsCss, c as TYPESET_TAG, d as ShikiOptions, f as MermaidOptions, i as Renderer, l as typeset, n as Preset, o as criticCss, r as PresetOptions, s as preset, t as AnyPlugin, u as ShikiHighlighter } from "./index-7SBL13EQ.mjs";
|
|
3
3
|
export { AnyPlugin, type MermaidOptions, Preset, PresetOptions, Renderer, type ShikiHighlighter, type ShikiOptions, TYPESET_TAG, commentsCss, criticCss, preset, typeset, typesetCss, typesetStylesheet };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as typeset, i as TYPESET_TAG, n as criticCss, r as preset, t as commentsCss } from "./src-
|
|
2
|
-
import { n as typesetStylesheet, t as typesetCss } from "./typeset-css-
|
|
1
|
+
import { a as typeset, i as TYPESET_TAG, n as criticCss, r as preset, t as commentsCss } from "./src-C3nMECXA.mjs";
|
|
2
|
+
import { n as typesetStylesheet, t as typesetCss } from "./typeset-css-BqBoCTMG.mjs";
|
|
3
3
|
export { TYPESET_TAG, commentsCss, criticCss, preset, typeset, typesetCss, typesetStylesheet };
|
package/dist/react.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as commentsCss, n as Preset, o as criticCss, r as PresetOptions } from "./index-
|
|
1
|
+
import { a as commentsCss, n as Preset, o as criticCss, r as PresetOptions } from "./index-7SBL13EQ.mjs";
|
|
2
2
|
import { ComponentType } from "react";
|
|
3
3
|
//#region src/react.d.ts
|
|
4
4
|
type AnyComponent = ComponentType<any>;
|
package/dist/react.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as criticCss, r as preset, t as commentsCss } from "./src-
|
|
2
|
-
import { t as typesetCss } from "./typeset-css-
|
|
3
|
-
import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-
|
|
1
|
+
import { n as criticCss, r as preset, t as commentsCss } from "./src-C3nMECXA.mjs";
|
|
2
|
+
import { t as typesetCss } from "./typeset-css-BqBoCTMG.mjs";
|
|
3
|
+
import { i as startMermaid, n as mermaidBar, t as addMermaidStyles } from "./client-zWY8yhUx.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";
|
|
@@ -33,6 +33,7 @@ import * as steps from "comark-steps/react";
|
|
|
33
33
|
import * as table from "comark-table/react";
|
|
34
34
|
import * as tabs from "comark-tabs/react";
|
|
35
35
|
import * as tests from "comark-tests/react";
|
|
36
|
+
import * as time from "comark-time/react";
|
|
36
37
|
import * as zoom from "comark-zoom/react";
|
|
37
38
|
//#region src/react.ts
|
|
38
39
|
const Pele = createPeleComponent({
|
|
@@ -43,6 +44,7 @@ function Mermaid(props) {
|
|
|
43
44
|
useEffect(() => startMermaid(), []);
|
|
44
45
|
return createElement("div", {
|
|
45
46
|
className: "cmm",
|
|
47
|
+
"data-comark-frame": "",
|
|
46
48
|
"data-source": props.content
|
|
47
49
|
}, createElement(Pele, props), createElement("div", {
|
|
48
50
|
className: "cmm-bar",
|
|
@@ -90,6 +92,7 @@ var react_default = (options) => preset({
|
|
|
90
92
|
table,
|
|
91
93
|
tabs,
|
|
92
94
|
tests,
|
|
95
|
+
time,
|
|
93
96
|
typeset,
|
|
94
97
|
zoom
|
|
95
98
|
}, options);
|
|
@@ -19,7 +19,7 @@ An animation, a transition, a loading sequence, or feedback while dragging happe
|
|
|
19
19
|
|
|
20
20
|
## 1. Capture a comparable pair
|
|
21
21
|
|
|
22
|
-
Take the after capture from the branch or its preview, and the before capture 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.
|
|
22
|
+
Take the after capture from the branch or its preview, and the before capture from a deployment, or from a separate worktree at the base commit, that still behaves the old way. Do not use `git stash` to switch between them: worktrees of one repository share the stash, so another session can pop your changes. Capture both with the same viewport, zoom, theme, data, scroll position, and open menus, so the change is the only difference.
|
|
23
23
|
|
|
24
24
|
- Set up the state that shows the change before you capture: the long name that wrapped, the error that stayed hidden, the empty list.
|
|
25
25
|
- 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.
|
|
@@ -17,6 +17,8 @@ pnpm add comark @comark/html comark-ui
|
|
|
17
17
|
|
|
18
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
19
|
|
|
20
|
+
If pnpm reports `ERR_PNPM_TRUST_DOWNGRADE`, read [pnpm trust errors](https://comark-ui.onmax.me/getting-started/#pnpm-trust-errors) for the publishing history and exact-version exceptions. Apply an exception only when the user's instructions authorize the policy change.
|
|
21
|
+
|
|
20
22
|
```ts
|
|
21
23
|
import { createHtmlRenderer } from "@comark/html";
|
|
22
24
|
import comarkUi from "comark-ui/html";
|
|
@@ -51,7 +53,7 @@ Problems land in `meta.issues` after `parseMarkdown`, one sentence each, and a h
|
|
|
51
53
|
|
|
52
54
|
## Shape the document
|
|
53
55
|
|
|
54
|
-
This skill teaches the blocks.
|
|
56
|
+
This skill teaches the blocks. Five sibling skills teach a whole document, what goes in it and in what order: `before-after-evidence`, `review-document`, `report`, `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.
|
|
55
57
|
|
|
56
58
|
## Pick the blocks
|
|
57
59
|
|
|
@@ -77,6 +79,7 @@ Read the reference for each block you use before you write it. Mix the blocks fr
|
|
|
77
79
|
| A test run's passed, failed, and skipped counts and its failures, from a JUnit report | `::tests` | [tests](references/tests.md) |
|
|
78
80
|
| A note, tip, or warning box, or long output folded away, as on GitHub | `> [!NOTE]`, `<details>` | [github](references/github.md) |
|
|
79
81
|
| The review's status, pull request, author, date, and tags in a box | frontmatter, `::properties` | [properties](references/properties.md) |
|
|
82
|
+
| When something happened or is due, as "2 days ago" or a date, with the full date on hover | `:time[2026-11-02]` | [time](references/time.md) |
|
|
80
83
|
| Claims backed by links, with numbered citations | `:cite`, `::sources` | [sources](references/sources.md) |
|
|
81
84
|
| An icon in a sentence or a table cell, such as a logo or a status | `:icon` | [icon](references/icon.md) |
|
|
82
85
|
| A preview link a reviewer can scan with a phone | `:qr` | [qr](references/qr.md) |
|
|
@@ -35,7 +35,7 @@ Moon phase and vibes don't fit, so the toolbar wraps and pushes the table down.
|
|
|
35
35
|
|
|
36
36
|
- `::compare`: `title` is optional and names the comparison for screen readers. `mode` is `columns` (default, side by side) or `slider`. `ink` colors every annotation in both screenshots that doesn't set its own `color`. Its two screenshots are titled Before and After unless they set their own.
|
|
37
37
|
- `::screenshot`: `src` and `alt` are required; `title` (the heading above the image), `href` (the captured page, linked by its host), `viewport` (like `1440x900`), and `ink` are optional. Other content becomes the caption.
|
|
38
|
-
- `::annotation`: `x y w h` are required percentages of the image, numbers from 0 to 100 without `%`. `mark` is `box` (default), `circle`, `underline`, `line` (straight through the middle, along the longer side), `highlight`, `bracket
|
|
38
|
+
- `::annotation`: `x y w h` are required percentages of the image, numbers from 0 to 100 without `%`. `mark` is `box` (default), `circle`, `underline`, `line` (straight through the middle, along the longer side), `highlight`, `bracket` (down the side of a box at least twice as tall as it is wide, and across a wide one, whatever its `side`), `cross`, `check`, or `none`, which needs a `side`. `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. `side` is `top`, `bottom`, `left`, or `right`; keep top or bottom notes at least a quarter of the image apart and left or right notes an eighth apart.
|
|
39
39
|
- `at` moves a side note along its margin, as a percentage of the image (across for top and bottom, down for left and right). A note set off from its point reads away from it, so put it on the side with room: a top note at `at=40` pointing left of 40 reads to the right. `to="x,y"` is the point the arrow lands on, measured like the box; without it the arrow meets the middle of the mark's facing edge. Both need a `side`.
|
|
40
40
|
- Annotations must be direct children of a screenshot.
|
|
41
41
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Dates as relative time
|
|
2
|
+
|
|
3
|
+
comark-time draws `:time[2026-11-02]` as the words a reader would say, such as "2 days ago" or "in 3 weeks", and past a threshold as a date, such as "Sep 30". Hover or focus opens a tooltip with the full date and time. Use it where a document says when something happened or will: when a pull request opened, when a benchmark ran, when a deadline falls. Write the date in ISO form and leave the words to the plugin.
|
|
4
|
+
|
|
5
|
+
## Syntax
|
|
6
|
+
|
|
7
|
+
```md
|
|
8
|
+
Opened :time[2026-11-02], last run :time[2026-11-04T11:59:56Z], due :time[2026-11-25].
|
|
9
|
+
|
|
10
|
+
The first sighting was :time[2026-09-30]{threshold="8w"}.
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- The date goes in the brackets: `2026-11-02`, or with a time of day, `2026-11-02T09:30:00Z`. A date alone counts whole days, so it reads "today", "yesterday", "2 days ago", or "in 3 days", never hours. A time with no offset is in the `timeZone` option's zone.
|
|
14
|
+
- `threshold` sets how far from now it stops reading as relative: a number and `s`, `m`, `h`, `d`, `w`, `mo`, or `y`, such as `3w`. The default is `4w`. `none` keeps it relative at any distance.
|
|
15
|
+
- Past the threshold the text is "Sep 30", or "Sep 30, 2025" in another year, and the tooltip shows how far away it is. Before it, the tooltip shows the full date, with the time and zone when the date has one.
|
|
16
|
+
- No script runs. The words are worked out while parsing and are fixed in rendered HTML, so "2 days ago" stays that until the document is parsed again.
|
|
17
|
+
|
|
18
|
+
## Write the dates
|
|
19
|
+
|
|
20
|
+
- Write the exact date from the source: the pull request's `createdAt`, the report's timestamp. Never write "yesterday" or "last week" in prose for a date a reader may look up.
|
|
21
|
+
- A zone-less date and time is read in the host's zone, which the reader can't see. Give a time an offset, such as `Z`, unless it is the author's local time.
|
|
22
|
+
- A date the plugin can't read, such as `next friday` or `2026-02-31`, is shown as written and reported. Fix it to ISO.
|
|
23
|
+
- Leave the threshold alone unless a date is better read as one or the other: `threshold="none"` for a date that must always read relative, a short one such as `1w` for a date that is better absolute within a month.
|
|
24
|
+
|
|
25
|
+
## Register
|
|
26
|
+
|
|
27
|
+
- In `comark-ui`: on. `comarkUi({ time: { now, threshold, locale, timeZone } })` passes its options; `now` pins the moment every date is measured from, which a host that caches rendered HTML should do. `time: false` turns it off.
|
|
28
|
+
- Alone: `pnpm add comark @comark/html comark-time`, then `createHtmlRenderer({ plugins: [time()], components })` with both from `comark-time/html`. `comark-time/vue`, `/react`, and `/svelte` render it too. It has no script.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: report
|
|
3
|
+
description: Writes research, a ranked shortlist, a comparison, or a plan as one Markdown page someone decides from, with the answer and decisions first, the numbers in charts, and the detail folded. Use when the reader has to choose between options or approve a plan.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Report
|
|
8
|
+
|
|
9
|
+
The reader scans a report before reading it. They want the answer, the decisions they have to make, and the numbers behind both, and they check the rest only when they doubt something. So the page has a fixed shape: the answer on top, one picture per section, short table cells, and detail folded underneath.
|
|
10
|
+
|
|
11
|
+
For findings about something that already exists, such as a code review or an audit, use `review-document`. For how something works, use `visual-explanation`. This skill shapes a report you already have the material for; gathering the material is the research itself.
|
|
12
|
+
|
|
13
|
+
## 1. Write the answer and the decisions
|
|
14
|
+
|
|
15
|
+
Write the answer in two sentences: what you recommend and the main reason. For a plan, that's the first step and the goal it serves. Under it, write each decision the reader has to make as a question they can answer yes or no. Up to three go in the callout as a numbered list. Past three, ask one question ("Take my recommendation on all seven?") and give the decisions their own table right after the answer.
|
|
16
|
+
|
|
17
|
+
Done when someone who reads only the answer and the callout knows what you recommend and what they have to decide.
|
|
18
|
+
|
|
19
|
+
## 2. Give every fact one home
|
|
20
|
+
|
|
21
|
+
List what the report holds: the items, their numbers, any order, the blockers, the findings, what you left out, how you checked. Give each one exactly one section. When the items have both a rank and an order to do them in, keep the order the reader acts on and put the rank's inputs, if it has any, in the picture. Another section refers to a fact by its name or number ("change 3", "D2") and leaves its value where it lives. Reuse the source's numbering; when you have to add IDs, give each kind a prefix no other kind starts with. A chart's table is already under it in a Data fold, so it gets no second copy.
|
|
22
|
+
|
|
23
|
+
Done when no value, list, or order is written out in two sections.
|
|
24
|
+
|
|
25
|
+
## 3. Pick one picture per section
|
|
26
|
+
|
|
27
|
+
A section opens with one sentence that says what to look at, then its picture. A second picture fits only when it answers the question the first one raises, such as the table of steps under the flowchart of their order. Numbers live in charts and table columns, and a sentence names only the number that matters.
|
|
28
|
+
|
|
29
|
+
| The section answers | Picture |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| Which items are worth it, by two scores and a size | Bubble chart: `::chart` with a `vega-lite` spec |
|
|
32
|
+
| How big each item is, or what each blocker would unlock | `::chart{type="bar" horizontal labels}` |
|
|
33
|
+
| How each item's size splits into parts | `::chart{type="stacked" horizontal}` |
|
|
34
|
+
| In what order, and what has to land first | `mermaid` flowchart, blockers as `{{hexagons}}` |
|
|
35
|
+
| How a mechanism moves between states | `mermaid` `stateDiagram-v2` |
|
|
36
|
+
| A number before and after | `::stats` or `:delta` |
|
|
37
|
+
| A share of a whole | `:progress` |
|
|
38
|
+
| Findings, options, or decisions compared across the same columns | Table of at most five columns, `:badge` for categories and verdicts |
|
|
39
|
+
| Detail per item | `::accordion`, one item per row, the same parts inside every item |
|
|
40
|
+
| What you left out | Bar chart of the largest, then `::accordion` grouped by reason |
|
|
41
|
+
| How you checked | `> [!CAUTION]` for what you didn't verify, kept open; sources in `::details` |
|
|
42
|
+
|
|
43
|
+
A table cell holds a value, a badge, or a few words. The reasoning behind a cell goes in that item's accordion. A column whose cells are all the same says one thing, so say it in the section's sentence and drop the column. Two charts that compare the same unit share one scale, so draw them as one chart, or fix the axis domain in both.
|
|
44
|
+
|
|
45
|
+
[references/blocks.md](references/blocks.md) has the syntax for each, with the gotchas the renderer doesn't report. The `comark-ui` skill documents every block in full.
|
|
46
|
+
|
|
47
|
+
Done when every section opens with its sentence and its picture, every number the answer rests on sits in a chart or a table column, and no table cell runs past a line or two at reading width.
|
|
48
|
+
|
|
49
|
+
## 4. Assemble the page
|
|
50
|
+
|
|
51
|
+
The two ends are fixed, and the middle holds the sections your material has, in the order the reader acts on them:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
Frontmatter title = the answer in under 70 characters; status, date, tags
|
|
55
|
+
The answer two sentences, the decisions in > [!IMPORTANT], at most one > [!WARNING]
|
|
56
|
+
Decisions the decision table, only when there are more than three
|
|
57
|
+
middle from step 3, such as: the picture the answer rests on, findings,
|
|
58
|
+
the order, blockers, before and after, each item, left out
|
|
59
|
+
How I checked > [!CAUTION] open, sources folded
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Keep open what the reader decides on, and fold what they would only check: each decision and your recommendation stay open in the callout or the decision table, and the arguments behind them fold. When each item fits one table row, the table is the item list and needs no accordion. Past about a dozen items, the table is the list, and only the items that need reasoning get an accordion item. Lists stay one level deep; a second level becomes a table. Cards are for links to the next pages only.
|
|
63
|
+
|
|
64
|
+
[references/example.md](references/example.md) is a full report in this shape: the answer and decision on top, a bubble chart, one order shown as a flowchart and a table, blockers sized by what they unlock, one accordion item per module with the same parts, the left-out chart with its groups, and the open "Not verified" callout.
|
|
65
|
+
|
|
66
|
+
Done when the page follows this order and nothing the reader decides on is folded.
|
|
67
|
+
|
|
68
|
+
## 5. Check the render
|
|
69
|
+
|
|
70
|
+
1. Lint it, and fix until it reports no problems:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
vp dlx --package comark-linter --package comark-ui comark-lint report.md
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
These commands use the [global Vite+ CLI](https://viteplus.dev/guide/global-cli). Without it, `pnpm dlx` takes the same arguments.
|
|
77
|
+
|
|
78
|
+
2. Render it as [references/blocks.md](references/blocks.md#render-it-locally) shows, and read every screenshot, closed and open. Labels must not overlap, each chart must say what its sentence says, table cells must stay short, and diagram text must read at the size of the body text. A wide flowchart renders small: switch it to `TB`, shorten its labels, or split it past about ten nodes.
|
|
79
|
+
3. Hand it over where the task says. When it goes to a host such as Drop, publish it as the host's skill describes and give the link.
|
|
80
|
+
|
|
81
|
+
Done when lint reports no problems and you've read every screenshot of the render.
|