From 25aac6a4ce28fc8124925f91dd7f1e4ac0e6e9d2 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 21 Jul 2026 02:17:48 -0700 Subject: [PATCH 1/3] Move FAQ table support indicators into remark-sembr Keep plain Markdown consumers on the original prose while styling Supported, Not supported, and None during HTML render. --- src/content.config.ts | 17 +---------------- src/lib/remark-sembr.ts | 33 +++++++++++++++++++++++++++++++++ src/styles/global.css | 4 ++-- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/content.config.ts b/src/content.config.ts index bc0d738..778606f 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -25,21 +25,6 @@ function toPlainMarkdown(body: string): string { .replace(/\n{3,}/g, '\n\n'); } -// Inject inline HTML into the spec source before rendering so that the FAQ -// comparison table shows accessible support indicators in HTML, while the -// plain Markdown body keeps its prose for curl/llms.txt consumers. -function preprocessForRendering(source: string): string { - return source - .replace( - /\| Supported \|/g, - '| |', - ) - .replace( - /\| Not supported \|/g, - '| |', - ); -} - const specification = defineCollection({ loader: { name: 'sembr-specification', @@ -58,7 +43,7 @@ const specification = defineCollection({ const id = 'sembr'; const data = await parseData({ id, data: {} }); const digest = generateDigest(source); - const rendered = await renderMarkdown(preprocessForRendering(source)); + const rendered = await renderMarkdown(source); const body = toPlainMarkdown(source); store.set({ id, data, body, digest, rendered }); diff --git a/src/lib/remark-sembr.ts b/src/lib/remark-sembr.ts index c8d8753..d11918d 100644 --- a/src/lib/remark-sembr.ts +++ b/src/lib/remark-sembr.ts @@ -70,6 +70,39 @@ export const remarkSembr: Plugin<[], Root> = () => { } }); + + // FAQ comparison table: accessible support indicators and styled "None". + visit(tree, 'tableCell', (node) => { + if (node.children.length !== 1 || node.children[0]?.type !== 'text') { + return; + } + const value = node.children[0].value; + if (value === 'Supported') { + node.children = [ + { + type: 'html', + value: + '', + }, + ]; + } else if (value === 'Not supported') { + node.children = [ + { + type: 'html', + value: + '', + }, + ]; + } else if (value === 'None') { + node.children = [ + { + type: 'html', + value: 'None', + }, + ]; + } + }); + visit(tree, 'heading', (node) => { if ( node.depth === 1 && diff --git a/src/styles/global.css b/src/styles/global.css index fc2c741..13ae516 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -379,11 +379,11 @@ font-weight: 700; } - mark[data-supported="true"] { + :is(mark, em)[data-supported="true"] { color: var(--color-heading); } - mark[data-supported="false"] { + :is(mark, em)[data-supported="false"] { color: var(--color-rule); } From 3e78ab7406688dec729111c5a4d2b90bcc82d716 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 21 Jul 2026 02:17:52 -0700 Subject: [PATCH 2/3] Clarify remark-sembr visit stanzas and RFC 2119 naming --- src/lib/remark-sembr.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/remark-sembr.ts b/src/lib/remark-sembr.ts index d11918d..ac58c5f 100644 --- a/src/lib/remark-sembr.ts +++ b/src/lib/remark-sembr.ts @@ -2,7 +2,7 @@ import type { Definition, Html, Root, RootContent, Text } from 'mdast'; import type { Plugin } from 'unified'; import { visit } from 'unist-util-visit'; -const KEYWORDS = [ +const RFC_2119_KEYWORDS = [ 'MUST NOT', 'SHALL NOT', 'SHOULD NOT', @@ -15,7 +15,7 @@ const KEYWORDS = [ 'MAY', ] as const; -const KEYWORD_PATTERN = new RegExp(`\\b(${KEYWORDS.join('|')})\\b`, 'g'); +const RFC_2119_PATTERN = new RegExp(`\\b(${RFC_2119_KEYWORDS.join('|')})\\b`, 'g'); const EM_DASH = '\u2014'; const EM_DASH_PATTERN = /---/g; @@ -54,11 +54,13 @@ function ccSymbolsForLicenseUrl(rawUrl: string): string | null { export const remarkSembr: Plugin<[], Root> = () => { return (tree) => { + // Collect link definitions for resolving Creative Commons references. const definitions = new Map(); visit(tree, 'definition', (node: Definition) => { definitions.set(node.identifier, node.url); }); + // Mark multiline
 blocks as SemBr examples for whitespace styling.
 		visit(tree, 'html', (node) => {
 			const match = node.value.match(MULTILINE_PRE_PATTERN);
 			if (
@@ -70,7 +72,6 @@ export const remarkSembr: Plugin<[], Root> = () => {
 			}
 		});
 
-
 		// FAQ comparison table: accessible support indicators and styled "None".
 		visit(tree, 'tableCell', (node) => {
 			if (node.children.length !== 1 || node.children[0]?.type !== 'text') {
@@ -103,6 +104,7 @@ export const remarkSembr: Plugin<[], Root> = () => {
 			}
 		});
 
+		// Keep "Line Breaks" on one line in the title via a non-breaking space.
 		visit(tree, 'heading', (node) => {
 			if (
 				node.depth === 1 &&
@@ -114,13 +116,14 @@ export const remarkSembr: Plugin<[], Root> = () => {
 			}
 		});
 
+		// Highlight RFC 2119 keywords and normalize --- to an em dash.
 		visit(tree, 'text', (node, index, parent) => {
 			if (!parent || typeof index !== 'number') {
 				return;
 			}
 
 			const value = node.value.replace(EM_DASH_PATTERN, EM_DASH);
-			const matches = [...value.matchAll(KEYWORD_PATTERN)];
+			const matches = [...value.matchAll(RFC_2119_PATTERN)];
 
 			if (matches.length === 0) {
 				if (value !== node.value) {
@@ -157,6 +160,7 @@ export const remarkSembr: Plugin<[], Root> = () => {
 			parent.children.splice(index, 1, ...(nodes as typeof parent.children));
 		});
 
+		// Prefix Creative Commons license links with Unicode license symbols.
 		visit(tree, ['link', 'linkReference'], (node, index, parent) => {
 			if (!parent || typeof index !== 'number') {
 				return;

From 02bf54a945a737262ec657c0ee06efafb421bbe4 Mon Sep 17 00:00:00 2001
From: Mattt Zmuda 
Date: Tue, 21 Jul 2026 02:17:52 -0700
Subject: [PATCH 3/3] Balance text wrapping in lists and FAQ syntax cells

---
 src/styles/global.css | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/styles/global.css b/src/styles/global.css
index 13ae516..a9a43ed 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -185,6 +185,7 @@
 		position: relative;
 		padding-inline-start: var(--space-s);
 		margin-block-end: var(--space-s);
+		text-wrap: balance;
 	}
 
 	ol>li::marker {
@@ -379,6 +380,10 @@
 		font-weight: 700;
 	}
 
+	tbody tr>td:nth-child(2) {
+		text-wrap: balance;
+	}
+
 	:is(mark, em)[data-supported="true"] {
 		color: var(--color-heading);
 	}