@suzumiyaaoba/mdxr 0.3.0 → 0.5.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.
Files changed (83) hide show
  1. package/dist/cli.mjs +407 -212
  2. package/dist/components.d.mts +10 -10
  3. package/dist/components.mjs +2 -2
  4. package/dist/{config-DhBFpSdB.mjs → config-C-pcUAoU.mjs} +1 -1
  5. package/dist/{doc-context-B_H-VT1p.d.mts → doc-context-CWOZ0eDq.d.mts} +7 -1
  6. package/dist/index.d.mts +1 -1
  7. package/dist/index.mjs +2 -2
  8. package/dist/{ui-DOxuV3n8.mjs → ui-k-VrmhPJ.mjs} +661 -349
  9. package/package.json +3 -2
  10. package/skill/references/components/output.md +3 -1
  11. package/skill/references/components/reports.md +60 -0
  12. package/skill/references/components.md +2 -0
  13. package/src/ascii/ast.ts +129 -92
  14. package/src/ascii/charts.ts +2 -3
  15. package/src/ascii/document.ts +2 -4
  16. package/src/ascii/extended.ts +7 -4
  17. package/src/ascii/glyphs.ts +3 -1
  18. package/src/ascii/index.ts +2 -0
  19. package/src/ascii/investigation.ts +38 -36
  20. package/src/ascii/output.ts +12 -4
  21. package/src/ascii/parts.ts +12 -1
  22. package/src/ascii/planning.ts +151 -87
  23. package/src/ascii/reports.ts +29 -23
  24. package/src/ascii/research.ts +90 -0
  25. package/src/ascii/shadcn.ts +26 -32
  26. package/src/assets/css.ts +36 -1
  27. package/src/catalog.ts +60 -44
  28. package/src/client/doc-events.ts +12 -13
  29. package/src/components/ui/calendar.tsx +1 -0
  30. package/src/components/ui/carousel.tsx +39 -17
  31. package/src/components/ui/chart.tsx +2 -1
  32. package/src/components/ui/pagination.tsx +0 -1
  33. package/src/components/ui/slider.tsx +3 -6
  34. package/src/components/ui/tabs.tsx +1 -0
  35. package/src/components/ui/toggle-group.tsx +6 -3
  36. package/src/extended/data.ts +6 -4
  37. package/src/extended/diagrams.ts +3 -2
  38. package/src/extended/differences.ts +13 -9
  39. package/src/extended/plots.ts +36 -16
  40. package/src/extended/profiles.ts +3 -1
  41. package/src/extended/reports.ts +10 -5
  42. package/src/hydrate/import-scan.ts +9 -6
  43. package/src/lines.ts +5 -1
  44. package/src/remark/directives.ts +2 -0
  45. package/src/remark/references.ts +37 -10
  46. package/src/research.ts +147 -0
  47. package/src/ui/attrs.ts +5 -2
  48. package/src/ui/bar-chart.tsx +11 -18
  49. package/src/ui/bridge.tsx +15 -15
  50. package/src/ui/chart.ts +113 -34
  51. package/src/ui/children.ts +21 -1
  52. package/src/ui/comments.tsx +9 -8
  53. package/src/ui/data-context.ts +2 -1
  54. package/src/ui/data-table.tsx +3 -2
  55. package/src/ui/data-view.tsx +2 -1
  56. package/src/ui/diff-parse.ts +19 -1
  57. package/src/ui/diffstat.tsx +6 -5
  58. package/src/ui/document-inputs.tsx +13 -10
  59. package/src/ui/document-media.tsx +3 -2
  60. package/src/ui/document-tabs.tsx +4 -3
  61. package/src/ui/file-icon.ts +1 -1
  62. package/src/ui/funnel.tsx +12 -12
  63. package/src/ui/gantt.tsx +24 -9
  64. package/src/ui/graph-viewport.tsx +52 -0
  65. package/src/ui/graph.tsx +36 -29
  66. package/src/ui/index.ts +6 -0
  67. package/src/ui/line-chart.tsx +14 -17
  68. package/src/ui/matrix.tsx +16 -7
  69. package/src/ui/performance-target.tsx +48 -0
  70. package/src/ui/pie-chart.tsx +12 -12
  71. package/src/ui/props.tsx +15 -13
  72. package/src/ui/quadrant.tsx +8 -16
  73. package/src/ui/radar.tsx +9 -12
  74. package/src/ui/release.tsx +11 -5
  75. package/src/ui/research-claim.tsx +69 -0
  76. package/src/ui/sankey.tsx +4 -4
  77. package/src/ui/scatter.tsx +14 -17
  78. package/src/ui/series.tsx +2 -3
  79. package/src/ui/spark.tsx +2 -13
  80. package/src/ui/summary.tsx +1 -1
  81. package/src/ui/table-controls.tsx +9 -8
  82. package/src/ui/tests.tsx +4 -4
  83. package/src/ui/treemap.tsx +17 -17
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { Ap as splitPathLines, Cp as takeUsedIcons, Dp as fenceFilename, Fp as isRecord, Gd as reportModel, Ip as nonEmpty, Jf as isStatus, Kd as comparisonModel, Kf as STATUSES, Lp as own, Np as langForPath, Op as fenceLang, Pp as isComponent, Rp as safeHref, V as shadcnModules, Vd as calculate, Wd as REPORT_SPECS, _l as PLOT_NAMES, af as display, ap as textList, cf as parseJson, dc as queryPlanModel, df as tableRecords, fc as diagramModel, gp as numOf, hp as attrTrue, if as columnsOf, jl as PlanHeader, jp as SKIP_LANGS, kp as parseLineRange, lc as bundleModel, lf as readRecords, of as isDataRecord, pc as flameModel, qd as schemaRows, sf as numberValue, t as builtinComponents, uc as dependencyMatrixModel, uf as records, vl as plotModel, wp as DocContext, xf as parseDiff, zp as urlScheme } from "./ui-DOxuV3n8.mjs";
3
- import { a as loadUserModule, c as srcDir, i as importBundledCode, n as loadConfig, o as resolveModuleEntry, r as SHARED_PACKAGES, s as pkgRoot } from "./config-DhBFpSdB.mjs";
2
+ import { $d as reportModel, Bp as splitPathLines, Cu as CLAIM_LABELS, Du as performanceDetails, Eu as RESEARCH_CLAIM_SCHEMA, Fp as parseProps, Gp as isRecord, Jp as safeHref, Kp as nonEmpty, Lp as fenceFilename, Ml as PlanHeader, Mp as takeUsedIcons, Np as DocContext, Ou as performanceResult, Qd as REPORT_SPECS, Rp as fenceLang, Tp as numOf, Tu as PERFORMANCE_TARGET_SCHEMA, Up as langForPath, V as shadcnModules, Vp as SKIP_LANGS, Wp as isComponent, Yd as calculate, Yp as urlScheme, _f as readRecords, dc as queryPlanModel, ef as comparisonModel, ep as STATUSES, fc as diagramModel, ff as columnsOf, gf as parseJson, hf as numberValue, kf as parseDiff, lc as bundleModel, mf as isDataRecord, mp as textList, np as isStatus, pc as flameModel, pf as display, pp as numList, qp as own, t as builtinComponents, tf as schemaRows, uc as dependencyMatrixModel, vf as records, vl as PLOT_NAMES, wp as attrTrue, wu as PERFORMANCE_LABELS, yf as tableRecords, yl as plotModel, zp as parseLineRange } from "./ui-k-VrmhPJ.mjs";
3
+ import { a as loadUserModule, c as srcDir, i as importBundledCode, n as loadConfig, o as resolveModuleEntry, r as SHARED_PACKAGES, s as pkgRoot } from "./config-C-pcUAoU.mjs";
4
4
  import module from "node:module";
5
5
  import fsp, { access, readFile, readdir, writeFile } from "node:fs/promises";
6
6
  import path from "node:path";
@@ -26,6 +26,7 @@ import * as valibot from "valibot";
26
26
  import { Fragment, createElement } from "react";
27
27
  import { icons } from "@iconify-json/lucide";
28
28
  import { icons as icons$1 } from "@iconify-json/vscode-icons";
29
+ import { differenceInCalendarDays, isValid, parseISO } from "date-fns";
29
30
  import { build } from "esbuild";
30
31
  import os from "node:os";
31
32
  import { spawn } from "node:child_process";
@@ -1000,12 +1001,14 @@ const CONTAINER_COMPONENTS = {
1000
1001
  package: "Package",
1001
1002
  packages: "Packages",
1002
1003
  pathway: "Pathway",
1004
+ performancetarget: "PerformanceTarget",
1003
1005
  phase: "Phase",
1004
1006
  piechart: "PieChart",
1005
1007
  plan: "Plan",
1006
1008
  quadrant: "Quadrant",
1007
1009
  radar: "Radar",
1008
1010
  release: "Release",
1011
+ researchclaim: "ResearchClaim",
1009
1012
  review: "Review",
1010
1013
  sankey: "Sankey",
1011
1014
  scatter: "Scatter",
@@ -1336,6 +1339,7 @@ const INLINE_REFERENCES = /* @__PURE__ */ new Set([
1336
1339
  const REFERENCE_FEATURES = /* @__PURE__ */ new Set([
1337
1340
  ...INLINE_REFERENCES,
1338
1341
  "Source",
1342
+ "ResearchClaim",
1339
1343
  "TableOfFigures",
1340
1344
  "NumberedEquation",
1341
1345
  "Theorem"
@@ -1396,6 +1400,26 @@ const resolveCitation = (node, state) => {
1396
1400
  ids.push(id);
1397
1401
  state.backlinks.set(target, ids);
1398
1402
  };
1403
+ /** Claims reuse the bibliography's numbering and backlink contract. */
1404
+ const resolveClaimCitation = (node, state) => {
1405
+ const source = jsxAttr(node, "source");
1406
+ if (source === void 0) return;
1407
+ const citation = {
1408
+ attributes: [{
1409
+ name: "source",
1410
+ type: "mdxJsxAttribute",
1411
+ value: source
1412
+ }],
1413
+ children: [],
1414
+ name: "Cite",
1415
+ position: node.position,
1416
+ type: "mdxJsxTextElement"
1417
+ };
1418
+ resolveCitation(citation, state);
1419
+ set(node, "citationId", jsxAttr(citation, "id") ?? "");
1420
+ set(node, "sourceHref", jsxAttr(citation, "href") ?? "");
1421
+ set(node, "sourceLabel", jsxAttr(citation, "label") ?? "");
1422
+ };
1399
1423
  const resolveCrossReference = (node, state) => {
1400
1424
  const target = jsxAttr(node, "target") ?? (node.name === "TermRef" ? `term-${jsxAttr(node, "term") ?? ""}` : "");
1401
1425
  const reference = state.references.get(target);
@@ -1442,6 +1466,13 @@ const addLegacyAnchor = (node) => {
1442
1466
  type: "mdxJsxTextElement"
1443
1467
  });
1444
1468
  };
1469
+ const resolveReference = (node, state) => {
1470
+ if (node.name === "ResearchClaim") resolveClaimCitation(node, state);
1471
+ if (node.name === "Cite") resolveCitation(node, state);
1472
+ if (node.name === "CrossRef" || node.name === "TermRef") resolveCrossReference(node, state);
1473
+ if (node.name === "TableOfFigures") listFigures(node, state.references);
1474
+ addLegacyAnchor(node);
1475
+ };
1445
1476
  /** Two passes support forward references without changing older documents. */
1446
1477
  const remarkReferences = () => (tree, file) => {
1447
1478
  const elements = [];
@@ -1458,12 +1489,7 @@ const remarkReferences = () => (tree, file) => {
1458
1489
  sources: /* @__PURE__ */ new Map()
1459
1490
  };
1460
1491
  for (const node of elements) registerReference(node, state);
1461
- for (const node of elements) {
1462
- if (node.name === "Cite") resolveCitation(node, state);
1463
- if (node.name === "CrossRef" || node.name === "TermRef") resolveCrossReference(node, state);
1464
- if (node.name === "TableOfFigures") listFigures(node, state.references);
1465
- addLegacyAnchor(node);
1466
- }
1492
+ for (const node of elements) resolveReference(node, state);
1467
1493
  for (const [source, ids] of state.backlinks) {
1468
1494
  const reference = state.sources.get(source);
1469
1495
  if (reference) set(reference.node, "backlinks", ids.join(","));
@@ -1483,6 +1509,8 @@ const flag = (node, name) => {
1483
1509
  for (const a of node.attributes) if (isRecord(a) && a.name === name) return a.value === null || a.value === true || attrTrue(a.value);
1484
1510
  return false;
1485
1511
  };
1512
+ /** Attribute explicitly set to the string `"false"` (open="false" opt-outs). */
1513
+ const flagOff = (node, name) => attr(node, name) === "false";
1486
1514
  /** Numeric part of an attribute — `"120ms"` → 120. */
1487
1515
  const num = (node, name) => numOf(attr(node, name));
1488
1516
  /** Comma-separated attribute list (`labels="a,b"`). */
@@ -1501,36 +1529,29 @@ const hoistable = (c) => c.type === "paragraph" && isParent(c) && c.children.eve
1501
1529
  * elements hoisted out of element-only paragraphs (see `hoistable`).
1502
1530
  */
1503
1531
  const els = (node, name) => {
1504
- const out = [];
1505
- for (const c of node.children ?? []) {
1506
- if (isMdxEl(c)) {
1507
- if (name === void 0 || c.name === name) out.push(c);
1508
- continue;
1509
- }
1510
- if (hoistable(c)) {
1511
- for (const cc of c.children) if (isMdxEl(cc) && (name === void 0 || cc.name === name)) out.push(cc);
1512
- }
1513
- }
1514
- return out;
1532
+ return (node.children ?? []).flatMap((c) => hoistable(c) ? c.children : [c]).filter((c) => isMdxEl(c) && (name === void 0 || c.name === name));
1515
1533
  };
1516
1534
  /**
1517
1535
  * `node` minus the named JSX children — direct elements removed, matching
1518
1536
  * elements stripped out of paragraphs (now-empty paragraphs dropped).
1519
1537
  */
1520
- const withoutEls = (node, names) => ({
1521
- ...node,
1522
- children: (node.children ?? []).flatMap((c) => {
1523
- if (isMdxEl(c)) return names.includes(c.name ?? "") ? [] : [c];
1524
- if (c.type === "paragraph" && isParent(c)) {
1525
- const kids = c.children.filter((cc) => !(isMdxEl(cc) && names.includes(cc.name ?? "")));
1526
- return kids.every((cc) => cc.type === "text" && "value" in cc && typeof cc.value === "string" && cc.value.trim() === "") ? [] : [{
1527
- ...c,
1528
- children: kids
1529
- }];
1530
- }
1531
- return [c];
1532
- })
1533
- });
1538
+ const withoutEls = (node, names) => {
1539
+ const removed = new Set(names);
1540
+ return {
1541
+ ...node,
1542
+ children: (node.children ?? []).flatMap((c) => {
1543
+ if (isMdxEl(c)) return removed.has(c.name ?? "") ? [] : [c];
1544
+ if (hoistable(c)) {
1545
+ const kids = c.children.filter((cc) => !(isMdxEl(cc) && removed.has(cc.name ?? "")));
1546
+ return kids.every((cc) => cc.type === "text" && "value" in cc && typeof cc.value === "string" && cc.value.trim() === "") ? [] : [{
1547
+ ...c,
1548
+ children: kids
1549
+ }];
1550
+ }
1551
+ return [c];
1552
+ })
1553
+ };
1554
+ };
1534
1555
  /** All literal text under `node` (text + inlineCode values, tree order). */
1535
1556
  const textOf = (node) => Array.isArray(node) ? node.map(textContent$1).join("") : textContent$1(node);
1536
1557
  const txt = (value) => ({
@@ -1663,6 +1684,53 @@ const phrasing = (nodes) => nodes.flatMap((n) => {
1663
1684
  const t = deepText([n]).trim();
1664
1685
  return t === "" ? [] : [txt(t)];
1665
1686
  });
1687
+ const boldHtml = (node, ctx) => [strong(ctx.inline(node))];
1688
+ const emphasisHtml = (node, ctx) => [em(ctx.inline(node))];
1689
+ const deleteHtml = (node, ctx) => [del(ctx.inline(node))];
1690
+ const codeHtml = (node) => [icode(deepText(node.children ?? []))];
1691
+ const HTML_INLINE = {
1692
+ a: (node, ctx) => {
1693
+ const href = attr(node, "href");
1694
+ const inner = ctx.inline(node);
1695
+ return href === void 0 ? inner : [link(href, inner)];
1696
+ },
1697
+ b: boldHtml,
1698
+ br: () => [brk()],
1699
+ code: codeHtml,
1700
+ del: deleteHtml,
1701
+ em: emphasisHtml,
1702
+ i: emphasisHtml,
1703
+ img: (node) => {
1704
+ return [{
1705
+ alt: attr(node, "alt"),
1706
+ type: "image",
1707
+ url: attr(node, "src") ?? ""
1708
+ }];
1709
+ },
1710
+ kbd: codeHtml,
1711
+ s: deleteHtml,
1712
+ samp: codeHtml,
1713
+ strike: deleteHtml,
1714
+ strong: boldHtml
1715
+ };
1716
+ /** Wrap adjacent inline nodes while retaining nested block structure. */
1717
+ const blocksOf = (nodes) => {
1718
+ const blocks = [];
1719
+ let inline = [];
1720
+ for (const node of nodes) {
1721
+ if (PHRASING.has(node.type)) {
1722
+ inline.push(...phrasing([node]));
1723
+ continue;
1724
+ }
1725
+ if (inline.length > 0) {
1726
+ blocks.push(para(inline));
1727
+ inline = [];
1728
+ }
1729
+ blocks.push(node);
1730
+ }
1731
+ if (inline.length > 0) blocks.push(para(inline));
1732
+ return blocks;
1733
+ };
1666
1734
  /**
1667
1735
  * Raw HTML elements written into a document are mdxJsx elements too —
1668
1736
  * give the meaningful ones a markdown form (`<a>` → link, `<code>` →
@@ -1672,49 +1740,52 @@ const htmlEl = (node, ctx, inline) => {
1672
1740
  const name = node.name ?? "";
1673
1741
  const kids = () => phrasing(ctx.children(node));
1674
1742
  const blocks = () => ctx.children(node);
1675
- if (name === "a") {
1676
- const href = attr(node, "href");
1677
- const inner = kids();
1678
- return href === void 0 ? inner : [link(href, inner)];
1679
- }
1680
- if (name === "img") return [{
1681
- alt: attr(node, "alt"),
1682
- type: "image",
1683
- url: attr(node, "src") ?? ""
1684
- }];
1685
- if (name === "br") return [brk()];
1686
- if (name === "strong" || name === "b") return [strong(kids())];
1687
- if (name === "em" || name === "i") return [em(kids())];
1688
- if (name === "del" || name === "s" || name === "strike") return [del(kids())];
1689
- if (name === "code" || name === "kbd" || name === "samp") return [icode(deepText(node.children ?? []))];
1743
+ const inlineRenderer = own(HTML_INLINE, name);
1744
+ if (inlineRenderer !== void 0) return inlineRenderer(node, ctx);
1690
1745
  if (name === "hr") return [thematic()];
1691
1746
  if (name === "p") return [para(kids())];
1692
1747
  if (/^h[1-6]$/u.test(name)) return [heading(Number(name[1]), kids())];
1693
1748
  if (name === "blockquote") return [quote(blocks())];
1694
1749
  if (name === "pre") return [pre(deepText(node.children ?? []))];
1695
1750
  if (name === "ul" || name === "ol") {
1696
- const items = els(node, "li").map((li) => item([para(phrasing(ctx.children(li)))]));
1751
+ const items = els(node, "li").map((li) => item(blocksOf(ctx.children(li))));
1697
1752
  return items.length === 0 ? blocks() : [list(items, name === "ol")];
1698
1753
  }
1699
1754
  return inline ? kids() : blocks();
1700
1755
  };
1756
+ const renderMdxElement = (node, ctx, registry) => {
1757
+ const name = node.name ?? "";
1758
+ const entry = own(registry, name);
1759
+ if (entry === void 0) {
1760
+ if (/^[a-z]/u.test(name)) return htmlEl(node, ctx, node.type === "mdxJsxTextElement");
1761
+ ctx.warn(name);
1762
+ return ctx.children(node);
1763
+ }
1764
+ if (node.type === "mdxJsxTextElement") return entry.text?.(node, ctx) ?? phrasing(entry.flow?.(node, ctx) ?? ctx.children(node));
1765
+ if (entry.flow !== void 0) return entry.flow(node, ctx);
1766
+ const inlineOut = entry.text?.(node, ctx);
1767
+ return inlineOut === void 0 ? ctx.children(node) : [para(inlineOut)];
1768
+ };
1701
1769
  /** Replace every mdxJsx element under `root` with its ASCII representation. */
1702
1770
  const transformAscii = (root, registry, serialize, warn) => {
1703
1771
  const transformElement = (n, ctx) => {
1704
- if (isMdxEl(n)) {
1705
- const name = n.name ?? "";
1706
- const entry = own(registry, name);
1707
- if (entry === void 0) {
1708
- if (/^[a-z]/u.test(name)) return htmlEl(n, ctx, n.type === "mdxJsxTextElement");
1709
- warn(name);
1710
- return ctx.children(n);
1772
+ if (isMdxEl(n)) return renderMdxElement(n, ctx, registry);
1773
+ if (isParent(n)) {
1774
+ if (hoistable(n)) {
1775
+ const children = n.children.filter(isMdxEl).map((child) => ({
1776
+ ...child,
1777
+ type: "mdxJsxFlowElement"
1778
+ }));
1779
+ return blocksOf(ctx.children({
1780
+ ...n,
1781
+ children
1782
+ }));
1711
1783
  }
1712
- if (n.type === "mdxJsxTextElement") return entry.text?.(n, ctx) ?? phrasing(entry.flow?.(n, ctx) ?? ctx.children(n));
1713
- if (entry.flow !== void 0) return entry.flow(n, ctx);
1714
- const inlineOut = entry.text?.(n, ctx);
1715
- return inlineOut === void 0 ? ctx.children(n) : [para(inlineOut)];
1784
+ return [{
1785
+ ...n,
1786
+ children: ctx.children(n)
1787
+ }];
1716
1788
  }
1717
- if (isParent(n)) n.children = ctx.children(n);
1718
1789
  return [n];
1719
1790
  };
1720
1791
  const transformNodes = (nodes, ctx) => nodes.flatMap((node) => {
@@ -1784,7 +1855,7 @@ const STATUS_ICON = {
1784
1855
  unverified: "?",
1785
1856
  up: "✓"
1786
1857
  };
1787
- const statusIcon = (status) => status === void 0 ? "•" : STATUS_ICON[status.toLowerCase()] ?? "•";
1858
+ const statusIcon = (status) => status === void 0 ? "•" : own(STATUS_ICON, status.toLowerCase()) ?? "•";
1788
1859
  /** Left-align `s` in a `width`-wide column (no padding past the width). */
1789
1860
  const pad = (s, width) => s.length >= width ? s : s + " ".repeat(width - s.length);
1790
1861
  //#endregion
@@ -1796,6 +1867,11 @@ const suffix = (parts) => {
1796
1867
  };
1797
1868
  /** Bare `a · b · c` joining (no leading separator). */
1798
1869
  const joined = (parts) => parts.filter(nonEmpty).join(" · ");
1870
+ /** A disclosure with literal summary text and an already serialized body. */
1871
+ const detailsBlock = (summary, body) => {
1872
+ const escaped = summary.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1873
+ return html(`<details>\n<summary>${escaped}</summary>\n\n${body.trim()}\n\n</details>`);
1874
+ };
1799
1875
  /**
1800
1876
  * `**Title — extra**` caption paragraph, emitted only when something is
1801
1877
  * there. A title-less caption drops a leading ` — `/space so `extra` alone
@@ -1824,7 +1900,7 @@ const tally$1 = (entries, key) => {
1824
1900
  //#endregion
1825
1901
  //#region src/ascii/charts.ts
1826
1902
  /** Attribute → number list (`values="12, 8, 4"`). */
1827
- const nums = (node, key) => csv(node, key).map(Number).filter((v) => !Number.isNaN(v));
1903
+ const nums = (node, key) => numList(attr(node, key));
1828
1904
  /** `label ██████░░ 42` fence rows from name/value pairs. */
1829
1905
  const barRows = (rows, max) => {
1830
1906
  const W = 24;
@@ -2190,7 +2266,7 @@ const term = (node, ctx) => [para([strong([txt(attr(node, "name") ?? "")]), ...c
2190
2266
  const details = (node, ctx) => {
2191
2267
  const summary = attr(node, "summary") ?? "Details";
2192
2268
  const body = ctx.serialize(ctx.children(node)).trim();
2193
- return [html(`<details>\n<summary>${summary}</summary>\n\n${body}\n\n</details>`)];
2269
+ return [detailsBlock(summary, body)];
2194
2270
  };
2195
2271
  const documentRenderers = {
2196
2272
  Callout: { flow: (n, ctx) => {
@@ -2286,7 +2362,7 @@ const nodeRows = (node) => {
2286
2362
  if (grid && isParent(grid)) return tableRecords(grid.children.map((row) => isParent(row) ? row.children.map(textOf) : []));
2287
2363
  return els(node, "RecordItem").map(nodeAttributes);
2288
2364
  };
2289
- const recordsTable = (rows, cols) => rows.length ? [table(cols ?? columnsOf(rows), rows.map((row) => (cols ?? columnsOf(rows)).map((key) => display(row[key]) || "—")))] : [para([txt("No data")])];
2365
+ const recordsTable = (rows, cols) => rows.length ? [table(cols ?? columnsOf(rows), rows.map((row) => (cols ?? columnsOf(rows)).map((key) => display(own(row, key)) || "—")))] : [para([txt("No data")])];
2290
2366
  const narrative = (node, ctx) => {
2291
2367
  if (attr(node, "data") !== void 0) return ctx.children(node);
2292
2368
  const children = node.children?.filter((child) => {
@@ -2411,14 +2487,14 @@ Object.assign(extendedRenderers, {
2411
2487
  "pnpm",
2412
2488
  "yarn",
2413
2489
  "bun"
2414
- ].map((manager) => `${manager} ${manager === "npm" ? "install" : "add"}${attr(node, "dev") === "true" ? " -D" : ""} ${attr(node, "packages") ?? ""}`).join("\n"), "sh")] },
2490
+ ].map((manager) => `${manager} ${manager === "npm" ? "install" : "add"}${flag(node, "dev") ? " -D" : ""} ${attr(node, "packages") ?? ""}`).join("\n"), "sh")] },
2415
2491
  PageBreak: { flow: () => [{ type: "thematicBreak" }] },
2416
2492
  PdfPreview: { flow: (node) => sourceLink(node, "src", `${attr(node, "title") ?? "PDF document"} (page ${attr(node, "page") ?? "1"})`) },
2417
2493
  PrintLayout: { flow: (node, ctx) => ctx.children(node) },
2418
2494
  PromptTemplate: { flow: (node) => {
2419
2495
  const rows = nodeRows(node);
2420
2496
  const defaults = Object.fromEntries(rows.map((row) => [display(row.name), display(row.value)]));
2421
- const prompt = (attr(node, "template") ?? "").replaceAll(/\{\{\s*(?<variable>[\w.-]+)\s*\}\}/gu, (match, name) => defaults[name] ?? match);
2497
+ const prompt = (attr(node, "template") ?? "").replaceAll(/\{\{\s*(?<variable>[\w.-]+)\s*\}\}/gu, (match, name) => own(defaults, name) ?? match);
2422
2498
  return [
2423
2499
  ...caption(attr(node, "title") ?? "Prompt template"),
2424
2500
  ...recordsTable(rows),
@@ -2534,28 +2610,20 @@ const numbered = (entries, statusKey, ctx) => list(entries.map((e) => item([para
2534
2610
  ])])), true);
2535
2611
  const NOTE_RE = /\s+(?:—|#)\s+/u;
2536
2612
  const PLACEHOLDER_RE = /^(?:\.{3}|…)$/u;
2537
- const treeEntries = (nodes) => {
2538
- const out = [];
2539
- const walk = (n) => {
2540
- if (n.type === "list" && isParent(n)) {
2541
- for (const li of n.children) {
2542
- if (li.type !== "listItem" || !isParent(li)) continue;
2543
- const nested = li.children.filter((c) => c.type === "list");
2544
- const labelText = li.children.filter((c) => c.type !== "list").map((c) => textOf(c)).join("").trim();
2545
- const m = NOTE_RE.exec(labelText);
2546
- out.push({
2547
- children: treeEntries(nested),
2548
- name: (m === null ? labelText : labelText.slice(0, m.index)).trim(),
2549
- ...m === null ? {} : { note: labelText.slice(m.index + m[0].length).trim() }
2550
- });
2551
- }
2552
- return;
2553
- }
2554
- if (isParent(n)) for (const c of n.children) walk(c);
2613
+ const treeEntry = (node, children) => {
2614
+ const label = node.children.filter((child) => child.type !== "list").map(textOf).join("").trim();
2615
+ const note = NOTE_RE.exec(label);
2616
+ return {
2617
+ children,
2618
+ name: (note === null ? label : label.slice(0, note.index)).trim(),
2619
+ ...note === null ? {} : { note: label.slice(note.index + note[0].length).trim() }
2555
2620
  };
2556
- for (const c of nodes) walk(c);
2557
- return out;
2558
2621
  };
2622
+ const treeEntries = (nodes) => nodes.flatMap((node) => {
2623
+ if (!isParent(node)) return [];
2624
+ if (node.type === "list") return node.children.filter((child) => child.type === "listItem" && isParent(child)).map((child) => treeEntry(child, treeEntries(child.children.filter((nested) => nested.type === "list"))));
2625
+ return treeEntries(node.children);
2626
+ });
2559
2627
  const treeLines = (entries, prefix) => entries.flatMap((e, i) => {
2560
2628
  const last = i === entries.length - 1;
2561
2629
  const name = PLACEHOLDER_RE.test(e.name) ? "…" : e.name;
@@ -2652,14 +2720,18 @@ const investigationRenderers = {
2652
2720
  ...nonEmpty(attr(n, "path")) ? [txt(" — "), icode(pathAt(n))] : [],
2653
2721
  ...ctx.inline(n).length === 0 ? [] : [txt(": "), ...ctx.inline(n)]
2654
2722
  ])] },
2655
- Hypotheses: { flow: (n, ctx) => [...caption(attr(n, "title")), list(els(n, "Hypothesis").map((h) => {
2656
- const status = (attr(h, "status") ?? "untested").toLowerCase();
2657
- return item([para([
2658
- txt(`${statusIcon(status)} `),
2659
- ...nonEmpty(attr(h, "title")) ? [strong([txt(attr(h, "title") ?? "")])] : [],
2660
- ...ctx.inline(h).length === 0 ? [] : [txt(" — "), ...ctx.inline(h)]
2661
- ]), ...ctx.children(withoutEls(n, ["Hypothesis"]))], status === "supported" ? true : void 0);
2662
- }))] },
2723
+ Hypotheses: { flow: (n, ctx) => [
2724
+ ...caption(attr(n, "title")),
2725
+ list(els(n, "Hypothesis").map((h) => {
2726
+ const status = (attr(h, "status") ?? "untested").toLowerCase();
2727
+ return item([para([
2728
+ txt(`${statusIcon(status)} `),
2729
+ ...nonEmpty(attr(h, "title")) ? [strong([txt(attr(h, "title") ?? "")])] : [],
2730
+ ...ctx.inline(h).length === 0 ? [] : [txt(" — "), ...ctx.inline(h)]
2731
+ ])], status === "supported" ? true : void 0);
2732
+ })),
2733
+ ...ctx.children(withoutEls(n, ["Hypothesis"]))
2734
+ ] },
2663
2735
  Hypothesis: { flow: (n, ctx) => [para([txt(`${statusIcon(attr(n, "status"))} `), ...nonEmpty(attr(n, "title")) ? [strong([txt(attr(n, "title") ?? "")])] : []]), ...ctx.children(n)] },
2664
2736
  Prop: { flow: (n, ctx) => [para([
2665
2737
  icode(attr(n, "name") ?? ""),
@@ -2784,12 +2856,13 @@ const comment = (node, ctx) => {
2784
2856
  const graph = (node, ctx) => {
2785
2857
  const nodes = els(node, "Node");
2786
2858
  const edges = els(node, "Edge");
2859
+ const connected = new Set(edges.flatMap((edge) => [attr(edge, "from"), attr(edge, "to")]));
2787
2860
  const nodeNotes = nodes.flatMap((nd) => {
2788
2861
  const label = attr(nd, "label");
2789
2862
  const note = attr(nd, "note");
2790
2863
  const path = attr(nd, "path");
2791
2864
  const status = attr(nd, "status");
2792
- if ((label === void 0 || label === attr(nd, "id")) && note === void 0 && path === void 0 && status === void 0) return [];
2865
+ if ((label === void 0 || label === attr(nd, "id")) && note === void 0 && path === void 0 && status === void 0 && connected.has(attr(nd, "id"))) return [];
2793
2866
  const head = [
2794
2867
  icode(attr(nd, "id") ?? ""),
2795
2868
  txt(" = "),
@@ -2912,9 +2985,10 @@ const waterfall = (node, ctx) => {
2912
2985
  const title = attr(node, "title");
2913
2986
  if (nonEmpty(title)) lines.push(`${title} (0 → ${total}${unit})`);
2914
2987
  for (const s of spans) {
2915
- const from = Math.round(s.start / Math.max(1, total) * W);
2916
- const len = Math.max(1, Math.round(s.duration / Math.max(1, total) * W));
2917
- const cells = `${" ".repeat(Math.min(W, from))}${"█".repeat(Math.min(len, W - from))}`;
2988
+ const scale = Math.max(1, total);
2989
+ const from = Math.max(0, Math.min(W, Math.round(s.start / scale * W)));
2990
+ const to = Math.max(from, Math.min(W, Math.round((s.start + Math.max(0, s.duration)) / scale * W)));
2991
+ const cells = `${" ".repeat(from)}${"█".repeat(to - from)}`;
2918
2992
  lines.push(`${pad(s.name, nameW)} ${cells} ${s.durationRaw || `${s.duration}${unit}`}${nonEmpty(s.note) ? ` ${s.note}` : ""}`);
2919
2993
  }
2920
2994
  return [pre(lines.join("\n")), ...ctx.children(withoutEls(node, ["Span"]))];
@@ -2970,6 +3044,7 @@ const outputRenderers = {
2970
3044
  };
2971
3045
  //#endregion
2972
3046
  //#region src/ascii/planning.ts
3047
+ /** ASCII renderers for planning & status components. */
2973
3048
  /** `owner` attr → a normalized `@name` chip (leading `@`s collapsed). */
2974
3049
  const ownerChip = (node) => {
2975
3050
  const owner = attr(node, "owner");
@@ -3009,8 +3084,8 @@ const approvalStatus = (s) => {
3009
3084
  const DAY_MS = 864e5;
3010
3085
  const dayOf = (iso) => {
3011
3086
  if (iso === void 0) return;
3012
- const t = Date.parse(iso);
3013
- return Number.isNaN(t) ? void 0 : Math.floor(t / DAY_MS);
3087
+ const date = parseISO(iso);
3088
+ return isValid(date) ? differenceInCalendarDays(date, new Date(1970, 0, 1)) : void 0;
3014
3089
  };
3015
3090
  const TASK_CHAR = {
3016
3091
  blocked: "▒",
@@ -3018,56 +3093,75 @@ const TASK_CHAR = {
3018
3093
  done: "█",
3019
3094
  todo: "░"
3020
3095
  };
3096
+ const GANTT_WIDTH = 40;
3097
+ const ganttTasks = (node) => els(node, "Task").flatMap((el) => {
3098
+ const start = dayOf(attr(el, "start"));
3099
+ if (start === void 0) return [];
3100
+ return [{
3101
+ el,
3102
+ end: Math.max(start, dayOf(attr(el, "end")) ?? start),
3103
+ name: attr(el, "name") ?? "",
3104
+ progress: num(el, "progress"),
3105
+ start,
3106
+ status: (attr(el, "status") ?? "todo").toLowerCase()
3107
+ }];
3108
+ });
3109
+ const ganttMilestones = (node) => els(node, "Milestone").flatMap((el) => {
3110
+ const date = dayOf(attr(el, "date"));
3111
+ return date === void 0 ? [] : [{
3112
+ date,
3113
+ el,
3114
+ name: attr(el, "name") ?? ""
3115
+ }];
3116
+ });
3117
+ const ganttRange = (node, tasks, milestones) => {
3118
+ const start = dayOf(attr(node, "start"));
3119
+ const end = dayOf(attr(node, "end"));
3120
+ let first = start ?? Math.min(...tasks.map((task) => task.start), ...milestones.map((milestone) => milestone.date));
3121
+ let last = end ?? Math.max(...tasks.map((task) => task.end), ...milestones.map((milestone) => milestone.date));
3122
+ if (last < first) {
3123
+ if (end !== void 0 && start === void 0) first = last;
3124
+ else last = first;
3125
+ }
3126
+ return {
3127
+ first,
3128
+ last
3129
+ };
3130
+ };
3131
+ const ganttColumn = (day, range) => Math.max(0, Math.min(GANTT_WIDTH, Math.round((day - range.first) / (range.last - range.first + 1) * GANTT_WIDTH)));
3132
+ const ganttTaskLine = (task, range, nameWidth) => {
3133
+ const from = ganttColumn(task.start, range);
3134
+ const to = Math.max(from + 1, ganttColumn(task.end + 1, range));
3135
+ const outside = task.end < range.first || task.start > range.last;
3136
+ const glyph = own(TASK_CHAR, task.status) ?? "░";
3137
+ const filled = (to - from) * Math.min(100, Math.max(0, task.progress ?? 0)) / 100;
3138
+ const cells = Array.from({ length: GANTT_WIDTH }, (_, i) => {
3139
+ if (outside || i < from || i >= to) return " ";
3140
+ return i - from < filled ? "█" : glyph;
3141
+ }).join("");
3142
+ const tail = joined([ownerChip(task.el), attr(task.el, "note")]);
3143
+ return `${pad(task.name, nameWidth)} ${cells}${tail === "" ? "" : ` ${tail}`}`;
3144
+ };
3145
+ const ganttMilestoneLine = (milestone, range, nameWidth) => {
3146
+ const position = milestone.date < range.first || milestone.date > range.last ? -1 : Math.min(39, ganttColumn(milestone.date, range));
3147
+ const cells = Array.from({ length: GANTT_WIDTH }, (_, i) => i === position ? "◆" : " ").join("");
3148
+ const tail = joined([attr(milestone.el, "status"), attr(milestone.el, "note")]);
3149
+ return `${pad(milestone.name, nameWidth)} ${cells}${tail === "" ? "" : ` ${tail}`}`;
3150
+ };
3021
3151
  const gantt = (node, ctx) => {
3022
- const tasks = els(node, "Task").flatMap((t) => {
3023
- const start = dayOf(attr(t, "start"));
3024
- return start === void 0 ? [] : [{
3025
- el: t,
3026
- end: dayOf(attr(t, "end")) ?? start,
3027
- name: attr(t, "name") ?? "",
3028
- progress: num(t, "progress"),
3029
- start,
3030
- status: (attr(t, "status") ?? "todo").toLowerCase()
3031
- }];
3032
- });
3033
- const milestones = els(node, "Milestone").flatMap((m) => {
3034
- const date = dayOf(attr(m, "date"));
3035
- return date === void 0 ? [] : [{
3036
- date,
3037
- el: m,
3038
- name: attr(m, "name") ?? ""
3039
- }];
3040
- });
3152
+ const tasks = ganttTasks(node);
3153
+ const milestones = ganttMilestones(node);
3041
3154
  if (tasks.length === 0 && milestones.length === 0) return ctx.children(node);
3042
- const lo = dayOf(attr(node, "start")) ?? Math.min(...tasks.map((t) => t.start), ...milestones.map((m) => m.date));
3043
- const hi = dayOf(attr(node, "end")) ?? Math.max(...tasks.map((t) => t.end + 1), ...milestones.map((m) => m.date + 1));
3044
- const span = Math.max(1, hi - lo);
3045
- const W = 40;
3046
- const at = (day) => Math.max(0, Math.min(W, Math.round((day - lo) / span * W)));
3047
- const nameW = Math.min(24, Math.max(8, ...tasks.map((t) => t.name.length), ...milestones.map((m) => m.name.length)));
3048
- const iso = (d) => (/* @__PURE__ */ new Date(d * DAY_MS)).toISOString().slice(0, 10);
3049
- const lines = [];
3155
+ const range = ganttRange(node, tasks, milestones);
3156
+ const nameWidth = Math.min(24, Math.max(8, ...tasks.map((task) => task.name.length), ...milestones.map((milestone) => milestone.name.length)));
3157
+ const iso = (day) => (/* @__PURE__ */ new Date(day * DAY_MS)).toISOString().slice(0, 10);
3050
3158
  const title = attr(node, "title");
3051
- if (nonEmpty(title)) lines.push(title);
3052
- lines.push(`${pad("", nameW)} ${iso(lo)} → ${iso(hi)}`);
3053
- for (const t of tasks) {
3054
- const from = at(t.start);
3055
- const to = Math.max(from + 1, at(t.end + 1));
3056
- const cells = Array.from({ length: W }, (_, i) => {
3057
- if (i < from || i >= to) return " ";
3058
- const ch = TASK_CHAR[t.status] ?? "░";
3059
- if (t.progress === void 0 || t.progress <= 0) return ch;
3060
- return i - from < (to - from) * Math.min(100, t.progress) / 100 ? "█" : ch;
3061
- }).join("");
3062
- const owner = attr(t.el, "owner");
3063
- const tail = joined([nonEmpty(owner) ? `@${owner.replace(/^@+/u, "")}` : void 0, attr(t.el, "note")]);
3064
- lines.push(`${pad(t.name, nameW)} ${cells}${tail === "" ? "" : ` ${tail}`}`);
3065
- }
3066
- for (const m of milestones) {
3067
- const cells = Array.from({ length: W }, (_, i) => i === at(m.date) ? "◆" : " ").join("");
3068
- const tail = joined([attr(m.el, "status"), attr(m.el, "note")]);
3069
- lines.push(`${pad(m.name, nameW)} ${cells}${tail === "" ? "" : ` ${tail}`}`);
3070
- }
3159
+ const lines = [
3160
+ ...nonEmpty(title) ? [title] : [],
3161
+ `${pad("", nameWidth)} ${iso(range.first)} → ${iso(range.last)}`,
3162
+ ...tasks.map((task) => ganttTaskLine(task, range, nameWidth)),
3163
+ ...milestones.map((milestone) => ganttMilestoneLine(milestone, range, nameWidth))
3164
+ ];
3071
3165
  return [pre(lines.join("\n")), ...ctx.children(withoutEls(node, ["Task", "Milestone"]))];
3072
3166
  };
3073
3167
  const CELL_GLYPH = {
@@ -3103,7 +3197,7 @@ const CELL_GLYPH = {
3103
3197
  };
3104
3198
  const matrixCell = (cell) => {
3105
3199
  const key = cell.trim().toLowerCase();
3106
- return key === "" ? "—" : CELL_GLYPH[key] ?? cell.trim();
3200
+ return key === "" ? "—" : own(CELL_GLYPH, key) ?? cell.trim();
3107
3201
  };
3108
3202
  /** `- label | a | b` list items → matrix rows. */
3109
3203
  const matrixRows = (node) => {
@@ -3135,12 +3229,16 @@ const planningRenderers = {
3135
3229
  attr(a, "role"),
3136
3230
  attr(a, "status"),
3137
3231
  attr(a, "date")
3138
- ])], []))), ...ctx.children(withoutEls(n, ["Approval"]))] },
3232
+ ])], ctx.children(a)))), ...ctx.children(withoutEls(n, ["Approval"]))] },
3139
3233
  Board: { flow: (n, ctx) => [
3140
3234
  ...caption(attr(n, "title")),
3141
3235
  ...els(n, "Lane").flatMap((lane) => {
3142
3236
  const cards = els(lane, "BoardCard");
3143
- return [para([txt(`${statusIcon(attr(lane, "status"))} `), strong([txt(`${attr(lane, "title") ?? ""} (${cards.length})`)])]), list(cards.map((c) => boardCard(c, ctx)))];
3237
+ return [
3238
+ para([txt(`${statusIcon(attr(lane, "status"))} `), strong([txt(`${attr(lane, "title") ?? ""} (${cards.length})`)])]),
3239
+ list(cards.map((c) => boardCard(c, ctx))),
3240
+ ...ctx.children(withoutEls(lane, ["BoardCard"]))
3241
+ ];
3144
3242
  }),
3145
3243
  ...ctx.children(withoutEls(n, ["Lane"]))
3146
3244
  ] },
@@ -3202,11 +3300,11 @@ const planningRenderers = {
3202
3300
  ])],
3203
3301
  text: (n) => [strong([txt(attr(n, "value") ?? "")]), txt(` ${attr(n, "label") ?? ""}`)]
3204
3302
  },
3205
- Stats: { flow: (n) => [list(els(n, "Stat").map((s) => item([para([
3303
+ Stats: { flow: (n, ctx) => [list(els(n, "Stat").map((s) => item([para([
3206
3304
  strong([txt(attr(s, "value") ?? "")]),
3207
3305
  txt(` ${attr(s, "label") ?? ""}`),
3208
3306
  ...nonEmpty(attr(s, "delta")) ? [txt(` (${attr(s, "delta")})`)] : []
3209
- ])])))] },
3307
+ ])]))), ...ctx.children(withoutEls(n, ["Stat"]))] },
3210
3308
  StatusBadge: { text: (n) => [icode(attr(n, "status") ?? "")] },
3211
3309
  Step: { flow: (n, ctx) => [para([
3212
3310
  txt(`${statusIcon(attr(n, "status"))} `),
@@ -3269,7 +3367,7 @@ const VERDICT_LABEL = {
3269
3367
  pass: "Passed",
3270
3368
  warn: "Warning"
3271
3369
  };
3272
- const verdictLabel = (s) => s === void 0 ? void 0 : VERDICT_LABEL[s.toLowerCase()] ?? s;
3370
+ const verdictLabel = (s) => s === void 0 ? void 0 : own(VERDICT_LABEL, s.toLowerCase()) ?? s;
3273
3371
  const DURATION_MS = {
3274
3372
  h: 36e5,
3275
3373
  m: 6e4,
@@ -3318,9 +3416,14 @@ const DAY_CELL = {
3318
3416
  const uptimeStrip = (node) => {
3319
3417
  const days = els(node, "Day");
3320
3418
  if (days.length === 0) return "";
3321
- return days.map((d) => DAY_CELL[(attr(d, "status") ?? "up").toLowerCase()] ?? "█").join("");
3419
+ return days.map((d) => own(DAY_CELL, (attr(d, "status") ?? "up").toLowerCase()) ?? "█").join("");
3420
+ };
3421
+ const percentText = (value) => nonEmpty(value) && !value.endsWith("%") ? `${value}%` : value;
3422
+ const serviceRow = (node, ctx) => statusItem$1(attr(node, "status"), [txt(attr(node, "name") ?? ""), ...suffix([attr(node, "status"), percentText(attr(node, "uptime"))])], ctx.children(node));
3423
+ const releaseVersion = (node) => {
3424
+ const version = attr(node, "version") ?? "";
3425
+ return version.startsWith("v") ? version : `v${version}`;
3322
3426
  };
3323
- const serviceRow = (node) => statusItem$1(attr(node, "status"), [txt(attr(node, "name") ?? ""), ...suffix([attr(node, "status"), nonEmpty(attr(node, "uptime")) ? `${attr(node, "uptime")}%` : void 0])], []);
3324
3427
  const entryRow = (node, ctx) => {
3325
3428
  const scope = attr(node, "scope");
3326
3429
  return item([para([
@@ -3368,7 +3471,7 @@ const reportRenderers = {
3368
3471
  attr(b, "before") ?? "",
3369
3472
  attr(b, "after") ?? "",
3370
3473
  d,
3371
- attr(b, "note") ?? ctx.text(b)
3474
+ joined([attr(b, "note"), ctx.text(b)])
3372
3475
  ];
3373
3476
  })),
3374
3477
  ...ctx.children(withoutEls(n, ["Bench"]))
@@ -3439,7 +3542,8 @@ const reportRenderers = {
3439
3542
  "field",
3440
3543
  "type",
3441
3544
  "flags",
3442
- "default"
3545
+ "default",
3546
+ "description"
3443
3547
  ], els(n, "DbField").map((f) => {
3444
3548
  const flags = [
3445
3549
  flag(f, "pk") ? "pk" : void 0,
@@ -3451,7 +3555,8 @@ const reportRenderers = {
3451
3555
  [icode(attr(f, "name") ?? "")],
3452
3556
  [icode(attr(f, "type") ?? "")],
3453
3557
  flags,
3454
- attr(f, "default") ?? ""
3558
+ attr(f, "default") ?? "",
3559
+ ctx.text(f)
3455
3560
  ];
3456
3561
  })),
3457
3562
  ...ctx.children(withoutEls(n, ["DbField"]))
@@ -3464,7 +3569,7 @@ const reportRenderers = {
3464
3569
  EnvVar: { flow: (n, ctx) => [para([
3465
3570
  icode(attr(n, "name") ?? ""),
3466
3571
  ...flag(n, "required") ? [txt(" "), icode("required")] : [],
3467
- ...suffix([flag(n, "secret") ? "secret" : attr(n, "value"), nonEmpty(attr(n, "default")) ? `default ${attr(n, "default")}` : void 0]),
3572
+ ...suffix([flag(n, "secret") ? "secret" : attr(n, "value"), !flag(n, "secret") && nonEmpty(attr(n, "default")) ? `default ${attr(n, "default")}` : void 0]),
3468
3573
  ...ctx.inline(n).length === 0 ? [] : [txt(" — "), ...ctx.inline(n)]
3469
3574
  ])] },
3470
3575
  EnvVars: { flow: (n, ctx) => [
@@ -3479,7 +3584,7 @@ const reportRenderers = {
3479
3584
  [icode(attr(e, "name") ?? "")],
3480
3585
  flag(e, "required") ? "yes" : "",
3481
3586
  flag(e, "secret") ? "•••" : attr(e, "value") ?? "",
3482
- attr(e, "default") ?? "",
3587
+ flag(e, "secret") ? "•••" : attr(e, "default") ?? "",
3483
3588
  ctx.text(e)
3484
3589
  ])),
3485
3590
  ...ctx.children(withoutEls(n, ["EnvVar"]))
@@ -3553,7 +3658,7 @@ const reportRenderers = {
3553
3658
  attr(p, "version") ?? "",
3554
3659
  attr(p, "kind") ?? "",
3555
3660
  attr(p, "license") ?? "",
3556
- ctx.text(p)
3661
+ joined([attr(p, "note"), ctx.text(p)])
3557
3662
  ])),
3558
3663
  ...ctx.children(withoutEls(n, ["Package"]))
3559
3664
  ] },
@@ -3563,17 +3668,17 @@ const reportRenderers = {
3563
3668
  ...ctx.children(withoutEls(n, ["Stop"]))
3564
3669
  ] },
3565
3670
  Release: { flow: (n, ctx) => [
3566
- heading(3, [...nonEmpty(attr(n, "href")) ? [link(attr(n, "href") ?? "", [txt(`v${attr(n, "version") ?? ""}`)])] : [icode(`v${attr(n, "version") ?? ""}`)], ...nonEmpty(attr(n, "title")) ? [txt(` — ${attr(n, "title")}`)] : []]),
3671
+ heading(3, [...nonEmpty(attr(n, "href")) ? [link(attr(n, "href") ?? "", [txt(releaseVersion(n))])] : [icode(releaseVersion(n))], ...nonEmpty(attr(n, "title")) ? [txt(` — ${attr(n, "title")}`)] : []]),
3567
3672
  ...nonEmpty(attr(n, "date")) ? [para([em([txt(attr(n, "date") ?? "")])])] : [],
3568
3673
  ...els(n, "Entry").length === 0 ? ctx.children(n) : [list(els(n, "Entry").map((e) => entryRow(e, ctx))), ...ctx.children(withoutEls(n, ["Entry"]))]
3569
3674
  ] },
3570
3675
  Review: { flow: (n, ctx) => [...caption(attr(n, "title"), `${nonEmpty(attr(n, "verdict")) ? ` — ${verdictLabel(attr(n, "verdict"))}` : ""}${tally$1(els(n, "Comment"), "severity")}`), ...ctx.children(n)] },
3571
3676
  Schema: { flow: (n, ctx) => [...caption(attr(n, "title"), nonEmpty(attr(n, "engine")) ? ` (${attr(n, "engine")})` : ""), ...ctx.children(n)] },
3572
- Service: { flow: (n) => [para([
3677
+ Service: { flow: (n, ctx) => [para([
3573
3678
  txt(`${statusIcon(attr(n, "status"))} `),
3574
3679
  txt(attr(n, "name") ?? ""),
3575
- ...suffix([attr(n, "status"), nonEmpty(attr(n, "uptime")) ? `${attr(n, "uptime")}%` : void 0])
3576
- ])] },
3680
+ ...suffix([attr(n, "status"), percentText(attr(n, "uptime"))])
3681
+ ]), ...ctx.children(n)] },
3577
3682
  Severity: { text: (n) => [icode((attr(n, "level") ?? "").toUpperCase())] },
3578
3683
  StatusPage: { flow: (n, ctx) => {
3579
3684
  const services = els(n, "Service");
@@ -3588,7 +3693,7 @@ const reportRenderers = {
3588
3693
  ...suffix([[attr(u, "from"), attr(u, "to")].filter(nonEmpty).join(" → ")])
3589
3694
  ]), ...strip === "" ? [] : [pre(strip)]];
3590
3695
  }),
3591
- ...services.length === 0 ? [] : [list(services.map(serviceRow))],
3696
+ ...services.length === 0 ? [] : [list(services.map((service) => serviceRow(service, ctx)))],
3592
3697
  ...ctx.children(withoutEls(n, ["Service", "Uptime"]))
3593
3698
  ];
3594
3699
  } },
@@ -3622,6 +3727,48 @@ const reportRenderers = {
3622
3727
  ]), ...ctx.children(n)] }
3623
3728
  };
3624
3729
  //#endregion
3730
+ //#region src/ascii/research.ts
3731
+ const attributes = (node, names) => Object.fromEntries(names.map((name) => [name, attr(node, name)]));
3732
+ const researchRenderers = {
3733
+ PerformanceTarget: { flow: (node, ctx) => {
3734
+ const props = parseProps(PERFORMANCE_TARGET_SCHEMA, attributes(node, [
3735
+ "actual",
3736
+ "better",
3737
+ "conditions",
3738
+ "measuredAt",
3739
+ "name",
3740
+ "statistic",
3741
+ "status",
3742
+ "target",
3743
+ "unit"
3744
+ ]), "PerformanceTarget");
3745
+ return [
3746
+ ...caption(props.name, ` — ${PERFORMANCE_LABELS[performanceResult(props)]}`),
3747
+ table(["Field", "Value"], performanceDetails(props)),
3748
+ ...ctx.children(node)
3749
+ ];
3750
+ } },
3751
+ ResearchClaim: { flow: (node, ctx) => {
3752
+ const props = parseProps(RESEARCH_CLAIM_SCHEMA, attributes(node, [
3753
+ "checked",
3754
+ "citationId",
3755
+ "id",
3756
+ "kind",
3757
+ "source",
3758
+ "sourceHref",
3759
+ "sourceLabel",
3760
+ "title"
3761
+ ]), "ResearchClaim");
3762
+ return [
3763
+ ...caption(props.title, ` — ${CLAIM_LABELS[props.kind]}`),
3764
+ ...ctx.children(node),
3765
+ ...props.source === void 0 || props.citationId === void 0 ? [] : [html(`<a id="${props.citationId.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")}"></a>`)],
3766
+ ...props.source === void 0 ? [] : [para([txt("Source: "), link(props.sourceHref ?? `#${props.source}`, [txt(props.sourceLabel ?? props.source)])])],
3767
+ ...props.checked === void 0 ? [] : [para([txt(`Checked: ${props.checked}`)])]
3768
+ ];
3769
+ } }
3770
+ };
3771
+ //#endregion
3625
3772
  //#region src/ascii/shadcn.ts
3626
3773
  /** `ctx.inline` result wrapped so empty children fall back to `alt`. */
3627
3774
  const inlineOr = (node, ctx, alt) => {
@@ -3661,33 +3808,39 @@ const menuItem = {
3661
3808
  text: (n, ctx) => ctx.inline(n)
3662
3809
  };
3663
3810
  /** `[x]`/`[ ]`/`( )` control from a checked-ish attribute. */
3811
+ const controlOn = (n) => {
3812
+ if (flag(n, "checked") || flagOff(n, "checked")) return flag(n, "checked");
3813
+ return flag(n, "defaultChecked") || flag(n, "pressed");
3814
+ };
3664
3815
  const control = (mark) => ({
3665
3816
  flow: (n, ctx) => {
3666
3817
  const inner = ctx.inline(n);
3667
3818
  const label = attr(n, "label");
3668
3819
  return [para([
3669
- icode(mark(flag(n, "checked") || flag(n, "defaultChecked") || flag(n, "pressed"))),
3820
+ icode(mark(controlOn(n))),
3670
3821
  ...inner.length === 0 ? [] : [txt(" "), ...inner],
3671
3822
  ...nonEmpty(label) ? [txt(` ${label}`)] : []
3672
3823
  ])];
3673
3824
  },
3674
- text: (n, ctx) => [icode(mark(flag(n, "checked") || flag(n, "defaultChecked") || flag(n, "pressed"))), ...ctx.inline(n).length === 0 ? [] : [txt(" "), ...ctx.inline(n)]]
3825
+ text: (n, ctx) => [icode(mark(controlOn(n))), ...ctx.inline(n).length === 0 ? [] : [txt(" "), ...ctx.inline(n)]]
3675
3826
  });
3676
3827
  /** `` `[____]` ``-style empty input stand-in. */
3828
+ const inputText = (n) => {
3829
+ const value = attr(n, "value") ?? attr(n, "defaultValue");
3830
+ if (value !== void 0) return attr(n, "type") === "password" ? "••••" : value;
3831
+ return attr(n, "placeholder") ?? attr(n, "label") ?? "____";
3832
+ };
3677
3833
  const input = {
3678
- flow: (n) => [pre(`[ ${attr(n, "placeholder") ?? attr(n, "value") ?? attr(n, "label") ?? "____"} ]`)],
3679
- text: (n) => [icode(`[${attr(n, "placeholder") ?? attr(n, "value") ?? attr(n, "label") ?? "____"}]`)]
3834
+ flow: (n) => [pre(`[ ${inputText(n)} ]`)],
3835
+ text: (n) => [icode(`[${inputText(n)}]`)]
3680
3836
  };
3681
3837
  /** `<details>` disclosure — summary from AccordionTrigger/collapsible label. */
3682
3838
  const disclosure = (trigger, fallback) => ({ flow: (n, ctx) => {
3683
3839
  const [head] = els(n, trigger);
3684
3840
  const summary = head === void 0 ? attr(n, "label") ?? fallback : ctx.text(head);
3685
- const rest = ctx.children({
3686
- ...n,
3687
- children: (n.children ?? []).filter((c) => !named(c, trigger))
3688
- });
3841
+ const rest = ctx.children(withoutEls(n, [trigger]));
3689
3842
  const body = ctx.serialize(rest).trim();
3690
- return [html(`<details>\n<summary>${summary}</summary>\n\n${body}\n\n</details>`)];
3843
+ return [detailsBlock(summary, body)];
3691
3844
  } });
3692
3845
  /** `<Tabs>` → `**tab**` headings + content sections. */
3693
3846
  const tabs = { flow: (n, ctx) => {
@@ -3762,7 +3915,7 @@ const frac = (n) => {
3762
3915
  const max = num(n, "max") ?? 100;
3763
3916
  return {
3764
3917
  text: `${attr(n, "value") ?? Math.round(value)}${nonEmpty(attr(n, "max")) ? `/${attr(n, "max")}` : "%"}`,
3765
- v: max === 0 ? 0 : Math.min(1, value / max)
3918
+ v: max <= 0 ? 0 : Math.max(0, Math.min(1, value / max))
3766
3919
  };
3767
3920
  };
3768
3921
  const progress = {
@@ -3813,7 +3966,7 @@ const otp = {
3813
3966
  return [pre(`[ ${"_ ".repeat(count).trim()} ]`)];
3814
3967
  },
3815
3968
  text: (n) => {
3816
- const count = num(n, "maxLength") ?? 6;
3969
+ const count = Math.max(0, num(n, "maxLength") ?? 6);
3817
3970
  return [icode(`[${"_".repeat(count)}]`)];
3818
3971
  }
3819
3972
  };
@@ -4015,6 +4168,7 @@ const asciiRenderers = {
4015
4168
  ...investigationRenderers,
4016
4169
  ...outputRenderers,
4017
4170
  ...reportRenderers,
4171
+ ...researchRenderers,
4018
4172
  ...chartRenderers,
4019
4173
  ...layoutRenderers,
4020
4174
  ...formRenderers,
@@ -4091,50 +4245,54 @@ const mdxToAscii = async (source, filePath = "document.mdx") => {
4091
4245
  };
4092
4246
  //#endregion
4093
4247
  //#region src/catalog.ts
4248
+ const OPTIONAL_TYPES = /* @__PURE__ */ new Set([
4249
+ "exact_optional",
4250
+ "nullish",
4251
+ "optional"
4252
+ ]);
4253
+ const describeLiteral = (value) => {
4254
+ if (typeof value === "bigint") return `${value}n`;
4255
+ return JSON.stringify(value) ?? String(value);
4256
+ };
4257
+ const pipeConstraint = (schema) => {
4258
+ if (!Array.isArray(schema.pipe)) return;
4259
+ return schema.pipe.find((s) => isRecord(s) && (s.type === "picklist" || s.type === "union" || s.type === "literal"));
4260
+ };
4094
4261
  /** Structural view of a valibot schema — enough for catalog introspection. */
4095
4262
  const describeSchema = (schema) => {
4096
4263
  if (!isRecord(schema)) return "unknown";
4097
- if (schema.type === "array") return `${describeSchema(schema.item)}[]`;
4098
- if (schema.type === "literal") return JSON.stringify(schema.literal);
4099
- if (schema.type === "picklist" && Array.isArray(schema.options)) return schema.options.map((o) => JSON.stringify(o)).join(" | ");
4100
- if (schema.type === "union" && Array.isArray(schema.options)) return schema.options.map(describeSchema).join(" | ");
4101
- if (Array.isArray(schema.pipe)) {
4102
- const constrained = schema.pipe.find((s) => isRecord(s) && (s.type === "picklist" || s.type === "union" || s.type === "literal"));
4103
- if (constrained !== void 0) return describeSchema(constrained);
4264
+ switch (schema.type) {
4265
+ case "nullable":
4266
+ case "nullish": return `${describeSchema(schema.wrapped)} | null`;
4267
+ case "optional":
4268
+ case "exact_optional": return describeSchema(schema.wrapped);
4269
+ case "array": {
4270
+ const item = describeSchema(schema.item);
4271
+ return item.includes(" | ") ? `(${item})[]` : `${item}[]`;
4272
+ }
4273
+ case "literal": return describeLiteral(schema.literal);
4274
+ case "picklist":
4275
+ case "union": if (Array.isArray(schema.options)) {
4276
+ const describe = schema.type === "picklist" ? describeLiteral : describeSchema;
4277
+ return schema.options.map(describe).join(" | ");
4278
+ }
4104
4279
  }
4280
+ const constrained = pipeConstraint(schema);
4281
+ if (constrained !== void 0) return describeSchema(constrained);
4105
4282
  return typeof schema.type === "string" ? schema.type : "unknown";
4106
4283
  };
4107
- const WRAPPER_TYPES = /* @__PURE__ */ new Set([
4108
- "nullable",
4109
- "nullish",
4110
- "optional"
4111
- ]);
4112
- const unwrapProp = (raw) => {
4113
- let s = isRecord(raw) ? raw : {};
4114
- let required = true;
4115
- let def;
4116
- while (typeof s.type === "string" && WRAPPER_TYPES.has(s.type)) {
4117
- required = false;
4118
- def ??= s.default;
4119
- s = isRecord(s.wrapped) ? s.wrapped : {};
4120
- }
4121
- return {
4122
- def,
4123
- required,
4124
- s
4125
- };
4126
- };
4127
4284
  const propsOf = (schema) => {
4128
4285
  if (!isRecord(schema) || !isRecord(schema.entries)) return {};
4129
4286
  const out = {};
4130
4287
  for (const [key, raw] of Object.entries(schema.entries)) {
4131
- const { def, required, s } = unwrapProp(raw);
4288
+ const optional = isRecord(raw) && typeof raw.type === "string" && OPTIONAL_TYPES.has(raw.type);
4289
+ const def = optional ? raw.default : void 0;
4132
4290
  Object.defineProperty(out, key, {
4133
4291
  configurable: true,
4134
4292
  enumerable: true,
4135
4293
  value: {
4136
- required,
4137
- type: describeSchema(s),
4294
+ required: !optional,
4295
+ type: describeSchema(raw),
4138
4296
  ...def === void 0 ? {} : { default: def }
4139
4297
  },
4140
4298
  writable: true
@@ -4883,14 +5041,16 @@ const iconSets = (usedIcons) => ({
4883
5041
  */
4884
5042
  const scanVProps = (stripped, alias) => {
4885
5043
  const esc = alias.replaceAll(/[$()*+.?[\\\]^{|}]/gu, "\\$&");
4886
- const access = new RegExp(`\\b${esc}\\s*\\?\\.\\s*(\\w+)|\\b${esc}\\s*\\.\\s*(\\w+)`, "gu");
5044
+ const identifierPart = String.raw`[\p{ID_Continue}$\u200C\u200D]`;
5045
+ const start = `(?<!${identifierPart})${esc}`;
5046
+ const access = new RegExp(`${start}\\s*\\??\\.\\s*(\\w+)`, "gu");
4887
5047
  const props = /* @__PURE__ */ new Set();
4888
5048
  for (const use of stripped.matchAll(access)) {
4889
- const prop = use[1] ?? use[2];
5049
+ const [, prop] = use;
4890
5050
  if (prop !== void 0) props.add(prop);
4891
5051
  }
4892
5052
  const leftover = stripped.replaceAll(access, "");
4893
- return new RegExp(`\\b${esc}\\b`, "u").test(leftover) ? "all" : props;
5053
+ return new RegExp(`${start}(?!${identifierPart})`, "u").test(leftover) ? "all" : props;
4894
5054
  };
4895
5055
  /**
4896
5056
  * `/* … *\/` and `// …` are legal inside a multiline import clause, and a
@@ -5633,6 +5793,20 @@ const BASE_CSS = `
5633
5793
  .task-list-item { list-style: none; }
5634
5794
  ul.contains-task-list { padding-left: 1.25rem; }
5635
5795
  .task-list-item input[type='checkbox'] { margin-right: 0.4em; }
5796
+ /* Graph fitting is native SVG sizing: readable minimum scale on screen,
5797
+ * full-width containment in print, and a keyboard-operable actual-size toggle. */
5798
+ .mdxr-graph-view:has(.mdxr-graph-actual:checked) .mdxr-graph-image {
5799
+ max-width: none !important;
5800
+ }
5801
+ .mdxr-graph-tools:focus-within { outline: 2px solid rgb(14 165 233); outline-offset: -2px; }
5802
+ @media print {
5803
+ .mdxr-graph-tools { display: none; }
5804
+ .mdxr-graph-scroll { overflow: visible; }
5805
+ .mdxr-graph-view:has(.mdxr-graph-actual:checked) .mdxr-graph-image,
5806
+ .mdxr-graph-view .mdxr-graph-image {
5807
+ max-width: 100% !important; min-width: 0 !important; height: auto !important;
5808
+ }
5809
+ }
5636
5810
  /* --- Interaction feedback -------------------------------------------
5637
5811
  * Every [data-copy] button carries an idle and a done icon
5638
5812
  * (.mdxr-copy-idle/.mdxr-copy-done); the delegated event handler
@@ -5909,7 +6083,7 @@ details[open] > summary .mdxr-chev { transform: rotate(90deg); }
5909
6083
  .dark .mdxr-toc-body > ul > li > a { color: rgb(212 212 212); }
5910
6084
  .mdxr-toc-body > ul > li > p > a::before,
5911
6085
  .mdxr-toc-body > ul > li > a::before {
5912
- content: counter(mdxr-toc); min-width: 1em;
6086
+ content: counter(mdxr-toc); min-width: 1em; flex-shrink: 0; white-space: nowrap;
5913
6087
  font-size: 0.72rem; font-weight: 400; font-variant-numeric: tabular-nums;
5914
6088
  color: rgb(163 163 163);
5915
6089
  }
@@ -5921,6 +6095,27 @@ details[open] > summary .mdxr-chev { transform: rotate(90deg); }
5921
6095
  }
5922
6096
  .dark .mdxr-toc-body ul ul { border-color: rgb(64 64 64); }
5923
6097
  .mdxr-toc-body ul ul a { font-size: 0.8125rem; padding: 0.2rem 0.45rem; }
6098
+ /* A 64rem viewport fits the usual 45rem article, a 14rem ToC, a 2rem
6099
+ * gap, and 1.5rem outer gutters. Only the first document-level ToC docks;
6100
+ * additional or nested tables stay in the document flow. Print stays inline. */
6101
+ @media screen and (min-width: 64rem) {
6102
+ #mdxr-root:has(> .mdxr-toc, > article:only-of-type > .mdxr-toc) {
6103
+ max-width: 64rem;
6104
+ padding-right: 17.5rem;
6105
+ }
6106
+ #mdxr-root > .mdxr-toc:nth-child(1 of .mdxr-toc),
6107
+ #mdxr-root:not(:has(> .mdxr-toc)) > article:only-of-type > .mdxr-toc:nth-child(1 of .mdxr-toc) {
6108
+ position: fixed;
6109
+ top: 4rem;
6110
+ right: max(1.5rem, calc((100vw - 64rem) / 2 + 1.5rem));
6111
+ width: 14rem;
6112
+ max-height: calc(100dvh - 5.5rem);
6113
+ margin: 0;
6114
+ overflow-y: auto;
6115
+ overscroll-behavior: contain;
6116
+ overflow-wrap: anywhere;
6117
+ }
6118
+ }
5924
6119
  /* Ask: native form controls stay interactive without hydration. The real
5925
6120
  * inputs are visually hidden; state is styled through :checked/~ siblings. */
5926
6121
  .mdxr-choice {