vantage-md 0.1.7 → 0.5.4

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.
package/dist/react.cjs CHANGED
@@ -53,25 +53,503 @@ var BLOCK_TAGS = /* @__PURE__ */ new Set([
53
53
  "hr",
54
54
  "div"
55
55
  ]);
56
- function visit(node) {
56
+ function visit(node, offset) {
57
57
  if ("children" in node) {
58
58
  for (const child of node.children) {
59
59
  if (child.type === "element") {
60
60
  if (BLOCK_TAGS.has(child.tagName) && child.position?.start?.line) {
61
61
  child.properties = child.properties || {};
62
- child.properties["dataSourceLine"] = child.position.start.line;
62
+ child.properties["dataSourceLine"] = child.position.start.line + offset;
63
63
  }
64
- visit(child);
64
+ visit(child, offset);
65
65
  }
66
66
  }
67
67
  }
68
68
  }
69
- var rehypeSourceLines = () => {
69
+ var rehypeSourceLines = (options) => {
70
+ const offset = options?.offset ?? 0;
70
71
  return (tree) => {
71
- visit(tree);
72
+ visit(tree, offset);
72
73
  };
73
74
  };
74
75
  var rehypeSourceLines_default = rehypeSourceLines;
76
+
77
+ // src/vantageDirectives.ts
78
+ var VANTAGE_TONES = [
79
+ "note",
80
+ "tip",
81
+ "important",
82
+ "warning",
83
+ "caution",
84
+ "muted"
85
+ ];
86
+ var VANTAGE_EMPHASIS = ["strong", "normal", "quiet"];
87
+ var VANTAGE_BADGES = [
88
+ "draft",
89
+ "stale",
90
+ "blocked",
91
+ "done",
92
+ "wip"
93
+ ];
94
+ var VANTAGE_COLLAPSED = ["true", "false"];
95
+ var VANTAGE_RUNS = ["start", "middle", "end", "only"];
96
+ var VANTAGE_STYLE_TARGETS = [
97
+ "p",
98
+ "h1",
99
+ "h2",
100
+ "h3",
101
+ "h4",
102
+ "h5",
103
+ "h6",
104
+ "li",
105
+ "blockquote",
106
+ "pre",
107
+ "table",
108
+ "tr",
109
+ "ul",
110
+ "ol",
111
+ "hr",
112
+ "div"
113
+ ];
114
+ var VANTAGE_ANCHOR_TARGETS = [
115
+ "p",
116
+ "h1",
117
+ "h2",
118
+ "h3",
119
+ "h4",
120
+ "h5",
121
+ "h6",
122
+ "li",
123
+ "blockquote",
124
+ "pre",
125
+ "table"
126
+ ];
127
+ var STYLE_KEYS = {
128
+ tone: VANTAGE_TONES,
129
+ emphasis: VANTAGE_EMPHASIS,
130
+ badge: VANTAGE_BADGES,
131
+ collapsed: VANTAGE_COLLAPSED
132
+ };
133
+ var DIRECTIVE_VOCABULARY = {
134
+ section: STYLE_KEYS,
135
+ block: STYLE_KEYS,
136
+ oq: { id: null, leaning: null }
137
+ };
138
+ var WS = /[ \t\r\n]*/y;
139
+ var SENTINEL_PREFIX = /^[ \t\r\n]*vantage:/;
140
+ var NAME = /[a-z][a-z0-9-]*/y;
141
+ var UNQUOTED = /[A-Za-z0-9_.:#-]+/y;
142
+ var QUOTED = /"[^"]*"/y;
143
+ function token(comment, offset) {
144
+ const rest = comment.slice(offset);
145
+ const end = rest.search(/[ \t\r\n]/);
146
+ const word = end === -1 ? rest : rest.slice(0, end);
147
+ return word.length > 24 ? `${word.slice(0, 24)}\u2026` : word;
148
+ }
149
+ function matchAt(pattern, comment, offset) {
150
+ pattern.lastIndex = offset;
151
+ const match = pattern.exec(comment);
152
+ return match === null ? null : match[0];
153
+ }
154
+ function skipWhitespace(comment, offset) {
155
+ return matchAt(WS, comment, offset)?.length ?? 0;
156
+ }
157
+ function malformed(reason, offset) {
158
+ return { kind: "malformed", reason, offset };
159
+ }
160
+ function parseVantageDirective(comment) {
161
+ const sentinel = SENTINEL_PREFIX.exec(comment);
162
+ if (sentinel === null) return null;
163
+ let at = sentinel[0].length;
164
+ at += skipWhitespace(comment, at);
165
+ const nameOffset = at;
166
+ const name = matchAt(NAME, comment, at);
167
+ if (name === null) {
168
+ return malformed("no directive name after `vantage:`", at);
169
+ }
170
+ at += name.length;
171
+ const pairs = [];
172
+ while (at < comment.length) {
173
+ const gap = skipWhitespace(comment, at);
174
+ at += gap;
175
+ if (at >= comment.length) break;
176
+ if (gap === 0) {
177
+ return malformed(`\`${token(comment, at)}\` needs a space before it`, at);
178
+ }
179
+ const keyOffset = at;
180
+ const key = matchAt(NAME, comment, at);
181
+ if (key === null) {
182
+ return malformed(
183
+ `\`${token(comment, at)}\` is not a \`key=value\` pair`,
184
+ at
185
+ );
186
+ }
187
+ at += key.length;
188
+ if (comment[at] !== "=") {
189
+ return malformed(`\`${key}\` is not followed by \`=value\``, at);
190
+ }
191
+ at += 1;
192
+ const valueOffset = at;
193
+ const quoted = matchAt(QUOTED, comment, at);
194
+ if (quoted !== null) {
195
+ at += quoted.length;
196
+ pairs.push({
197
+ key,
198
+ value: quoted.slice(1, -1),
199
+ keyOffset,
200
+ valueOffset,
201
+ quoted: true
202
+ });
203
+ continue;
204
+ }
205
+ const unquoted = matchAt(UNQUOTED, comment, at);
206
+ if (unquoted === null) {
207
+ const found = token(comment, at);
208
+ return malformed(
209
+ found === "" ? `\`${key}=\` has no value` : `\`${found}\` is not a valid value for \`${key}\``,
210
+ at
211
+ );
212
+ }
213
+ at += unquoted.length;
214
+ pairs.push({ key, value: unquoted, keyOffset, valueOffset, quoted: false });
215
+ }
216
+ return { kind: "directive", name, nameOffset, pairs };
217
+ }
218
+
219
+ // src/rehypeVantageDirectives.ts
220
+ var STYLE_TARGET_TAGS = new Set(VANTAGE_STYLE_TARGETS);
221
+ var ANCHOR_TARGET_TAGS = new Set(VANTAGE_ANCHOR_TARGETS);
222
+ var HEADING_DEPTHS = /* @__PURE__ */ new Map([
223
+ ["h1", 1],
224
+ ["h2", 2],
225
+ ["h3", 3],
226
+ ["h4", 4],
227
+ ["h5", 5],
228
+ ["h6", 6]
229
+ ]);
230
+ var RANGE_PROPERTIES = /* @__PURE__ */ new Map([
231
+ ["tone", "dataVantageTone"],
232
+ ["emphasis", "dataVantageEmphasis"]
233
+ ]);
234
+ var POINT_PROPERTIES = /* @__PURE__ */ new Map([["badge", "dataVantageBadge"]]);
235
+ var RUN_PROPERTY = "dataVantageRun";
236
+ var OQ_PROPERTY = "dataVantageOq";
237
+ var LEANING_PROPERTY = "dataVantageLeaning";
238
+ var COLLAPSED_PROPERTY = "dataVantageCollapsed";
239
+ var COLLAPSE_GROUP_PROPERTY = "dataVantageCollapseGroup";
240
+ var COLLAPSE_TOGGLE_PROPERTY = "dataVantageCollapseToggle";
241
+ var MAX_LEANING = 500;
242
+ function isSkippable(node) {
243
+ if (node.type === "comment" || node.type === "doctype") return true;
244
+ if (node.type === "text") return node.value.trim() === "";
245
+ return false;
246
+ }
247
+ function headingDepth(node) {
248
+ if (node.type !== "element") return void 0;
249
+ return HEADING_DEPTHS.get(node.tagName);
250
+ }
251
+ function setProperty(element, property, value) {
252
+ element.properties = element.properties ?? {};
253
+ element.properties[property] = value;
254
+ }
255
+ function runValue(index, length) {
256
+ if (length === 1) return "only";
257
+ if (index === 0) return "start";
258
+ return index === length - 1 ? "end" : "middle";
259
+ }
260
+ function vocabularyOf(name, key) {
261
+ return DIRECTIVE_VOCABULARY[name]?.[key];
262
+ }
263
+ function accepts(name, key, value) {
264
+ const values = vocabularyOf(name, key);
265
+ if (values === void 0) return false;
266
+ return values === null || values.includes(value);
267
+ }
268
+ function styleRange(children, targetIndex, name) {
269
+ const range = [targetIndex];
270
+ const depth = name === "section" ? headingDepth(children[targetIndex]) : void 0;
271
+ if (depth === void 0) return range;
272
+ for (let i = targetIndex + 1; i < children.length; i++) {
273
+ const node = children[i];
274
+ const nodeDepth = headingDepth(node);
275
+ if (nodeDepth !== void 0 && nodeDepth <= depth) break;
276
+ if (node.type === "element" && STYLE_TARGET_TAGS.has(node.tagName)) {
277
+ range.push(i);
278
+ }
279
+ }
280
+ return range;
281
+ }
282
+ function collapsesSection(name, pairs, target) {
283
+ if (name !== "section") return false;
284
+ if (pairs.get("collapsed") !== "true") return false;
285
+ return headingDepth(target) !== void 0;
286
+ }
287
+ function stampStyle(children, targetIndex, name, pairs, state) {
288
+ const target = children[targetIndex];
289
+ if (!STYLE_TARGET_TAGS.has(target.tagName)) return;
290
+ const rangeStamps = [];
291
+ const targetStamps = [];
292
+ for (const [key, value] of pairs) {
293
+ if (!accepts(name, key, value)) continue;
294
+ const rangeProperty = RANGE_PROPERTIES.get(key);
295
+ if (rangeProperty !== void 0) {
296
+ rangeStamps.push([rangeProperty, value]);
297
+ continue;
298
+ }
299
+ const pointProperty = POINT_PROPERTIES.get(key);
300
+ if (pointProperty !== void 0) targetStamps.push([pointProperty, value]);
301
+ }
302
+ const collapses = collapsesSection(name, pairs, target);
303
+ if (rangeStamps.length === 0 && targetStamps.length === 0 && !collapses) {
304
+ return;
305
+ }
306
+ const range = styleRange(children, targetIndex, name);
307
+ const group = collapses && range.length > 1 ? String(state.nextGroup++) : void 0;
308
+ for (let i = 0; i < range.length; i++) {
309
+ const element = children[range[i]];
310
+ for (const [property, value] of rangeStamps) {
311
+ setProperty(element, property, value);
312
+ }
313
+ if (i === 0) {
314
+ for (const [property, value] of targetStamps) {
315
+ setProperty(element, property, value);
316
+ }
317
+ }
318
+ if (rangeStamps.length > 0) {
319
+ setProperty(element, RUN_PROPERTY, runValue(i, range.length));
320
+ }
321
+ if (group === void 0) continue;
322
+ if (i === 0) {
323
+ setProperty(element, COLLAPSE_TOGGLE_PROPERTY, group);
324
+ } else {
325
+ setProperty(element, COLLAPSED_PROPERTY, "true");
326
+ setProperty(element, COLLAPSE_GROUP_PROPERTY, group);
327
+ }
328
+ }
329
+ }
330
+ function stampOq(target, pairs) {
331
+ setProperty(target, OQ_PROPERTY, "true");
332
+ const leaning = pairs.get("leaning");
333
+ if (leaning === void 0) return;
334
+ const text = leaning.replace(/\s+/g, " ").trim().slice(0, MAX_LEANING);
335
+ if (text !== "") setProperty(target, LEANING_PROPERTY, text);
336
+ }
337
+ function stampRun(children, targetIndex, run, state) {
338
+ const target = children[targetIndex];
339
+ const style = /* @__PURE__ */ new Map();
340
+ const oq = /* @__PURE__ */ new Map();
341
+ let styleName;
342
+ let hasOq = false;
343
+ for (const directive of run) {
344
+ if (directive.name === "section" || directive.name === "block") {
345
+ styleName = directive.name;
346
+ for (const pair of directive.pairs) style.set(pair.key, pair.value);
347
+ } else if (directive.name === "oq") {
348
+ hasOq = true;
349
+ for (const pair of directive.pairs) oq.set(pair.key, pair.value);
350
+ }
351
+ }
352
+ if (styleName !== void 0) {
353
+ stampStyle(children, targetIndex, styleName, style, state);
354
+ }
355
+ if (hasOq && ANCHOR_TARGET_TAGS.has(target.tagName)) {
356
+ stampOq(target, oq);
357
+ }
358
+ }
359
+ function directiveOf(node) {
360
+ if (node.type !== "comment") return void 0;
361
+ const parsed = parseVantageDirective(node.value);
362
+ return parsed !== null && parsed.kind === "directive" ? parsed : void 0;
363
+ }
364
+ function processChildren(parent, state) {
365
+ const children = parent.children;
366
+ let i = 0;
367
+ while (i < children.length) {
368
+ const node = children[i];
369
+ if (node.type === "element") {
370
+ processChildren(node, state);
371
+ i++;
372
+ continue;
373
+ }
374
+ const first = directiveOf(node);
375
+ if (first === void 0) {
376
+ i++;
377
+ continue;
378
+ }
379
+ const run = [first];
380
+ let j = i + 1;
381
+ let targetIndex = -1;
382
+ for (; j < children.length; j++) {
383
+ const next = children[j];
384
+ if (next.type === "element") {
385
+ targetIndex = j;
386
+ break;
387
+ }
388
+ if (!isSkippable(next)) break;
389
+ const directive = directiveOf(next);
390
+ if (directive !== void 0) run.push(directive);
391
+ }
392
+ if (targetIndex >= 0) stampRun(children, targetIndex, run, state);
393
+ i = j;
394
+ }
395
+ }
396
+ var rehypeVantageDirectives = () => {
397
+ return (tree) => {
398
+ processChildren(tree, { nextGroup: 1 });
399
+ };
400
+ };
401
+ var rehypeVantageDirectives_default = rehypeVantageDirectives;
402
+
403
+ // src/rehypeVantageMathStamps.ts
404
+ var CARRIED_KEY = "vantageDisplayMathStamps";
405
+ function classNames(node) {
406
+ const value = node.properties?.className;
407
+ return Array.isArray(value) ? value.map(String) : [];
408
+ }
409
+ function isDisplayMath(node) {
410
+ if (node.type !== "element" || node.tagName !== "pre") return false;
411
+ return node.children.some(
412
+ (child) => child.type === "element" && child.tagName === "code" && classNames(child).includes("language-math")
413
+ );
414
+ }
415
+ function carriedProperties(properties) {
416
+ const carried = {};
417
+ for (const [key, value] of Object.entries(properties ?? {})) {
418
+ if (key === "dataSourceLine" || key.startsWith("dataVantage")) {
419
+ carried[key] = value;
420
+ }
421
+ }
422
+ return carried;
423
+ }
424
+ function collect(parent, out) {
425
+ const children = parent.children;
426
+ for (let i = 0; i < children.length; i++) {
427
+ const node = children[i];
428
+ if (node.type !== "element") continue;
429
+ if (isDisplayMath(node)) {
430
+ const properties = carriedProperties(node.properties);
431
+ if (Object.keys(properties).length > 0) {
432
+ out.push({
433
+ parent,
434
+ anchor: i === 0 ? void 0 : children[i - 1],
435
+ properties
436
+ });
437
+ }
438
+ continue;
439
+ }
440
+ collect(node, out);
441
+ }
442
+ }
443
+ function reapply(carried) {
444
+ for (const { parent, anchor, properties } of carried) {
445
+ const siblings = parent.children;
446
+ let index = 0;
447
+ if (anchor !== void 0) {
448
+ const at = siblings.indexOf(anchor);
449
+ if (at === -1) continue;
450
+ index = at + 1;
451
+ }
452
+ const replacement = siblings[index];
453
+ if (replacement === void 0 || replacement.type !== "element") continue;
454
+ if (!classNames(replacement).some((name) => name.startsWith("katex"))) {
455
+ continue;
456
+ }
457
+ replacement.properties ??= {};
458
+ for (const [key, value] of Object.entries(properties)) {
459
+ replacement.properties[key] ??= value;
460
+ }
461
+ }
462
+ }
463
+ var rehypeCaptureMathStamps = () => {
464
+ return (tree, file) => {
465
+ const carried = [];
466
+ collect(tree, carried);
467
+ file.data[CARRIED_KEY] = carried;
468
+ };
469
+ };
470
+ var rehypeRestoreMathStamps = () => {
471
+ return (_tree, file) => {
472
+ const carried = file.data[CARRIED_KEY];
473
+ delete file.data[CARRIED_KEY];
474
+ if (Array.isArray(carried)) reapply(carried);
475
+ };
476
+ };
477
+ var SAFE_STYLE_PROPERTIES = [
478
+ // Box metrics. `top`/`right`/`bottom`/`left` are inert now that `position` is
479
+ // banned, and they stay only because dropping them would fail the whole
480
+ // attribute for a document that writes one — the all-or-nothing rule below
481
+ // makes every removal a behaviour change. They buy an attacker nothing that
482
+ // negative `margin` does not already buy.
483
+ "height",
484
+ "min-height",
485
+ "max-height",
486
+ "width",
487
+ "min-width",
488
+ "max-width",
489
+ "top",
490
+ "bottom",
491
+ "left",
492
+ "right",
493
+ "margin",
494
+ "margin-top",
495
+ "margin-right",
496
+ "margin-bottom",
497
+ "margin-left",
498
+ "padding",
499
+ "padding-top",
500
+ "padding-right",
501
+ "padding-bottom",
502
+ "padding-left",
503
+ // Rules and boxes.
504
+ "border",
505
+ "border-style",
506
+ "border-color",
507
+ "border-width",
508
+ "border-top-width",
509
+ "border-right-width",
510
+ "border-bottom-width",
511
+ "border-left-width",
512
+ "border-top-style",
513
+ "border-right-style",
514
+ "border-bottom-style",
515
+ "border-left-style",
516
+ "border-top-color",
517
+ "border-right-color",
518
+ "border-bottom-color",
519
+ "border-left-color",
520
+ "border-radius",
521
+ // Typography.
522
+ "color",
523
+ "background-color",
524
+ "font",
525
+ "font-size",
526
+ "font-style",
527
+ "font-weight",
528
+ "font-family",
529
+ "font-variant",
530
+ "line-height",
531
+ "letter-spacing",
532
+ "word-spacing",
533
+ "text-align",
534
+ "text-decoration",
535
+ "text-indent",
536
+ "white-space",
537
+ "vertical-align",
538
+ "list-style-type",
539
+ // Flow.
540
+ "display",
541
+ "float",
542
+ "clear",
543
+ "opacity",
544
+ "overflow"
545
+ ];
546
+ var VALUE = `[^;:()"'\\\\]*`;
547
+ var DECLARATION = `(?:(?:${SAFE_STYLE_PROPERTIES.join("|")})\\s*:${VALUE})`;
548
+ var SAFE_STYLE = new RegExp(
549
+ `^\\s*(?:${DECLARATION};\\s*)*${DECLARATION}?$`,
550
+ "i"
551
+ );
552
+ var COLLAPSE_GROUP_ID = /^[0-9]+$/;
75
553
  var sanitizeSchema = {
76
554
  ...rehypeSanitize.defaultSchema,
77
555
  tagNames: [
@@ -107,20 +585,94 @@ var sanitizeSchema = {
107
585
  "*": [
108
586
  ...rehypeSanitize.defaultSchema.attributes?.["*"] || [],
109
587
  "className",
110
- "style",
111
- "dataSourceLine"
588
+ ["style", SAFE_STYLE],
589
+ "dataSourceLine",
590
+ // What `rehypeVantageDirectives` compiles a `<!-- vantage: … -->` comment
591
+ // into, named individually — never by a `data-vantage-*` wildcard, which
592
+ // would readmit whatever a future bug emits and whatever a document
593
+ // hand-writes as raw HTML.
594
+ //
595
+ // The value lists are the belt to the plugin's braces: the vocabulary is
596
+ // closed in the plugin *and* here, imported from the one module that
597
+ // defines it, so even if a refactor let an unvalidated value reach the
598
+ // tree the sanitiser still refuses it.
599
+ ["dataVantageTone", ...VANTAGE_TONES],
600
+ ["dataVantageEmphasis", ...VANTAGE_EMPHASIS],
601
+ ["dataVantageBadge", ...VANTAGE_BADGES],
602
+ ["dataVantageCollapsed", ...VANTAGE_COLLAPSED],
603
+ // The other half of `collapsed`: which group a hidden block belongs to,
604
+ // and which group a heading toggles. Both are plugin-minted counters with
605
+ // no vocabulary to allowlist, so they take a pattern instead —
606
+ // `hast-util-sanitize` accepts a `RegExp` in place of a literal value.
607
+ // A pattern rather than a bare name because the JS interpolates the value
608
+ // into a selector: anything but digits has no business reaching it.
609
+ ["dataVantageCollapseGroup", COLLAPSE_GROUP_ID],
610
+ ["dataVantageCollapseToggle", COLLAPSE_GROUP_ID],
611
+ ["dataVantageRun", ...VANTAGE_RUNS],
612
+ ["dataVantageOq", "true"],
613
+ // The design's one genuinely free-text value: the body of a review
614
+ // comment, so it cannot be value-allowlisted and this entry is name-only.
615
+ // Two defences remain rather than three — `hast` escapes the value on
616
+ // serialisation and React sets it through the DOM property path, so it
617
+ // cannot break out of the attribute — and the honest record of that is in
618
+ // the design doc rather than a third layer implied here.
619
+ "dataVantageLeaning"
112
620
  ],
113
621
  code: [...rehypeSanitize.defaultSchema.attributes?.code || [], "className"],
114
- span: [...rehypeSanitize.defaultSchema.attributes?.span || [], "className", "style"],
115
- div: [...rehypeSanitize.defaultSchema.attributes?.div || [], "className", "style"],
622
+ span: [
623
+ ...rehypeSanitize.defaultSchema.attributes?.span || [],
624
+ "className",
625
+ ["style", SAFE_STYLE]
626
+ ],
627
+ div: [
628
+ ...rehypeSanitize.defaultSchema.attributes?.div || [],
629
+ "className",
630
+ ["style", SAFE_STYLE]
631
+ ],
116
632
  a: [...rehypeSanitize.defaultSchema.attributes?.a || [], "id", "className"],
117
633
  math: ["xmlns"],
118
634
  annotation: ["encoding"],
119
635
  img: [...rehypeSanitize.defaultSchema.attributes?.img || [], "loading"],
120
- td: [...rehypeSanitize.defaultSchema.attributes?.td || [], "style"],
121
- th: [...rehypeSanitize.defaultSchema.attributes?.th || [], "style"]
636
+ td: [...rehypeSanitize.defaultSchema.attributes?.td || [], ["style", SAFE_STYLE]],
637
+ th: [...rehypeSanitize.defaultSchema.attributes?.th || [], ["style", SAFE_STYLE]]
122
638
  }
123
639
  };
640
+
641
+ // src/pipeline.ts
642
+ function buildRemarkPlugins(options = {}) {
643
+ const { gfm = true, math = true } = options;
644
+ const plugins = [];
645
+ if (gfm) plugins.push([remarkGfm__default.default, { singleTilde: false }]);
646
+ if (math) plugins.push([remarkMath__default.default, { singleDollarTextMath: false }]);
647
+ return plugins;
648
+ }
649
+ function buildRehypePlugins(options = {}) {
650
+ const {
651
+ math = true,
652
+ highlight = true,
653
+ sourceLines = true,
654
+ sanitize = true,
655
+ bodyLineOffset = 0
656
+ } = options;
657
+ const plugins = [rehypeRaw__default.default];
658
+ if (sourceLines) {
659
+ plugins.push([rehypeSourceLines_default, { offset: bodyLineOffset }]);
660
+ }
661
+ plugins.push(rehypeVantageDirectives_default);
662
+ if (sanitize) plugins.push([rehypeSanitize__default.default, sanitizeSchema]);
663
+ plugins.push(rehypeSlug__default.default);
664
+ if (highlight) plugins.push(rehypeHighlight__default.default);
665
+ if (math) {
666
+ plugins.push(rehypeCaptureMathStamps, rehypeKatex__default.default, rehypeRestoreMathStamps);
667
+ }
668
+ return plugins;
669
+ }
670
+ function buildPipeline(options = {}) {
671
+ return {
672
+ remarkPlugins: buildRemarkPlugins(options),
673
+ rehypePlugins: buildRehypePlugins(options)
674
+ };
675
+ }
124
676
  function parseFrontmatter(content) {
125
677
  if (content.startsWith("+++")) {
126
678
  return parseFrontmatterWithDelimiter(content, "+++", "toml");
@@ -128,24 +680,74 @@ function parseFrontmatter(content) {
128
680
  if (content.startsWith("---")) {
129
681
  return parseFrontmatterWithDelimiter(content, "---", "yaml");
130
682
  }
131
- return { frontmatter: {}, body: content, format: "none" };
683
+ return withOffset(content, {
684
+ frontmatter: {},
685
+ body: content,
686
+ format: "none"
687
+ });
688
+ }
689
+ function withOffset(content, parsed) {
690
+ const stripped = content.slice(0, content.length - parsed.body.length);
691
+ let bodyLineOffset = 0;
692
+ for (const ch of stripped) {
693
+ if (ch === "\n") bodyLineOffset++;
694
+ }
695
+ return { ...parsed, bodyLineOffset };
132
696
  }
133
697
  function parseFrontmatterWithDelimiter(content, delimiter, format) {
134
698
  const searchStart = delimiter.length;
135
699
  const endIndex = content.indexOf(`
136
700
  ${delimiter}`, searchStart);
137
701
  if (endIndex === -1) {
138
- return { frontmatter: {}, body: content, format: "none" };
702
+ return withOffset(content, {
703
+ frontmatter: {},
704
+ body: content,
705
+ format: "none",
706
+ problem: { kind: "unterminated", delimiter }
707
+ });
139
708
  }
140
709
  const raw = content.slice(searchStart + 1, endIndex).trim();
141
710
  const bodyStart = endIndex + 1 + delimiter.length;
142
711
  const body = content.slice(bodyStart).replace(/^\n/, "");
143
712
  try {
144
- const frontmatter = format === "toml" ? smolToml.parse(raw) : YAML__default.default.parse(raw);
145
- return { frontmatter: frontmatter || {}, body, format };
146
- } catch {
147
- return { frontmatter: {}, body: content, format: "none" };
713
+ const parsed = format === "toml" ? smolToml.parse(raw) : YAML__default.default.parse(raw);
714
+ return withOffset(content, {
715
+ frontmatter: parsed || {},
716
+ body,
717
+ format,
718
+ ...isMapping(parsed) ? {} : { problem: { kind: "not-a-mapping", delimiter } }
719
+ });
720
+ } catch (error) {
721
+ return withOffset(content, {
722
+ frontmatter: {},
723
+ body: content,
724
+ format: "none",
725
+ problem: { kind: "invalid", delimiter, ...errorPosition(error) }
726
+ });
727
+ }
728
+ }
729
+ function isMapping(value) {
730
+ return value === null || value === void 0 || typeof value === "object" && !Array.isArray(value);
731
+ }
732
+ function errorPosition(error) {
733
+ const message = error instanceof Error ? error.message : String(error);
734
+ const source = error;
735
+ const yamlPosition = source?.linePos?.[0];
736
+ if (typeof yamlPosition?.line === "number") {
737
+ return {
738
+ message,
739
+ line: yamlPosition.line,
740
+ ...typeof yamlPosition.col === "number" ? { column: yamlPosition.col } : {}
741
+ };
742
+ }
743
+ if (typeof source?.line === "number") {
744
+ return {
745
+ message,
746
+ line: source.line,
747
+ ...typeof source.column === "number" ? { column: source.column } : {}
748
+ };
148
749
  }
750
+ return { message };
149
751
  }
150
752
 
151
753
  // src/mermaidCache.ts
@@ -172,23 +774,286 @@ async function getMermaid() {
172
774
  }
173
775
  return mermaidLoading;
174
776
  }
175
- var AlertTriangleIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "w-4 h-4 shrink-0", children: [
176
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }),
177
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 9v4" }),
178
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 17h.01" })
179
- ] });
180
- var ChevronDownIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "w-3 h-3", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) });
181
- var ChevronUpIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "w-3 h-3", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m18 15-6-6-6 6" }) });
182
- var MaximizeIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "w-4 h-4 text-gray-600", children: [
183
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "15 3 21 3 21 9" }),
184
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 21 3 21 3 15" }),
185
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", x2: "14", y1: "3", y2: "10" }),
186
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", x2: "10", y1: "21", y2: "14" })
187
- ] });
188
- var CloseIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "w-5 h-5", children: [
189
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
190
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
191
- ] });
777
+ var AlertTriangleIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(
778
+ "svg",
779
+ {
780
+ width: "16",
781
+ height: "16",
782
+ viewBox: "0 0 24 24",
783
+ fill: "none",
784
+ stroke: "currentColor",
785
+ strokeWidth: "2",
786
+ strokeLinecap: "round",
787
+ strokeLinejoin: "round",
788
+ className: "w-4 h-4 shrink-0",
789
+ children: [
790
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }),
791
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 9v4" }),
792
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 17h.01" })
793
+ ]
794
+ }
795
+ );
796
+ var ChevronDownIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
797
+ "svg",
798
+ {
799
+ width: "12",
800
+ height: "12",
801
+ viewBox: "0 0 24 24",
802
+ fill: "none",
803
+ stroke: "currentColor",
804
+ strokeWidth: "2",
805
+ strokeLinecap: "round",
806
+ strokeLinejoin: "round",
807
+ className: "w-3 h-3",
808
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" })
809
+ }
810
+ );
811
+ var ChevronUpIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
812
+ "svg",
813
+ {
814
+ width: "12",
815
+ height: "12",
816
+ viewBox: "0 0 24 24",
817
+ fill: "none",
818
+ stroke: "currentColor",
819
+ strokeWidth: "2",
820
+ strokeLinecap: "round",
821
+ strokeLinejoin: "round",
822
+ className: "w-3 h-3",
823
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m18 15-6-6-6 6" })
824
+ }
825
+ );
826
+ var MaximizeIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(
827
+ "svg",
828
+ {
829
+ width: "16",
830
+ height: "16",
831
+ viewBox: "0 0 24 24",
832
+ fill: "none",
833
+ stroke: "currentColor",
834
+ strokeWidth: "2",
835
+ strokeLinecap: "round",
836
+ strokeLinejoin: "round",
837
+ className: "w-4 h-4 text-gray-600",
838
+ children: [
839
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "15 3 21 3 21 9" }),
840
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 21 3 21 3 15" }),
841
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", x2: "14", y1: "3", y2: "10" }),
842
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", x2: "10", y1: "21", y2: "14" })
843
+ ]
844
+ }
845
+ );
846
+ var CloseIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(
847
+ "svg",
848
+ {
849
+ width: "20",
850
+ height: "20",
851
+ viewBox: "0 0 24 24",
852
+ fill: "none",
853
+ stroke: "currentColor",
854
+ strokeWidth: "2",
855
+ strokeLinecap: "round",
856
+ strokeLinejoin: "round",
857
+ className: "w-5 h-5",
858
+ children: [
859
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
860
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
861
+ ]
862
+ }
863
+ );
864
+ var PlusIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(
865
+ "svg",
866
+ {
867
+ width: "16",
868
+ height: "16",
869
+ viewBox: "0 0 24 24",
870
+ fill: "none",
871
+ stroke: "currentColor",
872
+ strokeWidth: "2",
873
+ strokeLinecap: "round",
874
+ strokeLinejoin: "round",
875
+ className: "w-4 h-4",
876
+ children: [
877
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12h14" }),
878
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5v14" })
879
+ ]
880
+ }
881
+ );
882
+ var MinusIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
883
+ "svg",
884
+ {
885
+ width: "16",
886
+ height: "16",
887
+ viewBox: "0 0 24 24",
888
+ fill: "none",
889
+ stroke: "currentColor",
890
+ strokeWidth: "2",
891
+ strokeLinecap: "round",
892
+ strokeLinejoin: "round",
893
+ className: "w-4 h-4",
894
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12h14" })
895
+ }
896
+ );
897
+ var ResetIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(
898
+ "svg",
899
+ {
900
+ width: "16",
901
+ height: "16",
902
+ viewBox: "0 0 24 24",
903
+ fill: "none",
904
+ stroke: "currentColor",
905
+ strokeWidth: "2",
906
+ strokeLinecap: "round",
907
+ strokeLinejoin: "round",
908
+ className: "w-4 h-4",
909
+ children: [
910
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
911
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3v5h5" })
912
+ ]
913
+ }
914
+ );
915
+ var MIN_SCALE = 0.2;
916
+ var MAX_SCALE = 8;
917
+ function ZoomableSvg({ svg }) {
918
+ const [scale, setScale] = react.useState(1);
919
+ const [offset, setOffset] = react.useState({ x: 0, y: 0 });
920
+ const dragRef = react.useRef(null);
921
+ const [isDragging, setIsDragging] = react.useState(false);
922
+ const viewportRef = react.useRef(null);
923
+ const reset = react.useCallback(() => {
924
+ setScale(1);
925
+ setOffset({ x: 0, y: 0 });
926
+ }, []);
927
+ const zoomBy = react.useCallback(
928
+ (factor, anchor) => {
929
+ setScale((prev) => {
930
+ const next = Math.min(MAX_SCALE, Math.max(MIN_SCALE, prev * factor));
931
+ if (next === prev) return prev;
932
+ const rect = viewportRef.current?.getBoundingClientRect();
933
+ if (rect) {
934
+ const ax = (anchor?.x ?? rect.width / 2) - rect.width / 2;
935
+ const ay = (anchor?.y ?? rect.height / 2) - rect.height / 2;
936
+ setOffset((o) => ({
937
+ x: ax - (ax - o.x) * next / prev,
938
+ y: ay - (ay - o.y) * next / prev
939
+ }));
940
+ }
941
+ return next;
942
+ });
943
+ },
944
+ []
945
+ );
946
+ const handleWheel = react.useCallback(
947
+ (e) => {
948
+ e.preventDefault();
949
+ const rect = viewportRef.current?.getBoundingClientRect();
950
+ const anchor = rect ? { x: e.clientX - rect.left, y: e.clientY - rect.top } : void 0;
951
+ zoomBy(e.deltaY < 0 ? 1.15 : 1 / 1.15, anchor);
952
+ },
953
+ [zoomBy]
954
+ );
955
+ const handlePointerDown = (e) => {
956
+ e.currentTarget.setPointerCapture(e.pointerId);
957
+ dragRef.current = {
958
+ startX: e.clientX,
959
+ startY: e.clientY,
960
+ originX: offset.x,
961
+ originY: offset.y
962
+ };
963
+ setIsDragging(true);
964
+ };
965
+ const handlePointerMove = (e) => {
966
+ const d = dragRef.current;
967
+ if (!d) return;
968
+ setOffset({
969
+ x: d.originX + (e.clientX - d.startX),
970
+ y: d.originY + (e.clientY - d.startY)
971
+ });
972
+ };
973
+ const endDrag = (e) => {
974
+ if (dragRef.current) {
975
+ try {
976
+ e.currentTarget.releasePointerCapture(e.pointerId);
977
+ } catch {
978
+ }
979
+ }
980
+ dragRef.current = null;
981
+ setIsDragging(false);
982
+ };
983
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full", children: [
984
+ /* @__PURE__ */ jsxRuntime.jsx(
985
+ "div",
986
+ {
987
+ ref: viewportRef,
988
+ onWheel: handleWheel,
989
+ onPointerDown: handlePointerDown,
990
+ onPointerMove: handlePointerMove,
991
+ onPointerUp: endDrag,
992
+ onPointerLeave: endDrag,
993
+ onDoubleClick: reset,
994
+ className: "w-full h-full overflow-hidden touch-none select-none",
995
+ style: { cursor: isDragging ? "grabbing" : "grab" },
996
+ children: /* @__PURE__ */ jsxRuntime.jsx(
997
+ "div",
998
+ {
999
+ className: "mermaid-zoom flex items-center justify-center w-full h-full",
1000
+ style: {
1001
+ transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})`,
1002
+ transformOrigin: "center center",
1003
+ transition: isDragging ? "none" : "transform 0.08s ease-out"
1004
+ },
1005
+ dangerouslySetInnerHTML: { __html: svg }
1006
+ }
1007
+ )
1008
+ }
1009
+ ),
1010
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-4 right-4 flex items-center gap-1 rounded-lg bg-white/95 shadow-md border border-gray-200 px-1.5 py-1", children: [
1011
+ /* @__PURE__ */ jsxRuntime.jsx(
1012
+ "button",
1013
+ {
1014
+ onClick: () => zoomBy(1 / 1.3),
1015
+ className: "p-1.5 rounded hover:bg-gray-100 text-gray-600",
1016
+ "aria-label": "Zoom out",
1017
+ title: "Zoom out",
1018
+ children: /* @__PURE__ */ jsxRuntime.jsx(MinusIcon, {})
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ jsxRuntime.jsxs(
1022
+ "button",
1023
+ {
1024
+ onClick: reset,
1025
+ className: "px-2 py-1 text-xs font-medium rounded hover:bg-gray-100 text-gray-600 min-w-[3rem]",
1026
+ title: "Reset zoom (or double-click)",
1027
+ children: [
1028
+ Math.round(scale * 100),
1029
+ "%"
1030
+ ]
1031
+ }
1032
+ ),
1033
+ /* @__PURE__ */ jsxRuntime.jsx(
1034
+ "button",
1035
+ {
1036
+ onClick: () => zoomBy(1.3),
1037
+ className: "p-1.5 rounded hover:bg-gray-100 text-gray-600",
1038
+ "aria-label": "Zoom in",
1039
+ title: "Zoom in",
1040
+ children: /* @__PURE__ */ jsxRuntime.jsx(PlusIcon, {})
1041
+ }
1042
+ ),
1043
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-5 bg-gray-200 mx-0.5" }),
1044
+ /* @__PURE__ */ jsxRuntime.jsx(
1045
+ "button",
1046
+ {
1047
+ onClick: reset,
1048
+ className: "p-1.5 rounded hover:bg-gray-100 text-gray-600",
1049
+ "aria-label": "Reset view",
1050
+ title: "Reset view",
1051
+ children: /* @__PURE__ */ jsxRuntime.jsx(ResetIcon, {})
1052
+ }
1053
+ )
1054
+ ] })
1055
+ ] });
1056
+ }
192
1057
  function extractErrorMessage(err) {
193
1058
  if (err instanceof Error) {
194
1059
  const msg = err.message;
@@ -227,12 +1092,12 @@ function DiagramModal({
227
1092
  children: /* @__PURE__ */ jsxRuntime.jsxs(
228
1093
  "div",
229
1094
  {
230
- className: "bg-white rounded-lg shadow-xl w-full max-w-5xl max-h-[90vh] flex flex-col",
1095
+ className: "bg-white rounded-lg shadow-xl w-[96vw] h-[94vh] flex flex-col overflow-hidden",
231
1096
  role: "dialog",
232
1097
  "aria-modal": "true",
233
1098
  onClick: (e) => e.stopPropagation(),
234
1099
  children: [
235
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-4 border-b", children: [
1100
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-4 border-b shrink-0", children: [
236
1101
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: "Mermaid Diagram" }),
237
1102
  /* @__PURE__ */ jsxRuntime.jsx(
238
1103
  "button",
@@ -244,7 +1109,7 @@ function DiagramModal({
244
1109
  }
245
1110
  )
246
1111
  ] }),
247
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 overflow-auto flex-1", children })
1112
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-hidden bg-gray-50", children })
248
1113
  ]
249
1114
  }
250
1115
  )
@@ -363,33 +1228,114 @@ var MermaidDiagramInner = ({ code }) => {
363
1228
  ]
364
1229
  }
365
1230
  ),
366
- /* @__PURE__ */ jsxRuntime.jsx(
367
- DiagramModal,
368
- {
369
- isOpen: isModalOpen,
370
- onClose: () => setIsModalOpen(false),
371
- children: /* @__PURE__ */ jsxRuntime.jsx(
372
- "div",
373
- {
374
- className: "flex justify-center items-center min-h-[50vh]",
375
- dangerouslySetInnerHTML: { __html: svg }
376
- }
377
- )
378
- }
379
- )
1231
+ /* @__PURE__ */ jsxRuntime.jsx(DiagramModal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(ZoomableSvg, { svg }) })
380
1232
  ] });
381
1233
  };
382
1234
  var MermaidDiagram = react.memo(
383
1235
  MermaidDiagramInner,
384
1236
  (prevProps, nextProps) => prevProps.code === nextProps.code
385
1237
  );
386
- var FileTextIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-slate-400", children: [
387
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
388
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
389
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 9H8" }),
390
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 13H8" }),
391
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 17H8" })
392
- ] });
1238
+
1239
+ // src/vantageFrontmatter.ts
1240
+ var DOC_STATUSES = [
1241
+ "draft",
1242
+ "in-review",
1243
+ "accepted",
1244
+ "deprecated"
1245
+ ];
1246
+ var VANTAGE_FRONTMATTER_KEYS = ["status-chip"];
1247
+ var DOC_STATUS_TONES = {
1248
+ draft: "muted",
1249
+ "in-review": "warning",
1250
+ accepted: "tip",
1251
+ deprecated: "caution"
1252
+ };
1253
+ var STATUS_CHIP_VALUES = [
1254
+ ...DOC_STATUSES,
1255
+ "true",
1256
+ "false"
1257
+ ];
1258
+ function isDocStatus(value) {
1259
+ return typeof value === "string" && DOC_STATUSES.includes(value);
1260
+ }
1261
+ function isTable(value) {
1262
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
1263
+ }
1264
+ function readVantageFrontmatter(frontmatter) {
1265
+ const issues = [];
1266
+ if (!Object.hasOwn(frontmatter, "vantage")) return { issues };
1267
+ const value = frontmatter["vantage"];
1268
+ if (!isTable(value)) {
1269
+ issues.push({ kind: "not-a-table", value });
1270
+ return { issues };
1271
+ }
1272
+ let statusChip;
1273
+ for (const key of Object.keys(value)) {
1274
+ if (!VANTAGE_FRONTMATTER_KEYS.includes(key)) {
1275
+ issues.push({ kind: "unknown-key", key });
1276
+ continue;
1277
+ }
1278
+ if (key === "status-chip") {
1279
+ statusChip = readStatusChip(frontmatter, value[key], issues);
1280
+ }
1281
+ }
1282
+ return { ...statusChip === void 0 ? {} : { statusChip }, issues };
1283
+ }
1284
+ function readStatusChip(frontmatter, raw, issues) {
1285
+ const status = frontmatter["status"];
1286
+ if (raw === false) return void 0;
1287
+ if (raw === true) {
1288
+ if (isDocStatus(status)) return status;
1289
+ issues.push({ kind: "status-chip-orphan", status });
1290
+ return void 0;
1291
+ }
1292
+ if (isDocStatus(raw)) {
1293
+ if (isDocStatus(status) && status !== raw) {
1294
+ issues.push({ kind: "status-chip-disagrees", chip: raw, status });
1295
+ }
1296
+ return raw;
1297
+ }
1298
+ issues.push({
1299
+ kind: "bad-value",
1300
+ key: "status-chip",
1301
+ value: raw,
1302
+ legal: STATUS_CHIP_VALUES
1303
+ });
1304
+ return void 0;
1305
+ }
1306
+ var DocumentStatusChipInner = ({
1307
+ status
1308
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
1309
+ "span",
1310
+ {
1311
+ className: `vantage-chip vantage-chip--${DOC_STATUS_TONES[status]}`,
1312
+ "data-vantage-status": status,
1313
+ title: `Document status: ${status}`,
1314
+ children: status
1315
+ }
1316
+ );
1317
+ var DocumentStatusChip = react.memo(DocumentStatusChipInner);
1318
+ var FileTextIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(
1319
+ "svg",
1320
+ {
1321
+ width: "14",
1322
+ height: "14",
1323
+ viewBox: "0 0 24 24",
1324
+ fill: "none",
1325
+ stroke: "currentColor",
1326
+ strokeWidth: "2",
1327
+ strokeLinecap: "round",
1328
+ strokeLinejoin: "round",
1329
+ className: "text-slate-400",
1330
+ children: [
1331
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
1332
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
1333
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 9H8" }),
1334
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 13H8" }),
1335
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 17H8" })
1336
+ ]
1337
+ }
1338
+ );
393
1339
  function isStringArray(value) {
394
1340
  return Array.isArray(value) && value.every((item) => typeof item === "string");
395
1341
  }
@@ -433,6 +1379,7 @@ function ValueCell({ value }) {
433
1379
  function flattenEntries(entries) {
434
1380
  const result = [];
435
1381
  for (const [key, value] of entries) {
1382
+ if (key === "vantage") continue;
436
1383
  if (key === "taxonomies" && isPlainObject(value)) {
437
1384
  for (const [taxKey, taxVal] of Object.entries(value)) {
438
1385
  result.push([taxKey, taxVal]);
@@ -450,31 +1397,33 @@ function flattenEntries(entries) {
450
1397
  var FrontmatterDisplayInner = ({
451
1398
  frontmatter
452
1399
  }) => {
453
- const raw = Object.entries(frontmatter);
454
- if (raw.length === 0) return null;
455
- const entries = flattenEntries(raw);
456
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-8 rounded-lg overflow-hidden bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-800 dark:to-slate-800/80 border border-slate-200 dark:border-slate-700 shadow-sm", children: [
457
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-2.5 bg-white/60 dark:bg-slate-900/40 border-b border-slate-200 dark:border-slate-700 flex items-center gap-2", children: [
458
- /* @__PURE__ */ jsxRuntime.jsx(FileTextIcon, {}),
459
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider", children: "Metadata" })
460
- ] }),
461
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: entries.map(([key, value]) => /* @__PURE__ */ jsxRuntime.jsxs(
462
- "tr",
463
- {
464
- className: "border-b border-slate-200/60 dark:border-slate-700/60 last:border-0",
465
- children: [
466
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 pr-4 font-medium text-slate-500 dark:text-slate-400 whitespace-nowrap align-top w-1/4 min-w-[100px]", children: key }),
467
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 text-slate-800 dark:text-slate-200 align-top", children: /* @__PURE__ */ jsxRuntime.jsx(ValueCell, { value }) })
468
- ]
469
- },
470
- key
471
- )) }) }) })
1400
+ const entries = flattenEntries(Object.entries(frontmatter));
1401
+ const { statusChip } = readVantageFrontmatter(frontmatter);
1402
+ if (entries.length === 0 && statusChip === void 0) return null;
1403
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1404
+ statusChip !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vantage-chrome", children: /* @__PURE__ */ jsxRuntime.jsx(DocumentStatusChip, { status: statusChip }) }),
1405
+ entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-8 rounded-lg overflow-hidden bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-800 dark:to-slate-800/80 border border-slate-200 dark:border-slate-700 shadow-sm", children: [
1406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-2.5 bg-white/60 dark:bg-slate-900/40 border-b border-slate-200 dark:border-slate-700 flex items-center gap-2", children: [
1407
+ /* @__PURE__ */ jsxRuntime.jsx(FileTextIcon, {}),
1408
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider", children: "Metadata" })
1409
+ ] }),
1410
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: entries.map(([key, value]) => /* @__PURE__ */ jsxRuntime.jsxs(
1411
+ "tr",
1412
+ {
1413
+ className: "border-b border-slate-200/60 dark:border-slate-700/60 last:border-0",
1414
+ children: [
1415
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 pr-4 font-medium text-slate-500 dark:text-slate-400 whitespace-nowrap align-top w-1/4 min-w-[100px]", children: key }),
1416
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 text-slate-800 dark:text-slate-200 align-top", children: /* @__PURE__ */ jsxRuntime.jsx(ValueCell, { value }) })
1417
+ ]
1418
+ },
1419
+ key
1420
+ )) }) }) })
1421
+ ] })
472
1422
  ] });
473
1423
  };
474
1424
  var FrontmatterDisplay = react.memo(FrontmatterDisplayInner);
475
1425
 
476
- // src/scrollToLineAnchor.ts
477
- var HIGHLIGHT_CLASS = "line-anchor-highlight";
1426
+ // src/lineAnchor.ts
478
1427
  function parseLineAnchor(hash) {
479
1428
  if (!hash) return null;
480
1429
  const frag = hash.startsWith("#") ? hash.slice(1) : hash;
@@ -484,6 +1433,9 @@ function parseLineAnchor(hash) {
484
1433
  const end = match[2] ? parseInt(match[2], 10) : start;
485
1434
  return { start: Math.min(start, end), end: Math.max(start, end) };
486
1435
  }
1436
+
1437
+ // src/scrollToLineAnchor.ts
1438
+ var HIGHLIGHT_CLASS = "line-anchor-highlight";
487
1439
  function clearLineAnchorHighlights(container) {
488
1440
  container.querySelectorAll(`.${HIGHLIGHT_CLASS}`).forEach((node) => {
489
1441
  node.classList.remove(HIGHLIGHT_CLASS);
@@ -496,10 +1448,7 @@ function scrollToLineAnchor(container, hash) {
496
1448
  const blocks = container.querySelectorAll("[data-source-line]");
497
1449
  let firstMatch = null;
498
1450
  for (const block of blocks) {
499
- const line = parseInt(
500
- block.dataset.sourceLine || "0",
501
- 10
502
- );
1451
+ const line = parseInt(block.dataset.sourceLine || "0", 10);
503
1452
  if (line >= range.start && line <= range.end) {
504
1453
  block.classList.add(HIGHLIGHT_CLASS);
505
1454
  if (!firstMatch) firstMatch = block;
@@ -646,9 +1595,13 @@ var MarkdownViewerInner = ({
646
1595
  }) => {
647
1596
  const containerRef = react.useRef(null);
648
1597
  useLineAnchor(containerRef, hash);
649
- const { frontmatter, body } = react.useMemo(() => {
1598
+ const { frontmatter, body, bodyLineOffset } = react.useMemo(() => {
650
1599
  return parseFrontmatter(content);
651
1600
  }, [content]);
1601
+ const { remarkPlugins, rehypePlugins } = react.useMemo(
1602
+ () => buildPipeline({ bodyLineOffset }),
1603
+ [bodyLineOffset]
1604
+ );
652
1605
  const handleLinkClick = react.useCallback(
653
1606
  (e, href) => {
654
1607
  if (href.startsWith("#")) {
@@ -771,18 +1724,8 @@ var MarkdownViewerInner = ({
771
1724
  /* @__PURE__ */ jsxRuntime.jsx(
772
1725
  ReactMarkdown__default.default,
773
1726
  {
774
- remarkPlugins: [
775
- [remarkGfm__default.default, { singleTilde: false }],
776
- [remarkMath__default.default, { singleDollarTextMath: false }]
777
- ],
778
- rehypePlugins: [
779
- rehypeRaw__default.default,
780
- rehypeSourceLines_default,
781
- [rehypeSanitize__default.default, sanitizeSchema],
782
- rehypeSlug__default.default,
783
- rehypeHighlight__default.default,
784
- rehypeKatex__default.default
785
- ],
1727
+ remarkPlugins,
1728
+ rehypePlugins,
786
1729
  urlTransform: transformImageUri,
787
1730
  components: markdownComponents,
788
1731
  children: body
@@ -809,27 +1752,22 @@ async function renderMarkdown(content, options = {}) {
809
1752
  if (parseFm) {
810
1753
  parsed = parseFrontmatter(content);
811
1754
  } else {
812
- parsed = { frontmatter: {}, body: content, format: "none" };
813
- }
814
- const remarkPlugins = [];
815
- const rehypePlugins = [];
816
- if (gfm) remarkPlugins.push([remarkGfm__default.default, { singleTilde: false }]);
817
- if (math) remarkPlugins.push([remarkMath__default.default, { singleDollarTextMath: false }]);
818
- rehypePlugins.push([rehypeRaw__default.default]);
819
- if (sourceLines) rehypePlugins.push([rehypeSourceLines_default]);
820
- if (sanitize) rehypePlugins.push([rehypeSanitize__default.default, sanitizeSchema]);
821
- rehypePlugins.push([rehypeSlug__default.default]);
822
- if (highlight) rehypePlugins.push([rehypeHighlight__default.default]);
823
- if (math) rehypePlugins.push([rehypeKatex__default.default]);
824
- let processor = unified.unified().use(remarkParse__default.default);
825
- for (const [plugin, ...args] of remarkPlugins) {
826
- processor = processor.use(plugin, ...args);
827
- }
828
- processor = processor.use(remarkRehype__default.default, { allowDangerousHtml: true });
829
- for (const [plugin, ...args] of rehypePlugins) {
830
- processor = processor.use(plugin, ...args);
831
- }
832
- processor = processor.use(rehypeStringify__default.default);
1755
+ parsed = {
1756
+ frontmatter: {},
1757
+ body: content,
1758
+ format: "none",
1759
+ bodyLineOffset: 0
1760
+ };
1761
+ }
1762
+ const { remarkPlugins, rehypePlugins } = buildPipeline({
1763
+ gfm,
1764
+ math,
1765
+ highlight,
1766
+ sourceLines,
1767
+ sanitize,
1768
+ bodyLineOffset: parsed.bodyLineOffset
1769
+ });
1770
+ const processor = unified.unified().use(remarkParse__default.default).use(remarkPlugins).use(remarkRehype__default.default, { allowDangerousHtml: true }).use(rehypePlugins).use(rehypeStringify__default.default);
833
1771
  const result = await processor.process(parsed.body);
834
1772
  return {
835
1773
  html: String(result),
@@ -910,12 +1848,20 @@ function resolveLinks(html, options = {}) {
910
1848
  );
911
1849
  }
912
1850
 
1851
+ exports.DOC_STATUSES = DOC_STATUSES;
1852
+ exports.DOC_STATUS_TONES = DOC_STATUS_TONES;
1853
+ exports.DocumentStatusChip = DocumentStatusChip;
913
1854
  exports.FrontmatterDisplay = FrontmatterDisplay;
914
1855
  exports.MarkdownViewer = MarkdownViewer;
915
1856
  exports.MermaidDiagram = MermaidDiagram;
1857
+ exports.VANTAGE_FRONTMATTER_KEYS = VANTAGE_FRONTMATTER_KEYS;
1858
+ exports.buildPipeline = buildPipeline;
1859
+ exports.buildRemarkPlugins = buildRemarkPlugins;
916
1860
  exports.clearLineAnchorHighlights = clearLineAnchorHighlights;
1861
+ exports.isDocStatus = isDocStatus;
917
1862
  exports.parseFrontmatter = parseFrontmatter;
918
1863
  exports.parseLineAnchor = parseLineAnchor;
1864
+ exports.readVantageFrontmatter = readVantageFrontmatter;
919
1865
  exports.rehypeSourceLines = rehypeSourceLines_default;
920
1866
  exports.renderMarkdown = renderMarkdown;
921
1867
  exports.renderMermaidBlocks = renderMermaidBlocks;