@refrakt-md/runes 0.17.0 → 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.
@@ -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,WAgxBxB,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) {
@@ -135,12 +83,13 @@ export const coreConfig = {
135
83
  },
136
84
  layout: { root: ['topbar'] },
137
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' },
138
92
  editHints: { panel: 'code' },
139
- postTransform(node) {
140
- // Opt in to the highlight transform's `theme.code.colorScheme`
141
- // cascade so the topbar + tab chrome flip with the inner code.
142
- return { ...node, attributes: { ...node.attributes, 'data-code-host': true } };
143
- },
144
93
  },
145
94
  PageSection: { block: 'page-section' },
146
95
  TableOfContents: { block: 'toc' },
@@ -202,45 +151,13 @@ export const coreConfig = {
202
151
  Embed: {
203
152
  block: 'embed',
204
153
  defaultDensity: 'compact',
205
- editHints: { fallback: 'none' },
206
- postTransform(node) {
207
- const block = node.attributes.class?.split(' ')[0] || 'rf-embed';
208
- const embedUrl = readMeta(node, 'embedUrl') || readMeta(node, 'url') || '';
209
- const title = readMeta(node, 'title') || 'Embedded content';
210
- const aspect = readMeta(node, 'aspect') || '16:9';
211
- const provider = readMeta(node, 'provider') || '';
212
- const [w, h] = aspect.split(':').map(Number);
213
- const paddingPercent = h && w ? (h / w) * 100 : 56.25;
214
- // Filter out consumed meta tags
215
- const contentChildren = node.children.filter(child => {
216
- if (!isTag(child) || child.name !== 'meta')
217
- return true;
218
- const prop = child.attributes['data-field'];
219
- return !['embedUrl', 'url', 'title', 'aspect', 'provider', 'type'].includes(prop);
220
- });
221
- const children = [];
222
- if (embedUrl) {
223
- children.push(makeTag('div', { class: `${block}__wrapper`, style: `padding-bottom: ${paddingPercent}%` }, [
224
- makeTag('iframe', {
225
- src: embedUrl,
226
- title,
227
- frameborder: '0',
228
- allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
229
- allowfullscreen: '',
230
- loading: 'lazy',
231
- }, []),
232
- ]));
233
- }
234
- children.push(makeTag('div', { class: `${block}__fallback` }, contentChildren));
235
- return {
236
- ...node,
237
- attributes: {
238
- ...node.attributes,
239
- ...(provider ? { 'data-provider': provider } : {}),
240
- },
241
- children,
242
- };
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 },
243
159
  },
160
+ editHints: { fallback: 'none' },
244
161
  },
245
162
  Breadcrumb: {
246
163
  block: 'breadcrumb',
@@ -290,53 +207,12 @@ export const coreConfig = {
290
207
  blocks: {
291
208
  meta: { fields: ['duration', { field: 'currency', align: 'end' }], layout: 'bar' },
292
209
  },
293
- layout: { root: ['meta', 'preamble'] },
294
- postTransform(node) {
295
- const block = 'rf-budget';
296
- const catBlock = 'rf-budget-category';
297
- // Read from data-* attributes (set by engine after consuming meta tags)
298
- const currency = node.attributes['data-currency'] || 'USD';
299
- const duration = node.attributes['data-duration'] || '';
300
- const showPerDay = node.attributes['data-show-per-day'] !== 'false';
301
- const symbol = BUDGET_CURRENCY_SYMBOLS[currency.toUpperCase()] || currency + ' ';
302
- // Find all BudgetCategory children and compute totals
303
- const categories = collectByRune(node.children, 'budget-category');
304
- let grandTotal = 0;
305
- for (const cat of categories) {
306
- // Read from data attributes set by engine from label/subtotal modifiers
307
- const label = cat.attributes['data-label'] || '';
308
- const subtotalStr = cat.attributes['data-subtotal'] || '0';
309
- const subtotal = parseFloat(subtotalStr) || 0;
310
- grandTotal += subtotal;
311
- // Inject category header with label and formatted subtotal
312
- const catHeader = makeTag('div', { class: `${catBlock}__header` }, [
313
- makeTag('span', { class: `${catBlock}__label` }, [label]),
314
- makeTag('span', { class: `${catBlock}__subtotal` }, [formatBudgetAmount(subtotal, symbol)]),
315
- ]);
316
- cat.children.unshift(catHeader);
317
- }
318
- // Build footer with totals
319
- const footerChildren = [
320
- makeTag('div', { class: `${block}__total` }, [
321
- makeTag('span', { class: `${block}__total-label` }, ['Total']),
322
- makeTag('span', { class: `${block}__total-amount` }, [formatBudgetAmount(grandTotal, symbol)]),
323
- ]),
324
- ];
325
- if (duration && showPerDay) {
326
- const days = parseBudgetDays(duration);
327
- if (days > 0) {
328
- const perDay = grandTotal / days;
329
- footerChildren.push(makeTag('div', { class: `${block}__per-day` }, [
330
- makeTag('span', { class: `${block}__per-day-label` }, ['Per day']),
331
- makeTag('span', { class: `${block}__per-day-amount` }, [formatBudgetAmount(perDay, symbol)]),
332
- ]));
333
- }
334
- }
335
- const footer = makeTag('div', { class: `${block}__footer` }, footerChildren);
336
- return {
337
- ...node,
338
- children: [...node.children, footer],
339
- };
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'] },
340
216
  },
341
217
  },
342
218
  BudgetCategory: {
@@ -493,115 +369,14 @@ export const coreConfig = {
493
369
  Chart: {
494
370
  block: 'chart',
495
371
  defaultDensity: 'compact',
496
- editHints: { data: 'none' },
497
- postTransform(node) {
498
- const block = node.attributes.class?.split(' ')[0] || 'rf-chart';
499
- const chartType = readMeta(node, 'type') || 'bar';
500
- const title = readMeta(node, 'title') || '';
501
- const dataJson = findByDataName(node, 'data')?.attributes?.content || '{}';
502
- let chartData = { headers: [], rows: [] };
503
- try {
504
- chartData = JSON.parse(dataJson);
505
- }
506
- catch { /* fallback */ }
507
- const colors = [
508
- 'var(--rf-color-info)', 'var(--rf-color-success)',
509
- 'var(--rf-color-warning)', 'var(--rf-color-danger)',
510
- '#7c3aed', '#0891b2',
511
- ];
512
- const svgW = 600, svgH = 300;
513
- const pad = { top: 30, right: 20, bottom: 40, left: 50 };
514
- const cw = svgW - pad.left - pad.right;
515
- const ch = svgH - pad.top - pad.bottom;
516
- const labels = chartData.rows.map(r => r[0] || '');
517
- const series = chartData.headers.slice(1);
518
- const values = chartData.rows.map(r => r.slice(1).map(v => parseFloat(v) || 0));
519
- const maxVal = Math.max(...values.flat(), 1);
520
- const bgw = cw / Math.max(labels.length, 1);
521
- const bw = bgw / Math.max(series.length + 1, 2);
522
- // Build SVG children
523
- const svgChildren = [];
524
- // Axes
525
- svgChildren.push(makeTag('line', {
526
- x1: String(pad.left), y1: String(pad.top),
527
- x2: String(pad.left), y2: String(svgH - pad.bottom),
528
- stroke: 'var(--rf-color-border)', 'stroke-width': '1',
529
- }, []));
530
- svgChildren.push(makeTag('line', {
531
- x1: String(pad.left), y1: String(svgH - pad.bottom),
532
- x2: String(svgW - pad.right), y2: String(svgH - pad.bottom),
533
- stroke: 'var(--rf-color-border)', 'stroke-width': '1',
534
- }, []));
535
- if (chartType === 'bar') {
536
- for (let i = 0; i < labels.length; i++) {
537
- for (let si = 0; si < series.length; si++) {
538
- const h = (values[i][si] / maxVal) * ch;
539
- svgChildren.push(makeTag('rect', {
540
- x: String(pad.left + i * bgw + si * bw + bw * 0.25),
541
- y: String(pad.top + ch - h),
542
- width: String(bw * 0.75),
543
- height: String(h),
544
- style: `fill: ${colors[si % colors.length]}`,
545
- rx: '2',
546
- }, []));
547
- }
548
- svgChildren.push(makeTag('text', {
549
- x: String(pad.left + i * bgw + bgw / 2),
550
- y: String(svgH - pad.bottom + 20),
551
- 'text-anchor': 'middle', 'font-size': '12',
552
- fill: 'var(--rf-color-muted)',
553
- }, [labels[i]]));
554
- }
555
- }
556
- else if (chartType === 'line') {
557
- for (let si = 0; si < series.length; si++) {
558
- const pts = labels.map((_, i) => `${pad.left + i * bgw + bgw / 2},${pad.top + ch - (values[i][si] / maxVal) * ch}`).join(' ');
559
- svgChildren.push(makeTag('polyline', {
560
- points: pts, fill: 'none',
561
- style: `stroke: ${colors[si % colors.length]}`,
562
- 'stroke-width': '2',
563
- }, []));
564
- for (let i = 0; i < labels.length; i++) {
565
- svgChildren.push(makeTag('circle', {
566
- cx: String(pad.left + i * bgw + bgw / 2),
567
- cy: String(pad.top + ch - (values[i][si] / maxVal) * ch),
568
- r: '4',
569
- style: `fill: ${colors[si % colors.length]}`,
570
- }, []));
571
- }
572
- }
573
- for (let i = 0; i < labels.length; i++) {
574
- svgChildren.push(makeTag('text', {
575
- x: String(pad.left + i * bgw + bgw / 2),
576
- y: String(svgH - pad.bottom + 20),
577
- 'text-anchor': 'middle', 'font-size': '12',
578
- fill: 'var(--rf-color-muted)',
579
- }, [labels[i]]));
580
- }
581
- }
582
- const children = [];
583
- if (title) {
584
- children.push(makeTag('figcaption', { class: `${block}__title` }, [title]));
585
- }
586
- children.push(makeTag('div', { class: `${block}__container` }, [
587
- makeTag('svg', {
588
- viewBox: `0 0 ${svgW} ${svgH}`,
589
- class: `${block}__svg`,
590
- }, svgChildren),
591
- ]));
592
- // Legend
593
- if (series.length > 1) {
594
- const legendItems = series.map((name, i) => makeTag('span', { class: `${block}__legend-item` }, [
595
- makeTag('span', {
596
- class: `${block}__legend-color`,
597
- style: `background: ${colors[i % colors.length]};`,
598
- }, []),
599
- name,
600
- ]));
601
- children.push(makeTag('div', { class: `${block}__legend` }, legendItems));
602
- }
603
- 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 },
604
378
  },
379
+ editHints: { data: 'none' },
605
380
  },
606
381
  // ─── Text formatting & layout runes ───
607
382
  PullQuote: {
@@ -735,31 +510,10 @@ export const coreConfig = {
735
510
  block: 'diagram',
736
511
  defaultDensity: 'compact',
737
512
  editHints: { source: 'code' },
738
- postTransform(node) {
739
- const block = node.attributes.class?.split(' ')[0] || 'rf-diagram';
740
- const language = readMeta(node, 'language') || 'mermaid';
741
- const title = readMeta(node, 'title') || '';
742
- const sourceMeta = findByDataName(node, 'source');
743
- const source = sourceMeta?.attributes?.content || '';
744
- // Build fallback HTML (visible in SSR, replaced by web component)
745
- const children = [];
746
- if (title) {
747
- children.push(makeTag('figcaption', { class: `${block}__title` }, [title]));
748
- }
749
- const containerChildren = source
750
- ? [makeTag('pre', { class: `${block}__source` }, [makeTag('code', {}, [source])])]
751
- : [];
752
- children.push(makeTag('div', { class: `${block}__container` }, containerChildren));
753
- // Hidden source for web component to read
754
- if (source) {
755
- children.push(makeTag('div', { 'data-content': 'source', style: 'display:none' }, [source]));
756
- }
757
- return {
758
- ...node,
759
- name: 'rf-diagram',
760
- attributes: { ...node.attributes, 'data-language': language },
761
- children,
762
- };
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 },
763
517
  },
764
518
  },
765
519
  Tint: { block: 'tint', parent: '*' },
@@ -769,64 +523,6 @@ export const coreConfig = {
769
523
  block: 'sandbox',
770
524
  defaultDensity: 'compact',
771
525
  editHints: { source: 'code' },
772
- postTransform(node) {
773
- // Read meta values
774
- const content = readMeta(node, 'content') || '';
775
- const framework = readMeta(node, 'framework') || '';
776
- const dependencies = readMeta(node, 'dependencies') || '';
777
- const label = readMeta(node, 'label') || '';
778
- const height = readMeta(node, 'height') || 'auto';
779
- const designTokens = readMeta(node, 'design-tokens') || '';
780
- const securityMode = readMeta(node, 'security-mode') || 'trusted';
781
- const allowJs = readMeta(node, 'allow-js') || 'true';
782
- const sandboxOrigin = readMeta(node, 'sandbox-origin') || '';
783
- // Keep non-meta children (fallback pre) and extract source panels
784
- const fallbackChildren = [];
785
- const sourcePanelOrigins = [];
786
- for (const child of node.children) {
787
- if (!isTag(child)) {
788
- fallbackChildren.push(child);
789
- continue;
790
- }
791
- if (child.name === 'meta') {
792
- // Collect origin data from source panels
793
- if (child.attributes?.['data-field'] === 'source-panel' && child.attributes?.['data-origin']) {
794
- sourcePanelOrigins.push(`${child.attributes['data-label'] || ''}\t${child.attributes['data-origin']}`);
795
- }
796
- continue;
797
- }
798
- fallbackChildren.push(child);
799
- }
800
- // Wrap fallback and source in <template> tags (inert/invisible).
801
- // Using <template> instead of <div> avoids HTML parser issues:
802
- // when <rf-sandbox> is inside <p>, block elements like <pre> or
803
- // <div> cause <p> to auto-close, pushing children out of the
804
- // custom element. <template> is parsed but never rendered.
805
- const children = [
806
- ...(fallbackChildren.length > 0
807
- ? [makeTag('template', { 'data-content': 'fallback' }, fallbackChildren)]
808
- : []),
809
- makeTag('template', { 'data-content': 'source' }, [content]),
810
- ];
811
- return {
812
- ...node,
813
- name: 'rf-sandbox',
814
- attributes: {
815
- ...node.attributes,
816
- 'data-source-content': content,
817
- ...(framework ? { 'data-framework': framework } : {}),
818
- ...(dependencies ? { 'data-dependencies': dependencies } : {}),
819
- ...(label ? { 'data-label': label } : {}),
820
- 'data-height': height,
821
- ...(designTokens ? { 'data-design-tokens': designTokens } : {}),
822
- ...(sourcePanelOrigins.length > 0 ? { 'data-source-origins': sourcePanelOrigins.join('\n') } : {}),
823
- 'data-security-mode': securityMode,
824
- 'data-allow-js': allowJs,
825
- ...(sandboxOrigin ? { 'data-sandbox-origin': sandboxOrigin } : {}),
826
- },
827
- children,
828
- };
829
- },
830
526
  },
831
527
  },
832
528
  };
@@ -2007,14 +1703,12 @@ function resolveBlogPosts(renderable, allPosts, ctx, pageUrl) {
2007
1703
  const result = mapBlogTags(renderable, (tag) => {
2008
1704
  if (tag.attributes['data-rune'] !== 'blog')
2009
1705
  return tag;
2010
- const folderMeta = tag.children.find((c) => Tag.isTag(c) && c.attributes['data-field'] === 'folder');
2011
- const sortMeta = tag.children.find((c) => Tag.isTag(c) && c.attributes['data-field'] === 'sort');
2012
- const filterMeta = tag.children.find((c) => Tag.isTag(c) && c.attributes['data-field'] === 'filter');
2013
- const limitMeta = tag.children.find((c) => Tag.isTag(c) && c.attributes['data-field'] === 'limit');
2014
- const folder = Tag.isTag(folderMeta) ? folderMeta.attributes.content : '';
2015
- const sort = Tag.isTag(sortMeta) ? sortMeta.attributes.content : 'date-desc';
2016
- const filterStr = Tag.isTag(filterMeta) ? filterMeta.attributes.content : '';
2017
- 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') ?? '';
2018
1712
  const limit = limitStr ? parseInt(limitStr, 10) : undefined;
2019
1713
  if (!folder) {
2020
1714
  ctx.warn('Blog rune missing folder attribute', pageUrl);