@refrakt-md/runes 0.32.0 → 0.34.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 (303) hide show
  1. package/dist/aggregate-resolve.d.ts.map +1 -1
  2. package/dist/aggregate-resolve.js +18 -4
  3. package/dist/aggregate-resolve.js.map +1 -1
  4. package/dist/attributes.d.ts +15 -0
  5. package/dist/attributes.d.ts.map +1 -1
  6. package/dist/attributes.js +22 -7
  7. package/dist/attributes.js.map +1 -1
  8. package/dist/collection-helpers.d.ts.map +1 -1
  9. package/dist/collection-helpers.js +1 -1
  10. package/dist/collection-helpers.js.map +1 -1
  11. package/dist/collection-resolve.d.ts.map +1 -1
  12. package/dist/collection-resolve.js +31 -8
  13. package/dist/collection-resolve.js.map +1 -1
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +214 -77
  16. package/dist/config.js.map +1 -1
  17. package/dist/data-adapters.d.ts.map +1 -1
  18. package/dist/data-adapters.js +28 -11
  19. package/dist/data-adapters.js.map +1 -1
  20. package/dist/data-emit.d.ts +1 -0
  21. package/dist/data-emit.d.ts.map +1 -1
  22. package/dist/data-emit.js +40 -0
  23. package/dist/data-emit.js.map +1 -1
  24. package/dist/data-pipeline.d.ts +10 -4
  25. package/dist/data-pipeline.d.ts.map +1 -1
  26. package/dist/data-pipeline.js +331 -32
  27. package/dist/data-pipeline.js.map +1 -1
  28. package/dist/data-projection.d.ts +21 -0
  29. package/dist/data-projection.d.ts.map +1 -1
  30. package/dist/data-projection.js +41 -3
  31. package/dist/data-projection.js.map +1 -1
  32. package/dist/data-resolve.d.ts.map +1 -1
  33. package/dist/data-resolve.js +5 -2
  34. package/dist/data-resolve.js.map +1 -1
  35. package/dist/documents/page.d.ts.map +1 -1
  36. package/dist/documents/page.js.map +1 -1
  37. package/dist/drawer-pipeline.d.ts.map +1 -1
  38. package/dist/drawer-pipeline.js +3 -2
  39. package/dist/drawer-pipeline.js.map +1 -1
  40. package/dist/expand-pipeline.d.ts.map +1 -1
  41. package/dist/expand-pipeline.js +3 -3
  42. package/dist/expand-pipeline.js.map +1 -1
  43. package/dist/fence-escape.d.ts.map +1 -1
  44. package/dist/fence-escape.js +1 -3
  45. package/dist/fence-escape.js.map +1 -1
  46. package/dist/field-match.d.ts.map +1 -1
  47. package/dist/field-match.js.map +1 -1
  48. package/dist/file-ref-resolve.js +6 -4
  49. package/dist/file-ref-resolve.js.map +1 -1
  50. package/dist/fixtures.js +2 -2
  51. package/dist/fixtures.js.map +1 -1
  52. package/dist/functions.d.ts.map +1 -1
  53. package/dist/functions.js +16 -3
  54. package/dist/functions.js.map +1 -1
  55. package/dist/include-pipeline.d.ts +52 -0
  56. package/dist/include-pipeline.d.ts.map +1 -0
  57. package/dist/include-pipeline.js +206 -0
  58. package/dist/include-pipeline.js.map +1 -0
  59. package/dist/index.d.ts +94 -37
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +298 -59
  62. package/dist/index.js.map +1 -1
  63. package/dist/interfaces.d.ts.map +1 -1
  64. package/dist/interfaces.js.map +1 -1
  65. package/dist/layouts/grid.d.ts +1 -1
  66. package/dist/layouts/grid.d.ts.map +1 -1
  67. package/dist/layouts/grid.js +4 -6
  68. package/dist/layouts/grid.js.map +1 -1
  69. package/dist/lib/component.d.ts +20 -0
  70. package/dist/lib/component.d.ts.map +1 -1
  71. package/dist/lib/component.js +42 -7
  72. package/dist/lib/component.js.map +1 -1
  73. package/dist/lib/icon-resolve.d.ts.map +1 -1
  74. package/dist/lib/icon-resolve.js.map +1 -1
  75. package/dist/lib/image-schemes.js +2 -2
  76. package/dist/lib/image-schemes.js.map +1 -1
  77. package/dist/lib/index.d.ts +2 -2
  78. package/dist/lib/index.d.ts.map +1 -1
  79. package/dist/lib/index.js +252 -51
  80. package/dist/lib/index.js.map +1 -1
  81. package/dist/lib/node.d.ts.map +1 -1
  82. package/dist/lib/node.js +3 -2
  83. package/dist/lib/node.js.map +1 -1
  84. package/dist/lib/placeholder.d.ts.map +1 -1
  85. package/dist/lib/placeholder.js +6 -9
  86. package/dist/lib/placeholder.js.map +1 -1
  87. package/dist/lib/read-file.d.ts.map +1 -1
  88. package/dist/lib/read-file.js.map +1 -1
  89. package/dist/lib/renderable.d.ts.map +1 -1
  90. package/dist/lib/renderable.js +5 -5
  91. package/dist/lib/renderable.js.map +1 -1
  92. package/dist/lib/resolver.d.ts.map +1 -1
  93. package/dist/lib/resolver.js +26 -35
  94. package/dist/lib/resolver.js.map +1 -1
  95. package/dist/lib/svg.d.ts.map +1 -1
  96. package/dist/lib/svg.js.map +1 -1
  97. package/dist/nodes.d.ts.map +1 -1
  98. package/dist/nodes.js +24 -22
  99. package/dist/nodes.js.map +1 -1
  100. package/dist/outline-scope.js +1 -3
  101. package/dist/outline-scope.js.map +1 -1
  102. package/dist/plugins.d.ts.map +1 -1
  103. package/dist/plugins.js +13 -10
  104. package/dist/plugins.js.map +1 -1
  105. package/dist/reference.d.ts +27 -2
  106. package/dist/reference.d.ts.map +1 -1
  107. package/dist/reference.js +58 -25
  108. package/dist/reference.js.map +1 -1
  109. package/dist/relationships-resolve.d.ts.map +1 -1
  110. package/dist/relationships-resolve.js +31 -7
  111. package/dist/relationships-resolve.js.map +1 -1
  112. package/dist/rune.d.ts.map +1 -1
  113. package/dist/rune.js.map +1 -1
  114. package/dist/sandbox-sources.js +6 -6
  115. package/dist/sandbox-sources.js.map +1 -1
  116. package/dist/seo.d.ts.map +1 -1
  117. package/dist/seo.js +4 -4
  118. package/dist/seo.js.map +1 -1
  119. package/dist/snippet-pipeline.d.ts.map +1 -1
  120. package/dist/snippet-pipeline.js +1 -1
  121. package/dist/snippet-pipeline.js.map +1 -1
  122. package/dist/tags/accordion.d.ts.map +1 -1
  123. package/dist/tags/accordion.js +43 -11
  124. package/dist/tags/accordion.js.map +1 -1
  125. package/dist/tags/aggregate.d.ts.map +1 -1
  126. package/dist/tags/aggregate.js +65 -10
  127. package/dist/tags/aggregate.js.map +1 -1
  128. package/dist/tags/annotate.d.ts.map +1 -1
  129. package/dist/tags/annotate.js +12 -9
  130. package/dist/tags/annotate.js.map +1 -1
  131. package/dist/tags/badge.d.ts.map +1 -1
  132. package/dist/tags/badge.js.map +1 -1
  133. package/dist/tags/bar.d.ts.map +1 -1
  134. package/dist/tags/bar.js +3 -7
  135. package/dist/tags/bar.js.map +1 -1
  136. package/dist/tags/bg.d.ts.map +1 -1
  137. package/dist/tags/bg.js +60 -12
  138. package/dist/tags/bg.js.map +1 -1
  139. package/dist/tags/blog.d.ts.map +1 -1
  140. package/dist/tags/blog.js +35 -10
  141. package/dist/tags/blog.js.map +1 -1
  142. package/dist/tags/breadcrumb.d.ts.map +1 -1
  143. package/dist/tags/breadcrumb.js +33 -11
  144. package/dist/tags/breadcrumb.js.map +1 -1
  145. package/dist/tags/budget.d.ts.map +1 -1
  146. package/dist/tags/budget.js +54 -16
  147. package/dist/tags/budget.js.map +1 -1
  148. package/dist/tags/card.d.ts.map +1 -1
  149. package/dist/tags/card.js +24 -6
  150. package/dist/tags/card.js.map +1 -1
  151. package/dist/tags/chart.d.ts.map +1 -1
  152. package/dist/tags/chart.js +33 -11
  153. package/dist/tags/chart.js.map +1 -1
  154. package/dist/tags/code.d.ts +25 -0
  155. package/dist/tags/code.d.ts.map +1 -0
  156. package/dist/tags/code.js +36 -0
  157. package/dist/tags/code.js.map +1 -0
  158. package/dist/tags/codegroup.d.ts.map +1 -1
  159. package/dist/tags/codegroup.js +39 -21
  160. package/dist/tags/codegroup.js.map +1 -1
  161. package/dist/tags/collection.d.ts.map +1 -1
  162. package/dist/tags/collection.js +55 -9
  163. package/dist/tags/collection.js.map +1 -1
  164. package/dist/tags/common.d.ts.map +1 -1
  165. package/dist/tags/common.js +34 -9
  166. package/dist/tags/common.js.map +1 -1
  167. package/dist/tags/compare.d.ts.map +1 -1
  168. package/dist/tags/compare.js +26 -14
  169. package/dist/tags/compare.js.map +1 -1
  170. package/dist/tags/conversation.d.ts.map +1 -1
  171. package/dist/tags/conversation.js +19 -17
  172. package/dist/tags/conversation.js.map +1 -1
  173. package/dist/tags/data.d.ts.map +1 -1
  174. package/dist/tags/data.js +91 -15
  175. package/dist/tags/data.js.map +1 -1
  176. package/dist/tags/datatable.d.ts.map +1 -1
  177. package/dist/tags/datatable.js +22 -8
  178. package/dist/tags/datatable.js.map +1 -1
  179. package/dist/tags/deflist.d.ts.map +1 -1
  180. package/dist/tags/deflist.js +5 -11
  181. package/dist/tags/deflist.js.map +1 -1
  182. package/dist/tags/details.d.ts.map +1 -1
  183. package/dist/tags/details.js +8 -5
  184. package/dist/tags/details.js.map +1 -1
  185. package/dist/tags/diagram.d.ts.map +1 -1
  186. package/dist/tags/diagram.js +9 -5
  187. package/dist/tags/diagram.js.map +1 -1
  188. package/dist/tags/diff.d.ts.map +1 -1
  189. package/dist/tags/diff.js +43 -24
  190. package/dist/tags/diff.js.map +1 -1
  191. package/dist/tags/drawer.d.ts.map +1 -1
  192. package/dist/tags/drawer.js +4 -6
  193. package/dist/tags/drawer.js.map +1 -1
  194. package/dist/tags/embed.d.ts.map +1 -1
  195. package/dist/tags/embed.js +10 -5
  196. package/dist/tags/embed.js.map +1 -1
  197. package/dist/tags/expand.d.ts.map +1 -1
  198. package/dist/tags/expand.js.map +1 -1
  199. package/dist/tags/figure.d.ts.map +1 -1
  200. package/dist/tags/figure.js +29 -9
  201. package/dist/tags/figure.js.map +1 -1
  202. package/dist/tags/file-ref.d.ts.map +1 -1
  203. package/dist/tags/file-ref.js.map +1 -1
  204. package/dist/tags/form.d.ts.map +1 -1
  205. package/dist/tags/form.js +52 -25
  206. package/dist/tags/form.js.map +1 -1
  207. package/dist/tags/gallery.d.ts.map +1 -1
  208. package/dist/tags/gallery.js +26 -12
  209. package/dist/tags/gallery.js.map +1 -1
  210. package/dist/tags/grid.d.ts.map +1 -1
  211. package/dist/tags/grid.js +57 -14
  212. package/dist/tags/grid.js.map +1 -1
  213. package/dist/tags/hint.d.ts.map +1 -1
  214. package/dist/tags/hint.js +9 -5
  215. package/dist/tags/hint.js.map +1 -1
  216. package/dist/tags/icon.d.ts.map +1 -1
  217. package/dist/tags/icon.js +5 -1
  218. package/dist/tags/icon.js.map +1 -1
  219. package/dist/tags/include.d.ts +24 -0
  220. package/dist/tags/include.d.ts.map +1 -0
  221. package/dist/tags/include.js +46 -0
  222. package/dist/tags/include.js.map +1 -0
  223. package/dist/tags/juxtapose.d.ts.map +1 -1
  224. package/dist/tags/juxtapose.js +32 -14
  225. package/dist/tags/juxtapose.js.map +1 -1
  226. package/dist/tags/layout.d.ts.map +1 -1
  227. package/dist/tags/layout.js +7 -4
  228. package/dist/tags/layout.js.map +1 -1
  229. package/dist/tags/mediatext.d.ts.map +1 -1
  230. package/dist/tags/mediatext.js +19 -8
  231. package/dist/tags/mediatext.js.map +1 -1
  232. package/dist/tags/nav.d.ts.map +1 -1
  233. package/dist/tags/nav.js +43 -23
  234. package/dist/tags/nav.js.map +1 -1
  235. package/dist/tags/pagination.d.ts.map +1 -1
  236. package/dist/tags/pagination.js +27 -5
  237. package/dist/tags/pagination.js.map +1 -1
  238. package/dist/tags/progress.d.ts.map +1 -1
  239. package/dist/tags/progress.js +25 -5
  240. package/dist/tags/progress.js.map +1 -1
  241. package/dist/tags/pullquote.d.ts.map +1 -1
  242. package/dist/tags/pullquote.js +16 -9
  243. package/dist/tags/pullquote.js.map +1 -1
  244. package/dist/tags/region.d.ts.map +1 -1
  245. package/dist/tags/region.js +9 -4
  246. package/dist/tags/region.js.map +1 -1
  247. package/dist/tags/relationships.d.ts.map +1 -1
  248. package/dist/tags/relationships.js +55 -9
  249. package/dist/tags/relationships.js.map +1 -1
  250. package/dist/tags/reveal.d.ts.map +1 -1
  251. package/dist/tags/reveal.js +16 -7
  252. package/dist/tags/reveal.js.map +1 -1
  253. package/dist/tags/sandbox.d.ts.map +1 -1
  254. package/dist/tags/sandbox.js +104 -44
  255. package/dist/tags/sandbox.js.map +1 -1
  256. package/dist/tags/section.d.ts.map +1 -1
  257. package/dist/tags/section.js +13 -3
  258. package/dist/tags/section.js.map +1 -1
  259. package/dist/tags/showcase.d.ts.map +1 -1
  260. package/dist/tags/showcase.js +33 -11
  261. package/dist/tags/showcase.js.map +1 -1
  262. package/dist/tags/sidenote.d.ts.map +1 -1
  263. package/dist/tags/sidenote.js +9 -5
  264. package/dist/tags/sidenote.js.map +1 -1
  265. package/dist/tags/snippet.d.ts.map +1 -1
  266. package/dist/tags/snippet.js +14 -6
  267. package/dist/tags/snippet.js.map +1 -1
  268. package/dist/tags/tabs.d.ts.map +1 -1
  269. package/dist/tags/tabs.js +16 -9
  270. package/dist/tags/tabs.js.map +1 -1
  271. package/dist/tags/textblock.d.ts.map +1 -1
  272. package/dist/tags/textblock.js +9 -5
  273. package/dist/tags/textblock.js.map +1 -1
  274. package/dist/tags/tint.d.ts +1 -1
  275. package/dist/tags/tint.d.ts.map +1 -1
  276. package/dist/tags/tint.js +14 -2
  277. package/dist/tags/tint.js.map +1 -1
  278. package/dist/tags/toc.d.ts.map +1 -1
  279. package/dist/tags/toc.js +16 -7
  280. package/dist/tags/toc.js.map +1 -1
  281. package/dist/tags/xref.d.ts.map +1 -1
  282. package/dist/tags/xref.js.map +1 -1
  283. package/dist/universal-attributes.d.ts.map +1 -1
  284. package/dist/universal-attributes.js +28 -5
  285. package/dist/universal-attributes.js.map +1 -1
  286. package/dist/util.d.ts +41 -1
  287. package/dist/util.d.ts.map +1 -1
  288. package/dist/util.js +72 -14
  289. package/dist/util.js.map +1 -1
  290. package/dist/xref-patterns.js +1 -1
  291. package/dist/xref-patterns.js.map +1 -1
  292. package/dist/xref-preview-resolve.js +3 -4
  293. package/dist/xref-preview-resolve.js.map +1 -1
  294. package/dist/xref-resolve.d.ts.map +1 -1
  295. package/dist/xref-resolve.js +4 -4
  296. package/dist/xref-resolve.js.map +1 -1
  297. package/i18n/de.json +67 -67
  298. package/i18n/en.json +67 -67
  299. package/package.json +3 -3
  300. package/dist/tags/error.d.ts +0 -3
  301. package/dist/tags/error.d.ts.map +0 -1
  302. package/dist/tags/error.js +0 -34
  303. package/dist/tags/error.js.map +0 -1
package/dist/config.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isTag, readField, resolveGap, ratioToFr, resolveValign } from '@refrakt-md/transform';
1
+ import { isTag, readField, resolveGap, ratioToFr, resolveValign, } from '@refrakt-md/transform';
2
2
  import Markdoc from '@markdoc/markdoc';
3
3
  const { Tag } = Markdoc;
4
4
  import { createComponentRenderable } from './lib/index.js';
@@ -8,7 +8,8 @@ import { PAGINATION_AUTO_SENTINEL } from './tags/pagination.js';
8
8
  import { resolveXrefs } from './xref-resolve.js';
9
9
  import { preprocessSnippets, wrapStandaloneSnippets } from './snippet-pipeline.js';
10
10
  import { preprocessData } from './data-pipeline.js';
11
- import { registerDrawers, resolveAutoDrawerTitleLevels, hoistPreviewDrawers } from './drawer-pipeline.js';
11
+ import { preprocessIncludes } from './include-pipeline.js';
12
+ import { registerDrawers, resolveAutoDrawerTitleLevels, hoistPreviewDrawers, } from './drawer-pipeline.js';
12
13
  import { resolveFileRefs } from './file-ref-resolve.js';
13
14
  import { resolveXrefPreviews } from './xref-preview-resolve.js';
14
15
  import { applyOutlineScopeWalkers, harvestHeadingsFromRenderable } from './outline-scope.js';
@@ -43,7 +44,12 @@ import { tabGroupSections } from './tags/tabs.js';
43
44
  /** Read text content from a property span child */
44
45
  function readPropText(node, prop) {
45
46
  for (const c of node.children) {
46
- if (isTag(c) && c.attributes?.['data-field'] === prop.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/([A-Z])([A-Z][a-z])/g, '$1-$2').toLowerCase()) {
47
+ if (isTag(c) &&
48
+ c.attributes?.['data-field'] ===
49
+ prop
50
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
51
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
52
+ .toLowerCase()) {
47
53
  return c.children.filter((ch) => typeof ch === 'string').join('');
48
54
  }
49
55
  }
@@ -65,22 +71,35 @@ export const coreConfig = {
65
71
  icons: {},
66
72
  runes: {
67
73
  // ─── Simple runes (block name only, engine adds BEM classes) ───
68
- Accordion: { block: 'accordion', defaultDensity: 'full', sections: accordionSections, autoLabel: pageSectionAutoLabel, editHints: { headline: 'inline', eyebrow: 'inline', blurb: 'inline' } },
74
+ Accordion: {
75
+ block: 'accordion',
76
+ defaultDensity: 'full',
77
+ sections: accordionSections,
78
+ autoLabel: pageSectionAutoLabel,
79
+ editHints: { headline: 'inline', eyebrow: 'inline', blurb: 'inline' },
80
+ },
69
81
  // SPEC-125 Phase 1 — the answer panel is the item's `body` role, so
70
82
  // `reading` / `dropcap` land on it. The header decision is recorded in
71
83
  // `sectionRoleExceptions` so it travels with the rune.
72
84
  AccordionItem: {
73
- block: 'accordion-item', parent: 'Accordion', requiresParent: 'Accordion',
85
+ block: 'accordion-item',
86
+ parent: 'Accordion',
87
+ requiresParent: 'Accordion',
74
88
  rootAttributes: { 'data-state': 'closed' },
75
89
  autoLabel: { name: 'header' },
76
90
  sections: accordionItemSections,
77
91
  provides: ['prose'],
78
92
  sectionRoleExceptions: {
79
- header: 'The `header` slot is the <summary> disclosure control, not a page-section header. `header` would inherit the chrome-row rhythm (a 3rem margin under every summary); `title` would tighten every summary\'s line-height while leaving `prominence` inert anyway, since `.rf-accordion-item__header` pins the control\'s font size on purpose. The parent Accordion already holds `title`.',
93
+ header: "The `header` slot is the <summary> disclosure control, not a page-section header. `header` would inherit the chrome-row rhythm (a 3rem margin under every summary); `title` would tighten every summary's line-height while leaving `prominence` inert anyway, since `.rf-accordion-item__header` pins the control's font size on purpose. The parent Accordion already holds `title`.",
80
94
  },
81
95
  editHints: { header: 'inline', body: 'none' },
82
96
  },
83
- Details: { block: 'details', defaultElevation: 'flush', autoLabel: { summary: 'summary' }, editHints: { summary: 'inline', body: 'none' } },
97
+ Details: {
98
+ block: 'details',
99
+ defaultElevation: 'flush',
100
+ autoLabel: { summary: 'summary' },
101
+ editHints: { summary: 'inline', body: 'none' },
102
+ },
84
103
  Grid: {
85
104
  block: 'grid',
86
105
  defaultDensity: 'full',
@@ -109,7 +128,10 @@ export const coreConfig = {
109
128
  interactive: true,
110
129
  defaultDensity: 'compact',
111
130
  defaultElevation: 'flat',
112
- modifiers: { title: { source: 'meta', noBemClass: true }, overflow: { source: 'meta', default: 'scroll' } },
131
+ modifiers: {
132
+ title: { source: 'meta', noBemClass: true },
133
+ overflow: { source: 'meta', default: 'scroll' },
134
+ },
113
135
  // The window chrome (three dots) is pure decoration — drawn in CSS
114
136
  // on `.rf-codegroup__topbar`. The only metadata is the optional
115
137
  // filename `title`, a bare monospace field in the topbar bar.
@@ -147,6 +169,14 @@ export const coreConfig = {
147
169
  * table renders through the standard `table` node (no `data`-specific
148
170
  * block CSS). */
149
171
  Data: { block: 'data' },
172
+ /* Include leaves nothing behind at all — its preprocess hook splices the
173
+ * tag away and pastes the partial's AST in its place, so the engine never
174
+ * sees an `Include` tag and there is no `include` block to style. The
175
+ * entry exists so the rune is known to inspect/contracts tooling. */
176
+ Include: { block: 'include' },
177
+ /* An inline `<code>`, styled by the same rule as a backtick code span so a
178
+ * page mixing the two is not visibly inconsistent (WORK-551). */
179
+ Code: { block: 'code', universalAttributes: 'inline' },
150
180
  /* Expand emits a placeholder during transform; the postProcess hook
151
181
  * substitutes the entity content wrapped in `<section
152
182
  * class="rf-expand" data-rune="expand">`. Engine config provides the
@@ -254,7 +284,11 @@ export const coreConfig = {
254
284
  separator: { prop: '--separator', template: '"{}"' },
255
285
  },
256
286
  },
257
- BreadcrumbItem: { block: 'breadcrumb-item', parent: 'Breadcrumb', requiresParent: 'Breadcrumb' },
287
+ BreadcrumbItem: {
288
+ block: 'breadcrumb-item',
289
+ parent: 'Breadcrumb',
290
+ requiresParent: 'Breadcrumb',
291
+ },
258
292
  Blog: {
259
293
  block: 'blog',
260
294
  defaultDensity: 'full',
@@ -318,7 +352,7 @@ export const coreConfig = {
318
352
  defaultDensity: 'compact',
319
353
  defaultElevation: 'sunken',
320
354
  modifiers: { hintType: { source: 'meta', default: 'note' } },
321
- contextModifiers: { 'hero': 'in-hero', 'feature': 'in-feature' },
355
+ contextModifiers: { hero: 'in-hero', feature: 'in-feature' },
322
356
  sections: hintSections,
323
357
  // SPEC-035 Zone 6 — the hintType value doubles as the visible title
324
358
  // (CSS capitalizes it). Declaring it as an enum lets `core.hint.<value>`
@@ -392,7 +426,12 @@ export const coreConfig = {
392
426
  modifiers: { layout: { source: 'meta', default: 'side-by-side' } },
393
427
  editHints: { panels: 'none' },
394
428
  },
395
- Conversation: { block: 'conversation', defaultDensity: 'compact', defaultElevation: 'flush', editHints: { messages: 'none' } },
429
+ Conversation: {
430
+ block: 'conversation',
431
+ defaultDensity: 'compact',
432
+ defaultElevation: 'flush',
433
+ editHints: { messages: 'none' },
434
+ },
396
435
  ConversationMessage: {
397
436
  block: 'conversation-message',
398
437
  parent: 'Conversation',
@@ -435,7 +474,7 @@ export const coreConfig = {
435
474
  // Extract slug from span[property="slug"] child → data-slug attribute
436
475
  // Keep slug text as visible fallback for SSR; web component replaces with <a> links
437
476
  let slug = '';
438
- const children = node.children.filter(child => {
477
+ const children = node.children.filter((child) => {
439
478
  if (isTag(child) && child.name === 'span' && child.attributes['data-field'] === 'slug') {
440
479
  slug = child.children.filter((c) => typeof c === 'string').join('');
441
480
  return false; // remove slug span from DOM
@@ -452,10 +491,16 @@ export const coreConfig = {
452
491
  };
453
492
  }
454
493
  // Explicit link item: add nav-item__link class to <a> tags for styling
455
- const styledChildren = children.map(child => {
494
+ const styledChildren = children.map((child) => {
456
495
  if (isTag(child) && child.name === 'a') {
457
496
  const existing = child.attributes.class || '';
458
- return { ...child, attributes: { ...child.attributes, class: ['rf-nav-item__link', existing].filter(Boolean).join(' ') } };
497
+ return {
498
+ ...child,
499
+ attributes: {
500
+ ...child.attributes,
501
+ class: ['rf-nav-item__link', existing].filter(Boolean).join(' '),
502
+ },
503
+ };
459
504
  }
460
505
  return child;
461
506
  });
@@ -465,7 +510,12 @@ export const coreConfig = {
465
510
  Diff: {
466
511
  block: 'diff',
467
512
  modifiers: { mode: { source: 'meta', default: 'unified' } },
468
- editHints: { line: 'none', 'gutter-num': 'none', 'gutter-prefix': 'none', 'line-content': 'none' },
513
+ editHints: {
514
+ line: 'none',
515
+ 'gutter-num': 'none',
516
+ 'gutter-prefix': 'none',
517
+ 'line-content': 'none',
518
+ },
469
519
  },
470
520
  Chart: {
471
521
  block: 'chart',
@@ -541,9 +591,27 @@ export const coreConfig = {
541
591
  editHints: { viewport: 'none' },
542
592
  },
543
593
  // ─── Interactive runes (still get BEM classes, components add behavior) ───
544
- TabGroup: { block: 'tabs', interactive: true, defaultDensity: 'full', sections: tabGroupSections, autoLabel: pageSectionAutoLabel, editHints: { headline: 'inline', eyebrow: 'inline', blurb: 'inline' } },
545
- Tab: { block: 'tab', parent: 'TabGroup', requiresParent: 'TabGroup', rootAttributes: { 'data-state': 'inactive' }, editHints: { name: 'inline' } },
546
- TabPanel: { block: 'tab-panel', parent: 'TabGroup', requiresParent: 'TabGroup', rootAttributes: { 'data-state': 'inactive' } },
594
+ TabGroup: {
595
+ block: 'tabs',
596
+ interactive: true,
597
+ defaultDensity: 'full',
598
+ sections: tabGroupSections,
599
+ autoLabel: pageSectionAutoLabel,
600
+ editHints: { headline: 'inline', eyebrow: 'inline', blurb: 'inline' },
601
+ },
602
+ Tab: {
603
+ block: 'tab',
604
+ parent: 'TabGroup',
605
+ requiresParent: 'TabGroup',
606
+ rootAttributes: { 'data-state': 'inactive' },
607
+ editHints: { name: 'inline' },
608
+ },
609
+ TabPanel: {
610
+ block: 'tab-panel',
611
+ parent: 'TabGroup',
612
+ requiresParent: 'TabGroup',
613
+ rootAttributes: { 'data-state': 'inactive' },
614
+ },
547
615
  DataTable: {
548
616
  block: 'datatable',
549
617
  interactive: true,
@@ -592,7 +660,12 @@ export const coreConfig = {
592
660
  autoLabel: pageSectionAutoLabel,
593
661
  editHints: { headline: 'inline', eyebrow: 'inline', blurb: 'inline', steps: 'none' },
594
662
  },
595
- RevealStep: { block: 'reveal-step', parent: 'Reveal', rootAttributes: { 'data-state': 'closed' }, editHints: { body: 'none' } },
663
+ RevealStep: {
664
+ block: 'reveal-step',
665
+ parent: 'Reveal',
666
+ rootAttributes: { 'data-state': 'closed' },
667
+ editHints: { body: 'none' },
668
+ },
596
669
  Section: {
597
670
  block: 'section',
598
671
  modifiers: {
@@ -618,7 +691,13 @@ export const coreConfig = {
618
691
  },
619
692
  editHints: { panels: 'none' },
620
693
  },
621
- JuxtaposePanel: { block: 'juxtapose-panel', parent: 'Juxtapose', requiresParent: 'Juxtapose', rootAttributes: { 'data-state': 'inactive' }, editHints: { body: 'none' } },
694
+ JuxtaposePanel: {
695
+ block: 'juxtapose-panel',
696
+ parent: 'Juxtapose',
697
+ requiresParent: 'Juxtapose',
698
+ rootAttributes: { 'data-state': 'inactive' },
699
+ editHints: { body: 'none' },
700
+ },
622
701
  Diagram: {
623
702
  block: 'diagram',
624
703
  defaultDensity: 'compact',
@@ -703,7 +782,7 @@ function deriveParentUrl(url) {
703
782
  function resolveAutoBreadcrumbs(renderable, pageUrl, breadcrumbPaths, pagesByUrl, ctx) {
704
783
  if (!Tag.isTag(renderable)) {
705
784
  if (Array.isArray(renderable)) {
706
- const newChildren = renderable.map(c => resolveAutoBreadcrumbs(c, pageUrl, breadcrumbPaths, pagesByUrl, ctx));
785
+ const newChildren = renderable.map((c) => resolveAutoBreadcrumbs(c, pageUrl, breadcrumbPaths, pagesByUrl, ctx));
707
786
  // Return original if nothing changed
708
787
  if (newChildren.every((c, i) => c === renderable[i]))
709
788
  return renderable;
@@ -743,7 +822,9 @@ function buildAutoBreadcrumb(originalTag, pageUrl, breadcrumbPaths, pagesByUrl,
743
822
  continue;
744
823
  const nameSpan = new Tag('span', { hidden: true }, [ancestorPage.title]);
745
824
  const urlLink = new Tag('a', { href: ancestorUrl }, [ancestorPage.title]);
746
- listItems.push(createComponentRenderable({ rune: 'breadcrumb-item', schemaOrgType: 'ListItem',
825
+ listItems.push(createComponentRenderable({
826
+ rune: 'breadcrumb-item',
827
+ schemaOrgType: 'ListItem',
747
828
  tag: 'li',
748
829
  properties: { name: nameSpan, url: urlLink },
749
830
  children: [nameSpan, urlLink],
@@ -753,14 +834,18 @@ function buildAutoBreadcrumb(originalTag, pageUrl, breadcrumbPaths, pagesByUrl,
753
834
  const currentPage = pagesByUrl.get(pageUrl);
754
835
  const currentTitle = currentPage?.title ?? pageUrl;
755
836
  const currentSpan = new Tag('span', {}, [currentTitle]);
756
- listItems.push(createComponentRenderable({ rune: 'breadcrumb-item', schemaOrgType: 'ListItem',
837
+ listItems.push(createComponentRenderable({
838
+ rune: 'breadcrumb-item',
839
+ schemaOrgType: 'ListItem',
757
840
  tag: 'li',
758
841
  properties: { name: currentSpan },
759
842
  children: [currentSpan],
760
843
  }));
761
844
  const newSeparatorMeta = new Tag('meta', { content: separator });
762
845
  const itemsList = new Tag('ol', {}, listItems);
763
- return createComponentRenderable({ rune: 'breadcrumb', schemaOrgType: 'BreadcrumbList',
846
+ return createComponentRenderable({
847
+ rune: 'breadcrumb',
848
+ schemaOrgType: 'BreadcrumbList',
764
849
  tag: 'nav',
765
850
  properties: { separator: newSeparatorMeta },
766
851
  refs: { items: itemsList },
@@ -771,7 +856,7 @@ function buildAutoBreadcrumb(originalTag, pageUrl, breadcrumbPaths, pagesByUrl,
771
856
  function resolveAutoNavs(renderable, pageUrl, pagesByUrl, ctx) {
772
857
  if (!Tag.isTag(renderable)) {
773
858
  if (Array.isArray(renderable)) {
774
- const newChildren = renderable.map(c => resolveAutoNavs(c, pageUrl, pagesByUrl, ctx));
859
+ const newChildren = renderable.map((c) => resolveAutoNavs(c, pageUrl, pagesByUrl, ctx));
775
860
  if (newChildren.every((c, i) => c === renderable[i]))
776
861
  return renderable;
777
862
  return newChildren;
@@ -799,21 +884,23 @@ function resolveAutoNavs(renderable, pageUrl, pagesByUrl, ctx) {
799
884
  * declared. */
800
885
  function buildAutoNav(pageUrl, pagesByUrl, ctx, originalTag) {
801
886
  // Find direct children: pages whose parentUrl is this page's URL (excluding self)
802
- const children = Array.from(pagesByUrl.values()).filter(p => p.parentUrl === pageUrl && p.url !== pageUrl);
887
+ const children = Array.from(pagesByUrl.values()).filter((p) => p.parentUrl === pageUrl && p.url !== pageUrl);
803
888
  if (children.length === 0) {
804
889
  ctx.warn(`Nav auto: page '${pageUrl}' has no registered child pages`, pageUrl);
805
890
  }
806
- const listItems = children.map(child => {
891
+ const listItems = children.map((child) => {
807
892
  const titleSpan = new Tag('span', { 'data-field': 'slug' }, [child.title]);
808
893
  const link = new Tag('a', { href: child.url }, [titleSpan]);
809
- return createComponentRenderable({ rune: 'nav-item',
894
+ return createComponentRenderable({
895
+ rune: 'nav-item',
810
896
  tag: 'li',
811
897
  properties: { slug: titleSpan },
812
898
  children: [link],
813
899
  });
814
900
  });
815
901
  const itemsList = new Tag('ul', {}, listItems);
816
- const newNav = createComponentRenderable({ rune: 'nav',
902
+ const newNav = createComponentRenderable({
903
+ rune: 'nav',
817
904
  tag: 'nav',
818
905
  properties: {
819
906
  group: [],
@@ -823,7 +910,14 @@ function buildAutoNav(pageUrl, pagesByUrl, ctx, originalTag) {
823
910
  });
824
911
  // Preserve contextual attributes from the original nav so layout / auto /
825
912
  // source-path survive sentinel replacement.
826
- const carry = ['layout', 'data-layout', 'data-auto', 'data-source-path', 'data-collapsible', 'data-default-open'];
913
+ const carry = [
914
+ 'layout',
915
+ 'data-layout',
916
+ 'data-auto',
917
+ 'data-source-path',
918
+ 'data-collapsible',
919
+ 'data-default-open',
920
+ ];
827
921
  for (const k of carry) {
828
922
  const v = originalTag?.attributes?.[k];
829
923
  if (v !== undefined) {
@@ -911,7 +1005,7 @@ function findNavSlugSuggestions(slug, baseDir, pagesByUrl) {
911
1005
  }
912
1006
  sameSlug.sort();
913
1007
  typoMatches.sort((a, b) => a.distance - b.distance || a.url.localeCompare(b.url));
914
- const suggestions = [...sameSlug, ...typoMatches.map(t => t.url)];
1008
+ const suggestions = [...sameSlug, ...typoMatches.map((t) => t.url)];
915
1009
  return suggestions.slice(0, 3);
916
1010
  }
917
1011
  /** SPEC-055 nav-location-relative slug resolution.
@@ -951,15 +1045,14 @@ function sharedPrefixLength(a, b) {
951
1045
  * been migrated to use pre-resolved hrefs yet. SPEC-055's
952
1046
  * `resolveNavSlug` is the canonical resolver for new code. */
953
1047
  function resolveSlugToUrl(slug, pagesByUrl, currentUrl) {
954
- const candidates = Array.from(pagesByUrl.values()).filter(p => p.url.endsWith('/' + slug) || p.url === '/' + slug);
1048
+ const candidates = Array.from(pagesByUrl.values()).filter((p) => p.url.endsWith('/' + slug) || p.url === '/' + slug);
955
1049
  if (candidates.length === 0)
956
1050
  return null;
957
1051
  if (candidates.length === 1)
958
1052
  return candidates[0].url;
959
1053
  return candidates
960
1054
  .slice()
961
- .sort((a, b) => sharedPrefixLength(b.url, currentUrl) - sharedPrefixLength(a.url, currentUrl))[0]
962
- .url;
1055
+ .sort((a, b) => sharedPrefixLength(b.url, currentUrl) - sharedPrefixLength(a.url, currentUrl))[0].url;
963
1056
  }
964
1057
  /** Read a NavItem's slug from either `data-slug` (post-engine) or a nested
965
1058
  * `<span data-field="slug">` (pre-engine, e.g. layout regions). */
@@ -1075,7 +1168,7 @@ function urlMatchesGroup(currentUrl, itemUrls) {
1075
1168
  function resolveCollapsibleNavs(renderable, pageUrl, pagesByUrl) {
1076
1169
  if (!Tag.isTag(renderable)) {
1077
1170
  if (Array.isArray(renderable)) {
1078
- const newChildren = renderable.map(c => resolveCollapsibleNavs(c, pageUrl, pagesByUrl));
1171
+ const newChildren = renderable.map((c) => resolveCollapsibleNavs(c, pageUrl, pagesByUrl));
1079
1172
  if (newChildren.every((c, i) => c === renderable[i]))
1080
1173
  return renderable;
1081
1174
  return newChildren;
@@ -1083,11 +1176,13 @@ function resolveCollapsibleNavs(renderable, pageUrl, pagesByUrl) {
1083
1176
  return renderable;
1084
1177
  }
1085
1178
  const tag = renderable;
1086
- if (tag.attributes?.['data-rune'] === 'nav' &&
1087
- tag.attributes?.['data-collapsible'] === 'true') {
1179
+ if (tag.attributes?.['data-rune'] === 'nav' && tag.attributes?.['data-collapsible'] === 'true') {
1088
1180
  const defaultOpenRaw = String(tag.attributes?.['data-default-open'] ?? '');
1089
1181
  const defaultOpen = defaultOpenRaw
1090
- ? defaultOpenRaw.split(',').map(s => s.trim()).filter(Boolean)
1182
+ ? defaultOpenRaw
1183
+ .split(',')
1184
+ .map((s) => s.trim())
1185
+ .filter(Boolean)
1091
1186
  : [];
1092
1187
  const groups = [];
1093
1188
  const findGroups = (node) => {
@@ -1107,7 +1202,7 @@ function resolveCollapsibleNavs(renderable, pageUrl, pagesByUrl) {
1107
1202
  const inDefault = title && defaultOpen.includes(title);
1108
1203
  const itemUrls = collectGroupItemUrls(group, pagesByUrl, pageUrl);
1109
1204
  const matches = urlMatchesGroup(pageUrl, itemUrls);
1110
- group.attributes['data-collapsed'] = (inDefault || matches) ? 'false' : 'true';
1205
+ group.attributes['data-collapsed'] = inDefault || matches ? 'false' : 'true';
1111
1206
  }
1112
1207
  const newChildren = (tag.children ?? []).map((c) => resolveCollapsibleNavs(c, pageUrl, pagesByUrl));
1113
1208
  if (newChildren.every((c, i) => c === tag.children[i]))
@@ -1130,7 +1225,9 @@ function enrichNavItemAsCard(item, pageMeta, itemUrl) {
1130
1225
  }
1131
1226
  linkChildren.push(new Tag('span', { 'data-name': 'title', class: 'rf-nav-item__title' }, [titleText]));
1132
1227
  if (description) {
1133
- linkChildren.push(new Tag('span', { 'data-name': 'description', class: 'rf-nav-item__description' }, [description]));
1228
+ linkChildren.push(new Tag('span', { 'data-name': 'description', class: 'rf-nav-item__description' }, [
1229
+ description,
1230
+ ]));
1134
1231
  }
1135
1232
  const link = href
1136
1233
  ? new Tag('a', { href, class: 'rf-nav-item__link' }, linkChildren)
@@ -1197,7 +1294,9 @@ function augmentNavItemFromFrontmatter(item, pageMeta) {
1197
1294
  newLinkChildren.unshift(new Tag('rf-icon', { name: pageMeta.icon, 'data-name': 'icon', class: 'rf-nav-item__icon' }, []));
1198
1295
  }
1199
1296
  if (pageMeta.description && !hasDescription) {
1200
- newLinkChildren.push(new Tag('span', { 'data-name': 'description', class: 'rf-nav-item__description' }, [pageMeta.description]));
1297
+ newLinkChildren.push(new Tag('span', { 'data-name': 'description', class: 'rf-nav-item__description' }, [
1298
+ pageMeta.description,
1299
+ ]));
1201
1300
  }
1202
1301
  return { ...t, children: newLinkChildren };
1203
1302
  });
@@ -1206,7 +1305,7 @@ function augmentNavItemFromFrontmatter(item, pageMeta) {
1206
1305
  function resolveCardsNavs(renderable, pageUrl, pagesByUrl) {
1207
1306
  if (!Tag.isTag(renderable)) {
1208
1307
  if (Array.isArray(renderable)) {
1209
- const newChildren = renderable.map(c => resolveCardsNavs(c, pageUrl, pagesByUrl));
1308
+ const newChildren = renderable.map((c) => resolveCardsNavs(c, pageUrl, pagesByUrl));
1210
1309
  if (newChildren.every((c, i) => c === renderable[i]))
1211
1310
  return renderable;
1212
1311
  return newChildren;
@@ -1282,7 +1381,13 @@ function formatNavResolutionError(slug, sourcePath, attemptedUrl, suggestions) {
1282
1381
  const head = `Nav item \`${slug}\`${where} cannot be resolved (no page at \`${attemptedUrl}\`).`;
1283
1382
  if (suggestions.length === 0)
1284
1383
  return head;
1285
- const lines = ['', 'Did you mean one of:', ...suggestions.map(s => ` - ${s}`), '', 'Use a multi-segment slug (e.g. `section/page`) or an explicit `[Label](/path)` link.'];
1384
+ const lines = [
1385
+ '',
1386
+ 'Did you mean one of:',
1387
+ ...suggestions.map((s) => ` - ${s}`),
1388
+ '',
1389
+ 'Use a multi-segment slug (e.g. `section/page`) or an explicit `[Label](/path)` link.',
1390
+ ];
1286
1391
  return head + '\n' + lines.join('\n');
1287
1392
  }
1288
1393
  /** Walk a nav subtree, resolve every NavItem's `data-slug` to a real `<a href>`
@@ -1345,7 +1450,7 @@ function resolveNavItemsInSubtree(navTag, pagesByUrl, ctx, pageUrl) {
1345
1450
  function resolveNavSlugs(renderable, pagesByUrl, ctx, pageUrl) {
1346
1451
  if (!Tag.isTag(renderable)) {
1347
1452
  if (Array.isArray(renderable)) {
1348
- const next = renderable.map(c => resolveNavSlugs(c, pagesByUrl, ctx, pageUrl));
1453
+ const next = renderable.map((c) => resolveNavSlugs(c, pagesByUrl, ctx, pageUrl));
1349
1454
  return next.every((c, i) => c === renderable[i]) ? renderable : next;
1350
1455
  }
1351
1456
  return renderable;
@@ -1561,10 +1666,10 @@ function getSiblingPages(currentUrl, pagesByUrl, scope) {
1561
1666
  cursor = parent;
1562
1667
  }
1563
1668
  const prefix = sectionRoot.endsWith('/') ? sectionRoot : sectionRoot + '/';
1564
- candidates = pages.filter(p => p.url === sectionRoot || p.url.startsWith(prefix));
1669
+ candidates = pages.filter((p) => p.url === sectionRoot || p.url.startsWith(prefix));
1565
1670
  }
1566
1671
  else {
1567
- candidates = pages.filter(p => p.parentUrl === current.parentUrl);
1672
+ candidates = pages.filter((p) => p.parentUrl === current.parentUrl);
1568
1673
  }
1569
1674
  // Sort by frontmatter order (asc, missing → end), tie-break by URL.
1570
1675
  candidates.sort((a, b) => {
@@ -1574,7 +1679,7 @@ function getSiblingPages(currentUrl, pagesByUrl, scope) {
1574
1679
  return ao - bo;
1575
1680
  return a.url.localeCompare(b.url);
1576
1681
  });
1577
- return candidates.map(p => ({ url: p.url, title: p.title }));
1682
+ return candidates.map((p) => ({ url: p.url, title: p.title }));
1578
1683
  }
1579
1684
  function pickPrevNextFromSequence(sequence, currentUrl, pagesByUrl) {
1580
1685
  const idx = sequence.indexOf(currentUrl);
@@ -1611,7 +1716,7 @@ function buildPaginationLink(target, direction, label) {
1611
1716
  function resolveAutoPagination(renderable, pageUrl, pagesByUrl, rootRenderable) {
1612
1717
  if (!Tag.isTag(renderable)) {
1613
1718
  if (Array.isArray(renderable)) {
1614
- const newChildren = renderable.map(c => resolveAutoPagination(c, pageUrl, pagesByUrl, rootRenderable));
1719
+ const newChildren = renderable.map((c) => resolveAutoPagination(c, pageUrl, pagesByUrl, rootRenderable));
1615
1720
  if (newChildren.every((c, i) => c === renderable[i]))
1616
1721
  return renderable;
1617
1722
  return newChildren;
@@ -1629,10 +1734,14 @@ function resolveAutoPagination(renderable, pageUrl, pagesByUrl, rootRenderable)
1629
1734
  const scope = (scopeValue === 'section' ? 'section' : 'siblings');
1630
1735
  const prevLabelMeta = (tag.children ?? []).find((c) => Tag.isTag(c) && c.attributes?.['data-field'] === 'prev-label');
1631
1736
  const nextLabelMeta = (tag.children ?? []).find((c) => Tag.isTag(c) && c.attributes?.['data-field'] === 'next-label');
1632
- const prevLabel = prevLabelMeta ? String(prevLabelMeta.attributes.content ?? '') : undefined;
1633
- const nextLabel = nextLabelMeta ? String(nextLabelMeta.attributes.content ?? '') : undefined;
1737
+ const prevLabel = prevLabelMeta
1738
+ ? String(prevLabelMeta.attributes.content ?? '')
1739
+ : undefined;
1740
+ const nextLabel = nextLabelMeta
1741
+ ? String(nextLabelMeta.attributes.content ?? '')
1742
+ : undefined;
1634
1743
  // Skip when current page is a section index (has children)
1635
- const hasChildren = Array.from(pagesByUrl.values()).some(p => p.parentUrl === pageUrl && p.url !== pageUrl);
1744
+ const hasChildren = Array.from(pagesByUrl.values()).some((p) => p.parentUrl === pageUrl && p.url !== pageUrl);
1636
1745
  if (hasChildren) {
1637
1746
  return { ...tag, children: [] };
1638
1747
  }
@@ -1653,7 +1762,7 @@ function resolveAutoPagination(renderable, pageUrl, pagesByUrl, rootRenderable)
1653
1762
  else {
1654
1763
  // 2. Sibling pages by frontmatter order → directory order
1655
1764
  const siblings = getSiblingPages(pageUrl, pagesByUrl, scope);
1656
- const idx = siblings.findIndex(s => s.url === pageUrl);
1765
+ const idx = siblings.findIndex((s) => s.url === pageUrl);
1657
1766
  if (idx !== -1) {
1658
1767
  prev = idx > 0 ? siblings[idx - 1] : null;
1659
1768
  next = idx < siblings.length - 1 ? siblings[idx + 1] : null;
@@ -1680,7 +1789,9 @@ function resolveAutoPagination(renderable, pageUrl, pagesByUrl, rootRenderable)
1680
1789
  const newChildren = (tag.children ?? []).map((c) => {
1681
1790
  if (!Tag.isTag(c))
1682
1791
  return c;
1683
- if (c.name === 'a' && typeof c.attributes?.href === 'string' && c.attributes.href.startsWith('__slug:')) {
1792
+ if (c.name === 'a' &&
1793
+ typeof c.attributes?.href === 'string' &&
1794
+ c.attributes.href.startsWith('__slug:')) {
1684
1795
  const slug = c.attributes.href.slice('__slug:'.length);
1685
1796
  const resolvedUrl = resolveSlugToUrl(slug, pagesByUrl, pageUrl);
1686
1797
  if (resolvedUrl) {
@@ -1689,14 +1800,20 @@ function resolveAutoPagination(renderable, pageUrl, pagesByUrl, rootRenderable)
1689
1800
  // Replace label text if user didn't override (label child has same text as slug)
1690
1801
  const newChildren = (c.children ?? []).map((child) => {
1691
1802
  if (Tag.isTag(child) && child.attributes?.['data-name'] === 'label') {
1692
- const currentLabel = (child.children ?? []).filter((x) => typeof x === 'string').join('');
1803
+ const currentLabel = (child.children ?? [])
1804
+ .filter((x) => typeof x === 'string')
1805
+ .join('');
1693
1806
  if (currentLabel === slug) {
1694
1807
  return { ...child, children: [newLabel] };
1695
1808
  }
1696
1809
  }
1697
1810
  return child;
1698
1811
  });
1699
- return { ...c, attributes: { ...c.attributes, href: resolvedUrl }, children: newChildren };
1812
+ return {
1813
+ ...c,
1814
+ attributes: { ...c.attributes, href: resolvedUrl },
1815
+ children: newChildren,
1816
+ };
1700
1817
  }
1701
1818
  }
1702
1819
  return c;
@@ -1715,7 +1832,7 @@ function walkBlogTags(node, fn) {
1715
1832
  walkBlogTags(child, fn);
1716
1833
  }
1717
1834
  else if (Array.isArray(node)) {
1718
- node.forEach(n => walkBlogTags(n, fn));
1835
+ node.forEach((n) => walkBlogTags(n, fn));
1719
1836
  }
1720
1837
  }
1721
1838
  function mapBlogTags(node, fn) {
@@ -1723,12 +1840,12 @@ function mapBlogTags(node, fn) {
1723
1840
  const mapped = fn(node);
1724
1841
  if (mapped !== node)
1725
1842
  return mapped;
1726
- const newChildren = node.children.map(c => mapBlogTags(c, fn));
1843
+ const newChildren = node.children.map((c) => mapBlogTags(c, fn));
1727
1844
  const changed = newChildren.some((c, i) => c !== node.children[i]);
1728
1845
  return changed ? new Tag(node.name, node.attributes, newChildren) : node;
1729
1846
  }
1730
1847
  if (Array.isArray(node))
1731
- return node.map(n => mapBlogTags(n, fn));
1848
+ return node.map((n) => mapBlogTags(n, fn));
1732
1849
  return node;
1733
1850
  }
1734
1851
  /** Normalise folder path for prefix matching: ensure leading slash and trailing slash */
@@ -1752,7 +1869,7 @@ function isInFolder(pageUrl, folder) {
1752
1869
  function parseBlogFilter(filter) {
1753
1870
  if (!filter || !filter.trim())
1754
1871
  return [];
1755
- return filter.split(',').map(part => {
1872
+ return filter.split(',').map((part) => {
1756
1873
  const colonIdx = part.indexOf(':');
1757
1874
  if (colonIdx === -1)
1758
1875
  return { field: part.trim(), value: '' };
@@ -1771,7 +1888,7 @@ function matchesBlogFilter(post, filters) {
1771
1888
  return false;
1772
1889
  }
1773
1890
  else if (Array.isArray(fmValue)) {
1774
- if (!fmValue.some(v => String(v).toLowerCase() === value.toLowerCase()))
1891
+ if (!fmValue.some((v) => String(v).toLowerCase() === value.toLowerCase()))
1775
1892
  return false;
1776
1893
  }
1777
1894
  else {
@@ -1803,9 +1920,7 @@ function sortBlogPosts(posts, sort) {
1803
1920
  }
1804
1921
  /** Build an <article> Tag for a single blog post entry */
1805
1922
  function createBlogPostTag(post) {
1806
- const titleTag = new Tag('h3', {}, [
1807
- new Tag('a', { href: post.url }, [post.title]),
1808
- ]);
1923
+ const titleTag = new Tag('h3', {}, [new Tag('a', { href: post.url }, [post.title])]);
1809
1924
  const children = [titleTag];
1810
1925
  if (post.date) {
1811
1926
  children.push(new Tag('time', { datetime: post.date }, [post.date]));
@@ -1834,7 +1949,7 @@ function resolveBlogPosts(renderable, allPosts, ctx, pageUrl) {
1834
1949
  }
1835
1950
  const normalised = normaliseFolderPath(folder);
1836
1951
  const filters = parseBlogFilter(filterStr);
1837
- let posts = allPosts.filter(post => {
1952
+ let posts = allPosts.filter((post) => {
1838
1953
  if (post.draft)
1839
1954
  return false;
1840
1955
  if (!isInFolder(post.url, normalised))
@@ -1881,9 +1996,7 @@ navSearchScope) {
1881
1996
  result = resolveNavSlugs(result, coreData.pagesByUrl, ctx, pageUrl);
1882
1997
  result = resolveCollapsibleNavs(result, pageUrl, coreData.pagesByUrl);
1883
1998
  result = resolveCardsNavs(result, pageUrl, coreData.pagesByUrl);
1884
- const searchRoot = navSearchScope && navSearchScope.length > 0
1885
- ? [result, ...navSearchScope]
1886
- : result;
1999
+ const searchRoot = navSearchScope && navSearchScope.length > 0 ? [result, ...navSearchScope] : result;
1887
2000
  result = resolveAutoPagination(result, pageUrl, coreData.pagesByUrl, searchRoot);
1888
2001
  // SPEC-055 — mark aria-current / data-active="ancestor" on resolved nav
1889
2002
  // links per page. Runs after slug + auto-pagination resolution so the
@@ -1909,9 +2022,15 @@ navSearchScope) {
1909
2022
  * any custom field.
1910
2023
  */
1911
2024
  const RESERVED_PAGE_FRONTMATTER = new Set([
1912
- 'layout', 'tint', 'tint-mode', 'tint-lock', 'slug', 'redirect',
2025
+ 'layout',
2026
+ 'tint',
2027
+ 'tint-mode',
2028
+ 'tint-lock',
2029
+ 'slug',
2030
+ 'redirect',
1913
2031
  // entity-declaration keys (SPEC-092 Layer 2) — meta, not queryable content
1914
- 'type', 'id',
2032
+ 'type',
2033
+ 'id',
1915
2034
  ]);
1916
2035
  export function createCorePipelineHooks(opts = {}) {
1917
2036
  const xrefPatterns = opts.xrefPatterns ?? [];
@@ -1919,13 +2038,20 @@ export function createCorePipelineHooks(opts = {}) {
1919
2038
  const repoUrl = opts.repoUrl;
1920
2039
  const repoBranch = opts.repoBranch;
1921
2040
  return {
1922
- // Compose the core preprocess steps: snippet (→ fence) then data (→ table).
1923
- // Both mutate the AST in place, so a single pass over the same tree applies
1924
- // both; either mutating means the caller takes the returned AST.
2041
+ // Compose the core preprocess steps: include (→ pasted AST), then snippet
2042
+ // (→ fence), then data (→ table). All three mutate the AST in place, so a
2043
+ // single pass over the same tree applies them; any one mutating means the
2044
+ // caller takes the returned AST.
2045
+ //
2046
+ // **Include runs first, and the order is load-bearing** (SPEC-129): the
2047
+ // whole point of the rune is that the pasted content is in the tree when the
2048
+ // later preprocessors walk it. Anything added to this phase that authors may
2049
+ // want inside an included file belongs after include, not before.
1925
2050
  preprocess(ast, page, ctx) {
2051
+ const includeChanged = preprocessIncludes(ast, page, ctx);
1926
2052
  const snippetChanged = preprocessSnippets(ast, page, ctx);
1927
2053
  const dataChanged = preprocessData(ast, page, ctx);
1928
- return (snippetChanged || dataChanged) ? ast : undefined;
2054
+ return includeChanged || snippetChanged || dataChanged ? ast : undefined;
1929
2055
  },
1930
2056
  register(pages, registry, ctx) {
1931
2057
  for (const page of pages) {
@@ -1979,7 +2105,7 @@ export function createCorePipelineHooks(opts = {}) {
1979
2105
  },
1980
2106
  aggregate(registry, ctx) {
1981
2107
  const pageEntities = registry.getAll('page');
1982
- const pages = pageEntities.map(e => ({
2108
+ const pages = pageEntities.map((e) => ({
1983
2109
  url: e.data.url,
1984
2110
  title: e.data.title,
1985
2111
  parentUrl: e.data.parentUrl,
@@ -1990,14 +2116,14 @@ export function createCorePipelineHooks(opts = {}) {
1990
2116
  const pageTree = buildPageTree(pages);
1991
2117
  const breadcrumbPaths = buildBreadcrumbPaths(pages);
1992
2118
  // Quick lookup: url → { url, title } for postProcess use
1993
- const pagesByUrl = new Map(pages.map(p => [p.url, p]));
2119
+ const pagesByUrl = new Map(pages.map((p) => [p.url, p]));
1994
2120
  // Build heading index: "url#id" → heading data
1995
2121
  const headingIndex = new Map();
1996
2122
  for (const h of registry.getAll('heading')) {
1997
2123
  headingIndex.set(h.id, h.data);
1998
2124
  }
1999
2125
  // Blog: collect all pages as potential blog posts
2000
- const allPosts = pageEntities.map(e => ({
2126
+ const allPosts = pageEntities.map((e) => ({
2001
2127
  title: e.data.title || '',
2002
2128
  url: e.data.url || e.id,
2003
2129
  date: e.data.date || '',
@@ -2005,7 +2131,18 @@ export function createCorePipelineHooks(opts = {}) {
2005
2131
  draft: e.data.draft || false,
2006
2132
  frontmatter: e.data,
2007
2133
  }));
2008
- return { pageTree, breadcrumbPaths, pagesByUrl, headingIndex, allPosts, registry, xrefPatterns, embedConfig, repoUrl, repoBranch };
2134
+ return {
2135
+ pageTree,
2136
+ breadcrumbPaths,
2137
+ pagesByUrl,
2138
+ headingIndex,
2139
+ allPosts,
2140
+ registry,
2141
+ xrefPatterns,
2142
+ embedConfig,
2143
+ repoUrl,
2144
+ repoBranch,
2145
+ };
2009
2146
  },
2010
2147
  postProcess(page, aggregated, ctx) {
2011
2148
  const coreData = aggregated['__core__'];