@markii/html 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,5 +14,11 @@ import type { HtmlComponent } from '../registry.js';
14
14
  * exact same allowlist check the sanitizer uses) and dropping the image
15
15
  * entirely when it fails, rather than re-implementing URL-scheme parsing
16
16
  * here. Matches `@markii/react`'s `Figure` markup byte-for-byte.
17
+ *
18
+ * `ctx.resolveImageSrc` (`../render.js`'s `renderMarkToHtml` option) then
19
+ * gets the same chance at an already-safe `src` that an ordinary markdown
20
+ * image gets (`../render.js`'s `applyImageResolver`), so a host resolving
21
+ * relative images sees this component's picture too, not just the ones
22
+ * markdown itself wrote.
17
23
  */
18
24
  export declare const Figure: HtmlComponent;
@@ -1,4 +1,5 @@
1
1
  import { isSafeUrl } from '@markii/core';
2
+ import { resolveImageAttribute } from '../image-resolve.js';
2
3
  const DEFAULT_ALT = '';
3
4
  /**
4
5
  * `:::figure{src="..." alt="..."} caption markdown :::` — an image with a
@@ -15,11 +16,20 @@ const DEFAULT_ALT = '';
15
16
  * exact same allowlist check the sanitizer uses) and dropping the image
16
17
  * entirely when it fails, rather than re-implementing URL-scheme parsing
17
18
  * here. Matches `@markii/react`'s `Figure` markup byte-for-byte.
19
+ *
20
+ * `ctx.resolveImageSrc` (`../render.js`'s `renderMarkToHtml` option) then
21
+ * gets the same chance at an already-safe `src` that an ordinary markdown
22
+ * image gets (`../render.js`'s `applyImageResolver`), so a host resolving
23
+ * relative images sees this component's picture too, not just the ones
24
+ * markdown itself wrote.
18
25
  */
19
26
  export const Figure = (attributes, childrenHtml, ctx) => {
20
27
  const rawSrc = attributes.src ?? null;
21
28
  const alt = attributes.alt ?? DEFAULT_ALT;
22
- const src = rawSrc && isSafeUrl(rawSrc) ? rawSrc : null;
29
+ const safeSrc = rawSrc && isSafeUrl(rawSrc) ? rawSrc : null;
30
+ const src = safeSrc
31
+ ? resolveImageAttribute(safeSrc, ctx.resolveImageSrc)
32
+ : null;
23
33
  const imgHtml = src
24
34
  ? `<img class="mk-figure__img" src="${ctx.esc(src)}" alt="${ctx.esc(alt)}">`
25
35
  : '';
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The shared logic behind `renderMarkToHtml`'s `resolveImageSrc` option
3
+ * (see `render.ts`'s `RenderMarkOptions`), used everywhere an `<img>`
4
+ * reaches the output string: an ordinary markdown image (`render.ts`'s
5
+ * `makeTransform`, which rewrites a plain hast `img` element in place) and
6
+ * the standard `Figure` component, which builds its own `<img>` HTML from a
7
+ * directive attribute rather than from parsed markdown.
8
+ *
9
+ * A host resolver is only ever asked about a source that could plausibly
10
+ * be its own: one with no scheme, no protocol-relative `//host/...` form,
11
+ * no bare `#fragment`, and no empty/whitespace value. Everything else
12
+ * already resolves on its own (or is not a path at all) and is left
13
+ * exactly as written — the identical rule `@markii/react`'s
14
+ * `image-resolve.ts` applies, so the two engines cannot diverge on what
15
+ * counts as "relative".
16
+ *
17
+ * The scheme test mirrors `@markii/core`'s `isSafeUrl`: text before the
18
+ * first `:`, but only when that `:` precedes any `/`, `?`, or `#` — so a
19
+ * path that merely contains a colon later on (`notes/a:b.png`) still reads
20
+ * as relative.
21
+ *
22
+ * WHY THE RESULT CHECK IS NOT `isSafeUrl`. `isSafeUrl`'s allowlist
23
+ * (`http`/`https`/`mailto`/`tel`) exists to judge a URL an AUTHOR typed
24
+ * into the document, where any other scheme is suspicious. A resolver's
25
+ * RETURN VALUE is the opposite trust direction: it is the HOST's own
26
+ * answer for where its resolved image actually lives, and both reference
27
+ * hosts already return values `isSafeUrl` would reject outright — VS
28
+ * Code's embedded bundle assets are `data:image/...` URIs and Obsidian's
29
+ * vault resource path is an `app://` URL (`@markii/react`'s
30
+ * `image-resolve.ts` names both call sites). Applying `isSafeUrl` here
31
+ * would blank every image either host resolves. What still needs guarding
32
+ * against is a resolver, hostile or merely buggy, echoing a
33
+ * `javascript:`/`vbscript:` value back out — the one class of scheme that
34
+ * turns an `<img src>` into a script-execution vector rather than an image
35
+ * request. `isSafeResolvedImageSrc` below is a narrow denylist for exactly
36
+ * that, not a repeat of the author-facing allowlist. Matches
37
+ * `@markii/react`'s identical function so the two engines cannot diverge.
38
+ */
39
+ /** The shape `renderMarkToHtml`/`renderMarkNodeToHtml` accept, and the one carried on `HtmlRenderContext` for a component that builds its own `<img>`. */
40
+ export type ResolveImageSrc = (src: string) => string | undefined;
41
+ /**
42
+ * The value one `<img src>` should actually carry: `value` unchanged unless
43
+ * `resolveImageSrc` is present, `value` is worth resolving at all, the
44
+ * resolver returns something, and that something passes
45
+ * `isSafeResolvedImageSrc` — so a resolver can never smuggle a
46
+ * `javascript:` URL past the sanitizer that already ran on everything else
47
+ * in the document, while a legitimate `data:`/`app:`/host-scheme result
48
+ * still reaches the page. A resolver that throws is treated exactly like
49
+ * one that returned `undefined`: `value` is kept, and the render is never
50
+ * broken over one image.
51
+ */
52
+ export declare function resolveImageAttribute(value: string, resolveImageSrc: ResolveImageSrc | undefined): string;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The shared logic behind `renderMarkToHtml`'s `resolveImageSrc` option
3
+ * (see `render.ts`'s `RenderMarkOptions`), used everywhere an `<img>`
4
+ * reaches the output string: an ordinary markdown image (`render.ts`'s
5
+ * `makeTransform`, which rewrites a plain hast `img` element in place) and
6
+ * the standard `Figure` component, which builds its own `<img>` HTML from a
7
+ * directive attribute rather than from parsed markdown.
8
+ *
9
+ * A host resolver is only ever asked about a source that could plausibly
10
+ * be its own: one with no scheme, no protocol-relative `//host/...` form,
11
+ * no bare `#fragment`, and no empty/whitespace value. Everything else
12
+ * already resolves on its own (or is not a path at all) and is left
13
+ * exactly as written — the identical rule `@markii/react`'s
14
+ * `image-resolve.ts` applies, so the two engines cannot diverge on what
15
+ * counts as "relative".
16
+ *
17
+ * The scheme test mirrors `@markii/core`'s `isSafeUrl`: text before the
18
+ * first `:`, but only when that `:` precedes any `/`, `?`, or `#` — so a
19
+ * path that merely contains a colon later on (`notes/a:b.png`) still reads
20
+ * as relative.
21
+ *
22
+ * WHY THE RESULT CHECK IS NOT `isSafeUrl`. `isSafeUrl`'s allowlist
23
+ * (`http`/`https`/`mailto`/`tel`) exists to judge a URL an AUTHOR typed
24
+ * into the document, where any other scheme is suspicious. A resolver's
25
+ * RETURN VALUE is the opposite trust direction: it is the HOST's own
26
+ * answer for where its resolved image actually lives, and both reference
27
+ * hosts already return values `isSafeUrl` would reject outright — VS
28
+ * Code's embedded bundle assets are `data:image/...` URIs and Obsidian's
29
+ * vault resource path is an `app://` URL (`@markii/react`'s
30
+ * `image-resolve.ts` names both call sites). Applying `isSafeUrl` here
31
+ * would blank every image either host resolves. What still needs guarding
32
+ * against is a resolver, hostile or merely buggy, echoing a
33
+ * `javascript:`/`vbscript:` value back out — the one class of scheme that
34
+ * turns an `<img src>` into a script-execution vector rather than an image
35
+ * request. `isSafeResolvedImageSrc` below is a narrow denylist for exactly
36
+ * that, not a repeat of the author-facing allowlist. Matches
37
+ * `@markii/react`'s identical function so the two engines cannot diverge.
38
+ */
39
+ /** The scheme text before the first `:` when one is present in scheme position, lowercased; `undefined` for a schemeless value. Delimiter rule matches `@markii/core`'s `isSafeUrl`. */
40
+ function schemeOf(value) {
41
+ const colon = value.indexOf(':');
42
+ if (colon === -1)
43
+ return undefined;
44
+ const slash = value.indexOf('/');
45
+ const questionMark = value.indexOf('?');
46
+ const numberSign = value.indexOf('#');
47
+ const hasSchemeBeforeDelimiter = (slash === -1 || colon < slash) &&
48
+ (questionMark === -1 || colon < questionMark) &&
49
+ (numberSign === -1 || colon < numberSign);
50
+ return hasSchemeBeforeDelimiter
51
+ ? value.slice(0, colon).toLowerCase()
52
+ : undefined;
53
+ }
54
+ /** True for a source worth offering to a resolver at all. */
55
+ function isResolvableImageSrc(value) {
56
+ if (value.trim() === '')
57
+ return false;
58
+ if (value.startsWith('#'))
59
+ return false;
60
+ if (value.startsWith('//'))
61
+ return false;
62
+ return schemeOf(value) === undefined;
63
+ }
64
+ /** Schemes that turn an `<img src>` into a script-execution vector. Everything else a resolver returns — `https:`, `data:`, `app:`, a host's own custom scheme — is a legitimate resolved location, not a smuggled script. */
65
+ const DANGEROUS_IMAGE_SCHEMES = new Set(['javascript', 'vbscript']);
66
+ /**
67
+ * `value` reduced to what a browser will actually parse a scheme out of:
68
+ * ASCII tab, line feed and carriage return removed wherever they appear,
69
+ * then leading C0 controls and spaces stripped. The URL parser ignores
70
+ * exactly these, so `"java<TAB>script:alert(1)"` and `" javascript:alert(1)"`
71
+ * both reach the page as the `javascript:` scheme. A scheme test that reads
72
+ * the raw text instead would call both of them schemeless and wave them
73
+ * through, which is the difference between a denylist that holds and one
74
+ * that only looks like it does.
75
+ */
76
+ function forSchemeTest(value) {
77
+ return value
78
+ .replace(/[\u0009\u000a\u000d]/g, '')
79
+ .replace(/^[\u0000-\u0020]+/, '');
80
+ }
81
+ /**
82
+ * True unless `value` carries one of `DANGEROUS_IMAGE_SCHEMES`, judged
83
+ * against `forSchemeTest`'s browser-equivalent reading rather than the raw
84
+ * string. See this module's top comment for why this is a narrow denylist
85
+ * and not `@markii/core`'s author-facing `isSafeUrl` allowlist. Because it
86
+ * IS a denylist, an unrecognized scheme is allowed, so the parsing it rests
87
+ * on has to match the browser's exactly: an allowlist fails closed on a
88
+ * spelling it does not recognize, and this cannot.
89
+ */
90
+ function isSafeResolvedImageSrc(value) {
91
+ const scheme = schemeOf(forSchemeTest(value));
92
+ return scheme === undefined || !DANGEROUS_IMAGE_SCHEMES.has(scheme);
93
+ }
94
+ /**
95
+ * The value one `<img src>` should actually carry: `value` unchanged unless
96
+ * `resolveImageSrc` is present, `value` is worth resolving at all, the
97
+ * resolver returns something, and that something passes
98
+ * `isSafeResolvedImageSrc` — so a resolver can never smuggle a
99
+ * `javascript:` URL past the sanitizer that already ran on everything else
100
+ * in the document, while a legitimate `data:`/`app:`/host-scheme result
101
+ * still reaches the page. A resolver that throws is treated exactly like
102
+ * one that returned `undefined`: `value` is kept, and the render is never
103
+ * broken over one image.
104
+ */
105
+ export function resolveImageAttribute(value, resolveImageSrc) {
106
+ if (!resolveImageSrc)
107
+ return value;
108
+ if (!isResolvableImageSrc(value))
109
+ return value;
110
+ let resolved;
111
+ try {
112
+ resolved = resolveImageSrc(value);
113
+ }
114
+ catch {
115
+ return value;
116
+ }
117
+ if (resolved === undefined)
118
+ return value;
119
+ return isSafeResolvedImageSrc(resolved) ? resolved : value;
120
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { renderMarkToHtml, renderMarkNodeToHtml } from './render.js';
1
+ export { renderMarkToHtml, renderMarkNodeToHtml, type RenderMarkOptions, } from './render.js';
2
+ export { type ResolveImageSrc } from './image-resolve.js';
2
3
  export { escapeHtml } from './escape.js';
3
4
  export { exportHtmlDocument, type ExportHtmlDocumentOptions, } from './document.js';
4
5
  export { resolveStorePath, resolveScopedPath, VAULT_NAME_PREFIX, type StorePathResolution, type ValueScope, } from './resolve.js';
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // stopped-changing document can be rendered for publishing, CI, email, or an
4
4
  // archive with no React runtime. It is one platform renderer among possible
5
5
  // many; the React renderer (@markii/react) is another consumer of the same core.
6
- export { renderMarkToHtml, renderMarkNodeToHtml } from './render.js';
6
+ export { renderMarkToHtml, renderMarkNodeToHtml, } from './render.js';
7
7
  export { escapeHtml } from './escape.js';
8
8
  export { exportHtmlDocument, } from './document.js';
9
9
  export { resolveStorePath, resolveScopedPath, VAULT_NAME_PREFIX, } from './resolve.js';
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import type { FailureKind, ValueStatus } from '@markii/runtime';
10
10
  import type { LayoutAxis } from '@markii/stdlib';
11
+ import type { ResolveImageSrc } from './image-resolve.js';
11
12
  /**
12
13
  * Attributes parsed off a directive, e.g. `{type=warning title="Careful"}`. A
13
14
  * bare attribute (present but valueless, e.g. `{collapsed}`) arrives as
@@ -81,6 +82,15 @@ export interface HtmlRenderContext {
81
82
  * arguments and has no room for a fourth.
82
83
  */
83
84
  layoutClassName?: string;
85
+ /**
86
+ * `renderMarkToHtml`'s `resolveImageSrc` option (`render.ts`'s
87
+ * `RenderMarkOptions`), carried on `ctx` so a component that builds its
88
+ * own `<img>` from an attribute — the standard `Figure` is the only one
89
+ * today — can resolve it the same way an ordinary markdown image does.
90
+ * `undefined` when the render call supplied none, in which case a
91
+ * component must leave its `src` exactly as authored.
92
+ */
93
+ resolveImageSrc?: ResolveImageSrc;
84
94
  }
85
95
  /**
86
96
  * One registry component: receives the directive's raw string attributes
package/dist/render.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { MarkNode } from '@markii/core';
2
2
  import type { ValueStore, VaultStore } from '@markii/runtime';
3
3
  import type { HtmlRegistry } from './registry.js';
4
+ import type { ResolveImageSrc } from './image-resolve.js';
4
5
  /**
5
6
  * Renders Markii text to a static HTML string using `registry` to resolve
6
7
  * directive names. Pipeline: `@markii/core`'s `toHast` (parse -> tag directive
@@ -22,13 +23,29 @@ import type { HtmlRegistry } from './registry.js';
22
23
  * mine, `@name` = the vault's". With no `vault` supplied, every `@name`
23
24
  * degrades to `'missing'` the same way an absent `store` degrades a bare
24
25
  * name.
26
+ *
27
+ * `options.resolveImageSrc` resolves a relative `<img src>` — an ordinary
28
+ * markdown image or one `Figure` built from an attribute — to a URL a host
29
+ * can actually load. It is never asked about a source that already carries
30
+ * a scheme, a protocol-relative `//host/...`, a bare `#fragment`, or an
31
+ * empty value, and its result is re-checked against `@markii/core`'s
32
+ * `isSafeUrl` before use, so it cannot introduce a `javascript:` URL the
33
+ * sanitizer would otherwise have dropped. Returning `undefined`, or
34
+ * throwing, leaves the source exactly as the author wrote it. Omitted
35
+ * entirely, every image renders with the source unchanged, matching every
36
+ * render before this option existed. The identical option on
37
+ * `@markii/react`'s `renderMark` uses the same rules, so the two engines
38
+ * cannot diverge on what a resolver is offered or how its result is used.
25
39
  */
26
- export declare function renderMarkToHtml(text: string, registry: HtmlRegistry, store?: ValueStore, vault?: VaultStore): string;
40
+ export interface RenderMarkOptions {
41
+ readonly resolveImageSrc?: ResolveImageSrc;
42
+ }
43
+ export declare function renderMarkToHtml(text: string, registry: HtmlRegistry, store?: ValueStore, vault?: VaultStore, options?: RenderMarkOptions): string;
27
44
  /**
28
45
  * The block-level twin of `renderMarkToHtml`: renders one already-parsed mdast
29
46
  * node (`@markii/core`'s `MarkNode`) to HTML instead of a whole document's
30
47
  * text, via `nodeToHast`. Same registry resolution, same fallbacks, same
31
48
  * purity and never-throw guarantees, and the same optional `store`/`vault`
32
- * value-binding arguments.
49
+ * value-binding arguments and `resolveImageSrc` option.
33
50
  */
34
- export declare function renderMarkNodeToHtml(node: MarkNode, registry: HtmlRegistry, store?: ValueStore, vault?: VaultStore): string;
51
+ export declare function renderMarkNodeToHtml(node: MarkNode, registry: HtmlRegistry, store?: ValueStore, vault?: VaultStore, options?: RenderMarkOptions): string;
package/dist/render.js CHANGED
@@ -6,6 +6,7 @@ import { escapeHtml } from './escape.js';
6
6
  import { resolveScopedPath } from './resolve.js';
7
7
  import { failureKindClass, failureTitle, EMPTY_INLINE_MARKER_CLASS, emptyInlineTitle, } from './failure-presentation.js';
8
8
  import { formatValue } from '@markii/stdlib';
9
+ import { resolveImageAttribute } from './image-resolve.js';
9
10
  /** The hast tag name `@markii/core`'s `toHast` marks every directive with (`to-hast.ts`'s `DIRECTIVE_TAG`). */
10
11
  const DIRECTIVE_TAG = 'mk-directive';
11
12
  /** `data-mk-kind` value for a TEXT (inline) directive; the other two kinds (`leafDirective`/`containerDirective`) are block. */
@@ -64,7 +65,7 @@ function buildValueMarker(name, resolved, format, decimals) {
64
65
  * The `data*` fields are attached per-directive later (see
65
66
  * `withDataBinding`) — this base object never carries them.
66
67
  */
67
- function createBaseContext(scope) {
68
+ function createBaseContext(scope, resolveImageSrc) {
68
69
  return {
69
70
  esc: escapeHtml,
70
71
  resolve(name) {
@@ -80,6 +81,7 @@ function createBaseContext(scope) {
80
81
  : { value: undefined, status: 'missing' };
81
82
  return buildValueMarker(trimmed, resolved, format, decimals);
82
83
  },
84
+ resolveImageSrc,
83
85
  };
84
86
  }
85
87
  /**
@@ -386,6 +388,20 @@ function renderDirective(element, registry, ctx, scope) {
386
388
  * children so a nested directive is already resolved by the time its parent
387
389
  * serializes it.
388
390
  */
391
+ /**
392
+ * Rewrites an ordinary hast `<img>` element's `src` in place through
393
+ * `resolveImageSrc` — the plain-markdown-image half of the seam
394
+ * `Figure` implements for its own attribute-built `<img>` (see
395
+ * `./components/figure.ts`). With no resolver at all (the common case)
396
+ * this is a no-op, so a fixture rendered without one produces byte-
397
+ * identical output to before this option existed.
398
+ */
399
+ function applyImageResolver(node, resolveImageSrc) {
400
+ const src = node.properties.src;
401
+ if (typeof src === 'string') {
402
+ node.properties.src = resolveImageAttribute(src, resolveImageSrc);
403
+ }
404
+ }
389
405
  function makeTransform(registry, ctx, scope) {
390
406
  function transform(node) {
391
407
  if (node.type !== 'element')
@@ -398,6 +414,9 @@ function makeTransform(registry, ctx, scope) {
398
414
  if (marker !== undefined)
399
415
  return raw(marker);
400
416
  }
417
+ if (node.tagName === 'img') {
418
+ applyImageResolver(node, ctx.resolveImageSrc);
419
+ }
401
420
  return node;
402
421
  }
403
422
  return transform;
@@ -409,37 +428,15 @@ function renderFailureFallback(error) {
409
428
  `<p class="mk-unknown__label">failed to render document</p>` +
410
429
  `<pre class="mk-unknown__content">${escapeHtml(message)}</pre></div>`);
411
430
  }
412
- function renderRoot(root, registry, scope) {
413
- const ctx = createBaseContext(scope);
431
+ function renderRoot(root, registry, scope, resolveImageSrc) {
432
+ const ctx = createBaseContext(scope, resolveImageSrc);
414
433
  const transform = makeTransform(registry, ctx, scope);
415
434
  root.children = root.children.map(transform);
416
435
  return serialize(root.children);
417
436
  }
418
- /**
419
- * Renders Markii text to a static HTML string using `registry` to resolve
420
- * directive names. Pipeline: `@markii/core`'s `toHast` (parse -> tag directive
421
- * nodes -> remark-rehype -> sanitize URLs) -> a hast->HTML walk that swaps
422
- * directive elements for registry components (or the unknown-directive
423
- * fallback) and folds script fences into markers. Pure and never-throwing:
424
- * parsing is tolerant, unknown names always render a fallback, and any
425
- * unexpected internal error degrades to the "failed to render document" box.
426
- *
427
- * `store` is the note's value store (`@markii/runtime`, §8's pure read path)
428
- * — optional, matching how a missing/absent value degrades gracefully: with
429
- * no store, `:value[name]` renders its missing-value marker and every
430
- * `data=name` attribute resolves to `dataStatus: 'missing'`, but the
431
- * document still renders completely.
432
- *
433
- * `vault` is the optional app-scoped read seam (`@markii/runtime`'s
434
- * `VaultStore`) that an `@`-prefixed name (`data=@gh.stars`,
435
- * `:value[@gh.stars]`) resolves against instead of `store` — "bare name =
436
- * mine, `@name` = the vault's". With no `vault` supplied, every `@name`
437
- * degrades to `'missing'` the same way an absent `store` degrades a bare
438
- * name.
439
- */
440
- export function renderMarkToHtml(text, registry, store, vault) {
437
+ export function renderMarkToHtml(text, registry, store, vault, options) {
441
438
  try {
442
- return renderRoot(toHast(text), registry, { store, vault });
439
+ return renderRoot(toHast(text), registry, { store, vault }, options?.resolveImageSrc);
443
440
  }
444
441
  catch (error) {
445
442
  return renderFailureFallback(error);
@@ -450,11 +447,11 @@ export function renderMarkToHtml(text, registry, store, vault) {
450
447
  * node (`@markii/core`'s `MarkNode`) to HTML instead of a whole document's
451
448
  * text, via `nodeToHast`. Same registry resolution, same fallbacks, same
452
449
  * purity and never-throw guarantees, and the same optional `store`/`vault`
453
- * value-binding arguments.
450
+ * value-binding arguments and `resolveImageSrc` option.
454
451
  */
455
- export function renderMarkNodeToHtml(node, registry, store, vault) {
452
+ export function renderMarkNodeToHtml(node, registry, store, vault, options) {
456
453
  try {
457
- return renderRoot(nodeToHast(node), registry, { store, vault });
454
+ return renderRoot(nodeToHast(node), registry, { store, vault }, options?.resolveImageSrc);
458
455
  }
459
456
  catch (error) {
460
457
  return renderFailureFallback(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markii/html",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "A framework-free static HTML renderer for Markii (.mk.md): a registry-driven hast-to-HTML string engine. Zero React; for stopped-changing documents (publish, CI, email, archive).",
5
5
  "keywords": [
6
6
  "markdown",
@@ -54,9 +54,9 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "hast-util-to-html": "^9.0.0",
57
- "@markii/core": "^0.12.1",
58
- "@markii/runtime": "^0.12.1",
59
- "@markii/stdlib": "^0.12.1"
57
+ "@markii/core": "^0.13.0",
58
+ "@markii/runtime": "^0.13.0",
59
+ "@markii/stdlib": "^0.13.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/hast": "^3.0.4"