@refrakt-md/runes 0.16.1 → 0.18.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 (44) hide show
  1. package/dist/config.d.ts.map +1 -1
  2. package/dist/config.js +74 -377
  3. package/dist/config.js.map +1 -1
  4. package/dist/drawer-pipeline.d.ts.map +1 -1
  5. package/dist/drawer-pipeline.js +7 -22
  6. package/dist/drawer-pipeline.js.map +1 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +19 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/component.d.ts.map +1 -1
  12. package/dist/lib/component.js +50 -3
  13. package/dist/lib/component.js.map +1 -1
  14. package/dist/tags/badge.d.ts +5 -6
  15. package/dist/tags/badge.d.ts.map +1 -1
  16. package/dist/tags/badge.js +7 -18
  17. package/dist/tags/badge.js.map +1 -1
  18. package/dist/tags/bar.d.ts +23 -0
  19. package/dist/tags/bar.d.ts.map +1 -0
  20. package/dist/tags/bar.js +64 -0
  21. package/dist/tags/bar.js.map +1 -0
  22. package/dist/tags/budget.d.ts.map +1 -1
  23. package/dist/tags/budget.js +99 -9
  24. package/dist/tags/budget.js.map +1 -1
  25. package/dist/tags/chart.d.ts.map +1 -1
  26. package/dist/tags/chart.js +31 -64
  27. package/dist/tags/chart.js.map +1 -1
  28. package/dist/tags/codegroup.d.ts.map +1 -1
  29. package/dist/tags/codegroup.js +4 -1
  30. package/dist/tags/codegroup.js.map +1 -1
  31. package/dist/tags/deflist.d.ts +20 -0
  32. package/dist/tags/deflist.d.ts.map +1 -0
  33. package/dist/tags/deflist.js +173 -0
  34. package/dist/tags/deflist.js.map +1 -0
  35. package/dist/tags/diagram.d.ts.map +1 -1
  36. package/dist/tags/diagram.js +33 -7
  37. package/dist/tags/diagram.js.map +1 -1
  38. package/dist/tags/embed.d.ts.map +1 -1
  39. package/dist/tags/embed.js +28 -10
  40. package/dist/tags/embed.js.map +1 -1
  41. package/dist/tags/sandbox.d.ts.map +1 -1
  42. package/dist/tags/sandbox.js +32 -50
  43. package/dist/tags/sandbox.js.map +1 -1
  44. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA+B,MAAM,uBAAuB,CAAC;AAEtF,OAAO,KAAK,EAAE,mBAAmB,EAAmB,cAAc,EAAkB,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAS/H,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAkF9D;gFACgF;AAChF,eAAO,MAAM,UAAU,EAAE,WA6wBxB,CAAC;AAEF,sGAAsG;AACtG,eAAO,MAAM,UAAU,aAAa,CAAC;AAIrC,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,EAAE,CAAC;CACzB;AA0vCD,UAAU,YAAY;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAiLD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CACnC,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE;IACT,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChI,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IACnC;;;uBAGmB;IACnB,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACrC,EACD,GAAG,EAAE,eAAe;AACpB;;yEAEyE;AACzE,cAAc,CAAC,EAAE,OAAO,EAAE,GACxB,OAAO,CAqBT;AAED,2DAA2D;AAC3D,MAAM,WAAW,wBAAwB;IACxC;;8DAE0D;IAC1D,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC;;kFAE8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;6CACyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;2EAIuE;IACvE,WAAW,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC;;;yEAGiE;QACjE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,WAAW,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,wBAA6B,GAAG,mBAAmB,CA0ShG;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,EAAE,mBAA+C,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA+B,MAAM,uBAAuB,CAAC;AAEtF,OAAO,KAAK,EAAE,mBAAmB,EAAmB,cAAc,EAAkB,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAS/H,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AA8B9D;gFACgF;AAChF,eAAO,MAAM,UAAU,EAAE,WAsfxB,CAAC;AAEF,sGAAsG;AACtG,eAAO,MAAM,UAAU,aAAa,CAAC;AAIrC,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,EAAE,CAAC;CACzB;AA0vCD,UAAU,YAAY;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAsKD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CACnC,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE;IACT,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChI,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IACnC;;;uBAGmB;IACnB,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACrC,EACD,GAAG,EAAE,eAAe;AACpB;;yEAEyE;AACzE,cAAc,CAAC,EAAE,OAAO,EAAE,GACxB,OAAO,CAqBT;AAED,2DAA2D;AAC3D,MAAM,WAAW,wBAAwB;IACxC;;8DAE0D;IAC1D,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC;;kFAE8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;6CACyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;2EAIuE;IACvE,WAAW,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC;;;yEAGiE;QACjE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,WAAW,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,wBAA6B,GAAG,mBAAmB,CA0ShG;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,EAAE,mBAA+C,CAAC"}
package/dist/config.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isTag, makeTag, findByDataName, readMeta, resolveGap, ratioToFr, resolveOffset, resolveValign, parsePlacement } from '@refrakt-md/transform';
1
+ import { isTag, readField, resolveGap, ratioToFr, resolveOffset, resolveValign, parsePlacement } from '@refrakt-md/transform';
2
2
  import Markdoc from '@markdoc/markdoc';
3
3
  const { Tag } = Markdoc;
4
4
  import { createComponentRenderable } from './lib/index.js';
@@ -15,58 +15,6 @@ import { resolveExpands } from './expand-pipeline.js';
15
15
  import { resolveCollections } from './collection-resolve.js';
16
16
  import { resolveRelationships } from './relationships-resolve.js';
17
17
  import { resolveAggregates } from './aggregate-resolve.js';
18
- // ─── Budget postTransform helpers ───
19
- const BUDGET_CURRENCY_SYMBOLS = {
20
- USD: '$', EUR: '€', GBP: '£', JPY: '¥', CNY: '¥',
21
- AUD: 'A$', CAD: 'C$', CHF: 'CHF ', SEK: 'kr', NOK: 'kr', DKK: 'kr',
22
- INR: '₹', KRW: '₩', BRL: 'R$', MXN: 'MX$', ZAR: 'R',
23
- };
24
- function formatBudgetAmount(amount, symbol) {
25
- const parts = (amount % 1 === 0 ? String(amount) : amount.toFixed(2)).split('.');
26
- parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
27
- return symbol + parts.join('.');
28
- }
29
- function parseBudgetDays(duration) {
30
- let days = 0;
31
- const dayMatch = duration.match(/(\d+)\s*day/i);
32
- const weekMatch = duration.match(/(\d+)\s*week/i);
33
- const monthMatch = duration.match(/(\d+)\s*month/i);
34
- if (dayMatch)
35
- days += parseInt(dayMatch[1]);
36
- if (weekMatch)
37
- days += parseInt(weekMatch[1]) * 7;
38
- if (monthMatch)
39
- days += parseInt(monthMatch[1]) * 30;
40
- if (days === 0) {
41
- const num = parseInt(duration);
42
- if (!isNaN(num))
43
- days = num;
44
- }
45
- return days;
46
- }
47
- function parseBudgetAmount(str) {
48
- const cleaned = str.replace(/[€$£¥₹₩\s]/g, '').replace(/,/g, '');
49
- const range = cleaned.match(/^([\d.]+)\s*[-–]\s*([\d.]+)/);
50
- if (range)
51
- return (parseFloat(range[1]) + parseFloat(range[2])) / 2;
52
- const num = parseFloat(cleaned);
53
- return isNaN(num) ? 0 : num;
54
- }
55
- /** Recursively find all nodes with a specific data-rune attribute */
56
- function collectByRune(children, typeName) {
57
- const results = [];
58
- for (const c of children) {
59
- if (isTag(c)) {
60
- if (c.attributes?.['data-rune'] === typeName) {
61
- results.push(c);
62
- }
63
- else {
64
- results.push(...collectByRune(c.children, typeName));
65
- }
66
- }
67
- }
68
- return results;
69
- }
70
18
  /** Read text content from a property span child */
71
19
  function readPropText(node, prop) {
72
20
  for (const c of node.children) {
@@ -122,24 +70,26 @@ export const coreConfig = {
122
70
  block: 'codegroup',
123
71
  defaultDensity: 'compact',
124
72
  modifiers: { title: { source: 'meta', noBemClass: true }, overflow: { source: 'meta', default: 'scroll' } },
125
- structure: {
126
- topbar: {
127
- tag: 'div', before: true,
128
- children: [
129
- { tag: 'span', ref: 'dot' },
130
- { tag: 'span', ref: 'dot' },
131
- { tag: 'span', ref: 'dot' },
132
- { tag: 'span', ref: 'title', metaText: 'title', condition: 'title' },
133
- ],
134
- },
73
+ // The window chrome (three dots) is pure decoration — drawn in CSS
74
+ // on `.rf-codegroup__topbar`. The only metadata is the optional
75
+ // filename `title`, a bare monospace field in the topbar bar.
76
+ // `renderWhenEmpty` lets `title=""` still project the topbar (window
77
+ // chrome, no filename); an absent title renders no topbar.
78
+ metaFields: {
79
+ title: { metaType: 'code', condition: 'title', renderWhenEmpty: true },
135
80
  },
136
- sections: { topbar: 'header', title: 'title' },
137
- editHints: { panel: 'code', title: 'none' },
138
- postTransform(node) {
139
- // Opt in to the highlight transform's `theme.code.colorScheme`
140
- // cascade so the topbar + tab chrome flip with the inner code.
141
- return { ...node, attributes: { ...node.attributes, 'data-code-host': true } };
81
+ blocks: {
82
+ topbar: { fields: ['title'], layout: 'bar' },
142
83
  },
84
+ layout: { root: ['topbar'] },
85
+ sections: { topbar: 'header' },
86
+ // Opt in to the highlight transform's `theme.code.colorScheme` cascade
87
+ // (topbar + tab chrome flip with the inner code). Static flag → declared
88
+ // via rootAttributes rather than a postTransform. The `data-code-host`
89
+ // consumer reads it truthily, so `"true"` is equivalent to the old
90
+ // valueless boolean.
91
+ rootAttributes: { 'data-code-host': 'true' },
92
+ editHints: { panel: 'code' },
143
93
  },
144
94
  PageSection: { block: 'page-section' },
145
95
  TableOfContents: { block: 'toc' },
@@ -160,6 +110,12 @@ export const coreConfig = {
160
110
  * still needs an entry in the theme config so `computeUsedCssBlocks`
161
111
  * includes `badge.css` in CSS tree-shaking when a badge is rendered. */
162
112
  Badge: { block: 'badge' },
113
+ /* SPEC-079 composable rune handles — render the same DOM as the
114
+ * engine's `split` / `definition-list` layout primitives. CSS comes
115
+ * from the universal `[data-zone-layout=…]` selectors; per-rune
116
+ * blocks exist so CSS tree-shaking includes them. */
117
+ Bar: { block: 'bar' },
118
+ Deflist: { block: 'deflist' },
163
119
  /* Collection emits a sentinel during transform; the postProcess hook
164
120
  * (`resolveCollections`) fills it with queried entities. Engine config
165
121
  * provides the block name for CSS tree-shaking. */
@@ -195,45 +151,13 @@ export const coreConfig = {
195
151
  Embed: {
196
152
  block: 'embed',
197
153
  defaultDensity: 'compact',
198
- editHints: { fallback: 'none' },
199
- postTransform(node) {
200
- const block = node.attributes.class?.split(' ')[0] || 'rf-embed';
201
- const embedUrl = readMeta(node, 'embedUrl') || readMeta(node, 'url') || '';
202
- const title = readMeta(node, 'title') || 'Embedded content';
203
- const aspect = readMeta(node, 'aspect') || '16:9';
204
- const provider = readMeta(node, 'provider') || '';
205
- const [w, h] = aspect.split(':').map(Number);
206
- const paddingPercent = h && w ? (h / w) * 100 : 56.25;
207
- // Filter out consumed meta tags
208
- const contentChildren = node.children.filter(child => {
209
- if (!isTag(child) || child.name !== 'meta')
210
- return true;
211
- const prop = child.attributes['data-field'];
212
- return !['embedUrl', 'url', 'title', 'aspect', 'provider', 'type'].includes(prop);
213
- });
214
- const children = [];
215
- if (embedUrl) {
216
- children.push(makeTag('div', { class: `${block}__wrapper`, style: `padding-bottom: ${paddingPercent}%` }, [
217
- makeTag('iframe', {
218
- src: embedUrl,
219
- title,
220
- frameborder: '0',
221
- allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
222
- allowfullscreen: '',
223
- loading: 'lazy',
224
- }, []),
225
- ]));
226
- }
227
- children.push(makeTag('div', { class: `${block}__fallback` }, contentChildren));
228
- return {
229
- ...node,
230
- attributes: {
231
- ...node.attributes,
232
- ...(provider ? { 'data-provider': provider } : {}),
233
- },
234
- children,
235
- };
154
+ // SPEC-081: the rune transform builds the wrapper/iframe/fallback
155
+ // structure directly; `provider` is a bag-only modifier that surfaces
156
+ // as `data-provider`. No postTransform.
157
+ modifiers: {
158
+ provider: { source: 'meta', default: 'generic', noBemClass: true },
236
159
  },
160
+ editHints: { fallback: 'none' },
237
161
  },
238
162
  Breadcrumb: {
239
163
  block: 'breadcrumb',
@@ -265,75 +189,30 @@ export const coreConfig = {
265
189
  Budget: {
266
190
  block: 'budget',
267
191
  defaultDensity: 'full',
268
- sections: { header: 'header', preamble: 'preamble', headline: 'title', footer: 'footer' },
269
- editHints: { headline: 'inline', meta: 'none', 'meta-item': 'none' },
192
+ sections: { preamble: 'preamble', headline: 'title', footer: 'footer' },
193
+ editHints: { headline: 'inline' },
270
194
  modifiers: {
271
195
  currency: { source: 'meta', default: 'USD' },
272
196
  duration: { source: 'meta' },
273
197
  showPerDay: { source: 'meta', default: 'true' },
274
198
  variant: { source: 'meta', default: 'detailed' },
275
199
  },
276
- structure: {
277
- header: {
278
- tag: 'div', before: true,
279
- conditionAny: ['currency', 'duration'],
280
- children: [
281
- {
282
- tag: 'div', ref: 'meta',
283
- children: [
284
- { tag: 'span', ref: 'meta-item', metaText: 'currency', condition: 'currency', metaType: 'category', metaRank: 'primary' },
285
- { tag: 'span', ref: 'meta-item', metaText: 'duration', label: 'Duration:', condition: 'duration', metaType: 'temporal', metaRank: 'secondary' },
286
- ],
287
- },
288
- ],
289
- },
200
+ // Duration reads first as a bare chip (self-evident, no label);
201
+ // currency is pushed to the right edge where it reads naturally
202
+ // against the budget breakdown below.
203
+ metaFields: {
204
+ duration: { metaType: 'category', condition: 'duration' },
205
+ currency: { metaType: 'category', condition: 'currency' },
290
206
  },
291
- postTransform(node) {
292
- const block = 'rf-budget';
293
- const catBlock = 'rf-budget-category';
294
- // Read from data-* attributes (set by engine after consuming meta tags)
295
- const currency = node.attributes['data-currency'] || 'USD';
296
- const duration = node.attributes['data-duration'] || '';
297
- const showPerDay = node.attributes['data-show-per-day'] !== 'false';
298
- const symbol = BUDGET_CURRENCY_SYMBOLS[currency.toUpperCase()] || currency + ' ';
299
- // Find all BudgetCategory children and compute totals
300
- const categories = collectByRune(node.children, 'budget-category');
301
- let grandTotal = 0;
302
- for (const cat of categories) {
303
- // Read from data attributes set by engine from label/subtotal modifiers
304
- const label = cat.attributes['data-label'] || '';
305
- const subtotalStr = cat.attributes['data-subtotal'] || '0';
306
- const subtotal = parseFloat(subtotalStr) || 0;
307
- grandTotal += subtotal;
308
- // Inject category header with label and formatted subtotal
309
- const catHeader = makeTag('div', { class: `${catBlock}__header` }, [
310
- makeTag('span', { class: `${catBlock}__label` }, [label]),
311
- makeTag('span', { class: `${catBlock}__subtotal` }, [formatBudgetAmount(subtotal, symbol)]),
312
- ]);
313
- cat.children.unshift(catHeader);
314
- }
315
- // Build footer with totals
316
- const footerChildren = [
317
- makeTag('div', { class: `${block}__total` }, [
318
- makeTag('span', { class: `${block}__total-label` }, ['Total']),
319
- makeTag('span', { class: `${block}__total-amount` }, [formatBudgetAmount(grandTotal, symbol)]),
320
- ]),
321
- ];
322
- if (duration && showPerDay) {
323
- const days = parseBudgetDays(duration);
324
- if (days > 0) {
325
- const perDay = grandTotal / days;
326
- footerChildren.push(makeTag('div', { class: `${block}__per-day` }, [
327
- makeTag('span', { class: `${block}__per-day-label` }, ['Per day']),
328
- makeTag('span', { class: `${block}__per-day-amount` }, [formatBudgetAmount(perDay, symbol)]),
329
- ]));
330
- }
331
- }
332
- const footer = makeTag('div', { class: `${block}__footer` }, footerChildren);
333
- return {
334
- ...node,
335
- children: [...node.children, footer],
336
- };
207
+ blocks: {
208
+ meta: { fields: ['duration', { field: 'currency', align: 'end' }], layout: 'bar' },
209
+ },
210
+ // SPEC-081: the transform emits flat header slots and derives the
211
+ // totals (footer + category headers built there); `layout` builds the
212
+ // preamble <header>, and the categories / footer append after it.
213
+ layout: {
214
+ root: ['meta', 'preamble'],
215
+ preamble: { tag: 'header', children: ['headline', 'blurb', 'image'] },
337
216
  },
338
217
  },
339
218
  BudgetCategory: {
@@ -354,16 +233,16 @@ export const coreConfig = {
354
233
  modifiers: { hintType: { source: 'meta', default: 'note' } },
355
234
  contextModifiers: { 'hero': 'in-hero', 'feature': 'in-feature' },
356
235
  sections: { header: 'header' },
357
- editHints: { icon: 'none', title: 'none' },
358
- structure: {
359
- header: {
360
- tag: 'div', before: true,
361
- children: [
362
- { tag: 'span', ref: 'icon', icon: { group: 'hint', variant: 'hintType' } },
363
- { tag: 'span', ref: 'title', metaText: 'hintType' },
364
- ],
365
- },
236
+ // Header is a single `hintType` field, icon-decorated: the value
237
+ // (note/warning/caution/check) selects both the glyph and the
238
+ // label text.
239
+ metaFields: {
240
+ hintType: { icon: { group: 'hint' } },
241
+ },
242
+ blocks: {
243
+ header: { fields: ['hintType'], layout: 'bar' },
366
244
  },
245
+ layout: { root: ['header'] },
367
246
  },
368
247
  Drawer: {
369
248
  block: 'drawer',
@@ -490,115 +369,14 @@ export const coreConfig = {
490
369
  Chart: {
491
370
  block: 'chart',
492
371
  defaultDensity: 'compact',
493
- editHints: { data: 'none' },
494
- postTransform(node) {
495
- const block = node.attributes.class?.split(' ')[0] || 'rf-chart';
496
- const chartType = readMeta(node, 'type') || 'bar';
497
- const title = readMeta(node, 'title') || '';
498
- const dataJson = findByDataName(node, 'data')?.attributes?.content || '{}';
499
- let chartData = { headers: [], rows: [] };
500
- try {
501
- chartData = JSON.parse(dataJson);
502
- }
503
- catch { /* fallback */ }
504
- const colors = [
505
- 'var(--rf-color-info)', 'var(--rf-color-success)',
506
- 'var(--rf-color-warning)', 'var(--rf-color-danger)',
507
- '#7c3aed', '#0891b2',
508
- ];
509
- const svgW = 600, svgH = 300;
510
- const pad = { top: 30, right: 20, bottom: 40, left: 50 };
511
- const cw = svgW - pad.left - pad.right;
512
- const ch = svgH - pad.top - pad.bottom;
513
- const labels = chartData.rows.map(r => r[0] || '');
514
- const series = chartData.headers.slice(1);
515
- const values = chartData.rows.map(r => r.slice(1).map(v => parseFloat(v) || 0));
516
- const maxVal = Math.max(...values.flat(), 1);
517
- const bgw = cw / Math.max(labels.length, 1);
518
- const bw = bgw / Math.max(series.length + 1, 2);
519
- // Build SVG children
520
- const svgChildren = [];
521
- // Axes
522
- svgChildren.push(makeTag('line', {
523
- x1: String(pad.left), y1: String(pad.top),
524
- x2: String(pad.left), y2: String(svgH - pad.bottom),
525
- stroke: 'var(--rf-color-border)', 'stroke-width': '1',
526
- }, []));
527
- svgChildren.push(makeTag('line', {
528
- x1: String(pad.left), y1: String(svgH - pad.bottom),
529
- x2: String(svgW - pad.right), y2: String(svgH - pad.bottom),
530
- stroke: 'var(--rf-color-border)', 'stroke-width': '1',
531
- }, []));
532
- if (chartType === 'bar') {
533
- for (let i = 0; i < labels.length; i++) {
534
- for (let si = 0; si < series.length; si++) {
535
- const h = (values[i][si] / maxVal) * ch;
536
- svgChildren.push(makeTag('rect', {
537
- x: String(pad.left + i * bgw + si * bw + bw * 0.25),
538
- y: String(pad.top + ch - h),
539
- width: String(bw * 0.75),
540
- height: String(h),
541
- style: `fill: ${colors[si % colors.length]}`,
542
- rx: '2',
543
- }, []));
544
- }
545
- svgChildren.push(makeTag('text', {
546
- x: String(pad.left + i * bgw + bgw / 2),
547
- y: String(svgH - pad.bottom + 20),
548
- 'text-anchor': 'middle', 'font-size': '12',
549
- fill: 'var(--rf-color-muted)',
550
- }, [labels[i]]));
551
- }
552
- }
553
- else if (chartType === 'line') {
554
- for (let si = 0; si < series.length; si++) {
555
- const pts = labels.map((_, i) => `${pad.left + i * bgw + bgw / 2},${pad.top + ch - (values[i][si] / maxVal) * ch}`).join(' ');
556
- svgChildren.push(makeTag('polyline', {
557
- points: pts, fill: 'none',
558
- style: `stroke: ${colors[si % colors.length]}`,
559
- 'stroke-width': '2',
560
- }, []));
561
- for (let i = 0; i < labels.length; i++) {
562
- svgChildren.push(makeTag('circle', {
563
- cx: String(pad.left + i * bgw + bgw / 2),
564
- cy: String(pad.top + ch - (values[i][si] / maxVal) * ch),
565
- r: '4',
566
- style: `fill: ${colors[si % colors.length]}`,
567
- }, []));
568
- }
569
- }
570
- for (let i = 0; i < labels.length; i++) {
571
- svgChildren.push(makeTag('text', {
572
- x: String(pad.left + i * bgw + bgw / 2),
573
- y: String(svgH - pad.bottom + 20),
574
- 'text-anchor': 'middle', 'font-size': '12',
575
- fill: 'var(--rf-color-muted)',
576
- }, [labels[i]]));
577
- }
578
- }
579
- const children = [];
580
- if (title) {
581
- children.push(makeTag('figcaption', { class: `${block}__title` }, [title]));
582
- }
583
- children.push(makeTag('div', { class: `${block}__container` }, [
584
- makeTag('svg', {
585
- viewBox: `0 0 ${svgW} ${svgH}`,
586
- class: `${block}__svg`,
587
- }, svgChildren),
588
- ]));
589
- // Legend
590
- if (series.length > 1) {
591
- const legendItems = series.map((name, i) => makeTag('span', { class: `${block}__legend-item` }, [
592
- makeTag('span', {
593
- class: `${block}__legend-color`,
594
- style: `background: ${colors[i % colors.length]};`,
595
- }, []),
596
- name,
597
- ]));
598
- children.push(makeTag('div', { class: `${block}__legend` }, legendItems));
599
- }
600
- return { ...node, children };
372
+ // SPEC-083: the transform emits the rf-chart element wrapping the data
373
+ // `<table>`; `type` / `stacked` are bag-only modifiers ( data-type /
374
+ // data-stacked) the web component reads. No postTransform.
375
+ modifiers: {
376
+ type: { source: 'meta', default: 'bar', noBemClass: true },
377
+ stacked: { source: 'meta', noBemClass: true },
601
378
  },
379
+ editHints: { data: 'none' },
602
380
  },
603
381
  // ─── Text formatting & layout runes ───
604
382
  PullQuote: {
@@ -732,31 +510,10 @@ export const coreConfig = {
732
510
  block: 'diagram',
733
511
  defaultDensity: 'compact',
734
512
  editHints: { source: 'code' },
735
- postTransform(node) {
736
- const block = node.attributes.class?.split(' ')[0] || 'rf-diagram';
737
- const language = readMeta(node, 'language') || 'mermaid';
738
- const title = readMeta(node, 'title') || '';
739
- const sourceMeta = findByDataName(node, 'source');
740
- const source = sourceMeta?.attributes?.content || '';
741
- // Build fallback HTML (visible in SSR, replaced by web component)
742
- const children = [];
743
- if (title) {
744
- children.push(makeTag('figcaption', { class: `${block}__title` }, [title]));
745
- }
746
- const containerChildren = source
747
- ? [makeTag('pre', { class: `${block}__source` }, [makeTag('code', {}, [source])])]
748
- : [];
749
- children.push(makeTag('div', { class: `${block}__container` }, containerChildren));
750
- // Hidden source for web component to read
751
- if (source) {
752
- children.push(makeTag('div', { 'data-content': 'source', style: 'display:none' }, [source]));
753
- }
754
- return {
755
- ...node,
756
- name: 'rf-diagram',
757
- attributes: { ...node.attributes, 'data-language': language },
758
- children,
759
- };
513
+ // SPEC-081: the rune transform emits the `rf-diagram` element + SSR
514
+ // fallback; `language` is a bag-only modifier (→ data-language).
515
+ modifiers: {
516
+ language: { source: 'meta', default: 'mermaid', noBemClass: true },
760
517
  },
761
518
  },
762
519
  Tint: { block: 'tint', parent: '*' },
@@ -766,64 +523,6 @@ export const coreConfig = {
766
523
  block: 'sandbox',
767
524
  defaultDensity: 'compact',
768
525
  editHints: { source: 'code' },
769
- postTransform(node) {
770
- // Read meta values
771
- const content = readMeta(node, 'content') || '';
772
- const framework = readMeta(node, 'framework') || '';
773
- const dependencies = readMeta(node, 'dependencies') || '';
774
- const label = readMeta(node, 'label') || '';
775
- const height = readMeta(node, 'height') || 'auto';
776
- const designTokens = readMeta(node, 'design-tokens') || '';
777
- const securityMode = readMeta(node, 'security-mode') || 'trusted';
778
- const allowJs = readMeta(node, 'allow-js') || 'true';
779
- const sandboxOrigin = readMeta(node, 'sandbox-origin') || '';
780
- // Keep non-meta children (fallback pre) and extract source panels
781
- const fallbackChildren = [];
782
- const sourcePanelOrigins = [];
783
- for (const child of node.children) {
784
- if (!isTag(child)) {
785
- fallbackChildren.push(child);
786
- continue;
787
- }
788
- if (child.name === 'meta') {
789
- // Collect origin data from source panels
790
- if (child.attributes?.['data-field'] === 'source-panel' && child.attributes?.['data-origin']) {
791
- sourcePanelOrigins.push(`${child.attributes['data-label'] || ''}\t${child.attributes['data-origin']}`);
792
- }
793
- continue;
794
- }
795
- fallbackChildren.push(child);
796
- }
797
- // Wrap fallback and source in <template> tags (inert/invisible).
798
- // Using <template> instead of <div> avoids HTML parser issues:
799
- // when <rf-sandbox> is inside <p>, block elements like <pre> or
800
- // <div> cause <p> to auto-close, pushing children out of the
801
- // custom element. <template> is parsed but never rendered.
802
- const children = [
803
- ...(fallbackChildren.length > 0
804
- ? [makeTag('template', { 'data-content': 'fallback' }, fallbackChildren)]
805
- : []),
806
- makeTag('template', { 'data-content': 'source' }, [content]),
807
- ];
808
- return {
809
- ...node,
810
- name: 'rf-sandbox',
811
- attributes: {
812
- ...node.attributes,
813
- 'data-source-content': content,
814
- ...(framework ? { 'data-framework': framework } : {}),
815
- ...(dependencies ? { 'data-dependencies': dependencies } : {}),
816
- ...(label ? { 'data-label': label } : {}),
817
- 'data-height': height,
818
- ...(designTokens ? { 'data-design-tokens': designTokens } : {}),
819
- ...(sourcePanelOrigins.length > 0 ? { 'data-source-origins': sourcePanelOrigins.join('\n') } : {}),
820
- 'data-security-mode': securityMode,
821
- 'data-allow-js': allowJs,
822
- ...(sandboxOrigin ? { 'data-sandbox-origin': sandboxOrigin } : {}),
823
- },
824
- children,
825
- };
826
- },
827
526
  },
828
527
  },
829
528
  };
@@ -2004,14 +1703,12 @@ function resolveBlogPosts(renderable, allPosts, ctx, pageUrl) {
2004
1703
  const result = mapBlogTags(renderable, (tag) => {
2005
1704
  if (tag.attributes['data-rune'] !== 'blog')
2006
1705
  return tag;
2007
- const folderMeta = tag.children.find((c) => Tag.isTag(c) && c.attributes['data-field'] === 'folder');
2008
- const sortMeta = tag.children.find((c) => Tag.isTag(c) && c.attributes['data-field'] === 'sort');
2009
- const filterMeta = tag.children.find((c) => Tag.isTag(c) && c.attributes['data-field'] === 'filter');
2010
- const limitMeta = tag.children.find((c) => Tag.isTag(c) && c.attributes['data-field'] === 'limit');
2011
- const folder = Tag.isTag(folderMeta) ? folderMeta.attributes.content : '';
2012
- const sort = Tag.isTag(sortMeta) ? sortMeta.attributes.content : 'date-desc';
2013
- const filterStr = Tag.isTag(filterMeta) ? filterMeta.attributes.content : '';
2014
- const limitStr = Tag.isTag(limitMeta) ? limitMeta.attributes.content : '';
1706
+ // SPEC-082: read field values from the bag (bag-first, meta-fallback).
1707
+ // The cross-page tree still carries the `data-rune-fields` attribute.
1708
+ const folder = readField(tag, 'folder') ?? '';
1709
+ const sort = readField(tag, 'sort') || 'date-desc';
1710
+ const filterStr = readField(tag, 'filter') ?? '';
1711
+ const limitStr = readField(tag, 'limit') ?? '';
2015
1712
  const limit = limitStr ? parseInt(limitStr, 10) : undefined;
2016
1713
  if (!folder) {
2017
1714
  ctx.warn('Blog rune missing folder attribute', pageUrl);