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/dist/skills.mjs
CHANGED
|
@@ -12,7 +12,7 @@ const skills = [
|
|
|
12
12
|
"files": [
|
|
13
13
|
{
|
|
14
14
|
"path": "SKILL.md",
|
|
15
|
-
"text": "---\nname: before-after-evidence\ndescription: Shows a visible change as two comparable screenshots, before and after, with marks measured from the source and checked in the rendered page, and a change in motion as a short recording. Use when a pull request, bug fix, or design change alters what people see or how it moves, or when someone asks for before/after screenshots, a recording, or visual evidence.\nlicense: MIT\n---\n\n# Before/after evidence\n\nA before/after is evidence when a reviewer can check every claim against the two images and nothing else. The reader can't repair a bad pair, so get three things right: two captures of the same view, marks on what changed, and notes in the words the screen shows.\n\nThis skill writes the document. Your host decides where it's published and who can open it.\n\n## When the change is motion\n\nAn animation, a transition, a loading sequence, or feedback while dragging happens between frames, so record it. A still taken partway through a motion looks like a broken layout, and two of them can't show that anything moves. Follow [references/recording.md](references/recording.md) in place of steps 1 and 2, then write and check the document with steps 3 and 4, with the clip where the `::compare` goes.\n\n- When the old behavior jumped straight to its end state, the after clip alone is the evidence, and the document's first sentence says what happened before.\n- When the change retimes motion that already existed, such as its easing, its duration, or a stutter, record both sides with the same script.\n\n## 1. Capture a comparable pair\n\nTake 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.\n\n- Set up the state that shows the change before you capture: the long name that wrapped, the error that stayed hidden, the empty list.\n- 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.\n- Pin anything that moves the layout between the two states, such as a vertically centered dialog that grows by one line, so the same element sits at the same place in both.\n\nIf you can't capture one side comparably, stop and say what blocked you. A description in place of a screenshot, or one side alone, isn't before/after evidence.\n\nDone when both images exist, have the same size, and differ only where the change is.\n\n## 2. Measure every mark\n\nMarks take `x`, `y`, `w`, and `h` as percentages of the image. Measure them. An estimate drifts a few percent, which on a half-width image lands on the neighbouring button.\n\n- **A page you captured:** read the element's box in the same browser, viewport, and state, minus the clip's offset. [references/measuring.md](references/measuring.md) has the snippet.\n- **A still you didn't capture**, such as a frame from a screen recording, a design export, or an image attached to a bug report: there is no DOM to ask. Measure in image pixels and divide by the image's natural width and height. Confirm each box by cropping the image to it and looking at the crop, as the same reference shows.\n\nMark the change at its own size: the field, the label, the button. Use `red` on the before side for the problem and `green` on the after side for the fix, and leave what stayed the same unmarked. Two or three marks a side usually tell the whole story.\n\nDone when every mark has numbers you measured, and you can name the element or the pixel rectangle each came from.\n\n## 3. Write the document\n\nStart from [references/example.md](references/example.md) and keep its shape:\n\n1. One sentence: what changed, and for whom.\n2. One line of steps to reach the screen, so a reviewer can reproduce it.\n3. The `::compare`, each note a few words in the screen's own words. A value the screenshot doesn't show stays out of its note.\n4. A `::diff` of the line that caused it, only when the reader would otherwise ask.\n\nThe images carry the evidence, so the text only points into them. Put how the work went (checks, retries, CI) in your report to the user, not in the document. [references/blocks.md](references/blocks.md) has the syntax of each block.\n\nImage `src` must be a URL the renderer can reach: a path next to the document, or an https URL your host gives you for the upload.\n\n## 4. Check the render\n\n1. Lint it, and fix until it reports no problems:\n\n ```sh\n vp dlx --package comark-linter --package comark-ui comark-lint evidence.md\n ```\n\n These commands use the [global Vite+ CLI](https://viteplus.dev/guide/global-cli). Without it, `pnpm dlx` takes the same arguments.\n\n2. Open the page where it will be read and take a screenshot of it. To check the screenshots alone, copy the whole `::compare` block, nested screenshots and annotations included, into `evidence-screenshots.md` beside `evidence.md`.\n\n Make comark-annotate a direct dependency of this workspace package. A transitive one isn't linked, and `@latest` can run a different release from the project's renderers. Match the version of the installed Comark UI packages: `pnpm add -D comark-annotate@<version>`, with `-w` at the workspace root. With no Comark UI packages installed, omit `@<version>`. Then run:\n\n ```sh\n vp exec comark-annotate render evidence-screenshots.md -o evidence-screenshots.html\n ```\n\n Without Vite+, use `pnpm exec` with the same arguments. Open `evidence-screenshots.html` and take a screenshot of it. This CLI renders annotate, carousel, and zoom only; check any `::diff` or other blocks in the host's full Comark UI preview.\n\n3. Check that each mark sits on its target and each note matches what its side shows. A clip should start by itself and loop in the page. Measure a mark that misses again from the source; a box moved by eye stays a guess.\n\nDone when you've looked at the rendered page and every mark lands. Name any mark or block you couldn't check in your report.\n"
|
|
15
|
+
"text": "---\nname: before-after-evidence\ndescription: Shows a visible change as two comparable screenshots, before and after, with marks measured from the source and checked in the rendered page, and a change in motion as a short recording. Use when a pull request, bug fix, or design change alters what people see or how it moves, or when someone asks for before/after screenshots, a recording, or visual evidence.\nlicense: MIT\n---\n\n# Before/after evidence\n\nA before/after is evidence when a reviewer can check every claim against the two images and nothing else. The reader can't repair a bad pair, so get three things right: two captures of the same view, marks on what changed, and notes in the words the screen shows.\n\nThis skill writes the document. Your host decides where it's published and who can open it.\n\n## When the change is motion\n\nAn animation, a transition, a loading sequence, or feedback while dragging happens between frames, so record it. A still taken partway through a motion looks like a broken layout, and two of them can't show that anything moves. Follow [references/recording.md](references/recording.md) in place of steps 1 and 2, then write and check the document with steps 3 and 4, with the clip where the `::compare` goes.\n\n- When the old behavior jumped straight to its end state, the after clip alone is the evidence, and the document's first sentence says what happened before.\n- When the change retimes motion that already existed, such as its easing, its duration, or a stutter, record both sides with the same script.\n\n## 1. Capture a comparable pair\n\nTake 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.\n\n- Set up the state that shows the change before you capture: the long name that wrapped, the error that stayed hidden, the empty list.\n- 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.\n- Pin anything that moves the layout between the two states, such as a vertically centered dialog that grows by one line, so the same element sits at the same place in both.\n\nIf you can't capture one side comparably, stop and say what blocked you. A description in place of a screenshot, or one side alone, isn't before/after evidence.\n\nDone when both images exist, have the same size, and differ only where the change is.\n\n## 2. Measure every mark\n\nMarks take `x`, `y`, `w`, and `h` as percentages of the image. Measure them. An estimate drifts a few percent, which on a half-width image lands on the neighbouring button.\n\n- **A page you captured:** read the element's box in the same browser, viewport, and state, minus the clip's offset. [references/measuring.md](references/measuring.md) has the snippet.\n- **A still you didn't capture**, such as a frame from a screen recording, a design export, or an image attached to a bug report: there is no DOM to ask. Measure in image pixels and divide by the image's natural width and height. Confirm each box by cropping the image to it and looking at the crop, as the same reference shows.\n\nMark the change at its own size: the field, the label, the button. Use `red` on the before side for the problem and `green` on the after side for the fix, and leave what stayed the same unmarked. Two or three marks a side usually tell the whole story.\n\nDone when every mark has numbers you measured, and you can name the element or the pixel rectangle each came from.\n\n## 3. Write the document\n\nStart from [references/example.md](references/example.md) and keep its shape:\n\n1. One sentence: what changed, and for whom.\n2. One line of steps to reach the screen, so a reviewer can reproduce it.\n3. The `::compare`, each note a few words in the screen's own words. A value the screenshot doesn't show stays out of its note.\n4. A `::diff` of the line that caused it, only when the reader would otherwise ask.\n\nThe images carry the evidence, so the text only points into them. Put how the work went (checks, retries, CI) in your report to the user, not in the document. [references/blocks.md](references/blocks.md) has the syntax of each block.\n\nImage `src` must be a URL the renderer can reach: a path next to the document, or an https URL your host gives you for the upload.\n\n## 4. Check the render\n\n1. Lint it, and fix until it reports no problems:\n\n ```sh\n vp dlx --package comark-linter --package comark-ui comark-lint evidence.md\n ```\n\n These commands use the [global Vite+ CLI](https://viteplus.dev/guide/global-cli). Without it, `pnpm dlx` takes the same arguments.\n\n2. Open the page where it will be read and take a screenshot of it. To check the screenshots alone, copy the whole `::compare` block, nested screenshots and annotations included, into `evidence-screenshots.md` beside `evidence.md`.\n\n Make comark-annotate a direct dependency of this workspace package. A transitive one isn't linked, and `@latest` can run a different release from the project's renderers. Match the version of the installed Comark UI packages: `pnpm add -D comark-annotate@<version>`, with `-w` at the workspace root. With no Comark UI packages installed, omit `@<version>`. Then run:\n\n ```sh\n vp exec comark-annotate render evidence-screenshots.md -o evidence-screenshots.html\n ```\n\n Without Vite+, use `pnpm exec` with the same arguments. Open `evidence-screenshots.html` and take a screenshot of it. This CLI renders annotate, carousel, and zoom only; check any `::diff` or other blocks in the host's full Comark UI preview.\n\n3. Check that each mark sits on its target and each note matches what its side shows. A clip should start by itself and loop in the page. Measure a mark that misses again from the source; a box moved by eye stays a guess.\n\nDone when you've looked at the rendered page and every mark lands. Name any mark or block you couldn't check in your report.\n"
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"path": "agents/openai.yaml",
|
|
@@ -46,7 +46,7 @@ const skills = [
|
|
|
46
46
|
"files": [
|
|
47
47
|
{
|
|
48
48
|
"path": "SKILL.md",
|
|
49
|
-
"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\nTo move a project to Comark, start with `migrate-to-comark`, which finds its Markdown libraries and runs the migrator for each; see the [migrate-to-comark reference](references/migrate-to-comark.md). 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. One that renders with markdown-it moves with `markdown-it-to-comark`; see the [markdown-it-to-comark reference](references/markdown-it-to-comark.md). A project on marked moves with `marked-to-comark`; see the [marked-to-comark reference](references/marked-to-comark.md).\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 | `` | [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 | `` | [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"
|
|
49
|
+
"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\nIf 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.\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\nTo move a project to Comark, start with `migrate-to-comark`, which finds its Markdown libraries and runs the migrator for each; see the [migrate-to-comark reference](references/migrate-to-comark.md). 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. One that renders with markdown-it moves with `markdown-it-to-comark`; see the [markdown-it-to-comark reference](references/markdown-it-to-comark.md). A project on marked moves with `marked-to-comark`; see the [marked-to-comark reference](references/marked-to-comark.md).\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. 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.\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 | `` | [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| 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) |\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 | `` | [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"
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"path": "agents/openai.yaml",
|
|
@@ -54,7 +54,7 @@ const skills = [
|
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
"path": "references/annotate.md",
|
|
57
|
-
"text": "# Screenshots and compares\n\ncomark-annotate draws `::screenshot` and `::compare`. Their marks take the values under Marks in SKILL.md.\n\n## Choose the layout\n\nPick what explains the change best:\n\n- One `::screenshot` with marks, when a single state shows the point.\n- A `::compare` of two screenshots, when the change is the point. Capture both at the same viewport, zoom, scroll, and page state, and set the same `viewport` on both. Each image gets under half the page width, where the text of a full-page capture is too small to read, so clip both captures to the same region around the change, such as Playwright's `screenshot({ clip })`.\n- `::compare{mode=\"slider\"}` instead, when both screenshots show the same spot and the change is small, such as a number or a label at the same place. The reader drags a divider across one full-width image instead of reading two half-width ones. Side notes become numbered notes there, so skip `side`.\n- A side note for the two or three points that matter most. With `mark=\"none\"` and `to` on the exact spot, the arrow alone points and nothing covers the screen, which reads cleanest. Other notes stay numbered in the list under the image. In a side-by-side compare only `top` and `bottom` keep their arrows; left and right notes join the list.\n\nFor a review the reader should follow without a key, write it the way a designer marks up a board: the default ink with no `color`, every note a side note in the margin nearest its point, set off with `at` so its arrow sweeps in, and a `bracket` where a note covers a run of rows. Write the notes of a compare as a pair that argue the change: \"The type gets the headline…\" on the before, \"The message becomes the starting point.\" on the after.\n\nMark the change at its own size: the bar, field, or label that changed. What stayed the same goes unmarked, or in one sentence above the screenshots. Keep it to about six marks per screenshot, fewer in a compare.\n\n## Syntax\n\n```md\n::compare\n:::screenshot{src=\"before.png\" alt=\"Filter toolbar wrapping onto a second row\" viewport=\"1440x900\" href=\"https://pr12.preview.example.com/correlations\"}\n::::annotation{x=3.6 y=20.6 w=92.9 h=31.4 color=\"red\"}\nMoon phase and vibes don't fit, so the toolbar wraps and pushes the table down.\n::::\n:::\n\n:::screenshot{src=\"after.png\" alt=\"Filter toolbar on one row\" viewport=\"1440x900\"}\n::::annotation{x=87.6 y=24.2 w=7.4 h=9.9 mark=\"check\" color=\"green\"}\n**Apply** lines up with the chips.\n::::\n:::\n::\n```\n\n- `::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.\n- `::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.\n- `::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
|
|
57
|
+
"text": "# Screenshots and compares\n\ncomark-annotate draws `::screenshot` and `::compare`. Their marks take the values under Marks in SKILL.md.\n\n## Choose the layout\n\nPick what explains the change best:\n\n- One `::screenshot` with marks, when a single state shows the point.\n- A `::compare` of two screenshots, when the change is the point. Capture both at the same viewport, zoom, scroll, and page state, and set the same `viewport` on both. Each image gets under half the page width, where the text of a full-page capture is too small to read, so clip both captures to the same region around the change, such as Playwright's `screenshot({ clip })`.\n- `::compare{mode=\"slider\"}` instead, when both screenshots show the same spot and the change is small, such as a number or a label at the same place. The reader drags a divider across one full-width image instead of reading two half-width ones. Side notes become numbered notes there, so skip `side`.\n- A side note for the two or three points that matter most. With `mark=\"none\"` and `to` on the exact spot, the arrow alone points and nothing covers the screen, which reads cleanest. Other notes stay numbered in the list under the image. In a side-by-side compare only `top` and `bottom` keep their arrows; left and right notes join the list.\n\nFor a review the reader should follow without a key, write it the way a designer marks up a board: the default ink with no `color`, every note a side note in the margin nearest its point, set off with `at` so its arrow sweeps in, and a `bracket` where a note covers a run of rows. Write the notes of a compare as a pair that argue the change: \"The type gets the headline…\" on the before, \"The message becomes the starting point.\" on the after.\n\nMark the change at its own size: the bar, field, or label that changed. What stayed the same goes unmarked, or in one sentence above the screenshots. Keep it to about six marks per screenshot, fewer in a compare.\n\n## Syntax\n\n```md\n::compare\n:::screenshot{src=\"before.png\" alt=\"Filter toolbar wrapping onto a second row\" viewport=\"1440x900\" href=\"https://pr12.preview.example.com/correlations\"}\n::::annotation{x=3.6 y=20.6 w=92.9 h=31.4 color=\"red\"}\nMoon phase and vibes don't fit, so the toolbar wraps and pushes the table down.\n::::\n:::\n\n:::screenshot{src=\"after.png\" alt=\"Filter toolbar on one row\" viewport=\"1440x900\"}\n::::annotation{x=87.6 y=24.2 w=7.4 h=9.9 mark=\"check\" color=\"green\"}\n**Apply** lines up with the chips.\n::::\n:::\n::\n```\n\n- `::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.\n- `::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.\n- `::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.\n- `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`.\n- Annotations must be direct children of a screenshot.\n\n## Measure every mark\n\nTake each mark's coordinates from the page, in the browser you captured. Measure the element and divide by the viewport:\n\n```js\nconst r = document.querySelector(\"SELECTOR\").getBoundingClientRect();\n[(r.x / innerWidth) * 100, (r.y / innerHeight) * 100, (r.width / innerWidth) * 100, (r.height / innerHeight) * 100].map((v) => +v.toFixed(1));\n```\n\nFor a full-page screenshot, add `scrollX`/`scrollY` and divide by `document.documentElement.scrollWidth` and `scrollHeight` instead.\nFor a clipped capture, `getBoundingClientRect()` and Playwright's main-frame `clip` both use viewport-relative coordinates, so the formula takes no scroll offsets. Make sure the clip fully contains every marked element. Subtract the clip's `x` and `y` and divide by its `width` and `height`; if an element crosses the clip edge, intersect its measured rectangle with the clip before normalizing so every value stays between 0 and 100. If the clip is in document coordinates, convert it to viewport coordinates before subtracting.\n\n## Preview\n\nMake comark-annotate a direct dependency of the workspace package where you run the CLI, so its executable links. If it is only transitive, read the installed Comark UI version and add that exact version, such as `pnpm add -D comark-annotate@<version>` (add `-w` at the workspace root), because `@latest` could fetch a different renderer release. With no Comark UI packages installed, omit `@<version>`.\n\n```sh\nvp exec comark-annotate render report.md -o report.html\n```\n\n`render` writes a standalone page of the screenshots and compares, and relative `src` paths resolve next to it. Without comark-linter, `vp exec comark-annotate check report.md` lists these blocks' problems on its own. It names each one like `screenshot \"after.png\", annotation 2`, counting annotations within their screenshot, and exits with 1 while any remain.\n\n## Register\n\n- In `comark-ui`: on, in `comark-ui/html` only.\n- Alone: `pnpm add comark @comark/html comark-annotate`, then `createHtmlRenderer({ plugins: [annotate()], components })` with both from `comark-annotate/html`. It renders only to HTML. Screenshots reserve their `viewport`, or dimensions from a data URI. `annotate({ size: true })` also fetches remote screenshots at render time; enable it only for trusted documents, since requests follow redirects and can reach private networks. `annotate({ size: false })` disables measurement while retaining authored viewports. In `comark-ui`, put these options under `annotate`. Each document inlines the styles once, with style attributes for positions, so a strict Content Security Policy needs `style-src 'unsafe-inline'`.\n"
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
"path": "references/badge.md",
|
|
@@ -244,6 +244,10 @@ const skills = [
|
|
|
244
244
|
"path": "references/tests.md",
|
|
245
245
|
"text": "# Test results\n\ncomark-tests reads a JUnit XML report into one line of passed, failed, and skipped counts with the run's duration, and folds each failure under it with its message and stack. Use it when a pull request or a CI summary reports a test run, so the reader sees the run itself.\n\n## Syntax\n\n````md\n::tests{title=\"Pond census tests\"}\n```xml\n<testsuite name=\"pond.test.ts\" time=\"0.42\">\n <testcase classname=\"pond.test.ts\" name=\"counts the ducks\" time=\"0.01\"/>\n <testcase classname=\"pond.test.ts\" name=\"counts the geese\" time=\"0.02\">\n <failure message=\"expected 3 to be 4\">AssertionError: expected 3 to be 4\n at pond.test.ts:12:20</failure>\n </testcase>\n <testcase classname=\"pond.test.ts\" name=\"maps the herons\"><skipped/></testcase>\n</testsuite>\n```\n::\n````\n\nOr read the report from a file, and leave the block empty:\n\n```md\n::tests{src=\"reports/junit.xml\" title=\"CI run\"}\n::\n```\n\n| Prop | What it does |\n| --- | --- |\n| `src` | The JUnit XML file to read. The path starts at the document's folder, or at the host's `root` when it starts with `/`, and it can't leave `root` or be a URL. Without `src`, the block holds one fence with the report. |\n| `title` | Shown before the counts. It also names the results for screen readers, which hear \"Test results\" without one. |\n\nThe block shows:\n\n- The counts of `<testcase>` elements. A case with `<skipped>` is skipped, one with `<failure>` or `<error>` failed, and the rest passed. Failed and skipped show only when there are any.\n- The duration, from the `time` of `<testsuites>`, else the sum of the suites' times, else the cases', written as `250 ms`, `4.2 s`, or `1 min 21 s`. A report without times shows none.\n- Each failure as a closed disclosure named `classname › name`, with its `message` and its stack in a code block. In print, every failure prints open.\n\nThe root is `<testsuites>` or a single `<testsuite>`. A report with another root renders nothing, and so does malformed XML, which the plugin reports with its line.\n\n## Use the run's own report\n\nPaste or point at the report the runner wrote, unedited, so the counts are the run's. Most runners write JUnit XML with a flag:\n\n| Runner | Command |\n| --- | --- |\n| Vitest | `vitest run --reporter=junit --outputFile=junit.xml` |\n| Jest | `jest --reporters=jest-junit` |\n| pytest | `pytest --junitxml=junit.xml` |\n| Go | `go test -v ./... 2>&1 \\| go-junit-report > junit.xml` |\n| Playwright | `PLAYWRIGHT_JUNIT_OUTPUT_NAME=junit.xml playwright test --reporter=junit` |\n\nWhen the host reads files, have CI write the report next to the document and point `src` at it. Otherwise paste it into the fence. A long report stays short on the page, since the summary is one line and each failure stays closed until a reader opens it.\n\n## Register\n\n- In `comark-ui`: on. A pasted report renders, and every `src` reports a missing file until you pass `tests: { root: \".\" }` or `{ read }`.\n- Alone: `pnpm add comark @comark/html comark-tests`, then `createHtmlRenderer({ plugins: [tests({ root: \".\" })], components })` with both from `comark-tests/html`. `root` is the folder `src` can't leave, default `.`, the working directory. `base` is the document's folder under it, where relative paths start. `read(path)` returns a file's text, for browsers and edge runtimes. The output needs no script. `comark-tests/vue`, `/react`, and `/svelte` render it too. With comark-code registered, each stack gets a Copy button.\n"
|
|
246
246
|
},
|
|
247
|
+
{
|
|
248
|
+
"path": "references/time.md",
|
|
249
|
+
"text": "# Dates as relative time\n\ncomark-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.\n\n## Syntax\n\n```md\nOpened :time[2026-11-02], last run :time[2026-11-04T11:59:56Z], due :time[2026-11-25].\n\nThe first sighting was :time[2026-09-30]{threshold=\"8w\"}.\n```\n\n- 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.\n- `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.\n- 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.\n- 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.\n\n## Write the dates\n\n- 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.\n- 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.\n- 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.\n- 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.\n\n## Register\n\n- 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.\n- 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.\n"
|
|
250
|
+
},
|
|
247
251
|
{
|
|
248
252
|
"path": "references/ui.md",
|
|
249
253
|
"text": "# Every plugin in one install\n\ncomark-ui registers every comark-ui package for one renderer, in the order they need, with one icon resolver shared by `:icon`, cards, details, file trees, tabs, and code actions. SKILL.md's Install section shows the basic use. This page covers the options, what the preset turns on, the other renderers, and how the linter uses it.\n\n## Options\n\n`comarkUi(options)` returns `{ plugins, components }`. Each key is a package's name without `comark-`, in camelCase, such as `fileTree`, `codeNotes`, or `readingTime`. The exceptions are `math` for comark-mathml, `outline` for comark-outline, and `mermaid`, `shiki`, and `typeset`, which are not packages. `false` turns a plugin off along with its components, and an object turns it on with those options. `true` turns on `include` and leaves the others as they are:\n\n```ts\ncomarkUi({\n zoom: false,\n code: { actions: [{ id: \"run\", label: \"Run\" }] },\n github: { callouts: false, references: { repo: \"owner/name\" } },\n table: { root: \"data\" },\n include: { root: \"content\", base: \"guide\" },\n});\n```\n\n`github` takes its three plugins by name, `callouts`, `references`, and `html`, and `links` takes `external` and `anchors`. Each package's reference lists its own options. The order stays fixed whatever you pass: `include` runs first, so the others read what it pastes, and `code` and `codeNotes` run late, after every plugin that writes code blocks.\n\n## What it turns on\n\n- Every plugin except `include`, which reads files from disk. `include: { root, base }` turns it on, or `{ read }` in a browser or an edge runtime. `include: true` reads under the working directory.\n- `cast`, `table`, and `tests` get a reader that finds no file. Inline casts, CSV fences, and pasted JUnit reports render, and a `src` file doesn't load until you pass `{ root }` or `{ read }`. Any other options object replaces that reader too, so the plugin reads under the working directory.\n- Three plugins use the network while parsing by default: `chips` downloads favicons and avatars, `icon` fetches icon sets from the Iconify API, and `diagrams` sends each diagram's source to `https://kroki.io`. In a browser, pass `chips: { download: false }`, because a browser may not download another site's images. To parse offline, also pass `icon: { fetch: false }` with your icon sets in `collections`, and `diagrams: { draw: false }`. To keep diagram sources private, pass your own Kroki `server`.\n- Media sizing reads only data URIs by default. `zoom: { size: true }` and `media: { size: true }` also fetch remote files while parsing, and `annotate: { size: true }` does so at HTML render time. Enable these only for trusted documents, because the requests follow redirects and can reach private networks. `{ size: false }` turns measurement off for that plugin; authored dimensions and screenshot viewports still work.\n- `icon`'s options apply to every icon the preset draws, in cards, details, file trees, tabs, and code actions too.\n- `mermaid` draws `mermaid` fences with comark-pele, on the server and offline. In the HTML entry the diagram zooms with comark-zoom unless `zoom` is `false`. `mermaid: false` turns it off.\n- `shiki` highlights fenced code with your own Shiki, before comark-code: `{ highlighter: (language) => shiki | undefined, themes: { light, dark } }`. It is off until you pass it, so the preset bundles no grammars. `maxBlocks` and `maxSource` cap how many blocks, and how many characters of code, a document highlights; the blocks past them show plain, each with an issue.\n- comark-critic has no component to add its styles, so put `criticCss`, which every entry exports, in the page.\n- The preset styles the text around the blocks, scoped to the element the document renders into, on the same `--comark-*` tokens. Pass `typeset: false` when the site already styles Markdown, such as with Tailwind Typography's `prose`.\n\n## Renderers\n\n| Renderer | Entry |\n| --- | --- |\n| `@comark/html` | `comark-ui/html` |\n| `@comark/vue` | `comark-ui/vue` |\n| `@comark/react` | `comark-ui/react` |\n| `@comark/svelte` | `comark-ui/svelte` |\n\nThe Vue, React, and Svelte entries leave out annotate, comments, diff, and quiz, which render only to HTML. Screenshots, compares, diffs, suggestions and comments, and the marks on charts need `comark-ui/html`.\n\nTo use another renderer, install it in place of `@comark/html`, such as `pnpm add comark @comark/vue comark-ui`, and pass it `plugins` and `components` as you would one package's. The preset needs Vue 3.5, React 18, or Svelte 5.20 or later. Every entry exports the `PresetOptions` type, and `comark-ui` itself exports `preset(renderer, options)`, which builds the same pair from renderer entries you pass.\n\n## In the linter\n\ncomark-linter imports `comark-ui/html` when the project has it, and lints with all its plugins and components, ahead of any comark package installed on its own, so none runs twice. It calls `comarkUi()` with options of its own, so the app's options don't reach it:\n\n- It draws no chart or diagram, and downloads no chip image.\n- `::cast`, `::table`, and `::tests` read their `src` files from the working directory.\n- `include` stays off until a lint config adds it, as [include](include.md) says.\n\n## Register\n\n- In `comark-ui`: this is the preset itself, registered as Install in SKILL.md shows.\n- Alone: each plugin is its own package, registered as its reference says. The preset ships in lockstep with them, so `comark-ui@X` holds every plugin at version `X`.\n"
|
|
@@ -258,18 +262,49 @@ const skills = [
|
|
|
258
262
|
}
|
|
259
263
|
]
|
|
260
264
|
},
|
|
265
|
+
{
|
|
266
|
+
"name": "report",
|
|
267
|
+
"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.",
|
|
268
|
+
"frontmatter": {
|
|
269
|
+
"name": "report",
|
|
270
|
+
"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.",
|
|
271
|
+
"license": "MIT"
|
|
272
|
+
},
|
|
273
|
+
"files": [
|
|
274
|
+
{
|
|
275
|
+
"path": "SKILL.md",
|
|
276
|
+
"text": "---\nname: report\ndescription: 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.\nlicense: MIT\n---\n\n# Report\n\nThe 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.\n\nFor 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.\n\n## 1. Write the answer and the decisions\n\nWrite 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.\n\nDone when someone who reads only the answer and the callout knows what you recommend and what they have to decide.\n\n## 2. Give every fact one home\n\nList 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.\n\nDone when no value, list, or order is written out in two sections.\n\n## 3. Pick one picture per section\n\nA 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.\n\n| The section answers | Picture |\n| --- | --- |\n| Which items are worth it, by two scores and a size | Bubble chart: `::chart` with a `vega-lite` spec |\n| How big each item is, or what each blocker would unlock | `::chart{type=\"bar\" horizontal labels}` |\n| How each item's size splits into parts | `::chart{type=\"stacked\" horizontal}` |\n| In what order, and what has to land first | `mermaid` flowchart, blockers as `{{hexagons}}` |\n| How a mechanism moves between states | `mermaid` `stateDiagram-v2` |\n| A number before and after | `::stats` or `:delta` |\n| A share of a whole | `:progress` |\n| Findings, options, or decisions compared across the same columns | Table of at most five columns, `:badge` for categories and verdicts |\n| Detail per item | `::accordion`, one item per row, the same parts inside every item |\n| What you left out | Bar chart of the largest, then `::accordion` grouped by reason |\n| How you checked | `> [!CAUTION]` for what you didn't verify, kept open; sources in `::details` |\n\nA 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.\n\n[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.\n\nDone 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.\n\n## 4. Assemble the page\n\nThe two ends are fixed, and the middle holds the sections your material has, in the order the reader acts on them:\n\n```text\nFrontmatter title = the answer in under 70 characters; status, date, tags\nThe answer two sentences, the decisions in > [!IMPORTANT], at most one > [!WARNING]\nDecisions the decision table, only when there are more than three\n middle from step 3, such as: the picture the answer rests on, findings,\n the order, blockers, before and after, each item, left out\nHow I checked > [!CAUTION] open, sources folded\n```\n\nKeep 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.\n\n[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.\n\nDone when the page follows this order and nothing the reader decides on is folded.\n\n## 5. Check the render\n\n1. Lint it, and fix until it reports no problems:\n\n ```sh\n vp dlx --package comark-linter --package comark-ui comark-lint report.md\n ```\n\n These commands use the [global Vite+ CLI](https://viteplus.dev/guide/global-cli). Without it, `pnpm dlx` takes the same arguments.\n\n2. 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.\n3. 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.\n\nDone when lint reports no problems and you've read every screenshot of the render.\n"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"path": "agents/openai.yaml",
|
|
280
|
+
"text": "interface:\n display_name: \"Report\"\n short_description: \"Answer and decisions first, numbers in charts, detail folded\"\n"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"path": "references/blocks.md",
|
|
284
|
+
"text": "# Blocks for a report\n\nEach block below renders with the full `comark-ui` preset. The `comark-ui` skill documents every block in full; read its reference for a block before you change more than the values here.\n\n## The answer: callouts\n\n```md\n> [!IMPORTANT]\n> **Your calls:**\n>\n> 1. Hold `@nuxtjs/i18n` until #3564 has URL routing?\n> 2. Ship the Env and Resend codemods before Auth's SSR gap closes?\n\n> [!WARNING]\n> Every row needs Nuxt 5, and the codemods don't upgrade Nuxt.\n```\n\nFrontmatter `status`, `date`, and `tags` render as a properties box at the top with no extra markup.\n\n## Bubble chart: two scores and a size\n\n````md\n::chart{title=\"Shortlist: value and difficulty (1 to 5), bubble size = npm downloads a week\"}\n| module | difficulty | value | downloads |\n| --- | --: | --: | --: |\n| nuxt-auth-utils | 4 | 5 | 177926 |\n| Resend | 2 | 4 | 1251 |\n| t3-env | 2 | 3 | 6072 |\n| nuxt-cron | 2 | 3 | 3123 |\n\n```vega-lite\n{\n \"width\": 620,\n \"height\": 320,\n \"encoding\": {\n \"x\": { \"field\": \"difficulty\", \"type\": \"quantitative\", \"scale\": { \"domain\": [1.5, 5] }, \"title\": \"Difficulty (5 = hardest)\" },\n \"y\": { \"field\": \"value\", \"type\": \"quantitative\", \"scale\": { \"domain\": [2.5, 5.5] }, \"title\": \"Value (5 = most)\" }\n },\n \"layer\": [\n {\n \"mark\": { \"type\": \"circle\", \"opacity\": 0.55 },\n \"encoding\": { \"size\": { \"field\": \"downloads\", \"type\": \"quantitative\", \"scale\": { \"range\": [80, 3000] }, \"legend\": null } }\n },\n {\n \"transform\": [{ \"filter\": \"indexof(['Resend', 't3-env', 'nuxt-cron'], datum.module) < 0\" }],\n \"mark\": { \"type\": \"text\", \"dy\": -26, \"fontSize\": 12 },\n \"encoding\": { \"text\": { \"field\": \"module\" } }\n },\n {\n \"transform\": [{ \"filter\": \"indexof(['Resend', 't3-env'], datum.module) >= 0\" }],\n \"mark\": { \"type\": \"text\", \"align\": \"right\", \"dx\": -12, \"fontSize\": 12 },\n \"encoding\": { \"text\": { \"field\": \"module\" } }\n },\n {\n \"transform\": [{ \"filter\": \"datum.module === 'nuxt-cron'\" }],\n \"mark\": { \"type\": \"text\", \"dy\": 16, \"fontSize\": 12 },\n \"encoding\": { \"text\": { \"field\": \"module\" } }\n }\n ]\n}\n```\n::\n````\n\n- With a `vega-lite` spec, `::chart` takes only `title`; `type`, `x`, and `y` are lint errors. Name the columns in `encoding`.\n- Set `width` in pixels. `\"container\"` renders about 230px wide.\n- Labels overlap when two points share a spot or sit close. Give each label layer a `filter` and move those points' labels left (`align: right`, `dx: -12`) or below (`dy: 16`). Look at the render; the lint can't see overlap.\n- Write a range such as 4–5 as its middle, 4.5, and say so in the item's detail.\n\n## Bar chart: sizes, with one mark\n\n```md\n::chart{type=\"bar\" horizontal labels title=\"Left out: npm downloads a week (10k and up)\"}\n| module | downloads |\n| --- | --: |\n| @nuxtjs/i18n | 977490 |\n| nuxt-csurf | 223523 |\n:::annotation{at=\"@nuxtjs/i18n\" mark=\"box\" color=\"amber\" side=\"right\"}\nYour call: hold until #3564 has URL routing.\n:::\n::\n```\n\nRows draw in table order, so sort them largest first. Mark the bar a decision is about, at most one; a chart no decision points at gets no mark.\n\nFor sizes that split into parts, use `type=\"stacked\"`, one column per part:\n\n```md\n::chart{type=\"stacked\" horizontal title=\"Description characters per skill\"}\n| skill | sentence | trigger list |\n| --- | --: | --: |\n| emil-mobile-native | 760 | 551 |\n| emil-prep-for-prod | 865 | 386 |\n::\n```\n\nEach chart scales its own axis. When two charts compare the same unit, put the rows in one chart, or write both in `vega-lite` with the same `\"scale\": { \"domain\": [0, 1400] }` on the value axis, so equal values draw equal bars.\n\n## Order and blockers: a flowchart\n\n````md\n```mermaid\nflowchart LR\n shell[\"1 · Wizard shell + Env\"]\n shell --> resend[\"2 · Resend\"]\n shell --> auth[\"3 · Auth\"]\n ssr{{\"SSR session + page middleware\"}} --> auth\n upstash{{\"Upstash driver\"}} -.-> rl[\"5 · Rate limit\"]\n```\n````\n\nUse `LR` for up to about five steps side by side and `TB` for a longer chain or long labels; the diagram scales down to fit the page width, so a wide one renders small. Boxes are steps, numbered in the order the reader acts. Hexagons are work that has to land first; a dashed arrow means it should land first but doesn't have to. Say both in the sentence above the diagram.\n\n## The decision table: past three decisions\n\n```md\n| # | Decision | Opus | Sol | Recommend |\n| --- | --- | --- | --- | --- |\n| D1 | Import family for apps | `vite-hub/*` | `@vite-hub/*` | :badge[vite-hub/*]{color=\"success\"} |\n| D2 | Sandbox Task option key | `sandboxTask` | `sandbox.tasks` | :badge[sandboxTask]{color=\"success\"} |\n```\n\nEach cell is the position, not the argument. The arguments go in the decision's accordion item, labeled with its number.\n\n## The table: at most five columns\n\n```md\n| Step | Module → primitive | npm/week | Fit |\n| --- | --- | --: | --- |\n| 1 | `@t3-oss/env-nuxt` → Env | 6,072 | :badge[High]{color=\"success\"} |\n| 3 | `nuxt-auth-utils` → Auth | 177,926 | :badge[Partial]{color=\"warning\"} |\n```\n\nBadge colors: `success` good, `warning` partial or at risk, `error` blocked, `neutral` unknown. Scores that are already in a chart stay out of the table. A cell holds a value, a badge, or a few words; a column of sentences turns the table into a wall of text, so move it into the accordion.\n\nWrite file names and domains in backticks, such as `AGENTS.md` and `devtools.fm`. Bare, they render as link chips with an icon.\n\n## Each item: an accordion\n\n````md\n::accordion\n:::accordion-item{label=\"2 · Resend → Email\"}\n| Resend | ViteHub Email |\n| --- | --- |\n| `new Resend(key).emails.send(msg)` | `email.send(msg)` |\n\n- **Manual:** batch, contacts, audiences, broadcasts.\n- **First:** nothing.\n- **Gain:** a dev outbox and Console preview.\n:::\n::\n````\n\nEvery item has the same parts in the same order, with bold labels, a line or two each. A part that needs more becomes a table inside the item. The label starts with the item's step number from the table. Icons come from `lucide:`; pick one per kind of item.\n\n## Before and after: stats and delta\n\n```md\n::stats{better=\"lower\"}\n| Measure | Before | After |\n| --- | --: | --: |\n| Description characters loaded | 23,515 | 10,981 |\n| Model-invoked skills | 49 | 41 |\n::\n\nTrimming the lists saves :delta{from=23515 to=10981 unit=\"chars\" better=\"lower\"} every session.\n```\n\n`::stats` holds one table of exactly three columns: a name, before, after. Each cell is a number with an optional unit, the same unit in both cells, and the block adds the percent itself. A value with a qualifier, such as \"about 65\" or \"6 of 11\", belongs in a plain table, or keeps its number in the cell and moves the qualifier into the row's name. `better` applies to every row, so rows that are better higher go in a second block.\n\n## A share: progress\n\n```md\n:progress{value=293 max=785 label=\"nuxt-security configs that mention rateLimiter\"}\n```\n\n## How I checked\n\n````md\n> [!CAUTION]\n> **Not verified:** any source module on Nuxt 5; the Docus count (one npm call).\n\n::details{summary=\"Sources and method\"}\n- **Downloads:** `api.npmjs.org/downloads/point/last-week`, 2026-10-30 to 2026-11-05.\n::\n````\n\n## Render it locally\n\nA host renders the report only after you publish it, and a private page may need the user's sign-in, so check the render before you hand it over. Copy this skill's [scripts/render.mjs](../scripts/render.mjs) next to the report, then:\n\n```sh\nmkdir -p tmp/report-render && cd tmp/report-render\necho '{ \"private\": true, \"type\": \"module\" }' > package.json\npnpm add --ignore-workspace comark @comark/html comark-ui playwright\nnode ../../render.mjs ../../report.md out\n```\n\nWithout its own `package.json`, pnpm walks up to the repository's and can fail on its catalog. The script writes `out/report.html` and screenshots at 900px wide, 1600px tall each: `report-closed-*.png` as the reader first sees it, and `report-open-*.png` with every fold open. When Playwright has no browser downloaded, it uses the newest Chromium in `~/.cache/ms-playwright`.\n"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"path": "references/example.md",
|
|
288
|
+
"text": "---\ntitle: \"Migrate wizard: start with Env and Resend, then Auth and Content\"\nstatus: Proposal\ndate: 2026-11-07\ntags: [migrate wizard, codemods]\n---\n\n## The answer\n\nBuild the wizard shell with the Env codemod, then add Resend, Auth and Content. Auth and Content hold most of the users, but each needs ViteHub work first. Env and Resend are cheap and prove the wizard end to end.\n\n> [!IMPORTANT]\n> **Your call:** hold `@nuxtjs/i18n`, the largest audience at 977k a week, until #3564 merges and i18n has URL routing?\n\n> [!WARNING]\n> Every row needs Nuxt 5. Most source apps run Nuxt 4 and the codemods don't upgrade Nuxt, so the wizard checks the version first.\n\n## Value against cost\n\nUp and to the right is worth more and costs more. Bubble size is weekly npm downloads. Auth and Content are the two big bubbles. The cheap wins sit at difficulty 2.\n\n::chart{title=\"Shortlist: value and difficulty (1 to 5), bubble size = npm downloads a week\"}\n| module | difficulty | value | downloads |\n| --- | --: | --: | --: |\n| nuxt-auth-utils | 4 | 5 | 177926 |\n| @nuxt/content | 4 | 4 | 240394 |\n| Docus | 3 | 4 | 15800 |\n| Resend | 2 | 4 | 1251 |\n| nuxt-security limiter | 3 | 3 | 220740 |\n| t3-env | 2 | 3 | 6072 |\n| sidebase | 4.5 | 3 | 46448 |\n| nuxt-cron | 2 | 3 | 3123 |\n\n```vega-lite\n{\n \"width\": 620,\n \"height\": 320,\n \"encoding\": {\n \"x\": { \"field\": \"difficulty\", \"type\": \"quantitative\", \"scale\": { \"domain\": [1.5, 5] }, \"title\": \"Difficulty (5 = hardest)\" },\n \"y\": { \"field\": \"value\", \"type\": \"quantitative\", \"scale\": { \"domain\": [2.5, 5.5] }, \"title\": \"Value (5 = most)\" }\n },\n \"layer\": [\n {\n \"mark\": { \"type\": \"circle\", \"opacity\": 0.55 },\n \"encoding\": { \"size\": { \"field\": \"downloads\", \"type\": \"quantitative\", \"scale\": { \"range\": [80, 3000] }, \"legend\": null } }\n },\n {\n \"transform\": [{ \"filter\": \"indexof(['t3-env', 'Docus', 'Resend'], datum.module) < 0 && datum.module !== 'nuxt-cron'\" }],\n \"mark\": { \"type\": \"text\", \"dy\": -26, \"fontSize\": 12 },\n \"encoding\": { \"text\": { \"field\": \"module\" } }\n },\n {\n \"transform\": [{ \"filter\": \"indexof(['t3-env', 'Docus', 'Resend'], datum.module) >= 0\" }],\n \"mark\": { \"type\": \"text\", \"align\": \"right\", \"dx\": -12, \"fontSize\": 12 },\n \"encoding\": { \"text\": { \"field\": \"module\" } }\n },\n {\n \"transform\": [{ \"filter\": \"datum.module === 'nuxt-cron'\" }],\n \"mark\": { \"type\": \"text\", \"dy\": 16, \"fontSize\": 12 },\n \"encoding\": { \"text\": { \"field\": \"module\" } }\n }\n ]\n}\n```\n::\n\n## The order\n\nBoxes are codemods, numbered in build order. Hexagons are ViteHub work that has to land first; a dashed arrow means it should land first but doesn't have to.\n\n```mermaid\nflowchart LR\n shell[\"1 · Wizard shell + Env\"]\n shell --> resend[\"2 · Resend\"]\n shell --> auth[\"3 · Auth\"]\n shell --> content[\"4 · Content\"] --> docus[\"Docus, then nuxt-studio\"]\n shell --> rl[\"5 · Rate limit\"]\n shell --> cron[\"6 · nuxt-cron\"]\n shell --> i18n[\"7 · i18n\"]\n ssr{{\"SSR session + page middleware\"}} --> auth\n upstash{{\"Upstash driver\"}} -.-> rl\n pr{{\"#3564 + URL routing\"}} --> i18n\n```\n\n| Step | Module → primitive | npm/week | Fit |\n| --- | --- | --: | --- |\n| 1 | `@t3-oss/env-nuxt` → Env | 6,072 | :badge[High]{color=\"success\"} |\n| 2 | `resend`, `nuxt-resend` → Email | 1,251 | :badge[High]{color=\"success\"} |\n| 3 | `nuxt-auth-utils` → Auth | 177,926 | :badge[Partial]{color=\"warning\"} |\n| 3 | `@sidebase/nuxt-auth` → Auth | 46,448 | :badge[Partial]{color=\"warning\"} |\n| 4 | `@nuxt/content` v3 → Content | 240,394 | :badge[Partial]{color=\"warning\"} |\n| 4 | Docus → `@vite-hub/docs` | ~15,800 | :badge[High]{color=\"success\"} |\n| 5 | `nuxt-security` limiter → Rate Limit | 220,740 | :badge[Partial]{color=\"warning\"} |\n| 6 | `nuxt-cron` → Schedule | 3,123 | :badge[High]{color=\"success\"} |\n\nResend's count is `nuxt-resend` alone; 274 more apps call `resend` directly from server routes. Inside step 3, `@nuxtjs/better-auth` (2,101 a week) goes first: it runs Better Auth on both sides, so users, hashes and sessions keep their format.\n\n## What ViteHub has to add\n\nEach gap below blocks a codemod or a group of users. i18n routing unblocks the most by far.\n\n::chart{type=\"bar\" horizontal labels title=\"npm downloads a week each missing feature would unblock\"}\n| Missing in ViteHub | downloads |\n| --- | --: |\n| i18n: URL routing | 985785 |\n| Auth: SSR session + page middleware | 226475 |\n| MCP: app-level server | 42096 |\n| Email: SMTP driver | 4620 |\n| Rate Limit: Upstash driver | 2044 |\n::\n\n::details{summary=\"Which modules each gap unblocks\"}\n| Gap | Unblocks |\n| --- | --- |\n| i18n: URL routing | `@nuxtjs/i18n` 977,490, `nuxt-i18n-micro` 8,295 |\n| Auth: SSR session + page middleware | `nuxt-auth-utils` 177,926, sidebase 46,448, `@nuxtjs/better-auth` 2,101 |\n| MCP: app-level server | `@nuxtjs/mcp-toolkit` 42,096 |\n| Email: SMTP driver | `nuxt-nodemailer` 3,376, `nuxt-mail` 1,244, plus 609 apps on raw `nodemailer` |\n| Rate Limit: Upstash driver, windows past 10s and 1m on Cloudflare | `nuxt-api-shield` 2,044, `@upstash/ratelimit`, and the nuxt-security default of 5 minutes on Cloudflare |\n::\n\n## Each codemod\n\nEvery item has the same three parts: what the codemod rewrites, what stays manual, and what has to land first.\n\n::accordion\n:::accordion-item{label=\"1 · @t3-oss/env-nuxt → Env\"}\n| t3-env | ViteHub Env |\n| --- | --- |\n| `createEnv({ server, client })` | the existing converter in `nuxt-safe-runtime-config/schema.ts`, as it is |\n\n- **Manual:** nothing found.\n- **Bonus:** `@arkenv/nuxt` (202 a week) comes almost free, since the converter already reads ArkType.\n:::\n\n:::accordion-item{label=\"2 · Resend → Email\"}\n| Resend | ViteHub Email |\n| --- | --- |\n| `new Resend(key).emails.send(msg)`, `useResend()` | `email.send(msg)`, same message fields |\n| `const { data, error }` | `const [error, data]` |\n| (nothing) | `vitehub.email = { driver: 'resend', options: { apiKey: env(...) } }` |\n\n- **Manual:** batch, contacts, audiences, broadcasts, `react` payloads, Resend templates.\n- **Gain:** a dev outbox, Console preview, a path to Cloudflare Email.\n- **The wizard's report must say:** in dev, sends go to the outbox unless `remote: true`.\n:::\n\n:::accordion-item{label=\"3 · nuxt-auth-utils → Auth\"}\n| nuxt-auth-utils | ViteHub Auth |\n| --- | --- |\n| `'nuxt-auth-utils'` in `modules` | `'vite-hub/nuxt'` |\n| `runtimeConfig.oauth.<p>`, `NUXT_OAUTH_<P>_*` | `socialProviders.<p>` in `server/auth.ts`, with Env |\n| `useUserSession()` `fetch`, `clear` | `refresh`, `signOut` (`loggedIn`, `user`, `ready` match) |\n| `/auth/github` links | `signIn.social({ provider: 'github' })` |\n| `getUserSession`, `requireUserSession` | `auth.api.getSession`, `withAuth` |\n\n- **Providers:** about 25 of 52 are in Better Auth core; auth0, keycloak, okta and others go through `genericOAuth`; six, such as steam, mastodon and atproto, have no equivalent.\n- **Manual:** `onSuccess` upserts move to `databaseHooks`; custom `setUserSession` data; user shape and IDs change (`user.login` breaks); everyone signs in again and each provider needs the `/api/auth/callback/<p>` URL; Adonis scrypt hashes need a custom `password.verify`; WebAuthn moves to the passkey table.\n- **First:** a server-rendered session (`useUserSession()` stays pending during SSR, `packages/auth/README.md:317`) and page middleware. Without them the migrated app flashes signed-out on every load.\n- **Gain:** a real users store, the Console, Auth events, admin, 2FA and organizations.\n:::\n\n:::accordion-item{label=\"3 · @sidebase/nuxt-auth → Auth\"}\n| sidebase (authjs) | ViteHub Auth |\n| --- | --- |\n| `NuxtAuthHandler({ providers })` | `socialProviders` |\n| `useAuth()`, `status === 'authenticated'` | `useUserSession()`, `loggedIn` |\n| `getServerSession` | `auth.api.getSession` |\n\n- **Refuse:** the `local` provider, which sends bearer tokens to an external backend.\n- **Manual:** the `jwt` and `session` callbacks.\n:::\n\n:::accordion-item{label=\"4 · @nuxt/content v3 → Content\"}\n| @nuxt/content | ViteHub Content |\n| --- | --- |\n| `content.config.ts` `defineCollection({ source })` | `server/content.ts` `defineContent({ sources })` with `glob()` |\n| `queryCollection(c).path(p).first()` | `content.get(p)` |\n| `queryCollectionNavigation` | `content.navigation` |\n| `<ContentRenderer>`, `<MDC>` | `<Markdown>` |\n\n- **Manual, reported with `file:line`:** every `.where/.order/.limit/.select/.count` chain (no query builder), the `database` adapters, remote `repository` sources, `queryCollectionItemSurroundings`.\n- **Gain:** drafts and publishing, Console editing, Agent access, no SQL dump at build.\n- **Won't move:** apps that query content heavily.\n:::\n\n:::accordion-item{label=\"4 · Docus → @vite-hub/docs\"}\n| Docus | @vite-hub/docs |\n| --- | --- |\n| `extends: ['docus']` + `app.config.ts` `seo`, `header`, `github`, `socials` | `modules: ['@vite-hub/docs/nuxt']` with `docs: { site, github, socials }` |\n| `content/**` | `content/docs/<section>/` |\n| `AppHeader`, `DocsAsideLeftTop` overrides | same names |\n\n- **Manual:** Docus i18n, which has no target.\n- **Not verified:** a Comark UI equivalent for every Nuxt UI prose component.\n- **Why it ranks high:** the whole site moves, vitehub.dev is the oracle, and it reuses the Content codemod.\n:::\n\n:::accordion-item{label=\"5 · nuxt-security limiter → Rate Limit\"}\n:progress{value=293 max=785 label=\"nuxt-security configs that mention rateLimiter\"}\n\nThe limiter is on by default: 150 requests per 300,000 ms per IP and route.\n\n| nuxt-security | ViteHub Rate Limit |\n| --- | --- |\n| `security.rateLimiter` | `false`; the module stays for headers and CSP |\n| (nothing) | `server/middleware/rate-limit.ts` with `requireRateLimit()`, `vitehub.rateLimit: true` |\n\n- **Manual:** per-route `routeRules` overrides, `whiteList`, `ipHeader`, Redis storage.\n- **Wall:** only memory and Cloudflare drivers, and Cloudflare allows only 10s or 1m windows, so the module's 5m default fails the build there.\n:::\n\n:::accordion-item{label=\"6 · nuxt-cron → Schedule\"}\n| nuxt-cron | ViteHub Schedule |\n| --- | --- |\n| `server/cron/<n>.ts` `defineCronHandler(time, cb)` | `server/schedules/<n>.ts` `defineSchedule({ cron, handler })` |\n| presets | 5-field cron |\n\n- **Manual:** `everySecond`, `timeZone`.\n- **Gain:** a node-only cron runs on Cloudflare, Vercel and Netlify, with run history.\n:::\n::\n\n## Left out\n\nThe chart shows what we leave on the table, 10k a week and up. i18n is the one big loss. The rest have no ViteHub target or wait on a codemod above.\n\n::chart{type=\"bar\" horizontal labels title=\"Left out: npm downloads a week (10k and up)\"}\n| module | downloads |\n| --- | --: |\n| @nuxtjs/i18n | 977490 |\n| nuxt-csurf | 223523 |\n| @nuxtjs/supabase | 94526 |\n| @nuxtjs/turnstile | 46735 |\n| @nuxtjs/mcp-toolkit | 42096 |\n| @clerk/nuxt | 40184 |\n| nitro-cloudflare-dev | 29560 |\n| nuxt-vuefire | 15428 |\n| nuxt-studio | 14956 |\n| nuxt-multi-cache | 14125 |\n| nuxt-oidc-auth | 10859 |\n:::annotation{at=\"@nuxtjs/i18n\" mark=\"box\" color=\"amber\" side=\"right\"}\nYour call: hold until #3564 has URL routing.\n:::\n::\n\n::accordion\n:::accordion-item{label=\"Waits on ViteHub work (8)\"}\n| Module | npm/week | Waits on |\n| --- | --: | --- |\n| `@nuxtjs/i18n` | 977,490 | #3564 and URL routing (`useLocalePath`, `NuxtLinkLocale`, hreflang) |\n| `nuxt-i18n-micro` | 8,295 | the same |\n| `nuxt-studio` | 14,956 | the Content codemod |\n| `nuxt-oidc-auth` | 10,859 | the Auth codemod; small audience, `h3` 1 |\n| `nuxt-nodemailer`, `nuxt-mail` | 3,376 + 1,244 | an SMTP driver; the largest email segment |\n| `nuxt-api-shield`, `@upstash/ratelimit` | 2,044 | an Upstash or Redis driver |\n| `nitro-cloudflare-dev` | 29,560 | proof that ViteHub provides every binding in dev (only KV checked, #3480) |\n| `nuxt-file-storage` | 845 | nothing; a cheap shim over `blob` after step 6 |\n:::\n\n:::accordion-item{label=\"No ViteHub primitive (5)\"}\n| Module | npm/week | Missing |\n| --- | --: | --- |\n| `nuxt-csurf` | 223,523 | CSRF |\n| `@nuxtjs/turnstile` | 46,735 | captcha |\n| `@nuxtjs/mcp-toolkit` | 42,096 | an app-level MCP server |\n| `nuxt-multi-cache` | 14,125 | component and route caching, CDN tags |\n| `nuxt-processor` (BullMQ) | 1,183 | priorities, repeats and flows in Queue |\n:::\n\n:::accordion-item{label=\"Data lives in a hosted service (4)\"}\n| Module | npm/week |\n| --- | --: |\n| `@nuxtjs/supabase` | 94,526 |\n| `@clerk/nuxt` | 40,184 |\n| `nuxt-vuefire` | 15,428 |\n| Strapi, Sanity, Storyblok, Directus, Prismic | various |\n\nUsers, hashes and content stay in the service, so a codemod has nothing to move. At most a skeleton and a link to Better Auth's guide.\n:::\n\n:::accordion-item{label=\"Nothing worth a codemod (6)\"}\n| Module | Why |\n| --- | --- |\n| `nuxt-authorization` (7,188) | keep it; rewrite its two resolvers inside the Auth codemod |\n| `nuxt-email-renderer` (5,291) | templates keep working through `email.send` |\n| `@workflow/nuxt` (2.8M, transitive) | Workflow DevKit is already portable |\n| `@prisma/nuxt` (896) | deprecated, and no Prisma support |\n| Nitro `scheduledTasks` (294 configs) | Nitro 3 already runs them; a Vite Doctor hint fits better |\n| Nitro `useStorage` with Redis (270 configs) | every call site changes and there's no ioredis driver; only `nitro.storage.cache` → `vitehub.cache` is cheap |\n:::\n::\n\n## How I checked\n\n> [!CAUTION]\n> **Not verified:** any source module on Nuxt 5; sessions surviving the `@nuxtjs/better-auth` move; how many nuxt-security users want only the limiter; the Docus count (one npm call); Comark covering every MDC feature, such as named slots and `{{ $doc }}`.\n\n::details{summary=\"Sources and method\"}\n- **The field:** all 334 modules in the `api.nuxt.com/modules` registry (2026-11-07), 375 npm search hits across 45 primitive terms, and about 120 named packages the registry misses, such as nuxt-auth-utils.\n- **Downloads:** `api.npmjs.org/downloads/point/last-week`, 2026-10-30 to 2026-11-05.\n- **Stars and last push:** the GitHub API.\n- **Module APIs:** the npm tarballs. **ViteHub APIs:** `docs/content/docs/*` and the package READMEs on main (`c845af3ae`).\n- **Code counts:** GitHub's legacy code search; rough and not limited to Nuxt.\n::\n"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"path": "scripts/render.mjs",
|
|
292
|
+
"text": "// Renders a comark Markdown report to HTML and screenshots it at reading width.\n// Usage: node render.mjs report.md [outDir]\n// Run it from a folder where comark, @comark/html, comark-ui and playwright are installed.\nimport { createRequire } from \"node:module\";\nimport { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nconst [input, outDir = \"render\"] = process.argv.slice(2);\nif (!input) throw new Error(\"Usage: node render.mjs report.md [outDir]\");\nconst require = createRequire(join(process.cwd(), \"noop.js\"));\nconst load = (name) => import(pathToFileURL(require.resolve(name)).href);\n\nconst { createHtmlRenderer } = await load(\"@comark/html\");\nconst { default: comarkUi } = await load(\"comark-ui/html\");\nconst playwright = await load(\"playwright\");\nconst { chromium } = playwright.chromium ? playwright : playwright.default;\n\nmkdirSync(outDir, { recursive: true });\nconst name = basename(input).replace(/\\.md$/, \"\");\nconst source = readFileSync(input, \"utf8\");\nconst rendered = await createHtmlRenderer(comarkUi())(source);\n// A host such as Drop shows the frontmatter title above the page; the renderer doesn't.\nconst title = source.match(/^---\\n[\\s\\S]*?^title:\\s*\"?(.*?)\"?\\s*$/m)?.[1] ?? \"\";\nconst escape = (text) =>\n text.replace(/[&<>]/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\" })[c]);\nconst htmlPath = resolve(outDir, `${name}.html`);\nwriteFileSync(\n htmlPath,\n `<!doctype html><meta charset=\"utf-8\"><style>body{max-width:760px;margin:2rem auto;padding:0 1rem;font-family:system-ui}</style>${title ? `<h1>${escape(title)}</h1>` : \"\"}${typeof rendered === \"string\" ? rendered : rendered.html}`,\n);\n\n// Playwright's own browser may be missing; fall back to the newest one in its cache.\nconst cache = join(homedir(), \".cache/ms-playwright\");\nconst fallback = existsSync(cache)\n ? readdirSync(cache)\n .filter((d) => /^chromium-\\d+$/.test(d))\n .sort((a, b) => Number(b.slice(9)) - Number(a.slice(9)))\n .map((d) => join(cache, d, \"chrome-linux64/chrome\"))\n .find((path) => existsSync(path))\n : undefined;\nlet browser;\ntry {\n browser = await chromium.launch();\n} catch {\n browser = await chromium.launch({ executablePath: fallback });\n}\n\nconst page = await browser.newPage({ viewport: { width: 900, height: 1600 } });\nawait page.goto(pathToFileURL(htmlPath).href);\nawait page.waitForTimeout(1000);\nconst shots = [];\nfor (const state of [\"closed\", \"open\"]) {\n if (state === \"open\")\n await page.evaluate(() => document.querySelectorAll(\"details\").forEach((d) => (d.open = true)));\n // Folds animate open, so wait until the page stops growing before measuring it.\n let height = 0;\n for (let last = -1; height !== last; await page.waitForTimeout(400)) {\n last = height;\n height = await page.evaluate(() => document.documentElement.scrollHeight);\n }\n for (let y = 0, i = 0; y < height; y += 1600, i++) {\n const path = resolve(outDir, `${name}-${state}-${i}.png`);\n await page.screenshot({\n path,\n fullPage: true,\n clip: { x: 0, y, width: 900, height: Math.min(1600, height - y) },\n });\n shots.push(path);\n }\n}\nawait browser.close();\nconsole.log([htmlPath, ...shots].join(\"\\n\"));\n"
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
},
|
|
261
296
|
{
|
|
262
297
|
"name": "review-document",
|
|
263
|
-
"description": "Turns a review's findings into one short Markdown document a reader can act on, with the verdict first and each finding ranked, located, backed by evidence, and paired with its fix. Use when writing up a code review, a design or QA review, an audit,
|
|
298
|
+
"description": "Turns a review's findings into one short Markdown document a reader can act on, with the verdict first and each finding ranked, located, backed by evidence, and paired with its fix. Use when writing up a code review, a design or QA review, or an audit, where the reader must decide what to do about what you found.",
|
|
264
299
|
"frontmatter": {
|
|
265
300
|
"name": "review-document",
|
|
266
|
-
"description": "Turns a review's findings into one short Markdown document a reader can act on, with the verdict first and each finding ranked, located, backed by evidence, and paired with its fix. Use when writing up a code review, a design or QA review, an audit,
|
|
301
|
+
"description": "Turns a review's findings into one short Markdown document a reader can act on, with the verdict first and each finding ranked, located, backed by evidence, and paired with its fix. Use when writing up a code review, a design or QA review, or an audit, where the reader must decide what to do about what you found.",
|
|
267
302
|
"license": "MIT"
|
|
268
303
|
},
|
|
269
304
|
"files": [
|
|
270
305
|
{
|
|
271
306
|
"path": "SKILL.md",
|
|
272
|
-
"text": "---\nname: review-document\ndescription: Turns a review's findings into one short Markdown document a reader can act on, with the verdict first and each finding ranked, located, backed by evidence, and paired with its fix. Use when writing up a code review, a design or QA review, an audit,
|
|
307
|
+
"text": "---\nname: review-document\ndescription: Turns a review's findings into one short Markdown document a reader can act on, with the verdict first and each finding ranked, located, backed by evidence, and paired with its fix. Use when writing up a code review, a design or QA review, or an audit, where the reader must decide what to do about what you found.\nlicense: MIT\n---\n\n# Review document\n\nA review's reader has one question: what do I do now? Answer it in the first two lines, then give each finding just enough evidence that the reader trusts it without redoing your work. This skill organizes findings you already have; finding them is the review itself.\n\nLength is the usual failure. A reader who has to dig through praise, process, and restated findings misses the one that blocks the merge. Every line below the verdict either supports a finding or says what you didn't check.\n\n## 1. Decide the verdict\n\nPick one: **approve**, **approve with changes**, **request changes**, or **blocked** on a named thing. Under it, write the reason in one sentence that names the finding behind it.\n\nDone when someone who reads only the verdict knows what to do.\n\n## 2. Keep the findings that change the decision\n\n- Keep a finding when fixing it, or not, changes what the reader should do.\n- Merge findings with one root cause into one, and list every place it appears.\n- Rank each one: **blocking** (wrong results, lost data, a security hole, broken for users), **should fix** (a real cost that can wait), or **nit**, only when the reader asked for them.\n\nOrder them blocking first. Done when every finding has a rank and no two share a cause.\n\n## 3. Back each finding\n\nEach finding is four parts, in this order:\n\n1. **A title that states the consequence**, for whom: \"Small avatars load the 512px image\", not \"Cache key issue\".\n2. **Where**: the file and line, linked when there's a URL.\n3. **Evidence**, the strongest you have: a failing test or command you ran (`::tests`, a fenced block of output), a screenshot of the wrong state (measured as the before-after-evidence skill describes), the lines that cause it (a fence with the line highlighted), then reasoning. Say which it is: a finding you reproduced reads differently from one you read in the code.\n4. **The fix**: a `::diff` of the smallest change, or one sentence when the fix is a decision for the reader.\n\nDone when every blocking finding has evidence you ran or captured, or says plainly that it comes from reading the code.\n\n## 4. Say what you checked\n\nEnd with two short lists: what you checked and how, and what you didn't check. A review that implies more coverage than it had loses the reader's trust in the parts that were real.\n\n## 5. Write it\n\nFollow [references/example.md](references/example.md):\n\n- The verdict in a callout: `> [!CAUTION]` for request changes or blocked, `> [!WARNING]` for approve with changes, `> [!TIP]` for approve.\n- One `##` heading per finding, numbered, with its rank in the heading. About a dozen lines each, evidence included.\n- No summary that repeats the findings, no section for an empty rank, no list of what's good unless the reader asked.\n\n[references/blocks.md](references/blocks.md) has the syntax for the callout, `::tests`, `::diff`, and highlighted lines.\n\n## 6. Check the render\n\n1. Lint it, and fix until it reports no problems:\n\n ```sh\n vp dlx --package comark-linter --package comark-ui comark-lint review.md\n ```\n\n These commands use the [global Vite+ CLI](https://viteplus.dev/guide/global-cli). Without it, `pnpm dlx` takes the same arguments.\n\n2. Open the rendered page where it will be read and read it top to bottom as the reader would. Each finding's evidence should be visible without expanding anything except a long log, and each `::diff` should start at the line its finding names.\n\nDone when the page renders without problems and you've read it once as the reader.\n"
|
|
273
308
|
},
|
|
274
309
|
{
|
|
275
310
|
"path": "agents/openai.yaml",
|
|
@@ -341,6 +376,6 @@ const skills = [
|
|
|
341
376
|
}
|
|
342
377
|
];
|
|
343
378
|
/** The comark-ui version the skills come from, for a host's cache key or a digest. */
|
|
344
|
-
const version = "0.
|
|
379
|
+
const version = "0.10.1";
|
|
345
380
|
//#endregion
|
|
346
381
|
export { skills, version };
|
|
@@ -168,6 +168,7 @@ const BLOCKS = [
|
|
|
168
168
|
["metrics", words("progress stats")],
|
|
169
169
|
["table", words("table")],
|
|
170
170
|
["tabs", words("code-group tabs-item")],
|
|
171
|
+
["time", words("time")],
|
|
171
172
|
["xref", words("ref")]
|
|
172
173
|
];
|
|
173
174
|
const on = (value, fallback = true) => value === void 0 ? fallback : value !== false;
|
|
@@ -256,6 +257,7 @@ function preset(renderer, options = {}) {
|
|
|
256
257
|
add(renderer.outline, options.outline);
|
|
257
258
|
parse(xref, options.xref);
|
|
258
259
|
add(renderer.tests, options.tests, optionsOf(options.tests) ?? noFiles);
|
|
260
|
+
add(renderer.time, options.time);
|
|
259
261
|
add(renderer.zoom, options.zoom);
|
|
260
262
|
if (options.shiki) plugins.push(shiki_default(options.shiki));
|
|
261
263
|
add(renderer.code, options.code, {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region ../../shared/styles.ts
|
|
2
|
+
/**
|
|
3
|
+
* Adds a package's styles to the page once, unless the page already carries them as a `style` or a `link` with
|
|
4
|
+
* `data-comark-<name>`, which is how the HTML renderer inlines them and how an app that imports `style.css` marks them.
|
|
5
|
+
*/
|
|
6
|
+
function addStyles(name, css) {
|
|
7
|
+
const attribute = `data-comark-${name}`;
|
|
8
|
+
if (document.querySelector(`style[${attribute}],link[${attribute}]`)) return;
|
|
9
|
+
const style = document.createElement("style");
|
|
10
|
+
style.setAttribute(attribute, "");
|
|
11
|
+
style.textContent = css;
|
|
12
|
+
document.head.append(style);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { addStyles as t };
|
package/dist/svelte/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import * as steps from "comark-steps/svelte";
|
|
|
31
31
|
import * as table from "comark-table/svelte";
|
|
32
32
|
import * as tabs from "comark-tabs/svelte";
|
|
33
33
|
import * as tests from "comark-tests/svelte";
|
|
34
|
+
import * as time from "comark-time/svelte";
|
|
34
35
|
import * as zoom from "comark-zoom/svelte";
|
|
35
36
|
import Mermaid from "./Mermaid.svelte";
|
|
36
37
|
import typeset from "./Typeset.svelte";
|
|
@@ -73,6 +74,7 @@ export default (options) =>
|
|
|
73
74
|
table,
|
|
74
75
|
tabs,
|
|
75
76
|
tests,
|
|
77
|
+
time,
|
|
76
78
|
typeset,
|
|
77
79
|
zoom,
|
|
78
80
|
},
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { theme } from "comark-zoom/theme";
|
|
2
|
+
//#region ../../shared/shiki.ts
|
|
3
|
+
const swap = (span) => `${span}[style*="--shiki-dark:"]{color:var(--shiki-dark)!important}${span}[style*="--shiki-dark-font-style:"]{font-style:var(--shiki-dark-font-style)!important}${span}[style*="--shiki-dark-font-weight:"]{font-weight:var(--shiki-dark-font-weight)!important}`;
|
|
4
|
+
const shikiDark = swap(".shiki:where(.dark *) span") + `@media (prefers-color-scheme:dark){${swap(".shiki:not(:where(.light *)) span")}}`;
|
|
5
|
+
//#endregion
|
|
2
6
|
//#region src/typeset-css.ts
|
|
3
7
|
const grain = `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E")`;
|
|
4
8
|
const own = ":where(.not-typeset, [data-not-typeset], .not-typeset *, [data-not-typeset] *)";
|
|
@@ -175,21 +179,6 @@ const rules = `
|
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
/* Code highlighted with a light and a dark theme follows the page into dark mode, as the tokens do: a .dark class
|
|
179
|
-
above it, or the system's dark mode on a page with no .light class. A span switches what it has a dark value for. */
|
|
180
|
-
.shiki:where(.dark *) span {
|
|
181
|
-
&[style*="--shiki-dark:"] { color: var(--shiki-dark) !important; }
|
|
182
|
-
&[style*="--shiki-dark-font-style:"] { font-style: var(--shiki-dark-font-style) !important; }
|
|
183
|
-
&[style*="--shiki-dark-font-weight:"] { font-weight: var(--shiki-dark-font-weight) !important; }
|
|
184
|
-
}
|
|
185
|
-
@media (prefers-color-scheme: dark) {
|
|
186
|
-
.shiki:not(:where(.light *)) span {
|
|
187
|
-
&[style*="--shiki-dark:"] { color: var(--shiki-dark) !important; }
|
|
188
|
-
&[style*="--shiki-dark-font-style:"] { font-style: var(--shiki-dark-font-style) !important; }
|
|
189
|
-
&[style*="--shiki-dark-font-weight:"] { font-weight: var(--shiki-dark-font-weight) !important; }
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
182
|
/* A block marked data-wide, such as a before/after comparison or a diagram wider than the text, grows past the column
|
|
194
183
|
into the room the page gives it on each side, --comark-wide-start and --comark-wide-end, up to its own width
|
|
195
184
|
(--comark-wide-width) and --comark-wide-max. It stays centered on the column while both sides have room, and moves
|
|
@@ -222,7 +211,7 @@ const rules = `
|
|
|
222
211
|
* page alone. The `typeset` layer sits inside `comark`, so every block's own rules win over it, and any plain rule of
|
|
223
212
|
* the page wins over both.
|
|
224
213
|
*/
|
|
225
|
-
const typesetCss = `@layer comark{@layer typeset{@scope{${theme(":scope", "primary")}${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}${shikiDark}}}}`.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/ ?([{};,>(]) ?/g, "$1").replace(/: /g, ":").trim();
|
|
226
215
|
/**
|
|
227
216
|
* The same styles for a page's own stylesheet, scoped to every element that matches `selector`, for Comark content
|
|
228
217
|
* that isn't a rendered document, such as a rich-text editor. `comark-ui/typeset.css` is this with `.comark-typeset`.
|
package/dist/typeset.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@layer comark{@layer typeset{@scope (.comark-typeset){:where(:scope){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#fff)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#0a0a0a)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#737373)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,#e5e5e5)));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#f5f5f5)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#a1a1a1)));--_comark-primary:var(--comark-primary,var(--ui-primary,var(--primary,var(--_comark-foreground))));--_comark-primary-foreground:var(--comark-primary-foreground,var(--ui-text-inverted,var(--primary-foreground,var(--_comark-background))));--_comark-secondary:var(--comark-secondary,var(--ui-secondary,var(--_comark-muted)));--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media(prefers-color-scheme:dark){:where(:not(.light,.light *)):is(:where(:scope)){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)));--_comark-primary:var(--comark-primary,var(--ui-primary,var(--primary,var(--_comark-foreground))));--_comark-primary-foreground:var(--comark-primary-foreground,var(--ui-text-inverted,var(--primary-foreground,var(--_comark-background))));--_comark-secondary:var(--comark-secondary,var(--ui-secondary,var(--_comark-muted)))}}:where(.dark,.dark *):is(:where(:scope)){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)));--_comark-primary:var(--comark-primary,var(--ui-primary,var(--primary,var(--_comark-foreground))));--_comark-primary-foreground:var(--comark-primary-foreground,var(--ui-text-inverted,var(--primary-foreground,var(--_comark-background))));--_comark-secondary:var(--comark-secondary,var(--ui-secondary,var(--_comark-muted)))}:scope{--_ty-flow:var(--typeset-flow,1.25em);--_ty-muted:var(--_comark-muted);--_ty-rule:var(--_comark-border);--_ty-surface:var(--_comark-accent);--_ty-radius:calc(var(--_comark-radius) * 0.8);--_ty-mono:var(--comark-font-mono,var(--font-mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace));font-family:var(--_comark-font);font-size:calc(var(--typeset-size,1em) * 1.125);line-height:var(--typeset-leading,1.75);color:var(--_comark-foreground);overflow-wrap:break-word;background-image:var(--comark-grain,url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E"));background-repeat:repeat;background-size:var(--comark-grain-size,256px 256px);@media(min-width:48rem),print{font-size:var(--typeset-size,1em);}}:scope *:not(:where(.not-typeset,[data-not-typeset],.not-typeset *,[data-not-typeset] *)){&:where(p){margin-block:var(--_ty-flow) 0;}&:where(h1,h2,h3,h4,h5,h6){color:var(--_comark-foreground);font-family:var(--comark-font-heading,var(--font-heading,inherit));font-weight:600;margin-block-end:0;}&:where(h1){font-size:1.75em;line-height:1.3;margin-block-start:var(--_ty-flow);}&:where(h2){font-size:1.25em;line-height:1.4;margin-block-start:calc(var(--_ty-flow) * 1.4);}&:where(h3){font-size:1.125em;line-height:1.45;margin-block-start:var(--_ty-flow);}&:where(h4){font-size:1em;line-height:1.5;margin-block-start:var(--_ty-flow);}&:where(h5){font-size:0.875em;line-height:1.5;font-weight:500;color:var(--_ty-muted);margin-block-start:calc(var(--_ty-flow) / 0.875);}&:where(h6){font-size:0.8125em;line-height:1.5;font-weight:500;letter-spacing:0.08em;text-transform:uppercase;color:var(--_ty-muted);margin-block-start:calc(var(--_ty-flow) / 0.8125);}&:where([id]){scroll-margin-block-start:var(--_ty-flow);}&:where(h1 + *,h2 + *,h3 + *,h4 + *,h5 + *,h6 + *){margin-block-start:1em;}&:where(:is(h1,h2,h3,h4) code){font-size:0.9em;}&:where(a){color:inherit;font-weight:500;text-decoration-line:underline;text-decoration-color:color-mix(in oklab,currentColor 30%,transparent);}&:where(a:hover){text-decoration-color:currentColor;}&:where(a:focus-visible){outline:2px solid var(--_comark-ring);outline-offset:2px;border-radius:0.125em;}&:where(:is(h1,h2,h3,h4,h5,h6) a){font-weight:inherit;}&:where(strong,b){font-weight:600;}&:where(mark){background-color:color-mix(in oklab,oklch(0.86 0.17 95) 40%,transparent);color:inherit;padding:0.05em 0.15em;border-radius:0.2em;}&:where(abbr[title]){text-decoration-line:underline;text-decoration-style:dotted;text-underline-offset:0.2em;cursor:help;}&:where(del,s){color:var(--_ty-muted);text-decoration-line:line-through;}&:where(sup,sub){font-size:0.75em;line-height:0;position:relative;vertical-align:baseline;}&:where(sup){top:-0.5em;}&:where(sub){bottom:-0.25em;}&:where(sup a){text-decoration-line:none;font-weight:500;}&:where(ul){list-style-type:disc;}&:where(ol){list-style-type:decimal;}&:where(ul ul){list-style-type:circle;}&:where(ul ul ul){list-style-type:square;}&:where(ol[type="a" i]){list-style-type:lower-alpha;}&:where(ol[type="i" i]){list-style-type:lower-roman;}&:where(ol[type="A" s]){list-style-type:upper-alpha;}&:where(ol[type="I" s]){list-style-type:upper-roman;}&:where(ul,ol){margin-block:var(--_ty-flow) 0;padding-inline-start:1.5em;}&:where(li){margin-block-start:0.5em;padding-inline-start:0.4em;}&:where(li>p,li>ul,li>ol){margin-block-start:0.5em;}&:where(ul>li,ol>li)::marker{color:var(--_ty-muted);}&:where(ul.contains-task-list){list-style-type:none;padding-inline-start:0.25em;}&:where(li.task-list-item>input[type="checkbox"]){margin-inline-end:0.5em;vertical-align:-0.1em;accent-color:var(--_comark-primary);}&:where(details){margin-block:var(--_ty-flow) 0;}&:where(summary){cursor:pointer;font-weight:500;}&:where(summary)::marker{color:var(--_ty-muted);}&:where(kbd){font-family:inherit;font-size:0.85em;font-weight:500;border:1px solid var(--_ty-rule);border-block-end-width:2px;border-radius:0.35em;padding:0.0625em 0.35em;}&:where(dl){margin-block:var(--_ty-flow) 0;}&:where(dt){font-weight:500;margin-block-start:1em;}&:where(dt + dt){margin-block-start:0.25em;}&:where(dd){margin-block-start:0.25em;margin-inline-start:0;padding-inline-start:1em;color:var(--_ty-muted);}&:where(:not(pre)>code){background-color:var(--_ty-surface);font-family:var(--_ty-mono);font-size:0.85em;border-radius:0.35em;padding:0.125em 0.3em;}&:where(pre){background-color:var(--_ty-surface);font-family:var(--_ty-mono);font-size:0.875em;line-height:1.5;tab-size:2;direction:ltr;border-radius:var(--_ty-radius);padding:0.75em 1em;overflow-x:auto;margin-block:calc(var(--_ty-flow) / 0.875) 0;}&:where(pre code){background-color:transparent;font-family:inherit;font-size:inherit;padding:0;border-radius:0;}&:where(blockquote){border-inline-start:2px solid var(--_ty-rule);padding-inline-start:1em;margin-block:var(--_ty-flow) 0;margin-inline:0;}&:where(hr){border:0;border-block-start:1px solid var(--_ty-rule);margin-block:calc(var(--_ty-flow) * 2.4) 0;}&:where(hr + h1,hr + h2,hr + h3,hr + h4){margin-block-start:var(--_ty-flow);}&:where(.footnotes,[data-footnotes]){margin-block-start:calc(var(--_ty-flow) * 2);font-size:0.875em;color:var(--_ty-muted);}&:where(.footnotes>hr:first-child){margin-block-start:0;}&:where(.footnote-ref a,.footnote-backref){text-decoration-line:none;}&:where(math[display="block"]){margin-block:var(--_ty-flow) 0;overflow-x:auto;overflow-y:hidden;padding-block:0.25em;}&:where(img,video){border-radius:var(--_ty-radius);max-width:100%;height:auto;margin-block:var(--_ty-flow) 0;}&:where(:is(p,li,td,th,a) img){margin-block-start:0;}&:where(figure){margin-block:var(--_ty-flow) 0;margin-inline:0;}&:where(figcaption,caption){color:var(--_ty-muted);font-size:0.875em;text-align:center;margin-block-start:calc(0.75em / 0.875);}&:where(caption){caption-side:bottom;}&:where(table){display:block;width:max-content;max-width:100%;overflow-x:auto;font-size:1em;line-height:1.5;font-variant-numeric:tabular-nums;border-collapse:separate;border-spacing:0;border-block-end:1px solid var(--_ty-rule);margin-block:var(--_ty-flow) 0;}&:where(thead th){font-weight:500;text-align:start;padding:0.65em 1em;}&:where(:is(tbody,tfoot) :is(td,th)){padding:0.75em 1em;text-align:start;vertical-align:top;border-block-start:1px solid var(--_ty-rule);}&:where(tbody th,tfoot th){font-weight:500;}&:where(th:first-child,td:first-child){padding-inline-start:0;}&:where(th[align="center"],td[align="center"]){text-align:center;}&:where(th[align="right"],td[align="right"]){text-align:end;}&:where(li>blockquote,li>table,li>figure){margin-block-start:0.5em;}&:where(li>pre){margin-block-start:calc(0.5em / 0.875);}@media print{&:where(pre,table,blockquote,figure){break-inside:avoid;}&:where(h1,h2,h3,h4){break-after:avoid;}}@media(forced-colors:active){&:where(:not(pre)>code){border:1px solid;}}}.shiki:where(.dark *) span{&[style*="--shiki-dark:"]{color:var(--shiki-dark) !important;}&[style*="--shiki-dark-font-style:"]{font-style:var(--shiki-dark-font-style) !important;}&[style*="--shiki-dark-font-weight:"]{font-weight:var(--shiki-dark-font-weight) !important;}}@media(prefers-color-scheme:dark){.shiki:not(:where(.light *)) span{&[style*="--shiki-dark:"]{color:var(--shiki-dark) !important;}&[style*="--shiki-dark-font-style:"]{font-style:var(--shiki-dark-font-style) !important;}&[style*="--shiki-dark-font-weight:"]{font-weight:var(--shiki-dark-font-weight) !important;}}}:scope>[data-wide],:scope>:is(h2,h3,h4,.cl-heading:has(>:is(h2,h3,h4))):has(+ .ca-compare,+ style + .ca-compare){--_ty-start:max(0px,var(--comark-wide-start,0px));--_ty-end:max(0px,var(--comark-wide-end,0px));--_ty-wide:max(100%,min(var(--comark-wide-width,100vw),var(--comark-wide-max,72rem),100% + var(--_ty-start) + var(--_ty-end)));width:var(--_ty-wide);margin-inline:clamp(-1 * var(--_ty-start),(100% - var(--_ty-wide)) / 2,100% + var(--_ty-end) - var(--_ty-wide)) 0;}:scope>:where(:first-child):not(:where(.not-typeset,[data-not-typeset],.not-typeset *,[data-not-typeset] *)),:scope>:where(:first-child)>:where(:first-child):not(:where(.not-typeset,[data-not-typeset],.not-typeset *,[data-not-typeset] *)),:scope :where(li>:first-child,blockquote>:first-child,td>:first-child,th>:first-child,dd>:first-child,figure>:first-child,figure>picture>img ):not(:where(.not-typeset,[data-not-typeset],.not-typeset *,[data-not-typeset] *)){margin-block-start:0;}}}}
|
|
1
|
+
@layer comark{@layer typeset{@scope (.comark-typeset){:where(:scope){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#fff)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#0a0a0a)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#737373)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,#e5e5e5)));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#f5f5f5)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#a1a1a1)));--_comark-primary:var(--comark-primary,var(--ui-primary,var(--primary,var(--_comark-foreground))));--_comark-primary-foreground:var(--comark-primary-foreground,var(--ui-text-inverted,var(--primary-foreground,var(--_comark-background))));--_comark-secondary:var(--comark-secondary,var(--ui-secondary,var(--_comark-muted)));--_comark-radius:var(--comark-radius,calc(var(--ui-radius,var(--radius,.625rem)/2)*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media(prefers-color-scheme:dark){:where(:not(.light,.light *)):is(:where(:scope)){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)));--_comark-primary:var(--comark-primary,var(--ui-primary,var(--primary,var(--_comark-foreground))));--_comark-primary-foreground:var(--comark-primary-foreground,var(--ui-text-inverted,var(--primary-foreground,var(--_comark-background))));--_comark-secondary:var(--comark-secondary,var(--ui-secondary,var(--_comark-muted)))}}:where(.dark,.dark *):is(:where(:scope)){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)));--_comark-primary:var(--comark-primary,var(--ui-primary,var(--primary,var(--_comark-foreground))));--_comark-primary-foreground:var(--comark-primary-foreground,var(--ui-text-inverted,var(--primary-foreground,var(--_comark-background))));--_comark-secondary:var(--comark-secondary,var(--ui-secondary,var(--_comark-muted)))}:scope{--_ty-flow:var(--typeset-flow,1.25em);--_ty-muted:var(--_comark-muted);--_ty-rule:var(--_comark-border);--_ty-surface:var(--_comark-accent);--_ty-radius:calc(var(--_comark-radius) * 0.8);--_ty-mono:var(--comark-font-mono,var(--font-mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace));font-family:var(--_comark-font);font-size:calc(var(--typeset-size,1em) * 1.125);line-height:var(--typeset-leading,1.75);color:var(--_comark-foreground);overflow-wrap:break-word;background-image:var(--comark-grain,url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E"));background-repeat:repeat;background-size:var(--comark-grain-size,256px 256px);@media(min-width:48rem),print{font-size:var(--typeset-size,1em);}}:scope *:not(:where(.not-typeset,[data-not-typeset],.not-typeset *,[data-not-typeset] *)){&:where(p){margin-block:var(--_ty-flow) 0;}&:where(h1,h2,h3,h4,h5,h6){color:var(--_comark-foreground);font-family:var(--comark-font-heading,var(--font-heading,inherit));font-weight:600;margin-block-end:0;}&:where(h1){font-size:1.75em;line-height:1.3;margin-block-start:var(--_ty-flow);}&:where(h2){font-size:1.25em;line-height:1.4;margin-block-start:calc(var(--_ty-flow) * 1.4);}&:where(h3){font-size:1.125em;line-height:1.45;margin-block-start:var(--_ty-flow);}&:where(h4){font-size:1em;line-height:1.5;margin-block-start:var(--_ty-flow);}&:where(h5){font-size:0.875em;line-height:1.5;font-weight:500;color:var(--_ty-muted);margin-block-start:calc(var(--_ty-flow) / 0.875);}&:where(h6){font-size:0.8125em;line-height:1.5;font-weight:500;letter-spacing:0.08em;text-transform:uppercase;color:var(--_ty-muted);margin-block-start:calc(var(--_ty-flow) / 0.8125);}&:where([id]){scroll-margin-block-start:var(--_ty-flow);}&:where(h1 + *,h2 + *,h3 + *,h4 + *,h5 + *,h6 + *){margin-block-start:1em;}&:where(:is(h1,h2,h3,h4) code){font-size:0.9em;}&:where(a){color:inherit;font-weight:500;text-decoration-line:underline;text-decoration-color:color-mix(in oklab,currentColor 30%,transparent);}&:where(a:hover){text-decoration-color:currentColor;}&:where(a:focus-visible){outline:2px solid var(--_comark-ring);outline-offset:2px;border-radius:0.125em;}&:where(:is(h1,h2,h3,h4,h5,h6) a){font-weight:inherit;}&:where(strong,b){font-weight:600;}&:where(mark){background-color:color-mix(in oklab,oklch(0.86 0.17 95) 40%,transparent);color:inherit;padding:0.05em 0.15em;border-radius:0.2em;}&:where(abbr[title]){text-decoration-line:underline;text-decoration-style:dotted;text-underline-offset:0.2em;cursor:help;}&:where(del,s){color:var(--_ty-muted);text-decoration-line:line-through;}&:where(sup,sub){font-size:0.75em;line-height:0;position:relative;vertical-align:baseline;}&:where(sup){top:-0.5em;}&:where(sub){bottom:-0.25em;}&:where(sup a){text-decoration-line:none;font-weight:500;}&:where(ul){list-style-type:disc;}&:where(ol){list-style-type:decimal;}&:where(ul ul){list-style-type:circle;}&:where(ul ul ul){list-style-type:square;}&:where(ol[type="a" i]){list-style-type:lower-alpha;}&:where(ol[type="i" i]){list-style-type:lower-roman;}&:where(ol[type="A" s]){list-style-type:upper-alpha;}&:where(ol[type="I" s]){list-style-type:upper-roman;}&:where(ul,ol){margin-block:var(--_ty-flow) 0;padding-inline-start:1.5em;}&:where(li){margin-block-start:0.5em;padding-inline-start:0.4em;}&:where(li>p,li>ul,li>ol){margin-block-start:0.5em;}&:where(ul>li,ol>li)::marker{color:var(--_ty-muted);}&:where(ul.contains-task-list){list-style-type:none;padding-inline-start:0.25em;}&:where(li.task-list-item>input[type="checkbox"]){margin-inline-end:0.5em;vertical-align:-0.1em;accent-color:var(--_comark-primary);}&:where(details){margin-block:var(--_ty-flow) 0;}&:where(summary){cursor:pointer;font-weight:500;}&:where(summary)::marker{color:var(--_ty-muted);}&:where(kbd){font-family:inherit;font-size:0.85em;font-weight:500;border:1px solid var(--_ty-rule);border-block-end-width:2px;border-radius:0.35em;padding:0.0625em 0.35em;}&:where(dl){margin-block:var(--_ty-flow) 0;}&:where(dt){font-weight:500;margin-block-start:1em;}&:where(dt + dt){margin-block-start:0.25em;}&:where(dd){margin-block-start:0.25em;margin-inline-start:0;padding-inline-start:1em;color:var(--_ty-muted);}&:where(:not(pre)>code){background-color:var(--_ty-surface);font-family:var(--_ty-mono);font-size:0.85em;border-radius:0.35em;padding:0.125em 0.3em;}&:where(pre){background-color:var(--_ty-surface);font-family:var(--_ty-mono);font-size:0.875em;line-height:1.5;tab-size:2;direction:ltr;border-radius:var(--_ty-radius);padding:0.75em 1em;overflow-x:auto;margin-block:calc(var(--_ty-flow) / 0.875) 0;}&:where(pre code){background-color:transparent;font-family:inherit;font-size:inherit;padding:0;border-radius:0;}&:where(blockquote){border-inline-start:2px solid var(--_ty-rule);padding-inline-start:1em;margin-block:var(--_ty-flow) 0;margin-inline:0;}&:where(hr){border:0;border-block-start:1px solid var(--_ty-rule);margin-block:calc(var(--_ty-flow) * 2.4) 0;}&:where(hr + h1,hr + h2,hr + h3,hr + h4){margin-block-start:var(--_ty-flow);}&:where(.footnotes,[data-footnotes]){margin-block-start:calc(var(--_ty-flow) * 2);font-size:0.875em;color:var(--_ty-muted);}&:where(.footnotes>hr:first-child){margin-block-start:0;}&:where(.footnote-ref a,.footnote-backref){text-decoration-line:none;}&:where(math[display="block"]){margin-block:var(--_ty-flow) 0;overflow-x:auto;overflow-y:hidden;padding-block:0.25em;}&:where(img,video){border-radius:var(--_ty-radius);max-width:100%;height:auto;margin-block:var(--_ty-flow) 0;}&:where(:is(p,li,td,th,a) img){margin-block-start:0;}&:where(figure){margin-block:var(--_ty-flow) 0;margin-inline:0;}&:where(figcaption,caption){color:var(--_ty-muted);font-size:0.875em;text-align:center;margin-block-start:calc(0.75em / 0.875);}&:where(caption){caption-side:bottom;}&:where(table){display:block;width:max-content;max-width:100%;overflow-x:auto;font-size:1em;line-height:1.5;font-variant-numeric:tabular-nums;border-collapse:separate;border-spacing:0;border-block-end:1px solid var(--_ty-rule);margin-block:var(--_ty-flow) 0;}&:where(thead th){font-weight:500;text-align:start;padding:0.65em 1em;}&:where(:is(tbody,tfoot) :is(td,th)){padding:0.75em 1em;text-align:start;vertical-align:top;border-block-start:1px solid var(--_ty-rule);}&:where(tbody th,tfoot th){font-weight:500;}&:where(th:first-child,td:first-child){padding-inline-start:0;}&:where(th[align="center"],td[align="center"]){text-align:center;}&:where(th[align="right"],td[align="right"]){text-align:end;}&:where(li>blockquote,li>table,li>figure){margin-block-start:0.5em;}&:where(li>pre){margin-block-start:calc(0.5em / 0.875);}@media print{&:where(pre,table,blockquote,figure){break-inside:avoid;}&:where(h1,h2,h3,h4){break-after:avoid;}}@media(forced-colors:active){&:where(:not(pre)>code){border:1px solid;}}}:scope>[data-wide],:scope>:is(h2,h3,h4,.cl-heading:has(>:is(h2,h3,h4))):has(+ .ca-compare,+ style + .ca-compare){--_ty-start:max(0px,var(--comark-wide-start,0px));--_ty-end:max(0px,var(--comark-wide-end,0px));--_ty-wide:max(100%,min(var(--comark-wide-width,100vw),var(--comark-wide-max,72rem),100% + var(--_ty-start) + var(--_ty-end)));width:var(--_ty-wide);margin-inline:clamp(-1 * var(--_ty-start),(100% - var(--_ty-wide)) / 2,100% + var(--_ty-end) - var(--_ty-wide)) 0;}:scope>:where(:first-child):not(:where(.not-typeset,[data-not-typeset],.not-typeset *,[data-not-typeset] *)),:scope>:where(:first-child)>:where(:first-child):not(:where(.not-typeset,[data-not-typeset],.not-typeset *,[data-not-typeset] *)),:scope :where(li>:first-child,blockquote>:first-child,td>:first-child,th>:first-child,dd>:first-child,figure>:first-child,figure>picture>img ):not(:where(.not-typeset,[data-not-typeset],.not-typeset *,[data-not-typeset] *)){margin-block-start:0;}.shiki:where(.dark *) span[style*="--shiki-dark:"]{color:var(--shiki-dark)!important}.shiki:where(.dark *) span[style*="--shiki-dark-font-style:"]{font-style:var(--shiki-dark-font-style)!important}.shiki:where(.dark *) span[style*="--shiki-dark-font-weight:"]{font-weight:var(--shiki-dark-font-weight)!important}@media(prefers-color-scheme:dark){.shiki:not(:where(.light *)) span[style*="--shiki-dark:"]{color:var(--shiki-dark)!important}.shiki:not(:where(.light *)) span[style*="--shiki-dark-font-style:"]{font-style:var(--shiki-dark-font-style)!important}.shiki:not(:where(.light *)) span[style*="--shiki-dark-font-weight:"]{font-weight:var(--shiki-dark-font-weight)!important}}}}}
|