diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6fa94765..8ca88f3d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -69,6 +69,12 @@ jobs: - name: Install documentation dependencies run: pip install -r requirements-docs.txt + - name: Validate documentation conversion script + run: pip install pytest && pytest docs/scripts/test_convert_docs.py + + - name: Convert Admonitions in Documentation + run: python docs/scripts/convert_docs.py --mode github-to-mkdocs + - name: Build Documentation (PR Check) if: github.event_name == 'pull_request' run: mkdocs build diff --git a/docs/community.md b/docs/community.md index 6f701e67..72ab8d44 100644 --- a/docs/community.md +++ b/docs/community.md @@ -8,8 +8,9 @@ A2UI is an open-source project licensed under Apache 2.0. We welcome contributio ## Community Showcase -!!! info "Coming soon..." - We are considering how best to showcase community projects, examples, themes, renderers, custom components, and more. A 4 minute (or less) demo video and code sample linked in Github discussions is a great way to show off your work. +> ℹ️ **Coming soon...** +> +> We are considering how best to showcase community projects, examples, themes, renderers, custom components, and more. A 4 minute (or less) demo video and code sample linked in Github discussions is a great way to show off your work. ## Project Partners diff --git a/docs/guides/agent-development.md b/docs/guides/agent-development.md index f6a94d66..1215af8b 100644 --- a/docs/guides/agent-development.md +++ b/docs/guides/agent-development.md @@ -94,8 +94,9 @@ Select `my_agent` from the list, and ask questions about restaurants in New York Getting the LLM to generate A2UI messages requires some prompt engineering. -!!! warning "Attention" - This is an area we are still designing. The developer ergonomics of this are not yet finalized. +> ⚠️ **Attention** +> +> This is an area we are still designing. The developer ergonomics of this are not yet finalized. For now, let's copy the `a2ui_schema.py` from the contact lookup example. This is the easiest way to get the A2UI schema and examples for your agent (subject to change). diff --git a/docs/guides/client-setup.md b/docs/guides/client-setup.md index d0ce28ea..03e88f74 100644 --- a/docs/guides/client-setup.md +++ b/docs/guides/client-setup.md @@ -15,9 +15,10 @@ Integrate A2UI into your application using the renderer for your platform. ## Web Components (Lit) -!!! warning "Attention" - The Lit client library is not yet published to NPM. Check back in the - coming days. +> ⚠️ **Attention** +> +> The Lit client library is not yet published to NPM. Check back in the +> coming days. ```bash npm install @a2ui/web-lib lit @lit-labs/signals @@ -35,9 +36,10 @@ TODO: Add verified setup example. ## Angular -!!! warning "Attention" - The Angular client library is not yet published to NPM. Check back in the - coming days. +> ⚠️ **Attention** +> +> The Angular client library is not yet published to NPM. Check back in the +> coming days. ```bash npm install @a2ui/angular @a2ui/web-lib diff --git a/docs/index.md b/docs/index.md index 0a8f186a..1f0122bf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,11 +20,12 @@ A2UI enables AI agents to generate rich, interactive user interfaces that render -!!! warning "️Status: Early Stage Public Preview" - A2UI is currently in **v0.8 (Public Preview)**. The specification and - implementations are functional but are still evolving. We are opening the project to - foster collaboration, gather feedback, and solicit contributions (e.g., on client renderers). - Expect changes. +> ⚠️ **️Status: Early Stage Public Preview** +> +> A2UI is currently in **v0.8 (Public Preview)**. The specification and +> implementations are functional but are still evolving. We are opening the project to +> foster collaboration, gather feedback, and solicit contributions (e.g., on client renderers). +> Expect changes. ## At a Glance diff --git a/docs/introduction/agent-ui-ecosystem.md b/docs/introduction/agent-ui-ecosystem.md index fa0c7fcc..acc75ca9 100644 --- a/docs/introduction/agent-ui-ecosystem.md +++ b/docs/introduction/agent-ui-ecosystem.md @@ -6,8 +6,9 @@ The space for agentic UI is evolving rapidly, with excellent tools emerging to s The A2UI approach is to send JSON as a message to the client, which then uses a renderer to convert it into native UI components. LLMs can generate the component layout on the fly or you can use a template. -!!! tip "" - **This makes it secure like data, and expressive like code.** +> 💡 +> +> **This makes it secure like data, and expressive like code.** This rest of this page will help you understand A2UI in relationship to other options. diff --git a/docs/quickstart.md b/docs/quickstart.md index 9ae3e076..4700fe74 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -18,8 +18,9 @@ Before you begin, make sure you have: - **Node.js** (v18 or later) - [Download here](https://nodejs.org/) - **A Gemini API key** - [Get one free from Google AI Studio](https://aistudio.google.com/apikey) -!!! warning "Security Notice" - This demo runs an A2A agent that uses Gemini to generate A2UI responses. The agent has access to your API key and will make requests to Google's Gemini API. Always review agent code before running it in production environments. +> ⚠️ **Security Notice** +> +> This demo runs an A2A agent that uses Gemini to generate A2UI responses. The agent has access to your API key and will make requests to Google's Gemini API. Always review agent code before running it in production environments. ## Step 1: Clone the Repository @@ -59,8 +60,9 @@ This command will: 4. Launch the development server 5. Open your browser to `http://localhost:5173` -!!! success "Demo Running" - If everything worked, you should see the web app in your browser. The agent is now ready to generate UI! +> ✅ **Demo Running** +> +> If everything worked, you should see the web app in your browser. The agent is now ready to generate UI! ## Step 5: Try It Out @@ -185,8 +187,9 @@ This populates the data model that components can bind to. This tells the client it has enough information to render the UI. -!!! tip "It's Just JSON" - Notice how readable and structured this is? LLMs can generate this easily, and it's safe to transmit and render—no code execution required. +> 💡 **It's Just JSON** +> +> Notice how readable and structured this is? LLMs can generate this easily, and it's safe to transmit and render—no code execution required. ## Exploring Other Demos diff --git a/docs/scripts/convert_docs.py b/docs/scripts/convert_docs.py new file mode 100644 index 00000000..d117521c --- /dev/null +++ b/docs/scripts/convert_docs.py @@ -0,0 +1,144 @@ +import os +import re +import argparse + +# Registry for bidirectional format conversion: +# +# Key: The MkDocs admonition type (the target for '!!! type' syntax). +# Value: +# - emoji: Used for mapping GitHub-style emoji quotes (> ⚠️) to MkDocs. +# - tag: Reserved for mapping official GitHub Alert syntax (> [!WARNING]). +MAPPING = { + "warning": {"emoji": "⚠️", "tag": "WARNING"}, + "tip": {"emoji": "💡", "tag": "TIP"}, + "info": {"emoji": "ℹ️", "tag": "NOTE"}, + "success": {"emoji": "✅", "tag": "SUCCESS"}, + "danger": {"emoji": "🚫", "tag": "CAUTION"}, + "note": {"emoji": "📝", "tag": "NOTE"} +} + +# Reverse lookup: mapping emojis back to their respective MkDocs types +EMOJI_TO_TYPE = {v["emoji"]: k for k, v in MAPPING.items()} + +# Emoji Pattern: Handles optional bold titles and standard emojis +EMOJI_PATTERN = r'>\s*(⚠️|💡|ℹ️|✅|🚫|📝)(?:\s*\*\*(.*?)\*\*)?\s*\n((?:>\s*.*\n?)*)' + +# GitHub Alert Pattern [!TYPE] +GITHUB_ALERT_PATTERN = r'>\s*\[\!(WARNING|TIP|NOTE|IMPORTANT|CAUTION)\]\s*\n((?:>\s*.*\n?)*)' + +# MkDocs Pattern: Captures '!!! type "Title"' blocks +MKDOCS_PATTERN = r'!!!\s+(\w+)\s+"(.*?)"\n((?:\s{4}.*\n?)*)' + + +def clean_body_for_mkdocs(body_text): + """ + Cleans blockquote content for MkDocs: + 1. Removes leading '>' markers. + 2. Strips ALL leading blank lines to close the gap with the title. + 3. Strips ALL trailing blank lines to prevent extra lines at the end. + 4. Preserves internal paragraph breaks. + """ + # Remove leading '>' and trailing whitespace from each line + raw_lines = [re.sub(r'^>\s?', '', line).rstrip() for line in body_text.split('\n')] + + # Find the first line with actual text (to strip leading blank lines) + start_idx = -1 + for i, line in enumerate(raw_lines): + if line.strip(): + start_idx = i + break + + if start_idx == -1: + return "" + + # Slice from the first content line + content_lines = raw_lines[start_idx:] + + # Join lines and rstrip the entire block to remove trailing blank lines + body = "\n".join([f" {l}".rstrip() for l in content_lines]).rstrip() + return body + +def to_mkdocs(content): + """Converts GitHub style to MkDocs style.""" + + def emoji_replacer(match): + emoji_char, title, raw_body = match.groups() + adm_type = EMOJI_TO_TYPE.get(emoji_char, "note") + body = clean_body_for_mkdocs(raw_body) + title_val = title if title else "" + # Return block with exactly one newline at the end + return f'!!! {adm_type} "{title_val}"\n{body}\n' + + + def alert_replacer(match): + alert_type = match.group(1).lower() + type_map = {"important": "info", "caution": "danger"} + mkdocs_type = type_map.get(alert_type, alert_type) + raw_body = match.group(2) + + first_line_match = re.search(r'^>\s*\*\*(.*?)\*\*\s*\n', raw_body) + title = first_line_match.group(1) if first_line_match else "" + if first_line_match: + raw_body = raw_body[first_line_match.end():] + + body = clean_body_for_mkdocs(raw_body) + return f'!!! {mkdocs_type} "{title}"\n{body}\n' + + content = re.sub(EMOJI_PATTERN, emoji_replacer, content, flags=re.MULTILINE) + content = re.sub(GITHUB_ALERT_PATTERN, alert_replacer, content, flags=re.MULTILINE) + return content + +def to_github(content): + """Converts MkDocs style to GitHub style.""" + + def mkdocs_replacer(match): + adm_type, title, body = match.groups() + # Safely retrieve the emoji, default to 'note' (📝) for unknown/unmapped types + emoji = MAPPING.get(adm_type, {"emoji": "📝"})["emoji"] + + # Strip trailing whitespace from captured body to prevent hanging '>' + clean_body = body.rstrip() + raw_lines = clean_body.split('\n') + content_lines = [re.sub(r'^\s{4}', '', line).rstrip() for line in raw_lines] + + # Header line logic + github_lines = [f"> {emoji} **{title}**"] if title.strip() else [f"> {emoji}"] + github_lines.append(">") # Spacer line + + for line in content_lines: + github_lines.append(f"> {line}" if line else ">") + + return "\n".join(github_lines) + "\n" + + return re.sub(MKDOCS_PATTERN, mkdocs_replacer, content) + +def process_file(path, mode): + mode_map = { + "github-to-mkdocs": to_mkdocs, + "mkdocs-to-github": to_github + } + if mode not in mode_map: + raise ValueError(f"Unsupported mode: {mode}. Choose from {list(mode_map.keys())}.") + target_func = mode_map[mode] + + with open(path, 'r', encoding='utf-8') as f: + content = f.read() + new_content = target_func(content) + if new_content != content: + with open(path, 'w', encoding='utf-8') as f: + f.write(new_content) + print(f"[{mode.upper()}] Converted: {path}") + +def run_conversion(mode): + for root, dirs, files in os.walk('docs'): + if any(x in root for x in ['scripts', 'assets', '__pycache__']): + continue + for file in files: + if file.endswith('.md'): + process_file(os.path.join(root, file), mode) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Bidirectional Markdown Admonition Converter") + parser.add_argument("--mode", choices=["github-to-mkdocs", "mkdocs-to-github"], required=True, help="Target format") + args = parser.parse_args() + run_conversion(args.mode) diff --git a/docs/scripts/test_convert_docs.py b/docs/scripts/test_convert_docs.py new file mode 100644 index 00000000..51b97694 --- /dev/null +++ b/docs/scripts/test_convert_docs.py @@ -0,0 +1,124 @@ +import pytest +from convert_docs import to_mkdocs, to_github + +# --- 1. A2UI Specific Header Cases --- +ADMONITION_CASES = [ + ('!!! info "Coming soon..."', "Coming soon..."), + ('!!! warning "Status: Early Stage Public Preview"', "Status: Early Stage Public Preview"), + ('!!! success "Stable Release"', "Stable Release"), + ('!!! note "Version Compatibility"', "Version Compatibility"), + ('!!! warning "Attention"', "Attention"), + ('!!! tip "It\'s Just JSON"', "It's Just JSON"), +] + +@pytest.mark.parametrize("header, expected_title", ADMONITION_CASES) +def test_standard_a2ui_round_trip(header, expected_title): + """Verifies that all standard A2UI headers survive a round-trip conversion.""" + body = " Line 1\n Line 2" + original = f"{header}\n{body}\n" + + # MkDocs -> GitHub + github = to_github(original) + assert f"**{expected_title}**" in github + + # GitHub -> MkDocs + back = to_mkdocs(github) + assert back.strip() == original.strip() + + +# --- 2. Empty Title Edge Case --- +def test_empty_title_case(): + """ + Verifies !!! tip "" converts to '> 💡' exactly. + - No trailing spaces + - No bold markers (****) + """ + original = '!!! tip ""\n Content.\n' + github = to_github(original) + + lines = github.splitlines() + assert lines[0] == "> 💡" # Strictly no space or bold markers + assert lines[1] == ">" # Spacer line + + back = to_mkdocs(github) + assert back == original + + +# --- 3. Spacing & Internal Paragraph Preservation --- +def test_paragraph_spacing_and_trailing_lines(): + """ + Ensures: + 1. GitHub spacer (header vs content) is removed in MkDocs. + 2. Internal blank lines (paragraph breaks) are preserved. + 3. Trailing blockquote markers ('>') are cleaned up. + """ + source_github = ( + "> ✅ **Stable Release**\n" + ">\n" # Spacer line + "> Line 1\n" + ">\n" # Internal break + "> Line 2\n" + ">\n" # Trailing line 1 + ">\n" # Trailing line 2 + ) + + result = to_mkdocs(source_github) + + expected = ( + '!!! success "Stable Release"\n' + ' Line 1\n' + '\n' + ' Line 2\n' + ) + assert result == expected + + +# --- 4. Unmapped/Unknown Type Fallback --- +def test_unknown_type_fallback(): + """ + Verifies that an unknown admonition type defaults to the 'note' emoji (📝). + """ + original = '!!! mystery "Secret"\n Content.\n' + github = to_github(original) + + assert "> 📝 **Secret**" in github + + # Note: Round trip will convert it back to '!!! note' + # because the source type 'mystery' wasn't in the map. + back = to_mkdocs(github) + assert '!!! note "Secret"' in back + + +# --- 5. Multiple Blocks & Isolation --- +def test_multiple_blocks_in_one_file(): + """Ensures multiple blocks are processed without bleeding into each other.""" + original = ( + '!!! success "Block 1"\n' + ' Content 1\n' + '\n' + '!!! info "Block 2"\n' + ' Content 2\n' + ) + github = to_github(original) + assert "> ✅ **Block 1**" in github + assert "> ℹ️ **Block 2**" in github + + back = to_mkdocs(github) + assert back == original + + +# --- 6. False Positive Prevention --- +def test_regular_blockquote_ignored(): + """Ensures regular quotes are not touched.""" + source = "> This is just a quote, not an admonition." + assert to_mkdocs(source) == source + assert to_github(source) == source + + +# --- 7. GitHub Official Alert Syntax Support --- +def test_github_alert_to_mkdocs(): + """Verifies official [!TYPE] syntax conversion.""" + source = "> [!WARNING]\n> **Security Notice**\n> Do not share keys." + expected = '!!! warning "Security Notice"\n Do not share keys.\n' + + assert to_mkdocs(source) == expected diff --git a/docs/specification/v0.8-a2a-extension.md b/docs/specification/v0.8-a2a-extension.md index c5aae221..73ec0526 100644 --- a/docs/specification/v0.8-a2a-extension.md +++ b/docs/specification/v0.8-a2a-extension.md @@ -1,10 +1,12 @@ # A2UI Extension for A2A Protocol (v0.8) -!!! info "Living Document" - This specification is automatically included from `specification/0.8/docs/a2ui_extension_specification.md`. Any updates to the specification will automatically appear here. +> ℹ️ **Living Document** +> +> This specification is automatically included from `specification/0.8/docs/a2ui_extension_specification.md`. Any updates to the specification will automatically appear here. -!!! note "Version Compatibility" - This extension specification applies to A2UI v0.8 and the A2A Protocol. For the base A2UI protocol, see [v0.8 Protocol Specification](v0.8-a2ui.md). +> 📝 **Version Compatibility** +> +> This extension specification applies to A2UI v0.8 and the A2A Protocol. For the base A2UI protocol, see [v0.8 Protocol Specification](v0.8-a2ui.md). **Related Documentation:** - [A2UI Protocol v0.8](v0.8-a2ui.md) (Stable) diff --git a/docs/specification/v0.8-a2ui.md b/docs/specification/v0.8-a2ui.md index 3299dcea..c99c512b 100644 --- a/docs/specification/v0.8-a2ui.md +++ b/docs/specification/v0.8-a2ui.md @@ -1,10 +1,12 @@ # A2UI Protocol v0.8 (Stable) -!!! success "Stable Release" - Version 0.8 is the current stable release, recommended for production use. +> ✅ **Stable Release** +> +> Version 0.8 is the current stable release, recommended for production use. -!!! info "Living Document" - This specification is automatically included from `specification/0.8/docs/a2ui_protocol.md`. Any updates to the specification will automatically appear here. +> ℹ️ **Living Document** +> +> This specification is automatically included from `specification/0.8/docs/a2ui_protocol.md`. Any updates to the specification will automatically appear here. **See also:** - [v0.9 Protocol Specification](v0.9-a2ui.md) (Draft) diff --git a/docs/specification/v0.9-a2ui.md b/docs/specification/v0.9-a2ui.md index d8896797..f187ebf9 100644 --- a/docs/specification/v0.9-a2ui.md +++ b/docs/specification/v0.9-a2ui.md @@ -1,10 +1,12 @@ # A2UI Protocol v0.9 (Draft) -!!! info "Living Document" - This specification is automatically included from `specification/0.9/docs/a2ui_protocol.md`. Any updates to the specification will automatically appear here. +> ℹ️ **Living Document** +> +> This specification is automatically included from `specification/0.9/docs/a2ui_protocol.md`. Any updates to the specification will automatically appear here. -!!! warning "Draft Status" - Version 0.9 is currently in draft status. For production use, consider [v0.8 (Stable)](v0.8-a2ui.md). +> ⚠️ **Draft Status** +> +> Version 0.9 is currently in draft status. For production use, consider [v0.8 (Stable)](v0.8-a2ui.md). **See also:** - [v0.8 Protocol Specification](v0.8-a2ui.md) (Stable) diff --git a/docs/specification/v0.9-evolution-guide.md b/docs/specification/v0.9-evolution-guide.md index 30319f4f..a799b455 100644 --- a/docs/specification/v0.9-evolution-guide.md +++ b/docs/specification/v0.9-evolution-guide.md @@ -1,7 +1,8 @@ # Evolution Guide: v0.8 → v0.9 -!!! info "Living Document" - This guide is automatically included from `specification/0.9/docs/evolution_guide.md`. Any updates will automatically appear here. +> ℹ️ **Living Document** +> +> This guide is automatically included from `specification/0.9/docs/evolution_guide.md`. Any updates will automatically appear here. **Related Documentation:** - [A2UI Protocol v0.8](v0.8-a2ui.md) (Stable - what you're migrating from) diff --git a/mkdocs.yaml b/mkdocs.yaml index 96f22d65..f6ba4667 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -67,6 +67,9 @@ nav: - Component Reference: reference/components.md - Message Reference: reference/messages.md +exclude_docs: | + scripts/** + # Repository repo_name: google/A2UI repo_url: https://github.com/google/A2UI