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