Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .rdoc_options
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
op_dir: _site # for GitHub Pages and should match the config of RDoc task in Rakefile
title: rdoc Documentation
title: RDoc
main_page: README.md
autolink_excluded_words:
- RDoc
Expand Down
52 changes: 52 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,26 @@ Use Red, Green, Refactor approach:
3. Check output in `_site/` directory
4. Check coverage: `bundle exec rake coverage`

### Modifying Themes/Styling

When making changes to theme CSS or templates (e.g., Darkfish or Aliki themes):

1. **Generate documentation**: Run `bundle exec rake rerdoc` to create baseline
2. **Start HTTP server**: Run `cd _site && python3 -m http.server 8000` (use different port if 8000 is in use)
3. **Investigate with sub-agent**: Use Task tool to launch a general-purpose agent to inspect the documentation with Browser MCP
- The agent will connect browser to `http://localhost:8000`, navigate pages, and take screenshots
- Agent reports findings back (styling issues, layout problems, etc.)
- This saves context in main conversation
4. **Make changes**: Edit files in `lib/rdoc/generator/template/<theme>/` as needed
5. **Regenerate**: Run `bundle exec rake rerdoc` to rebuild documentation with changes
6. **Verify with sub-agent**: Use Task tool again to launch agent that uses Browser MCP to verify fixes
- Agent takes screenshots and compares to original issues
- Agent reports back whether issues are resolved
7. **Lint templates** (if modified): Run `npx @herb-tools/linter "lib/rdoc/generator/template/**/*.rhtml"`
8. **Stop server**: Kill the HTTP server process when done

**Tip:** Keep HTTP server running during iteration. Just regenerate with `bundle exec rake rerdoc` between changes.

## Notes for AI Agents

1. **Always run tests** after making changes: `bundle exec rake`
Expand All @@ -221,3 +241,35 @@ Use Red, Green, Refactor approach:
4. **Use `rake rerdoc`** to regenerate documentation (not just `rdoc`)
5. **Verify generated files** with `rake verify_generated`
6. **Don't edit generated files** directly (in `lib/rdoc/markdown/` and `lib/rdoc/rd/`)

## Browser MCP for Testing Generated Documentation

Browser MCP allows AI agents to visually inspect and interact with the generated HTML documentation. This is useful for verifying CSS styling, layout issues, and overall appearance.

**Repository:** <https://github.com/BrowserMCP/mcp>

### Setup

If Browser MCP is not already installed, users should:

1. Install the BrowserMCP Chrome extension from the Chrome Web Store
2. Run: `claude mcp add --scope user browsermcp npx @browsermcp/mcp@latest`
3. Connect a browser tab by clicking the BrowserMCP extension icon and selecting "Connect"

### Testing Generated Documentation

To test the generated documentation with Browser MCP:

```bash
# Generate documentation
bundle exec rake rerdoc

# Start a simple HTTP server in the _site directory (use an available port)
cd _site && python3 -m http.server 8000
```

If port 8000 is already in use, try another port (e.g., `python3 -m http.server 9000`).

Then navigate to the appropriate URL (e.g., `http://localhost:8000`) in your connected browser tab and ask Claude to use browser MCP tools (e.g., "use browser MCP to navigate to <http://localhost:8000> and take a screenshot").

**Note:** Browser MCP requires a proper HTTP server (not `file://` URLs) for full functionality. The generated documentation must be served via HTTP/HTTPS.
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ task rdoc: :generate
RDoc::Task.new do |doc|
# RDoc task defaults to /html and overrides the op_dir option in .rdoc_options
doc.rdoc_dir = "_site" # for GitHub Pages
doc.template = "aliki"
end

task "coverage" do
Expand Down
1 change: 1 addition & 0 deletions lib/rdoc/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ module RDoc::Generator

autoload :Markup, "#{__dir__}/generator/markup"

autoload :Aliki, "#{__dir__}/generator/aliki"
autoload :Darkfish, "#{__dir__}/generator/darkfish"
autoload :JsonIndex, "#{__dir__}/generator/json_index"
autoload :RI, "#{__dir__}/generator/ri"
Expand Down
78 changes: 78 additions & 0 deletions lib/rdoc/generator/aliki.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

##
# Aliki RDoc HTML Generator - A modern documentation theme
#
# Based on Darkfish by Michael Granger ([email protected])
#
# == Description
#
# Aliki brings modern design patterns to RDoc documentation with:
#
# * Three-column responsive layout (navigation, content, table of contents)
# * Dark mode support with theme toggle and localStorage persistence
# * Auto-generated right sidebar TOC with scroll spy (Intersection Observer)
# * Mobile-optimized search modal with keyboard shortcuts
# * Enhanced syntax highlighting for light and dark themes
# * Responsive design with mobile navigation
# * Zero additional JavaScript dependencies
# * Modern CSS Grid and Flexbox layout
#
# == Usage
#
# rdoc --format=aliki --op=doc/
#
# == Author
#
# Based on Darkfish by Michael Granger
# Modernized as Aliki theme by Stan Lo
#

class RDoc::Generator::Aliki < RDoc::Generator::Darkfish

RDoc::RDoc.add_generator self

##
# Version of the Aliki generator

VERSION = '1'

##
# Description of this generator

DESCRIPTION = 'Modern HTML generator based on Darkfish'

##
# Initialize the Aliki generator with the aliki template directory

def initialize(store, options)
super
aliki_template_dir = File.expand_path(File.join(__dir__, 'template', 'aliki'))
@template_dir = Pathname.new(aliki_template_dir)
end

##
# Copy only the static assets required by the Aliki theme. Unlike Darkfish we
# don't ship embedded fonts or image sprites, so limit the asset list to keep
# generated documentation lightweight.

def write_style_sheet
debug_msg "Copying Aliki static files"
options = { verbose: $DEBUG_RDOC, noop: @dry_run }

install_rdoc_static_file @template_dir + 'css/rdoc.css', "./css/rdoc.css", options

unless @options.template_stylesheets.empty?
FileUtils.cp @options.template_stylesheets, '.', **options
end

Dir[(@template_dir + 'js/**/*').to_s].each do |path|
next if File.directory?(path)
next if File.basename(path).start_with?('.')

dst = Pathname.new(path).relative_path_from(@template_dir)

install_rdoc_static_file @template_dir + path, dst, options
end
end
end
Empty file.
8 changes: 8 additions & 0 deletions lib/rdoc/generator/template/aliki/_aside_toc.rhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<aside class="table-of-contents" role="complementary" aria-label="Table of Contents">
<div class="toc-sticky">
<h3 class="toc-heading">On This Page</h3>
<nav class="toc-nav" id="toc-nav" aria-label="Page sections">
<!-- Generated by JavaScript based on page headings -->
</nav>
</div>
</aside>
25 changes: 25 additions & 0 deletions lib/rdoc/generator/template/aliki/_footer.rhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<footer class="site-footer">
<div class="footer-content">
<div>
<h3>Documentation</h3>
<ul>
<li><a href="<%= h asset_rel_path %>/index.html">Home</a></li>
</ul>
</div>

<div>
<h3>Resources</h3>
<ul>
<li><a href="https://ruby.github.io/rdoc/">RDoc Documentation</a></li>
<li><a href="https://github.com/ruby/rdoc">RDoc GitHub</a></li>
</ul>
</div>
</div>

<div class="footer-bottom">
<p>
Generated by <a href="https://ruby.github.io/rdoc/">RDoc <%= RDoc::VERSION %></a>
using the Aliki theme by <a href="http://st0012.dev">Stan Lo</a>
</p>
</div>
</footer>
90 changes: 90 additions & 0 deletions lib/rdoc/generator/template/aliki/_head.rhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<meta charset="<%= @options.charset %>">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title><%= h @title %></title>

<%- if defined?(klass) %>
<meta name="keywords" content="ruby,<%= h "#{klass.type},#{klass.full_name}" %>">

<%- if klass.comment.empty? %>
<meta name="description" content="Documentation for the <%= h "#{klass.full_name} #{klass.type}" %>">
<%- else %>
<meta name="description" content="<%= h "#{klass.type} #{klass.full_name}: #{excerpt(klass.comment)}" %>">
<%- end %>
<%- elsif defined?(file) %>
<meta name="keywords" content="ruby,documentation,<%= h file.page_name %>">
<meta name="description" content="<%= h "#{file.page_name}: #{excerpt(file.comment)}" %>">
<%- elsif @title %>
<meta name="keywords" content="ruby,documentation,<%= h @title %>">

<%- if @options.main_page and
main_page = @files.find { |f| f.full_name == @options.main_page } then %>
<meta name="description" content="<%= h "#{@title}: #{excerpt(main_page.comment)}" %>">
<%- else %>
<meta name="description" content="Documentation for <%= h @title %>">
<%- end %>
<%- end %>

<%- if canonical_url = @options.canonical_root %>
<% canonical_url = current.canonical_url if defined?(current) %>
<link rel="canonical" href="<%= canonical_url %>">
<%- end %>

<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:title" content="<%= h @title %>">
<%- if defined?(klass) %>
<%- if klass.comment.empty? %>
<meta property="og:description" content="Documentation for <%= h klass.full_name %> <%= h klass.type %> - API reference and code examples">
<%- else %>
<meta property="og:description" content="<%= h excerpt(klass.comment) %>">
<%- end %>
<%- elsif defined?(file) %>
<%- if file.comment.empty? %>
<meta property="og:description" content="<%= h file.page_name %> - <%= h @title %> documentation">
<%- else %>
<meta property="og:description" content="<%= h excerpt(file.comment) %>">
<%- end %>
<%- else %>
<meta property="og:description" content="API documentation for <%= h @title %> - Browse classes, modules, and methods">
<%- end %>
<%- if canonical_url = @options.canonical_root %>
<% canonical_url = current.canonical_url if defined?(current) %>
<meta property="og:url" content="<%= canonical_url %>">
<%- end %>

<!-- Twitter -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="<%= h @title %>">
<%- if defined?(klass) %>
<%- if klass.comment.empty? %>
<meta name="twitter:description" content="Documentation for <%= h klass.full_name %> <%= h klass.type %> - API reference and code examples">
<%- else %>
<meta name="twitter:description" content="<%= h excerpt(klass.comment) %>">
<%- end %>
<%- elsif defined?(file) %>
<%- if file.comment.empty? %>
<meta name="twitter:description" content="<%= h file.page_name %> - <%= h @title %> documentation">
<%- else %>
<meta name="twitter:description" content="<%= h excerpt(file.comment) %>">
<%- end %>
<%- else %>
<meta name="twitter:description" content="API documentation for <%= h @title %> - Browse classes, modules, and methods">
<%- end %>

<script type="text/javascript">
var rdoc_rel_prefix = "<%= h asset_rel_prefix %>/";
var index_rel_prefix = "<%= h rel_prefix %>/";
</script>

<script src="<%= h asset_rel_prefix %>/js/theme-toggle.js"></script>
<script src="<%= h asset_rel_prefix %>/js/navigation.js" defer></script>
<script src="<%= h asset_rel_prefix %>/js/search.js" defer></script>
<script src="<%= h asset_rel_prefix %>/js/search_index.js" defer></script>
<script src="<%= h asset_rel_prefix %>/js/searcher.js" defer></script>
<script src="<%= h asset_rel_prefix %>/js/aliki.js" defer></script>

<link href="<%= h asset_rel_prefix %>/css/rdoc.css" rel="stylesheet">
<%- @options.template_stylesheets.each do |stylesheet| %>
<link href="<%= h asset_rel_prefix %>/<%= File.basename stylesheet %>" rel="stylesheet">
<%- end %>
55 changes: 55 additions & 0 deletions lib/rdoc/generator/template/aliki/_header.rhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<header class="top-navbar">
<a href="<%= rel_prefix %>/index.html" class="navbar-brand">
<%= h @options.title %>
</a>

<!-- Desktop search bar -->
<div class="navbar-search navbar-search-desktop" role="search">
<form action="#" method="get" accept-charset="utf-8">
<input id="search-field" role="combobox" aria-label="Search"
aria-autocomplete="list" aria-controls="search-results"
type="text" name="search" placeholder="Search (/) for a class, method..."
spellcheck="false" title="Type to search, Up and Down to navigate, Enter to load">
<ul id="search-results" aria-label="Search Results"
aria-busy="false" aria-expanded="false"
aria-atomic="false" class="initially-hidden"></ul>
</form>
</div>

<!-- Mobile search icon button -->
<button id="search-toggle" class="navbar-search-mobile" aria-label="Open search" type="button">
<span aria-hidden="true">🔍</span>
</button>

<!-- Theme toggle button -->
<button id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" type="button">
<span class="theme-toggle-icon" aria-hidden="true">🌙</span>
</button>
</header>

<!-- Search Modal (Mobile) -->
<div id="search-modal" class="search-modal" hidden aria-modal="true" role="dialog" aria-label="Search">
<div class="search-modal-backdrop"></div>
<div class="search-modal-content">
<div class="search-modal-header">
<form class="search-modal-form" action="#" method="get" accept-charset="utf-8">
<span class="search-modal-icon" aria-hidden="true">🔍</span>
<input id="search-field-mobile" role="combobox" aria-label="Search"
aria-autocomplete="list" aria-controls="search-results-mobile"
type="text" name="search" placeholder="Search documentation"
spellcheck="false" autocomplete="off">
<button type="button" class="search-modal-close" aria-label="Close search" id="search-modal-close">
<span aria-hidden="true">esc</span>
</button>
</form>
</div>
<div class="search-modal-body">
<ul id="search-results-mobile" aria-label="Search Results"
aria-busy="false" aria-expanded="false"
aria-atomic="false" class="search-modal-results initially-hidden"></ul>
<div class="search-modal-empty">
<p>No recent searches</p>
</div>
</div>
</div>
</div>
6 changes: 6 additions & 0 deletions lib/rdoc/generator/template/aliki/_sidebar_ancestors.rhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%- if klass.type == 'class' && (ancestors = klass.super_classes).any? %>
<div id="parent-class-section" class="nav-section">
<h3>Ancestors</h3>
<%= generate_ancestor_list(ancestors, klass) %>
</div>
<%- end %>
5 changes: 5 additions & 0 deletions lib/rdoc/generator/template/aliki/_sidebar_classes.rhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div id="classindex-section" class="nav-section">
<h3>Class and Module Index</h3>

<%= generate_class_index_content(@classes, rel_prefix) %>
</div>
15 changes: 15 additions & 0 deletions lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%- unless klass.extends.empty? then %>
<div id="extends-section" class="nav-section">
<h3>Extended With Modules</h3>

<ul class="link-list">
<%- klass.extends.each do |ext| %>
<%- unless String === ext.module then %>
<li><a class="extend" href="<%= klass.aref_to ext.module.path %>"><%= ext.module.full_name %></a></li>
<%- else %>
<li><span class="extend"><%= ext.name %></span></li>
<%- end %>
<%- end %>
</ul>
</div>
<%- end %>
15 changes: 15 additions & 0 deletions lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%- unless klass.includes.empty? then %>
<div id="includes-section" class="nav-section">
<h3>Included Modules</h3>

<ul class="link-list">
<%- klass.includes.each do |inc| %>
<%- unless String === inc.module then %>
<li><a class="include" href="<%= klass.aref_to inc.module.path %>"><%= inc.module.full_name %></a></li>
<%- else %>
<li><span class="include"><%= inc.name %></span></li>
<%- end %>
<%- end %>
</ul>
</div>
<%- end %>
Loading