@markii/ansi 0.17.0-next.1 → 0.17.0-next.2

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.
@@ -39,6 +39,8 @@ export declare function invalidAttributeValueTitle(directive: string, attribute:
39
39
  export declare function unsafeImageSrcTitle(directive: string): string;
40
40
  /** The diagnostic text for a host `renderCode` callback that threw; the code block rendered as usual. Wording identical to `@markii/html`'s `codeRendererFailedTitle`. */
41
41
  export declare function codeRendererFailedTitle(): string;
42
+ /** The diagnostic text for a directive that names no registered component; the unknown-component box was shown. */
43
+ export declare function unknownDirectiveTitle(directive: string): string;
42
44
  /** The diagnostic text for a registered block component written as an inline directive. Wording identical to `@markii/html`'s `formMismatchTitle`. */
43
45
  export declare function formMismatchTitle(directive: string): string;
44
46
  /**
@@ -103,6 +103,10 @@ export function unsafeImageSrcTitle(directive) {
103
103
  export function codeRendererFailedTitle() {
104
104
  return 'code block: the host code renderer failed, so the plain block was shown';
105
105
  }
106
+ /** The diagnostic text for a directive that names no registered component; the unknown-component box was shown. */
107
+ export function unknownDirectiveTitle(directive) {
108
+ return `${directive}: unknown component, shown as a placeholder`;
109
+ }
106
110
  /** The diagnostic text for a registered block component written as an inline directive. Wording identical to `@markii/html`'s `formMismatchTitle`. */
107
111
  export function formMismatchTitle(directive) {
108
112
  return `${directive}: block component was written inline and was not rendered`;
@@ -1,7 +1,25 @@
1
1
  import type { ReactNode } from 'react';
2
+ import { useInput } from 'ink';
2
3
  import type { DOMElement } from 'ink';
3
4
  import type { AnsiTheme } from './theme.js';
4
5
  import type { ColorLevel } from './ansi.js';
6
+ /**
7
+ * Wraps a focusable pack component's live element. It draws a two-column
8
+ * gutter (the accent marker while the component has focus, blanks
9
+ * otherwise) and names the component's focus id for `usePackInput`. The
10
+ * pack draws inside the width that is left.
11
+ */
12
+ export declare function PackFocusScope({ focusId, children, }: {
13
+ focusId: number;
14
+ children: ReactNode;
15
+ }): ReactNode;
16
+ /**
17
+ * Ink's `useInput`, for a terminal pack: the same handler and options, but
18
+ * keys reach the handler only while the pack component whose subtree calls
19
+ * it has focus. Outside a focusable pack component it never fires. A pack
20
+ * reads this as `__markiiInk.useInput`, so it never sees raw stdin.
21
+ */
22
+ export declare function usePackInput(handler: Parameters<typeof useInput>[0], options?: Parameters<typeof useInput>[1]): void;
5
23
  /** Whether the component identified by `focusId` is the currently focused one. `undefined` (a non-interactive render, or a component that never got a `focusId`) is never focused. */
6
24
  export declare function useIsFocused(focusId: number | undefined): boolean;
7
25
  /** Wraps `text` in the accent-colored focus marker when interactive AND `focusId` is the focused one; otherwise returns `text` unchanged. */
@@ -29,7 +47,7 @@ export declare function useFocusAnchor(focusId: number | undefined): (node: DOME
29
47
  * yet (mount, or a reader who has not switched panels).
30
48
  */
31
49
  export interface FocusableDescriptor {
32
- kind: 'tabs' | 'details';
50
+ kind: 'tabs' | 'details' | 'pack';
33
51
  label: string;
34
52
  }
35
53
  export interface InteractiveRootProps {
@@ -11,6 +11,36 @@ const FocusContext = createContext({
11
11
  reportActiveLabel: () => { },
12
12
  registerAnchor: () => { },
13
13
  });
14
+ /**
15
+ * The `focusId` of the pack component a subtree belongs to, or `undefined`
16
+ * outside one. Provided by `PackFocusScope`; read only by `usePackInput`.
17
+ */
18
+ const PackFocusContext = createContext(undefined);
19
+ /**
20
+ * Wraps a focusable pack component's live element. It draws a two-column
21
+ * gutter (the accent marker while the component has focus, blanks
22
+ * otherwise) and names the component's focus id for `usePackInput`. The
23
+ * pack draws inside the width that is left.
24
+ */
25
+ export function PackFocusScope({ focusId, children, }) {
26
+ const gutter = useFocusMarker('▌ ', focusId);
27
+ const focused = useIsFocused(focusId);
28
+ return (_jsx(PackFocusContext.Provider, { value: focusId, children: _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { children: focused ? gutter : ' ' }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: children })] }) }));
29
+ }
30
+ /**
31
+ * Ink's `useInput`, for a terminal pack: the same handler and options, but
32
+ * keys reach the handler only while the pack component whose subtree calls
33
+ * it has focus. Outside a focusable pack component it never fires. A pack
34
+ * reads this as `__markiiInk.useInput`, so it never sees raw stdin.
35
+ */
36
+ export function usePackInput(handler, options = {}) {
37
+ const focusId = useContext(PackFocusContext);
38
+ const focused = useIsFocused(focusId);
39
+ useInput(handler, {
40
+ ...options,
41
+ isActive: focused && options.isActive !== false,
42
+ });
43
+ }
14
44
  /** Whether the component identified by `focusId` is the currently focused one. `undefined` (a non-interactive render, or a component that never got a `focusId`) is never focused. */
15
45
  export function useIsFocused(focusId) {
16
46
  const api = useContext(FocusContext);
@@ -142,6 +142,9 @@ export function loadAnsiPack(manifest, componentModules) {
142
142
  const inline = kind === undefined ? module.inline : kind === 'inline';
143
143
  if (inline !== undefined)
144
144
  entry.inline = inline;
145
+ // Focus only makes sense for a block view.
146
+ if (module.focusable === true && inline !== true)
147
+ entry.focusable = true;
145
148
  entries[composed.name] = entry;
146
149
  }
147
150
  return { registry: createAnsiRegistry(entries) };
@@ -6,7 +6,7 @@ import type { AnsiPackToInstall } from './pack-loader.js';
6
6
  * globalThis.__markiiRegisterTerminalPack(manifestJson, componentModules)
7
7
  *
8
8
  * with the raw `pack.json` text and an own-property map of
9
- * `{ component, inline? }` keyed by local component name, the same shape
9
+ * `{ component, inline?, focusable? }` keyed by local component name, the same shape
10
10
  * `__markiiRegisterPack` takes for the webview. React and Ink are NOT
11
11
  * bundled into the script. The host sets `globalThis.__markiiReactInk`
12
12
  * (the React instance Ink uses) and `globalThis.__markiiInk` (Ink's
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import * as Ink from 'ink';
3
+ import { usePackInput } from './interactive.js';
3
4
  import { parsePackManifest } from '@markii/pack';
4
5
  /**
5
6
  * The terminal pack registration contract (docs/packs.md). A pack's
@@ -8,7 +9,7 @@ import { parsePackManifest } from '@markii/pack';
8
9
  * globalThis.__markiiRegisterTerminalPack(manifestJson, componentModules)
9
10
  *
10
11
  * with the raw `pack.json` text and an own-property map of
11
- * `{ component, inline? }` keyed by local component name, the same shape
12
+ * `{ component, inline?, focusable? }` keyed by local component name, the same shape
12
13
  * `__markiiRegisterPack` takes for the webview. React and Ink are NOT
13
14
  * bundled into the script. The host sets `globalThis.__markiiReactInk`
14
15
  * (the React instance Ink uses) and `globalThis.__markiiInk` (Ink's
@@ -32,7 +33,9 @@ export const TERMINAL_INK_GLOBAL = '__markiiInk';
32
33
  export function installTerminalPackGlobals() {
33
34
  const g = globalThis;
34
35
  g[TERMINAL_REACT_GLOBAL] = React;
35
- g[TERMINAL_INK_GLOBAL] = Ink;
36
+ // Ink's exports with `useInput` replaced by the focus-gated one: a pack
37
+ // gets keys while its component has focus and never raw stdin.
38
+ g[TERMINAL_INK_GLOBAL] = { ...Ink, useInput: usePackInput };
36
39
  }
37
40
  function isPlainObject(value) {
38
41
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -52,9 +55,16 @@ function toComponentModules(value) {
52
55
  if (Object.hasOwn(entry, 'inline') && typeof entry.inline !== 'boolean') {
53
56
  return undefined;
54
57
  }
58
+ if (Object.hasOwn(entry, 'focusable') &&
59
+ typeof entry.focusable !== 'boolean') {
60
+ return undefined;
61
+ }
55
62
  out[key] = {
56
63
  component: entry.component,
57
64
  ...(typeof entry.inline === 'boolean' ? { inline: entry.inline } : {}),
65
+ ...(typeof entry.focusable === 'boolean'
66
+ ? { focusable: entry.focusable }
67
+ : {}),
58
68
  };
59
69
  }
60
70
  return out;
@@ -171,6 +171,16 @@ export interface AnsiRegistryEntry {
171
171
  * Ink primitives Yoga can reflow.
172
172
  */
173
173
  selfLayout?: boolean;
174
+ /**
175
+ * Marks a BLOCK component whose returned Ink element the live viewer
176
+ * mounts and lets take focus, instead of drawing it as a static
177
+ * snapshot. It joins the focus order (up/down/j/k) and, while focused,
178
+ * its element's `useInput` calls receive keys (a pack reads
179
+ * `__markiiInk.useInput`, which is gated this way). In a render-once
180
+ * pass, or inside a framed body (a callout, a card) it renders as
181
+ * before: a snapshot with no focus.
182
+ */
183
+ focusable?: boolean;
174
184
  }
175
185
  /** One alias: a second name for an existing component, optionally carrying preset attributes. */
176
186
  export interface RegistryAlias {
package/dist/render.d.ts CHANGED
@@ -57,6 +57,13 @@ export interface RenderMarkOptions {
57
57
  readonly resolveImageSrc?: ResolveImageSrc;
58
58
  readonly resolveHref?: ResolveHref;
59
59
  readonly onDiagnostic?: OnDiagnostic;
60
+ /**
61
+ * Also report a directive that names no registered component as an
62
+ * `'unknown-directive'` event. Off by default: the fallback box already
63
+ * marks it in the output. A host that places findings in the note's
64
+ * source (a language server) turns it on.
65
+ */
66
+ readonly reportUnknownDirectives?: boolean;
60
67
  /**
61
68
  * Components of installed packs that this render cannot draw, as plain
62
69
  * data from the host. A directive named here renders the usual fallback
package/dist/render.js CHANGED
@@ -15,13 +15,13 @@ import { sanitizeBlockText, sanitizeUrlText, stripControlCharacters, } from './s
15
15
  import { readRegistryComponent, registryLayoutAxis, registrySelfLayout, resolveDirectiveAlias, } from './registry.js';
16
16
  import { applyLayout, resolveLayoutAttributes, } from './layout.js';
17
17
  import { resolveScopedPath } from './resolve.js';
18
- import { dataStateSuffix, emptyInlineTitle, failureToken, codeRendererFailedTitle, formMismatchTitle, NO_ENGINE_VIEW_LABEL, noEngineViewTitle, packLoadFailedLabel, packLoadFailedTitle, invalidAttributeValueLabel, invalidAttributeValueTitle, unsafeImageSrcLabel, unsafeImageSrcTitle, } from './failure-presentation.js';
18
+ import { dataStateSuffix, emptyInlineTitle, failureToken, codeRendererFailedTitle, formMismatchTitle, NO_ENGINE_VIEW_LABEL, noEngineViewTitle, packLoadFailedLabel, packLoadFailedTitle, invalidAttributeValueLabel, invalidAttributeValueTitle, unsafeImageSrcLabel, unknownDirectiveTitle, unsafeImageSrcTitle, } from './failure-presentation.js';
19
19
  import { createUnavailableComponentLookup, formatValue, getContract, reportDiagnostic, } from '@markii/stdlib';
20
20
  import { resolveImageAttribute, } from './image-resolve.js';
21
21
  import { resolveHrefAttribute } from './href-resolve.js';
22
22
  import { defaultAnsiRegistry } from './components/index.js';
23
23
  import { renderInkToString, renderInkToStringSync } from './ink-string.js';
24
- import { InteractiveRoot } from './interactive.js';
24
+ import { InteractiveRoot, PackFocusScope } from './interactive.js';
25
25
  import { glyphsFor } from './glyphs.js';
26
26
  import { InteractiveTabs } from './components/tabs.js';
27
27
  import { InteractiveDetails } from './components/details.js';
@@ -126,6 +126,29 @@ function nodeOrRootToHast(node) {
126
126
  }
127
127
  return { type: 'root', children };
128
128
  }
129
+ /**
130
+ * A walk context whose `onDiagnostic` stamps each event with the source
131
+ * range of `element`, unless an inner directive already did. This is how a
132
+ * host (the language server) places a finding in the note without any
133
+ * component knowing about positions. A node with no position, or a render
134
+ * with no `onDiagnostic`, returns `wctx` itself.
135
+ */
136
+ function withNodePosition(wctx, element) {
137
+ const downstream = wctx.onDiagnostic;
138
+ const range = element.position;
139
+ if (downstream === undefined || range === undefined)
140
+ return wctx;
141
+ const position = {
142
+ startLine: range.start.line,
143
+ startColumn: range.start.column,
144
+ endLine: range.end.line,
145
+ endColumn: range.end.column,
146
+ };
147
+ return {
148
+ ...wctx,
149
+ onDiagnostic: (event) => downstream(event.position === undefined ? { ...event, position } : event),
150
+ };
151
+ }
129
152
  function nextFocusId(wctx) {
130
153
  const id = wctx.focusCounter.current;
131
154
  wctx.focusCounter.current += 1;
@@ -389,6 +412,11 @@ function renderDirectiveContentString(name, kind, isBlock, attributes, childrenT
389
412
  });
390
413
  return unknownDirective(name, inline, safeChildrenText(childrenText), missingView.loadFailed ? 'pack-load-failed' : 'no-engine-view', width, wctx.color, wctx.glyphs, missingView.pack);
391
414
  }
415
+ reportDiagnostic(wctx.onDiagnostic, {
416
+ kind: 'unknown-directive',
417
+ directive: name || '(unnamed)',
418
+ message: unknownDirectiveTitle(name || '(unnamed)'),
419
+ });
392
420
  return unknownDirective(name || '(unnamed)', inline, safeChildrenText(childrenText), 'unregistered', width, wctx.color, wctx.glyphs);
393
421
  }
394
422
  if (isFormMismatch(entry, kind)) {
@@ -812,7 +840,8 @@ function renderBlocksString(nodes, width, indent, wctx) {
812
840
  }
813
841
  return blocks.join('\n\n');
814
842
  }
815
- function renderDirectiveString(element, width, indent, wctx) {
843
+ function renderDirectiveString(element, width, indent, walk) {
844
+ const wctx = withNodePosition(walk, element);
816
845
  const written = stringProperty(element, 'data-mk-name') ?? '';
817
846
  const kind = stringProperty(element, 'data-mk-kind');
818
847
  const rawAttributes = parseAttributes(stringProperty(element, 'data-mk-attrs'));
@@ -1241,7 +1270,8 @@ function renderFigureElement(element, attributes, width, indent, wctx) {
1241
1270
  return (_jsxs(Box, { flexDirection: "column", children: [headerElement, body] }));
1242
1271
  }
1243
1272
  /** Renders one directive as a real Ink element (block position only). Dispatches `row`/`tabs`/`details`/`card`/`figure` to their real builders; every other directive falls back to the string engine, wrapped in one `<Text>`. */
1244
- function renderDirectiveElement(element, width, indent, wctx) {
1273
+ function renderDirectiveElement(element, width, indent, walk) {
1274
+ const wctx = withNodePosition(walk, element);
1245
1275
  const written = stringProperty(element, 'data-mk-name') ?? '';
1246
1276
  const kind = stringProperty(element, 'data-mk-kind');
1247
1277
  const rawAttributes = parseAttributes(stringProperty(element, 'data-mk-attrs'));
@@ -1312,8 +1342,48 @@ function renderDirectiveElement(element, width, indent, wctx) {
1312
1342
  return styledText(componentError(name || '(unnamed)', false, '', width, wctx.color, wctx.glyphs));
1313
1343
  }
1314
1344
  }
1345
+ if (entry?.focusable === true && !formMismatch && wctx.interactive) {
1346
+ const focusable = renderFocusablePackElement(element, name, aliased, width, indent, wctx);
1347
+ if (focusable !== undefined)
1348
+ return focusable;
1349
+ }
1315
1350
  return styledText(renderDirectiveString(element, width, indent, wctx));
1316
1351
  }
1352
+ /**
1353
+ * A `focusable` registry component in the live viewer: the component runs
1354
+ * as usual, but an Ink element it returns is mounted (not drawn once) in a
1355
+ * `PackFocusScope`, so it joins the focus order and receives keys while
1356
+ * focused. Returns `undefined` when the component returns a string or
1357
+ * throws, so the caller falls back to the string path (which reports the
1358
+ * failure the usual way). The component draws two columns narrower: the
1359
+ * scope's gutter carries the focus marker.
1360
+ */
1361
+ function renderFocusablePackElement(element, name, attributes, width, indent, wctx) {
1362
+ const component = readRegistryComponent(wctx.registry[name]);
1363
+ if (component == null)
1364
+ return undefined;
1365
+ const innerWidth = Math.max(1, width - 2);
1366
+ const binding = resolveDataAttribute(attributes, wctx.scope);
1367
+ const focusId = nextFocusId(wctx);
1368
+ recordFocusable(wctx, { kind: 'pack', label: stripControlCharacters(name) });
1369
+ const ctx = buildComponentContext(innerWidth, indent, wctx, binding, undefined, focusId);
1370
+ let result;
1371
+ try {
1372
+ result = component({
1373
+ attributes: binding.attributes,
1374
+ children: renderDirectiveChildrenString(element.children, innerWidth, indent, wctx),
1375
+ ctx,
1376
+ });
1377
+ }
1378
+ catch {
1379
+ // Same as the string path: the failure is contained to a neutral box.
1380
+ return styledText(componentError(name || '(unnamed)', false, '', width, wctx.color, wctx.glyphs));
1381
+ }
1382
+ if (!isValidElement(result)) {
1383
+ return styledText(typeof result === 'string' ? result : '');
1384
+ }
1385
+ return _jsx(PackFocusScope, { focusId: focusId, children: result });
1386
+ }
1317
1387
  /** Renders one block-level hast node as a real Ink element, or `null` for a node that carries no content (whitespace-only text). */
1318
1388
  function renderBlockElement(node, width, indent, wctx) {
1319
1389
  if (node.type === 'text') {
@@ -1409,6 +1479,23 @@ function buildWalkContext(registry, scope, color, theme, glyphs, interactive, re
1409
1479
  focusables: undefined,
1410
1480
  };
1411
1481
  }
1482
+ /**
1483
+ * The `onDiagnostic` a render hands its walk: the caller's own, minus
1484
+ * `'unknown-directive'` events unless `reportUnknownDirectives` asked for
1485
+ * them. The fallback box already marks an unknown directive in the output,
1486
+ * so a host that does not place findings in a source (the viewer, an
1487
+ * export) keeps the quiet diagnostics surface it always had.
1488
+ */
1489
+ function diagnosticsFor(options) {
1490
+ const downstream = options?.onDiagnostic;
1491
+ if (downstream === undefined || options?.reportUnknownDirectives === true) {
1492
+ return downstream;
1493
+ }
1494
+ return (event) => {
1495
+ if (event.kind !== 'unknown-directive')
1496
+ downstream(event);
1497
+ };
1498
+ }
1412
1499
  /** The columns assumed when a caller supplies no `width` option. */
1413
1500
  const DEFAULT_WIDTH = 80;
1414
1501
  /** Builds the root Ink element for `root`, given every resolved render input. Exported for `buildMarkElement` (the live-viewer entry point, phase 3) as well as this module's own three string-producing entry points. */
@@ -1443,7 +1530,7 @@ async function renderRootToString(root, registry, scope, color, theme, glyphs, i
1443
1530
  export async function renderMarkToAnsi(text, registry = defaultAnsiRegistry, store, vault, options) {
1444
1531
  const color = resolveColorOption(options?.color);
1445
1532
  try {
1446
- return await renderRootToString(toHast(text), registry, { store: options?.store ?? store, vault: options?.vault ?? vault }, color, options?.theme ?? defaultAnsiTheme, glyphsFor(options?.box), options?.interactive ?? false, options?.resolveImageSrc, options?.resolveHref, options?.onDiagnostic, createUnavailableComponentLookup(options?.unavailableComponents), options?.width ?? DEFAULT_WIDTH, options?.renderCode);
1533
+ return await renderRootToString(toHast(text), registry, { store: options?.store ?? store, vault: options?.vault ?? vault }, color, options?.theme ?? defaultAnsiTheme, glyphsFor(options?.box), options?.interactive ?? false, options?.resolveImageSrc, options?.resolveHref, diagnosticsFor(options), createUnavailableComponentLookup(options?.unavailableComponents), options?.width ?? DEFAULT_WIDTH, options?.renderCode);
1447
1534
  }
1448
1535
  catch (error) {
1449
1536
  return renderFailureFallback(error, color);
@@ -1458,7 +1545,7 @@ export async function renderMarkToAnsi(text, registry = defaultAnsiRegistry, sto
1458
1545
  export async function renderMarkNodeToAnsi(node, registry = defaultAnsiRegistry, store, vault, options) {
1459
1546
  const color = resolveColorOption(options?.color);
1460
1547
  try {
1461
- return await renderRootToString(nodeOrRootToHast(node), registry, { store: options?.store ?? store, vault: options?.vault ?? vault }, color, options?.theme ?? defaultAnsiTheme, glyphsFor(options?.box), options?.interactive ?? false, options?.resolveImageSrc, options?.resolveHref, options?.onDiagnostic, createUnavailableComponentLookup(options?.unavailableComponents), options?.width ?? DEFAULT_WIDTH, options?.renderCode);
1548
+ return await renderRootToString(nodeOrRootToHast(node), registry, { store: options?.store ?? store, vault: options?.vault ?? vault }, color, options?.theme ?? defaultAnsiTheme, glyphsFor(options?.box), options?.interactive ?? false, options?.resolveImageSrc, options?.resolveHref, diagnosticsFor(options), createUnavailableComponentLookup(options?.unavailableComponents), options?.width ?? DEFAULT_WIDTH, options?.renderCode);
1462
1549
  }
1463
1550
  catch (error) {
1464
1551
  return renderFailureFallback(error, color);
@@ -1522,7 +1609,7 @@ export function buildMarkElement(document, registry = defaultAnsiRegistry, optio
1522
1609
  root = { type: 'root', children: [] };
1523
1610
  }
1524
1611
  const unavailable = createUnavailableComponentLookup(options?.unavailableComponents);
1525
- const content = buildRootElement(root, registry, scope, color, theme, glyphs, interactive, options?.resolveImageSrc, options?.resolveHref, options?.onDiagnostic, unavailable, width, options?.renderCode);
1612
+ const content = buildRootElement(root, registry, scope, color, theme, glyphs, interactive, options?.resolveImageSrc, options?.resolveHref, diagnosticsFor(options), unavailable, width, options?.renderCode);
1526
1613
  if (!interactive)
1527
1614
  return content;
1528
1615
  // The walk above already assigned every focusable component's `focusId`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markii/ansi",
3
- "version": "0.17.0-next.1",
3
+ "version": "0.17.0-next.2",
4
4
  "description": "The terminal renderer for Markii (.mk.md): a registry-driven hast-to-terminal engine built on Ink. Renders a note once as a string with optional color, for a pipe or a log, or as a live Ink tree a host can mount.",
5
5
  "keywords": [
6
6
  "markdown",
@@ -51,15 +51,15 @@
51
51
  "regenerate:fixtures": "npm run build:dist && node scripts/regenerate-ansi-fixtures.ts"
52
52
  },
53
53
  "dependencies": {
54
- "@markii/core": "^0.17.0-next.1",
55
- "@markii/pack": "^0.17.0-next.1",
56
- "@markii/stdlib": "^0.17.0-next.1",
54
+ "@markii/core": "^0.17.0-next.2",
55
+ "@markii/pack": "^0.17.0-next.2",
56
+ "@markii/stdlib": "^0.17.0-next.2",
57
57
  "ink": "^7.1.1",
58
58
  "react": "^19.2.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/hast": "^3.0.4",
62
- "@markii/runtime": "^0.17.0-next.1",
62
+ "@markii/runtime": "^0.17.0-next.2",
63
63
  "@types/react": "^19.2.0"
64
64
  }
65
65
  }