vantage-md 0.5.6 → 0.5.7

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,6 +42,7 @@ 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");
@@ -64,24 +65,145 @@ const BLOCK_TAGS = /* @__PURE__ */ new Set([
64
65
  "hr",
65
66
  "div"
66
67
  ]);
67
- function visit(node, offset) {
68
+ function visit$1(node, offset) {
68
69
  if ("children" in node) {
69
70
  for (const child of node.children) if (child.type === "element") {
70
71
  if (BLOCK_TAGS.has(child.tagName) && child.position?.start?.line) {
71
72
  child.properties = child.properties || {};
72
73
  child.properties["dataSourceLine"] = child.position.start.line + offset;
73
74
  }
74
- visit(child, offset);
75
+ visit$1(child, offset);
75
76
  }
76
77
  }
77
78
  }
78
79
  const rehypeSourceLines = (options) => {
79
80
  const offset = options?.offset ?? 0;
80
81
  return (tree) => {
81
- visit(tree, offset);
82
+ visit$1(tree, offset);
82
83
  };
83
84
  };
84
85
  //#endregion
86
+ //#region src/rehypeVantageAlerts.ts
87
+ /**
88
+ * GFM alerts — `> [!WARNING]` — compiled into `data-vantage-alert`.
89
+ *
90
+ * `remark-gfm` does not implement alerts, so until this plugin existed a
91
+ * `> [!WARNING]` rendered as an ordinary blockquote with the literal marker
92
+ * visible as its first words. Worse than merely unstyled: `@tailwindcss/typography`
93
+ * italicises blockquotes and draws `open-quote`/`close-quote` around the first
94
+ * paragraph, so a callout came out as an italic *quotation* whose opening words
95
+ * were `"[!WARNING]`. That was the "Known gaps" entry in
96
+ * `docs/reference/inline-markup.md` and OQ-10, filed rather than fixed, while
97
+ * `styleGuide.ts` went on telling every agent to write them.
98
+ *
99
+ * The tokens are deliberately the ones the `tone` vocabulary already resolves —
100
+ * an alert *is* the six-colour light/dark treatment `tone` shipped, which is
101
+ * exactly what the gap entry said whoever fixed this should do rather than
102
+ * building a second palette. `[!WARNING]` and `<!-- vantage: block tone=warning -->`
103
+ * therefore agree by construction, and adding a theme still touches one
104
+ * custom-property block.
105
+ *
106
+ * **This runs in the shared pipeline, so all four renderers get it** — the live
107
+ * viewer, the package's exported viewer, the static export and the CLI checker's
108
+ * `renderMarkdown`. That is what makes an injected title element acceptable here
109
+ * where the collapse caret's glyph had to be drawn in CSS: the caret is injected
110
+ * by app JS that may never run, and this is not (D5).
111
+ *
112
+ * ## What it does not do
113
+ *
114
+ * It does not touch a blockquote that carries no marker, and an unrecognised
115
+ * marker (`[!HINT]`) is left exactly as it was — visible literal text, which is
116
+ * the honest rendering of something GitHub also would not style. Silently
117
+ * swallowing it would hide a typo that reads as a callout on neither renderer.
118
+ */
119
+ /**
120
+ * The five GFM alert kinds, lowercased.
121
+ *
122
+ * Deliberately *not* re-derived from `VANTAGE_TONES`: that list carries a sixth
123
+ * token, `muted`, which is ours and is not an alert word. The overlap is the
124
+ * point — the five that coincide share a palette — but the two vocabularies are
125
+ * closed by different authorities and a change to one must not silently move the
126
+ * other. A test asserts the five are a subset of the tones.
127
+ */
128
+ const VANTAGE_ALERTS = [
129
+ "note",
130
+ "tip",
131
+ "important",
132
+ "warning",
133
+ "caution"
134
+ ];
135
+ /** The visible label per kind. Title case, as GitHub renders it. */
136
+ const ALERT_TITLES = {
137
+ note: "Note",
138
+ tip: "Tip",
139
+ important: "Important",
140
+ warning: "Warning",
141
+ caution: "Caution"
142
+ };
143
+ /**
144
+ * The marker, anchored and requiring the rest of its line to be empty.
145
+ *
146
+ * GFM puts the marker alone on the blockquote's first line, and holding to that
147
+ * is what keeps a paragraph that merely *begins* with bracketed text from being
148
+ * eaten. The trailing newline is optional only for the degenerate blockquote
149
+ * whose entire content is the marker.
150
+ *
151
+ * Measured against the real chain rather than assumed: `remark-parse` reads
152
+ * `[!TIP]` as a shortcut link reference, and because no definition matches,
153
+ * `mdast-util-to-hast` puts it back as **one** leading text node —
154
+ * `"[!TIP]\nThe generalization: "` — not as a `[`/label/`]` triple. So a single
155
+ * anchored test on the first text node is enough, and the plugin does not have
156
+ * to reassemble the marker across siblings.
157
+ */
158
+ const MARKER = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*(?:\r?\n|$)/;
159
+ /** The first child, if it is an element. */
160
+ function firstElement(node) {
161
+ const child = node.children.find((c) => c.type === "element" || c.type === "text" && c.value.trim() !== "");
162
+ return child?.type === "element" ? child : void 0;
163
+ }
164
+ /**
165
+ * Compile `> [!KIND]` blockquotes into `data-vantage-alert="kind"`.
166
+ *
167
+ * Order in the chain matters twice, and both are stated in `pipeline.ts`:
168
+ *
169
+ * - **after `rehypeSourceLines`**, so the injected title carries no
170
+ * `data-source-line`. That is what keeps it out of `anchorBlockWithin`, which
171
+ * filters candidates to those with a finite line — otherwise a review comment
172
+ * on an alert would anchor to the word "Warning" instead of to the prose.
173
+ * - **before `rehypeSanitize`**, so nothing reaches the DOM the schema has not
174
+ * passed. `dataVantageAlert` is allowlisted there by name *and* value, like
175
+ * every other `data-vantage-*` attribute.
176
+ */
177
+ function rehypeVantageAlerts() {
178
+ return (tree) => {
179
+ (0, unist_util_visit.visit)(tree, "element", (node) => {
180
+ if (node.tagName !== "blockquote") return;
181
+ const paragraph = firstElement(node);
182
+ if (paragraph === void 0 || paragraph.tagName !== "p") return;
183
+ const lead = paragraph.children[0];
184
+ if (lead === void 0 || lead.type !== "text") return;
185
+ const match = MARKER.exec(lead.value);
186
+ if (match === null) return;
187
+ const kind = match[1].toLowerCase();
188
+ lead.value = lead.value.slice(match[0].length);
189
+ if (lead.value === "" && paragraph.children.length === 1) node.children = node.children.filter((c) => c !== paragraph);
190
+ node.properties = {
191
+ ...node.properties,
192
+ dataVantageAlert: kind
193
+ };
194
+ node.children.unshift({
195
+ type: "element",
196
+ tagName: "div",
197
+ properties: { className: ["vantage-alert-title"] },
198
+ children: [{
199
+ type: "text",
200
+ value: ALERT_TITLES[kind]
201
+ }]
202
+ });
203
+ });
204
+ };
205
+ }
206
+ //#endregion
85
207
  //#region src/vantageDirectives.ts
86
208
  /**
87
209
  * The directive grammar and the closed vocabulary — one parser, no renderer.
@@ -894,6 +1016,7 @@ const sanitizeSchema = {
894
1016
  ["dataVantageCollapseToggle", COLLAPSE_GROUP_ID],
895
1017
  ["dataVantageRun", ...VANTAGE_RUNS],
896
1018
  ["dataVantageOq", "true"],
1019
+ ["dataVantageAlert", ...VANTAGE_ALERTS],
897
1020
  "dataVantageLeaning"
898
1021
  ],
899
1022
  code: [...rehype_sanitize.defaultSchema.attributes?.code || [], "className"],
@@ -939,6 +1062,7 @@ function buildRehypePlugins(options = {}) {
939
1062
  const { math = true, highlight = true, sourceLines = true, sanitize = true, bodyLineOffset = 0 } = options;
940
1063
  const plugins = [rehype_raw.default];
941
1064
  if (sourceLines) plugins.push([rehypeSourceLines, { offset: bodyLineOffset }]);
1065
+ plugins.push(rehypeVantageAlerts);
942
1066
  plugins.push(rehypeVantageDirectives);
943
1067
  if (sanitize) plugins.push([rehype_sanitize.default, sanitizeSchema]);
944
1068
  plugins.push(rehype_slug.default);
@@ -1584,6 +1708,7 @@ The steps below predate the rewrite.
1584
1708
  - **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
1709
  - **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
1710
  - **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.
1711
+ - **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
1712
  - **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
1713
 
1589
1714
  \`\`\`markdown
@@ -1601,12 +1726,14 @@ The steps below predate the rewrite.
1601
1726
  - Single dollars are **not** math delimiters: \`$HOME\` and \`$100\` stay literal, so prose and shell snippets are safe to write as-is.
1602
1727
  `;
1603
1728
  //#endregion
1729
+ exports.ALERT_TITLES = ALERT_TITLES;
1604
1730
  exports.DIRECTIVE_NAMES = DIRECTIVE_NAMES;
1605
1731
  exports.DIRECTIVE_VOCABULARY = DIRECTIVE_VOCABULARY;
1606
1732
  exports.DOC_STATUSES = DOC_STATUSES;
1607
1733
  exports.DOC_STATUS_TONES = DOC_STATUS_TONES;
1608
1734
  exports.SAFE_STYLE = SAFE_STYLE;
1609
1735
  exports.STYLE_GUIDE = STYLE_GUIDE;
1736
+ exports.VANTAGE_ALERTS = VANTAGE_ALERTS;
1610
1737
  exports.VANTAGE_BADGES = VANTAGE_BADGES;
1611
1738
  exports.VANTAGE_COLLAPSED = VANTAGE_COLLAPSED;
1612
1739
  exports.VANTAGE_EMPHASIS = VANTAGE_EMPHASIS;
@@ -1625,6 +1752,7 @@ exports.parseLineAnchor = parseLineAnchor;
1625
1752
  exports.parseVantageDirective = parseVantageDirective;
1626
1753
  exports.readVantageFrontmatter = readVantageFrontmatter;
1627
1754
  exports.rehypeSourceLines = rehypeSourceLines;
1755
+ exports.rehypeVantageAlerts = rehypeVantageAlerts;
1628
1756
  exports.rehypeVantageDirectives = rehypeVantageDirectives;
1629
1757
  exports.renderMarkdown = renderMarkdown;
1630
1758
  exports.renderMermaidBlocks = renderMermaidBlocks;