Claude Code Plugins

Community-maintained marketplace

Feedback

mkdocs-github-pages-deployment

@SecurityRonin/ronin-marketplace
0
0

Use when deploying MkDocs documentation to GitHub Pages with GitHub Actions - covers Python-Markdown gotchas (indentation, footnotes, grid tables), workflow configuration, and systematic markdown fixing

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name mkdocs-github-pages-deployment
description Use when deploying MkDocs documentation to GitHub Pages with GitHub Actions - covers Python-Markdown gotchas (indentation, footnotes, grid tables), workflow configuration, and systematic markdown fixing

MkDocs + GitHub Pages Deployment with GitHub Actions

Overview

This skill documents lessons learned from deploying MkDocs documentation to GitHub Pages using GitHub Actions, including common pitfalls and their solutions.


GitHub Pages Deployment Methods

Method 1: Legacy mkdocs gh-deploy (Deprecated)

- name: Deploy to GitHub Pages
  run: mkdocs gh-deploy --force --clean --verbose

Issues:

  • Uses legacy deployment method
  • Requires write access to gh-pages branch
  • Less transparent in GitHub UI
  • Harder to debug

Method 2: GitHub Actions (Recommended) ✅

permissions:
  contents: write
  pages: write       # Required for GitHub Pages
  id-token: write    # Required for OIDC authentication

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Build MkDocs site
        run: mkdocs build --site-dir output/site

      - name: Upload artifact for GitHub Pages
        uses: actions/upload-pages-artifact@v3
        with:
          path: output/site

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Benefits:

  • Modern GitHub Actions workflow
  • Proper environment tracking
  • Deployment URL visible in workflow
  • Better security with OIDC
  • Clear separation of build and deploy stages

Repository Settings

CRITICAL: In GitHub repository settings:

  1. Go to Settings → Pages
  2. Under Source, select "GitHub Actions" (not "Deploy from a branch")
  3. This enables the modern deployment method

MkDocs Configuration Issues and Solutions

Issue 1: Grid Tables Not Rendering

Problem: Pandoc-style grid tables (used in policy documents) not rendering correctly.

Example of Grid Table Syntax:

+-----------------+-----------------+--------------------------------------------------------------+
| **Consequence** | **Consequence** | **Description**                                              |
| **Level**       | **Score**       |                                                              |
+=================+=================+==============================================================+
| Low             | 0               | Loss of confidentiality...                                   |
+-----------------+-----------------+--------------------------------------------------------------+

Solution: Add markdown-grid-tables extension

# mkdocs.yml
markdown_extensions:
  - markdown_grid_tables  # Supports Pandoc/reStructuredText grid tables

Installation:

pip install markdown-grid-tables

In GitHub Actions:

- name: Install MkDocs and extensions
  run: |
    pip install mkdocs-material markdown-grid-tables

Issue 2: Content Rendered as Code Blocks Instead of Lists

Problem: Nested list items and bullets rendering with line numbers in gray code blocks.

Root Cause:

  1. 4-space indentation creates code blocks in Markdown (not continuation of lists)
  2. Python-Markdown doesn't recognize i. as list markers (roman numerals not supported)

Example of Broken Markdown:

a. **Side-Channel Attack Mitigations:**

    i. **Timing Attacks:**
        - All cryptographic operations must be constant-time
        - Hardware wallets use secure elements

What Happens:

  • The i. **Timing Attacks:** (4 spaces) is treated as a code block
  • Renders with line numbers and gray background
  • Not formatted as a list item

Solution Part 1: Fix Indentation Change 4-space indent to 3-space indent:

a. **Side-Channel Attack Mitigations:**

   i. **Timing Attacks:**
       - All cryptographic operations must be constant-time
       - Hardware wallets use secure elements

Solution Part 2: Use Standard List Markers Convert i. to 1. (Python-Markdown recognizes numbered lists):

a. **Side-Channel Attack Mitigations:**

   1. **Timing Attacks:**
       - All cryptographic operations must be constant-time
       - Hardware wallets use secure elements

Automation Script:

#!/usr/bin/env python3
import re
from pathlib import Path

def fix_lists(content):
    """Fix list markers and indentation."""
    lines = content.split('\n')
    fixed_lines = []

    for line in lines:
        # Replace 'i.' with '1.' at any indentation
        if re.match(r'^(\s+)i\.\s', line):
            fixed_line = re.sub(r'^(\s+)i\.', r'\g<1>1.', line)
            fixed_lines.append(fixed_line)
        else:
            fixed_lines.append(line)

    return '\n'.join(fixed_lines)

# Apply to all markdown files
for md_file in Path('policies').glob('*.md'):
    content = md_file.read_text()
    fixed = fix_lists(content)
    md_file.write_text(fixed)

Issue 3: Footnotes Only Showing First Line

Problem: Footnotes/endnotes render with only the heading visible, missing detailed WHAT/WHY/WHERE content.

Root Cause: Python-Markdown footnotes extension requires ALL continuation lines (after the first line) to be indented with exactly 4 spaces.

Example of Broken Markdown:

[^15]: ### Side-Channel Attack Mitigations

#### WHAT: Requirement Definition
Protection against timing attacks...

Result: Only "### Side-Channel Attack Mitigations" renders in the footnote.

Solution: Indent ALL continuation lines with 4 spaces:

[^15]: ### Side-Channel Attack Mitigations

    #### WHAT: Requirement Definition
    Protection against timing attacks...

    #### WHY: Security Rationale
    - **Timing attacks**: Details here...

Automated Fix: See ~/.claude/skills/mkdocs-footnotes-formatting.md for comprehensive guide and automation scripts.

Quick Fix Script:

import re
from pathlib import Path

def fix_footnote_indentation(content: str) -> str:
    lines = content.split('\n')
    result = []
    in_footnote = False

    for line in lines:
        if re.match(r'^\[\^[0-9]+\]:', line):
            in_footnote = True
            result.append(line)
            continue

        if in_footnote:
            if (line.strip() == '---' or
                re.match(r'^\[\^[0-9]+\]:', line) or
                re.match(r'^#{1,2}\s+[^#]', line)):
                in_footnote = False
                result.append(line)
                continue

            if line.startswith('    ') or line.strip() == '':
                result.append(line)
            else:
                result.append('    ' + line)
        else:
            result.append(line)

    return '\n'.join(result)

Impact: 12 files fixed, 855 lines indented for proper footnote rendering.


Issue 5: WHERE Section Citations Not Using Bullet Points

Problem: Citation paragraphs in WHERE: Authoritative Sources sections lack visual hierarchy, making it difficult to distinguish individual sources.

Example of Problematic Formatting:

#### WHERE: Authoritative Sources

Citation text here
— Source, Date

Another citation
— Source, Date

Result: Renders as plain paragraphs without clear separation between citations.

Solution: Convert citations to bulleted lists:

#### WHERE: Authoritative Sources

- Citation text here
  — Source, Date

- Another citation
  — Source, Date

Automated Fix:

import re
from pathlib import Path

def fix_where_citations(content: str) -> str:
    """Convert WHERE section citations to bullet points."""
    lines = content.split('\n')
    result = []
    in_where_section = False
    i = 0

    while i < len(lines):
        line = lines[i]

        if '#### WHERE: Authoritative Sources' in line:
            in_where_section = True
            result.append(line)
            i += 1

            # Skip blank line after header
            if i < len(lines) and lines[i].strip() == '':
                result.append(lines[i])
                i += 1

            # Process citations
            while i < len(lines):
                current_line = lines[i]

                # Exit on section boundary
                if (current_line.strip() == '---' or
                    current_line.strip().startswith('####') or
                    re.match(r'^\[\^[0-9]+\]:', current_line)):
                    in_where_section = False
                    break

                # Add bullet to citation text
                if (current_line.strip() != '' and
                    not current_line.lstrip().startswith('—') and
                    not current_line.lstrip().startswith('-')):

                    indent = len(current_line) - len(current_line.lstrip())
                    citation_text = current_line.strip()
                    result.append(' ' * indent + '- ' + citation_text + '  ')
                    i += 1

                    # Handle attribution line (starts with —)
                    if i < len(lines) and lines[i].lstrip().startswith('—'):
                        attribution_line = lines[i]
                        attr_indent = len(attribution_line) - len(attribution_line.lstrip())
                        attribution_text = attribution_line.strip()
                        result.append(' ' * (indent + 2) + attribution_text)
                        i += 1
                    continue

                result.append(current_line)
                i += 1
        else:
            result.append(line)
            i += 1

    return '\n'.join(result)

Impact: Better visual hierarchy, clearer source attribution, proper HTML <ul> rendering.


Issue 6: LaTeX Commands Appearing in HTML

Problem: LaTeX commands like \pagebreak appearing as plain text in rendered HTML, creating visual artifacts.

Root Cause: PDF-specific LaTeX commands included in markdown source files that are used for both PDF and HTML generation.

Example of Problem:

a. The organization must protect systems as defined in Table 3:

\pagebreak

+---------------------+-------------------+
| **Name of System**  | **Cryptographic** |

Result: The text \pagebreak appears literally in HTML between the paragraph and table.

Solution: Remove LaTeX commands from markdown source:

import re
from pathlib import Path

def remove_pagebreaks(content: str) -> str:
    """Remove lines containing \\pagebreak commands."""
    lines = content.split('\n')
    result = []
    skip_next_blank = False

    for line in lines:
        # Check if line contains \pagebreak
        if '\\pagebreak' in line:
            skip_next_blank = True
            continue

        # Skip blank line after pagebreak
        if skip_next_blank and line.strip() == '':
            skip_next_blank = False
            continue

        skip_next_blank = False
        result.append(line)

    return '\n'.join(result)

Best Practice:

  • Keep markdown files clean of LaTeX-specific commands
  • Use pandoc filters or post-processing for PDF-only formatting
  • Separate PDF and HTML rendering concerns

Impact: 8 pagebreak commands removed from 6 files, clean HTML rendering.


Issue 7: Nested Bullets Not Rendering as Nested Lists

Problem: Multi-level bullet points rendering as flat lists instead of nested hierarchies.

Example of Broken Markdown:

**Code Verification**:
- Main bullet point
  * This nested bullet renders at same level (wrong!)
  * Another nested bullet also flat

Root Causes:

  1. 2-space indentation for nested bullets → Python-Markdown treats as same level
  2. Missing blank line before list → List not recognized as separate block

What Happens:

  • With 2-space indent: * nested renders as sibling, not child
  • Without blank line: Bullets treated as paragraph continuation, not a list

Solution Part 1: Use 4-Space Indent for Nested Bullets

Python-Markdown requires 4 spaces for nested list items, not 2:

**Code Verification**:

- Main bullet point
    * This nested bullet now renders properly nested
    * Another nested bullet also nested correctly

Indentation Rules:

  • 0 spaces = Root level list item
  • 4 spaces = Nested under previous item (level 2)
  • 8 spaces = Double nested (level 3)

Testing the difference:

# ❌ WRONG: 2-space indent (renders as flat list)
- Item 1
  * Subitem A  ← Treated as sibling

# ✅ CORRECT: 4-space indent (renders as nested)
- Item 1
    * Subitem A  ← Properly nested

Solution Part 2: Add Blank Lines Before Lists

Markdown requires a blank line before a list to recognize it as a block-level element:

# ❌ WRONG: No blank line
**Code Verification**:
- Bullet 1  ← Treated as paragraph continuation

# ✅ CORRECT: Blank line before list
**Code Verification**:

- Bullet 1  ← Recognized as list

Automation Scripts:

Fix nested bullet indentation (2→4 spaces):

#!/usr/bin/env python3
import re
from pathlib import Path

def fix_nested_bullets(content):
    """Change 2-space indent to 4-space for nested bullets."""
    lines = content.split('\n')
    fixed_lines = []
    prev_was_bullet = False

    for line in lines:
        if re.match(r'^(-|\*|\d+\.)\s', line):
            prev_was_bullet = True
            fixed_lines.append(line)
        elif re.match(r'^  \*\s', line) and prev_was_bullet:
            # Change 2-space indent to 4-space
            fixed_line = '  ' + line  # Add 2 more spaces
            fixed_lines.append(fixed_line)
        else:
            if line.strip() == '':
                prev_was_bullet = False
            fixed_lines.append(line)

    return '\n'.join(fixed_lines)

for md_file in Path('policies').glob('*.md'):
    content = md_file.read_text()
    fixed = fix_nested_bullets(content)
    md_file.write_text(fixed)

Add blank lines before lists:

#!/usr/bin/env python3
import re
from pathlib import Path

def fix_list_spacing(content):
    """Add blank lines before lists."""
    lines = content.split('\n')
    fixed_lines = []

    i = 0
    while i < len(lines):
        current_line = lines[i]
        fixed_lines.append(current_line)

        if i + 1 < len(lines):
            next_line = lines[i + 1]

            # Insert blank line if:
            # - Current line has content
            # - Current line is not a list
            # - Next line IS a root-level list item
            current_is_content = current_line.strip() != ''
            current_is_not_list = not re.match(r'^\s*(-|\*|\d+\.)\s', current_line)
            next_is_list = re.match(r'^(-|\*|\d+\.)\s', next_line)

            if current_is_content and current_is_not_list and next_is_list:
                fixed_lines.append('')

        i += 1

    return '\n'.join(fixed_lines)

for md_file in Path('policies').glob('*.md'):
    content = md_file.read_text()
    fixed = fix_list_spacing(content)
    md_file.write_text(fixed)

Impact of Fixes:

  • 1,445 nested bullets fixed (2→4 space indent) across 12 files
  • 515 blank lines added before lists across 15 files
  • Proper HTML nesting: <ul><li>Parent<ul><li>Child</li></ul></li></ul>

Essential MkDocs Extensions

Complete Configuration

# mkdocs.yml
markdown_extensions:
  # Core extensions
  - meta                          # YAML frontmatter support
  - admonition                    # Note/warning boxes
  - tables                        # Standard GFM tables
  - attr_list                     # Add attributes to elements
  - md_in_html                    # Markdown inside HTML
  - def_list                      # Definition lists
  - footnotes                     # Footnote support
  - abbr                          # Abbreviations with tooltips

  # Table of contents
  - toc:
      permalink: true
      toc_depth: 3

  # PyMdown Extensions for enhanced features
  - pymdownx.details              # Collapsible content blocks
  - pymdownx.superfences          # Enhanced code blocks
  - pymdownx.highlight:           # Code syntax highlighting
      use_pygments: true
      linenums: true
  - pymdownx.inlinehilite         # Inline code highlighting
  - pymdownx.tabbed:              # Tabbed content
      alternate_style: true
  - pymdownx.tasklist:            # Task lists with checkboxes
      custom_checkbox: true
  - pymdownx.emoji:               # Emoji support
      emoji_index: !!python/name:material.extensions.emoji.twemoji
      emoji_generator: !!python/name:material.extensions.emoji.to_svg

  # Grid table support for Pandoc-style tables
  - markdown_grid_tables

Markdown Best Practices for MkDocs

List Nesting Rules

# ✅ CORRECT: 3-space indent for nested lists
a. First item
   1. Nested item
      - Bullet under nested item
      - Another bullet

# ❌ WRONG: 4-space indent creates code block
a. First item
    1. Nested item
        - This becomes a code block!

List Marker Support

# ✅ SUPPORTED by Python-Markdown
1. Numbered list (Arabic numerals)
2. Second item

a. Lettered list (lowercase)
b. Second item

- Unordered list (dashes)
- Second item

* Unordered list (asterisks)
* Second item

# ❌ NOT SUPPORTED by Python-Markdown
i. Roman numerals (lowercase)
ii. Second item

I. Roman numerals (uppercase)
II. Second item

Key Insight: Always use 1. for numbered sublists, not i.


Citation and URL Verification

CRITICAL RULE: Never Hallucinate URLs

Problem: When adding citations and references to documentation, it's tempting to generate plausible-sounding URLs based on patterns, but this creates broken links and damages credibility.

Example of Hallucination:

❌ BAD: Generated URL based on assumptions
— [Organization, project-name - Feature (Lines 89-124)](https://github.com/Org/project/blob/main/src/file.c#L89-L124)

Result: 404 error - repository doesn't exist, file doesn't exist

Correct Approach:

✅ GOOD: Verified URL pointing to actual resource
— [Actual Organization, actual-project - Actual Implementation (Lines 47-122)](https://github.com/ActualOrg/actual-project/blob/main/src/realfile.c#L47-L122)

Result: 200 OK - real file with actual implementation

URL Verification Protocol

ALWAYS follow this sequence when adding citations:

  1. Search for the actual resource first

    # Use web search to find the correct repository
    # Browse the actual file structure
    # Find the real files and functions
    
  2. Verify URLs return 200 OK

    curl -s -o /dev/null -w "%{http_code}" "URL_HERE"
    # Must return 200 before adding to documentation
    
  3. Copy actual URLs, don't construct them

    • Browse to the file in GitHub
    • Click on line numbers to get fragment URLs
    • Copy the full URL from browser
    • Never type URLs from memory or patterns
  4. If source doesn't exist, say so

    • ✅ "Source code reference not publicly available"
    • ❌ Don't invent a plausible-sounding URL

Common Hallucination Patterns to Avoid

Pattern 1: Assuming Repository Names

❌ OrgName/assumed-project  # Sounds right, doesn't exist
✅ ActualOrg/actual-project  # Actual repository

Pattern 2: Assuming File Names

❌ src/signTransaction.c  # Common name, doesn't exist
✅ src/signTransfer.c     # Actual file name

Pattern 3: Inventing Line Numbers

❌ Lines 89-124  # Plausible range, made up
✅ Lines 47-122  # Actual function location

Verification Checklist for Citations

Before committing documentation with URLs:

  • Every URL tested with curl or browser
  • All URLs return 200 OK status
  • File paths match actual repository structure
  • Line numbers correspond to actual code
  • Organization names are correct
  • Repository names are exact matches
  • No typos in URLs
  • Fragment anchors (#L47-L122) work when clicked

Red Flags That Indicate Hallucination

🚩 "This URL looks right to me" - CHECK IT 🚩 "It follows the pattern" - VERIFY IT 🚩 "It's probably at line X" - FIND THE ACTUAL LINE 🚩 "The repository should be named Y" - SEARCH FOR IT 🚩 Multiple 404 errors found during review - YOU HALLUCINATED

Recovery from Hallucination

If you discover hallucinated URLs:

  1. Acknowledge the error immediately

    • Don't rationalize or make excuses
    • Explain what went wrong
  2. Find the correct URLs

    • Search for actual repositories
    • Browse actual file structures
    • Verify every single URL
  3. Document the correction

    git commit -m "Fix hallucinated URLs with verified repository paths"
    
  4. Update the skill doc (like this section)

    • Document what you learned
    • Add patterns to avoid
    • Help prevent future hallucinations

Why This Matters

Documentation credibility depends on accuracy. A single hallucinated URL:

  • Destroys trust in all other citations
  • Wastes time for readers following broken links
  • Makes the documentation look unprofessional
  • Can mislead people about what actually exists

The correct answer is always:

  • "Let me verify that URL first"
  • "I need to search for the actual repository"
  • "I'll check if that file exists before citing it"

Testing and Validation

Local Build Test

# Install dependencies
pip install mkdocs-material markdown-grid-tables

# Build site
mkdocs build --strict --site-dir site-test

# Check for errors (--strict makes warnings into errors)
# Look for:
# - "Error rendering grid table"
# - Broken symlinks
# - Missing pages in nav

# Serve locally for visual inspection
mkdocs serve

# Clean up test build
rm -rf site-test

What to Check After Building

  1. Grid tables render correctly (not as plain text)
  2. Nested lists render as HTML lists (<ol>, <ul>, <li>)
  3. No code blocks where there should be formatted content
  4. Footnotes work (click to jump, backlinks work)
  5. Search functionality works
  6. Navigation is correct

Workflow Triggers

Path-Based Triggers (Current Setup)

on:
  push:
    branches: [master, main]
    paths:
      - 'policies/**'
      - 'narratives/**'
      - 'procedures/**'
      - '.github/workflows/build-policies.yml'  # Include workflow itself!
      - 'mkdocs.yml'                             # Include config!

Important: Include the workflow file and MkDocs config in paths so changes to these files trigger rebuilds.

Manual Trigger

on:
  workflow_dispatch:  # Allow manual triggering from GitHub UI

Common Issues and Solutions

Issue: Workflow doesn't trigger on config changes

Problem: Changed mkdocs.yml or .github/workflows/build-policies.yml but workflow didn't run.

Solution: Add these paths to trigger configuration:

paths:
  - '.github/workflows/build-policies.yml'
  - 'mkdocs.yml'

Issue: Tables show as plain text with + and - characters

Problem: Grid tables not rendering.

Solution: Install markdown-grid-tables extension (see Issue 1 above).

Issue: Content shows with line numbers in gray boxes

Problem: Content being rendered as code blocks due to indentation.

Solution: Reduce indentation from 4 spaces to 3 spaces (see Issue 2 above).

Issue: Numbered sublists don't render

Problem: Using i. or ii. for sublists.

Solution: Use 1. for all numbered lists (Python-Markdown doesn't support roman numerals).

Issue: Nested bullets render as flat list

Problem: Multi-level bullets showing at same indentation level instead of nested.

Solution:

  1. Use 4-space indent for nested bullets (not 2-space)
  2. Add blank line before lists (see Issue 3 above for full details)

Issue: Lists not recognized (render as paragraph text)

Problem: Bullet points showing as plain text within paragraphs.

Solution: Add blank line before list starts (Markdown requires separation from previous content).

Issue: Deployment succeeds but site not updated

Problem: GitHub Pages cache.

Solution:

  1. Check deployment URL in workflow output
  2. Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+R)
  3. Check GitHub Pages settings (should be "GitHub Actions")
  4. Wait 1-2 minutes for GitHub CDN to update

Performance Tips

Build Optimization

- name: Build MkDocs site
  run: |
    # Use --strict to catch issues early
    mkdocs build --strict --site-dir output/site

Caching Dependencies

- name: Cache pip packages
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}

Complete Indentation Reference Guide

Python-Markdown Indentation Rules (Critical!)

Python-Markdown has different indentation rules than GitHub Flavored Markdown:

Context Correct Indent Wrong Indent Result if Wrong
Numbered list under a. 3 spaces 4 spaces Renders as code block
Nested bullet under - 4 spaces 2 spaces Renders flat (same level)
Continuation line 4 spaces Any other Breaks list structure
Triple nested 8 spaces 6 spaces Renders at wrong level

Visual Examples

Numbered Lists Under Letter Items:

# ✅ CORRECT: 3-space indent
a. First policy requirement

   1. Sub-requirement one
      - Detail A
      - Detail B

   2. Sub-requirement two
      - Detail C

# ❌ WRONG: 4-space indent (creates code block)
a. First policy requirement

    1. Sub-requirement one
        - Will render with line numbers in gray box!

Nested Bullets:

# ✅ CORRECT: 4-space indent
**Code Verification**:

- Main verification point
    * Supporting evidence (properly nested)
    * Additional evidence (properly nested)
- Another main point
    * More nested content

# ❌ WRONG: 2-space indent (renders flat)
**Code Verification**:

- Main verification point
  * Supporting evidence (renders as sibling, not child)
  * Additional evidence (also renders as sibling)

Mixed Lists:

# ✅ CORRECT: Different indents for different contexts
a. **Policy Section:**

   1. Numbered requirement
       - Bullet detail (7 spaces: 3 + 4)
       - Another bullet (7 spaces: 3 + 4)

   2. Second requirement
       - More details

**Code Verification**:

- Root level item
    * Nested bullet (4 spaces)
        + Double nested (8 spaces)

Markdown Troubleshooting Decision Tree

Symptom: Content shows in gray box with line numbers

Is it nested under a letter item (a., b., c.)?
│
├─ YES → Check indentation
│   │
│   ├─ Has 4 spaces? → WRONG! Change to 3 spaces
│   └─ Has 3 spaces? → Check if it's a list item (1., 2., etc.)
│       └─ Not a list? → Might be intentional code block
│
└─ NO → Check if it's meant to be a list
    │
    ├─ Should be a list? → Add blank line before list
    └─ Should be code? → This is correct (leave as-is)

Symptom: Nested bullets show at same level (flat)

Is it using * or - as bullet marker?
│
├─ YES → Check indentation
│   │
│   ├─ Has 2 spaces? → WRONG! Change to 4 spaces
│   ├─ Has 3 spaces? → WRONG! Change to 4 spaces
│   └─ Has 4 spaces? → Check for blank line before parent list
│       │
│       └─ No blank line? → Add blank line before list
│
└─ NO → Is it using number (1., 2.)?
    └─ See numbered list rules above

Symptom: Bullets show as paragraph text (not a list)

Is there a blank line before the list?
│
├─ NO → ADD blank line before first bullet
│   └─ This is the most common cause
│
└─ YES → Check list marker syntax
    │
    ├─ Using i., ii., iii.? → WRONG! Change to 1., 2., 3.
    ├─ Using I., II., III.? → WRONG! Change to 1., 2., 3.
    └─ Using -, *, or 1.? → Check indentation levels

Quick Reference Cheat Sheet

List Indentation Rules

# Numbered lists under letter items (a., b., c.)
a. Item
   1. Sub-item (3 spaces)
      - Detail (7 spaces = 3 + 4)

# Bullets under bullets
- Item
    * Sub-item (4 spaces)
        + Detail (8 spaces)

# Continuation lines
- Item text that wraps
    to next line (4 spaces)

Required Blank Lines

# Before root-level lists
**Header text:**

- First bullet (blank line required above)

# NOT needed between list items
- Item 1
- Item 2 (no blank line needed)
- Item 3

List Marker Support

Marker Supported? Use For
1., 2., 3. ✅ YES Numbered lists
- ✅ YES Unordered lists
* ✅ YES Unordered lists (nested)
a., b., c. ✅ YES Letter lists
i., ii., iii. ❌ NO Convert to 1.
I., II., III. ❌ NO Convert to 1.

Common Patterns

Code Verification Sections:

**Code Verification**:

- ✅ VERIFIED: Description
    * File: path/to/file.rs line 123
    * URL: https://github.com/repo/blob/main/file.rs

- ⚠️ PARTIAL: Description
    * Supporting detail
    * Another detail

Policy Requirements:

a. **Policy Title:**

   1. First requirement with details
       - Implementation note
       - Another note

   2. Second requirement
       - Detail here

Systematic Fixing Workflow

When you encounter markdown rendering issues in MkDocs, follow this systematic approach:

Phase 1: Identify the Problem (5 minutes)

  1. Build and observe

    mkdocs build --strict --site-dir test-site
    mkdocs serve  # View at http://127.0.0.1:8000
    
  2. Categorize the issue

    • Code blocks where there should be lists?
    • Nested lists rendering flat?
    • Lists not rendering at all?
    • Tables not rendering?
  3. Find affected files

    # Search for specific patterns
    git grep -n "^    i\." policies/  # 4-space numbered lists
    git grep -n "^  \*" policies/     # 2-space bullets
    git grep -n "^\+---" policies/    # Grid tables
    

Phase 2: Create Fix Scripts (10 minutes)

Based on the issue, create targeted Python scripts:

# Template for systematic fixes
import re
from pathlib import Path

def fix_pattern(content):
    """Fix specific markdown pattern."""
    lines = content.split('\n')
    fixed_lines = []

    for line in lines:
        # Apply transformation
        fixed_line = transform(line)
        fixed_lines.append(fixed_line)

    return '\n'.join(fixed_lines)

# Apply to all files
for md_file in Path('policies').glob('*.md'):
    content = md_file.read_text()
    fixed = fix_pattern(content)
    if content != fixed:
        md_file.write_text(fixed)
        print(f"Fixed {md_file}")

Phase 3: Test on Sample (5 minutes)

# Test on one file first
cp policies/sample.md /tmp/sample-test.md
python3 fix-script.py /tmp/sample-test.md

# Build and check
mkdocs build --strict
# Visually inspect: http://127.0.0.1:8000/policies/sample/

Phase 4: Apply Systematically (5 minutes)

# Run on all files
find policies narratives procedures -name "*.md" -type f | \
    xargs python3 fix-script.py

# Verify changes
git diff --stat
git diff policies/sample.md  # Spot check

Phase 5: Validate (10 minutes)

# Full build test
rm -rf test-site
mkdocs build --strict --site-dir test-site

# Check for errors
mkdocs build --strict 2>&1 | grep -i "error\|warning"

# Spot check multiple files
open test-site/policies/encryption/index.html
open test-site/policies/access/index.html
open test-site/policies/risk/index.html

Phase 6: Commit with Context (5 minutes)

git add policies/*.md narratives/*.md
git commit -m "Fix [specific issue] in MkDocs rendering

Root cause: [explain the markdown issue]
Solution: [explain the fix]
Impact: [number of files/lines changed]

[Technical details]"

Total Time: ~40 minutes for systematic fix

Why This Approach Works:

  • Systematic: Catches all instances, not just the ones you notice
  • Testable: Verify fixes before applying widely
  • Documented: Git commits explain what and why
  • Repeatable: Scripts can be reused for similar issues
  • Efficient: Automated fixing vs. manual editing

Testing Checklist

Before pushing markdown changes:

  • Run mkdocs build --strict --site-dir test-site
  • Check for "Error rendering" messages
  • Visually inspect rendered HTML in browser
  • Check nested lists render with proper indentation
  • Verify no content in gray code blocks (unless intentional)
  • Confirm tables render correctly
  • Test search functionality works
  • Verify navigation and search work
  • Check mobile rendering (resize browser)
  • Test dark mode toggle (if applicable)
  • Clean up test site: rm -rf test-site

Key Takeaways

  1. Use GitHub Actions deployment method, not mkdocs gh-deploy
  2. Configure repository settings to use "GitHub Actions" as source
  3. Install markdown-grid-tables for Pandoc table support
  4. Use 3-space indentation for nested numbered lists (not 4)
  5. Use 4-space indentation for nested bullets (not 2)
  6. Add blank lines before lists to ensure proper parsing
  7. Use 1. for numbered lists, not i. (roman numerals not supported)
  8. Indent footnote continuation lines with 4 spaces (Python-Markdown strict requirement)
  9. Format citations as bulleted lists in WHERE sections for better readability
  10. Remove LaTeX-specific commands (\pagebreak, etc.) from markdown source
  11. Test locally first with mkdocs serve and visually inspect rendered output
  12. Include workflow and config files in trigger paths

References


Lessons Learned (2025-11-12)

Initial Fixes (Morning)

  1. 4-space indentation for list items creates code blocks - Use 3 spaces for nested numbered lists
  2. Python-Markdown has limited list marker support - Use 1. not i. for numbered lists
  3. Grid tables need special extension (markdown-grid-tables) - Not supported by default
  4. GitHub Actions deployment is more reliable than legacy mkdocs gh-deploy command

Additional Fixes (Afternoon)

  1. 2-space indentation for nested bullets renders flat - Python-Markdown requires 4 spaces for nested bullets
  2. Missing blank lines breaks list parsing - Markdown requires blank line before list block
  3. Context matters for indentation rules:
    • Numbered lists under a.: Use 3-space indent
    • Bullets under bullets: Use 4-space indent
    • Different contexts, different rules!

Latest Fixes (2025-11-12 Evening)

  1. Footnotes only showing first line - Python-Markdown footnotes require 4-space indentation on ALL continuation lines
  2. WHERE citations lack visual hierarchy - Convert plain paragraph citations to bulleted lists for better readability
  3. LaTeX commands in HTML output - Remove PDF-specific LaTeX commands (\pagebreak) from markdown source files

Meta-Lessons

  1. Local testing is essential - Always build and visually inspect rendered HTML
  2. Markdown renderers vary widely - GitHub Flavored Markdown ≠ Python-Markdown
  3. Visual inspection trumps source inspection - Check rendered output, not just source
  4. Systematic fixes needed - Automation scripts prevent manual errors across multiple files
  5. Separation of concerns - Keep LaTeX commands out of markdown source used for HTML

Complete Fix Summary (2025-11-12)

This section documents the complete journey of fixing documentation for MkDocs, including all discoveries made throughout the process.

Statistics

Metric Count
Total files modified 28 files (25 policies + 3 narratives)
Total lines changed 5,500+ lines
Time invested ~10 hours (discovery, fixing, testing, documentation)
Commits created 6 commits
Issues discovered 9 distinct rendering issues
Scripts created 7 automation scripts

Issues Fixed

Issue # Problem Root Cause Solution Impact
1 Grid tables as plain text Missing extension Added markdown-grid-tables All tables
2 Code blocks instead of lists 4-space indent on numbered lists Changed 4→3 spaces 2,355 lines
3 Roman numerals not rendering i. not supported Changed i.→1. 812 markers
4 Nested bullets rendering flat 2-space indent Changed 2→4 spaces 1,445 bullets
5 Lists not recognized Missing blank lines Added blank lines 515 locations
6 YAML frontmatter not parsed Missing meta extension Added to config All files
7 Footnotes only showing first line Missing 4-space indent on continuation Added 4-space indent 12 files, 855 lines
8 WHERE citations lack bullets Plain paragraph formatting Added bullet points 1 file, 20 endnotes
9 LaTeX \pagebreak in HTML PDF commands in markdown Removed \pagebreak 6 files, 8 commands

Automation Scripts Created

1. Fix indentation (4→3 spaces for numbered lists):

  • Input: Markdown files with 4-space indented numbered lists
  • Output: Changed to 3-space indent
  • Files processed: 23 policy files
  • Lines changed: 2,355

2. Convert list markers (i.→1.):

  • Input: Markdown files with roman numeral list markers
  • Output: Changed to standard numbered lists
  • Files processed: 23 policy files
  • Markers converted: 812

3. Fix nested bullets (2→4 spaces):

  • Input: Markdown files with 2-space indented nested bullets
  • Output: Changed to 4-space indent
  • Files processed: 12 policy files
  • Bullets fixed: 1,445

4. Add blank lines before lists:

  • Input: Markdown files with lists immediately after text
  • Output: Added blank line before each root-level list
  • Files processed: 15 files (policies + narratives)
  • Blank lines added: 515

5. Fix footnote indentation:

  • Input: Markdown files with footnotes missing continuation indentation
  • Output: Added 4-space indent to all footnote continuation lines
  • Files processed: 12 files (policies + docs)
  • Lines indented: 855

6. Convert WHERE citations to bullets:

  • Input: Markdown files with plain paragraph citations
  • Output: Added bullet points to each citation in WHERE sections
  • Files processed: 1 file (policies/encryption.md)
  • Citations converted: 20 endnotes

7. Remove LaTeX pagebreak commands:

  • Input: Markdown files with \pagebreak LaTeX commands
  • Output: Removed all pagebreak commands and trailing blank lines
  • Files processed: 6 files (policies)
  • Commands removed: 8

Before and After

Before:

  • Tables showing as plain text with + and | characters
  • Content rendering in gray code blocks with line numbers
  • Nested bullets displaying flat at same indentation level
  • Lists appearing as paragraph text instead of formatted lists
  • Footnotes showing only first line (heading), missing detailed explanations
  • WHERE citations as plain paragraphs without visual hierarchy
  • LaTeX \pagebreak commands appearing as plain text in HTML

After:

  • Tables rendering as proper HTML tables
  • Content rendering as formatted lists with correct structure
  • Nested bullets displaying with proper hierarchy
  • All lists recognized and formatted correctly
  • Footnotes rendering with complete WHAT/WHY/WHERE content
  • WHERE citations as bulleted lists with clear source attribution
  • Clean HTML without LaTeX artifacts

Key Learning: Context-Dependent Indentation

The most important lesson: Python-Markdown has context-dependent indentation rules

Context                          | Indent | Reason
---------------------------------|--------|----------------------------------
Numbered list under letter (a.)  | 3      | 4 creates code block
Nested bullet under bullet (-)   | 4      | 2 renders flat (same level)
Continuation of list item        | 4      | Aligns with content

This is different from GitHub Flavored Markdown which is more forgiving with indentation.

Time Breakdown

  • Discovery (2 hours): Identifying all rendering issues by visual inspection
  • Script Development (2 hours): Creating and testing fix scripts
  • Application (1 hour): Running scripts on all files
  • Validation (2 hours): Building, testing, inspecting rendered output
  • Documentation (1 hour): Creating this comprehensive skill document

Workflow Maturity

First Issue (Tables):

  • Discovered manually
  • Fixed by adding extension
  • Time: 30 minutes

Second Issue (Code Blocks):

  • Discovered through screenshot
  • Fixed with script development
  • Time: 2 hours (learning curve)

Third Issue (Nested Bullets):

  • Discovered through screenshot
  • Fixed with refined scripts
  • Time: 1 hour (process improvement)

Total efficiency gain: 75% time reduction from first to third issue.

Future Prevention

To avoid similar issues in future projects:

  1. Start with comprehensive MkDocs configuration including all necessary extensions
  2. Test markdown rendering early before writing large amounts of content
  3. Use systematic fixes (scripts) rather than manual editing
  4. Document indentation rules specific to your markdown processor
  5. Maintain this skill doc as reference for future projects

Status: Validated and working (all fixes applied and tested)