vantage-md 0.5.6 → 0.5.8

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/index.cjs CHANGED
@@ -42,10 +42,20 @@ let rehype_katex = require("rehype-katex");
42
42
  rehype_katex = __toESM(rehype_katex, 1);
43
43
  let rehype_slug = require("rehype-slug");
44
44
  rehype_slug = __toESM(rehype_slug, 1);
45
+ let unist_util_visit = require("unist-util-visit");
45
46
  let yaml = require("yaml");
46
47
  yaml = __toESM(yaml, 1);
47
48
  let smol_toml = require("smol-toml");
48
49
  //#region src/rehypeSourceLines.ts
50
+ /**
51
+ * Tags that get a `data-source-line`.
52
+ *
53
+ * `td`/`th` are here for review mode: a comment anchors to the cell it was
54
+ * written on, so the cell needs a line of its own to be found again. Every cell
55
+ * in a row reports the *row's* start line — a GFM row is one source line — so a
56
+ * line no longer names at most one anchorable element, and whatever resolves an
57
+ * anchor has to break the tie by block hash (`useReviewHighlights`).
58
+ */
49
59
  const BLOCK_TAGS = /* @__PURE__ */ new Set([
50
60
  "p",
51
61
  "h1",
@@ -58,30 +68,153 @@ const BLOCK_TAGS = /* @__PURE__ */ new Set([
58
68
  "blockquote",
59
69
  "pre",
60
70
  "table",
71
+ "td",
72
+ "th",
61
73
  "tr",
62
74
  "ul",
63
75
  "ol",
64
76
  "hr",
65
77
  "div"
66
78
  ]);
67
- function visit(node, offset) {
79
+ function visit$1(node, offset) {
68
80
  if ("children" in node) {
69
81
  for (const child of node.children) if (child.type === "element") {
70
82
  if (BLOCK_TAGS.has(child.tagName) && child.position?.start?.line) {
71
83
  child.properties = child.properties || {};
72
84
  child.properties["dataSourceLine"] = child.position.start.line + offset;
73
85
  }
74
- visit(child, offset);
86
+ visit$1(child, offset);
75
87
  }
76
88
  }
77
89
  }
78
90
  const rehypeSourceLines = (options) => {
79
91
  const offset = options?.offset ?? 0;
80
92
  return (tree) => {
81
- visit(tree, offset);
93
+ visit$1(tree, offset);
82
94
  };
83
95
  };
84
96
  //#endregion
97
+ //#region src/rehypeVantageAlerts.ts
98
+ /**
99
+ * GFM alerts — `> [!WARNING]` — compiled into `data-vantage-alert`.
100
+ *
101
+ * `remark-gfm` does not implement alerts, so until this plugin existed a
102
+ * `> [!WARNING]` rendered as an ordinary blockquote with the literal marker
103
+ * visible as its first words. Worse than merely unstyled: `@tailwindcss/typography`
104
+ * italicises blockquotes and draws `open-quote`/`close-quote` around the first
105
+ * paragraph, so a callout came out as an italic *quotation* whose opening words
106
+ * were `"[!WARNING]`. That was the "Known gaps" entry in
107
+ * `docs/reference/inline-markup.md` and OQ-10, filed rather than fixed, while
108
+ * `styleGuide.ts` went on telling every agent to write them.
109
+ *
110
+ * The tokens are deliberately the ones the `tone` vocabulary already resolves —
111
+ * an alert *is* the six-colour light/dark treatment `tone` shipped, which is
112
+ * exactly what the gap entry said whoever fixed this should do rather than
113
+ * building a second palette. `[!WARNING]` and `<!-- vantage: block tone=warning -->`
114
+ * therefore agree by construction, and adding a theme still touches one
115
+ * custom-property block.
116
+ *
117
+ * **This runs in the shared pipeline, so all four renderers get it** — the live
118
+ * viewer, the package's exported viewer, the static export and the CLI checker's
119
+ * `renderMarkdown`. That is what makes an injected title element acceptable here
120
+ * where the collapse caret's glyph had to be drawn in CSS: the caret is injected
121
+ * by app JS that may never run, and this is not (D5).
122
+ *
123
+ * ## What it does not do
124
+ *
125
+ * It does not touch a blockquote that carries no marker, and an unrecognised
126
+ * marker (`[!HINT]`) is left exactly as it was — visible literal text, which is
127
+ * the honest rendering of something GitHub also would not style. Silently
128
+ * swallowing it would hide a typo that reads as a callout on neither renderer.
129
+ */
130
+ /**
131
+ * The five GFM alert kinds, lowercased.
132
+ *
133
+ * Deliberately *not* re-derived from `VANTAGE_TONES`: that list carries a sixth
134
+ * token, `muted`, which is ours and is not an alert word. The overlap is the
135
+ * point — the five that coincide share a palette — but the two vocabularies are
136
+ * closed by different authorities and a change to one must not silently move the
137
+ * other. A test asserts the five are a subset of the tones.
138
+ */
139
+ const VANTAGE_ALERTS = [
140
+ "note",
141
+ "tip",
142
+ "important",
143
+ "warning",
144
+ "caution"
145
+ ];
146
+ /** The visible label per kind. Title case, as GitHub renders it. */
147
+ const ALERT_TITLES = {
148
+ note: "Note",
149
+ tip: "Tip",
150
+ important: "Important",
151
+ warning: "Warning",
152
+ caution: "Caution"
153
+ };
154
+ /**
155
+ * The marker, anchored and requiring the rest of its line to be empty.
156
+ *
157
+ * GFM puts the marker alone on the blockquote's first line, and holding to that
158
+ * is what keeps a paragraph that merely *begins* with bracketed text from being
159
+ * eaten. The trailing newline is optional only for the degenerate blockquote
160
+ * whose entire content is the marker.
161
+ *
162
+ * Measured against the real chain rather than assumed: `remark-parse` reads
163
+ * `[!TIP]` as a shortcut link reference, and because no definition matches,
164
+ * `mdast-util-to-hast` puts it back as **one** leading text node —
165
+ * `"[!TIP]\nThe generalization: "` — not as a `[`/label/`]` triple. So a single
166
+ * anchored test on the first text node is enough, and the plugin does not have
167
+ * to reassemble the marker across siblings.
168
+ */
169
+ const MARKER = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*(?:\r?\n|$)/;
170
+ /** The first child, if it is an element. */
171
+ function firstElement(node) {
172
+ const child = node.children.find((c) => c.type === "element" || c.type === "text" && c.value.trim() !== "");
173
+ return child?.type === "element" ? child : void 0;
174
+ }
175
+ /**
176
+ * Compile `> [!KIND]` blockquotes into `data-vantage-alert="kind"`.
177
+ *
178
+ * Order in the chain matters twice, and both are stated in `pipeline.ts`:
179
+ *
180
+ * - **after `rehypeSourceLines`**, so the injected title carries no
181
+ * `data-source-line`. That is what keeps it out of `anchorBlockWithin`, which
182
+ * filters candidates to those with a finite line — otherwise a review comment
183
+ * on an alert would anchor to the word "Warning" instead of to the prose.
184
+ * - **before `rehypeSanitize`**, so nothing reaches the DOM the schema has not
185
+ * passed. `dataVantageAlert` is allowlisted there by name *and* value, like
186
+ * every other `data-vantage-*` attribute.
187
+ */
188
+ function rehypeVantageAlerts() {
189
+ return (tree) => {
190
+ (0, unist_util_visit.visit)(tree, "element", (node) => {
191
+ if (node.tagName !== "blockquote") return;
192
+ const paragraph = firstElement(node);
193
+ if (paragraph === void 0 || paragraph.tagName !== "p") return;
194
+ const lead = paragraph.children[0];
195
+ if (lead === void 0 || lead.type !== "text") return;
196
+ const match = MARKER.exec(lead.value);
197
+ if (match === null) return;
198
+ const kind = match[1].toLowerCase();
199
+ lead.value = lead.value.slice(match[0].length);
200
+ if (lead.value === "" && paragraph.children.length === 1) node.children = node.children.filter((c) => c !== paragraph);
201
+ node.properties = {
202
+ ...node.properties,
203
+ dataVantageAlert: kind
204
+ };
205
+ node.children.unshift({
206
+ type: "element",
207
+ tagName: "div",
208
+ properties: { className: ["vantage-alert-title"] },
209
+ children: [{
210
+ type: "text",
211
+ value: ALERT_TITLES[kind]
212
+ }]
213
+ });
214
+ });
215
+ };
216
+ }
217
+ //#endregion
85
218
  //#region src/vantageDirectives.ts
86
219
  /**
87
220
  * The directive grammar and the closed vocabulary — one parser, no renderer.
@@ -894,6 +1027,7 @@ const sanitizeSchema = {
894
1027
  ["dataVantageCollapseToggle", COLLAPSE_GROUP_ID],
895
1028
  ["dataVantageRun", ...VANTAGE_RUNS],
896
1029
  ["dataVantageOq", "true"],
1030
+ ["dataVantageAlert", ...VANTAGE_ALERTS],
897
1031
  "dataVantageLeaning"
898
1032
  ],
899
1033
  code: [...rehype_sanitize.defaultSchema.attributes?.code || [], "className"],
@@ -939,6 +1073,7 @@ function buildRehypePlugins(options = {}) {
939
1073
  const { math = true, highlight = true, sourceLines = true, sanitize = true, bodyLineOffset = 0 } = options;
940
1074
  const plugins = [rehype_raw.default];
941
1075
  if (sourceLines) plugins.push([rehypeSourceLines, { offset: bodyLineOffset }]);
1076
+ plugins.push(rehypeVantageAlerts);
942
1077
  plugins.push(rehypeVantageDirectives);
943
1078
  if (sanitize) plugins.push([rehype_sanitize.default, sanitizeSchema]);
944
1079
  plugins.push(rehype_slug.default);
@@ -1584,6 +1719,7 @@ The steps below predate the rewrite.
1584
1719
  - **Anything outside those sets is silently ignored** — nothing breaks, and nothing styles either. Run \`vantage-check\` on the document: the \`vantage/*\` rules are the only thing that will ever tell you a directive did nothing.
1585
1720
  - **Always close the comment with \`-->\`.** Never \`--!>\`, and never leave it open: Markdown reads every line below an unclosed \`<!--\` as part of the comment, and the whole rest of the document vanishes from the page. For the same reason \`-->\` cannot appear *inside* a value — it ends the comment early and spills the remainder into the page as literal text.
1586
1721
  - **In a list, indent the directive inside the item**, with blank lines around it (below). At the start of a line between two items it ends the list and starts a second one, which changes the numbering and the spacing in every renderer — the one thing a directive must never do.
1722
+ - **Every open question (\u{1F4AC}) with a stated leaning gets an \`oq\` directive.** The convention's prose — the emoji, the \`OQ-N\` id, the \`_Leaning:_\` line, the fill-in \`**Answer:**\` — produces no button on its own. Writing the convention and stopping there is the most common way this feature goes missing: the questions look complete, review mode is on, and there is nothing to click. **\`vantage-check\` reports it as an error** (\`vantage/oq-missing\`), because a question awaiting a ruling that the reviewer cannot file is not a style preference. Mark it \u{1F512} if it is blocked on something upstream and cannot be answered yet, or \u2705 once it is decided; either state needs no directive.
1587
1723
  - **A \`leaning\` restates the leaning; it is never "yes".** The one-click button in review mode files that text as a review comment, and the comment is all the agent reading it has — nobody remembers which button was clicked. \`leaning="Yes"\` beside a two-branch question is a support ticket.
1588
1724
 
1589
1725
  \`\`\`markdown
@@ -1601,12 +1737,14 @@ The steps below predate the rewrite.
1601
1737
  - Single dollars are **not** math delimiters: \`$HOME\` and \`$100\` stay literal, so prose and shell snippets are safe to write as-is.
1602
1738
  `;
1603
1739
  //#endregion
1740
+ exports.ALERT_TITLES = ALERT_TITLES;
1604
1741
  exports.DIRECTIVE_NAMES = DIRECTIVE_NAMES;
1605
1742
  exports.DIRECTIVE_VOCABULARY = DIRECTIVE_VOCABULARY;
1606
1743
  exports.DOC_STATUSES = DOC_STATUSES;
1607
1744
  exports.DOC_STATUS_TONES = DOC_STATUS_TONES;
1608
1745
  exports.SAFE_STYLE = SAFE_STYLE;
1609
1746
  exports.STYLE_GUIDE = STYLE_GUIDE;
1747
+ exports.VANTAGE_ALERTS = VANTAGE_ALERTS;
1610
1748
  exports.VANTAGE_BADGES = VANTAGE_BADGES;
1611
1749
  exports.VANTAGE_COLLAPSED = VANTAGE_COLLAPSED;
1612
1750
  exports.VANTAGE_EMPHASIS = VANTAGE_EMPHASIS;
@@ -1625,6 +1763,7 @@ exports.parseLineAnchor = parseLineAnchor;
1625
1763
  exports.parseVantageDirective = parseVantageDirective;
1626
1764
  exports.readVantageFrontmatter = readVantageFrontmatter;
1627
1765
  exports.rehypeSourceLines = rehypeSourceLines;
1766
+ exports.rehypeVantageAlerts = rehypeVantageAlerts;
1628
1767
  exports.rehypeVantageDirectives = rehypeVantageDirectives;
1629
1768
  exports.renderMarkdown = renderMarkdown;
1630
1769
  exports.renderMermaidBlocks = renderMermaidBlocks;