Skip to content

build: add static-site target, macro placeholders, and heading guardrails - #1369

Open
paulirish wants to merge 10 commits into
mainfrom
devsite-sync-plan
Open

build: add static-site target, macro placeholders, and heading guardrails#1369
paulirish wants to merge 10 commits into
mainfrom
devsite-sync-plan

Conversation

@paulirish

Copy link
Copy Markdown
Member

Provides a dedicated static-site compilation pipeline to export processed markdown guides for DevSite and external documentation platforms, serializes macro placeholders for downstream hydration, and introduces linting guardrails for heading titles and HTML tag escaping.

Key Changes

  • Static-Site Build Target (serving/scripts/build-guides.ts):

    • Added --target static-site (pnpm run build:static-site) to generate standalone markdown guides in dist/static-site-guides without calculating vector embeddings.
    • Standardizes YAML frontmatter (title, description, category) with safe string escaping while preserving author H1 titles and stripping duplicates from compiled bodies.
    • Emits incremental pipeline cache hashes (dist/.cache/static-site) to skip rebuilding unchanged guides.
  • Macro Compilation for External Sites (serving/lib/macros.ts):

    • BASELINE_STATUS: Serializes into placeholder tags ([BASELINE_STATUS: <feature-id>]) for downstream DevSite hydration rather than baking in static text.
    • GUIDE_REF: Emits standard relative markdown links ([title](../<category>/<guide-name>.md)) under static-site instead of CLI retrieve commands (npx modern-web-guidance retrieve ...).
  • Validation Guardrails (lib/guide-validation.ts):

    • Heading validation: Added validateHeadings and validateGuideTitle to disallow vague top-level H1s (# Overview, # Introduction, # Guide, # Title) and require a clear title on non-stub guides while permitting subsection overviews (e.g. ## Overview).
    • HTML tag checking: Added validateHtmlTags to catch unescaped HTML element literals with accurate line number tracking while permitting safe typographic inline tags (<kbd>, <br>, <wbr>) and comments.
  • Guide Title Hygiene:

    • Added missing descriptive H1 headings to html, persistent-top-layer-ui, move-dom-element-without-losing-state, and agentic-javascript-tools.
    • Replaced vague # Overview in declarative-dialog-popover-control with # Declarative Dialog and Popover Control.

@paulirish

paulirish commented Aug 24, 2026

Copy link
Copy Markdown
Member Author

You can look at https://github.com/GoogleChrome/modern-web-guidance-src/actions/runs/32760160368?pr=1369 to see the net effect of the guide macro changes. (i see a bug im about to fix.. EDIT: just kidding its fine. the nuance was retained! woo.)

Comment thread serving/lib/macros.ts
content: (args, filePath, options) => {
const [featureId, bcdKey] = args;
if (options?.target === 'static-site') {
return `[BASELINE_STATUS: ${featureId}]`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this requires a script on the DevSite side to convert this format into the BaselineStatus macro.

Why not output the DevSite macro directly? Do we expect to use any other static sites than DevSite?

Suggested change
return `[BASELINE_STATUS: ${featureId}]`;
return `{{ macros.BaselineStatus('${featureId}') }}`;

Alternatively, we could keep the unrendered MWG macro syntax as the placeholder, rather than introducing the new square bracket format.

Comment thread serving/lib/macros.ts
content: (args, filePath, options) => {
const [featureId, bcdKey] = args;
if (options?.target === 'static-site') {
return `[BASELINE_STATUS: ${featureId}]`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to include the bcdKey here too?

Comment on lines +295 to +296
const h1Match = markdownBody.match(/^#\s+(.+)$/m);
const title = h1Match ? h1Match[1].trim() : (data.title || formatTitle(id));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use marked here?

const tokens = marked.lexer(markdownBody);
const h1Token = tokens.find((t: any) => t.type === 'heading' && t.depth === 1);
const title = h1Token ? (h1Token as any).text.trim() : (data.title || formatTitle(id));

fs.copyFileSync(paths.cachedTs, OUTPUT_FILE);
} else if (target === 'static-site') {
fs.mkdirSync(outputDir, { recursive: true });
fs.cpSync(paths.cachedGuides, outputDir, { recursive: true });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove existing outputDir if it exists? to prune stale files

if (fs.existsSync(outputDir)) {
  fs.rmSync(outputDir, { recursive: true, force: true });
}

Comment thread lib/guide-validation.ts
* Validates that a non-stub guide has either a frontmatter title or an explicit H1 heading,
* and that any H1 heading is not vague.
*/
export function validateGuideTitle(body: string, relativePath: string, data?: GuideData, options?: { requireTitle?: boolean }): string[] {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is unused except in test

Comment thread serving/lib/macros.ts
const target = options?.target || 'local-dev';

if (target === 'static-site') {
return `[${guideInfo.name}](../${guideInfo.category}/${guideInfo.name}.md)`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the part in [ ] be the formatted name? [${formatTitle(guideInfo.name)}]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants