Skip to content

Improve beamtalk doc HTML output to ExDoc quality (BT-616)#586

Merged
jamesc merged 4 commits intomainfrom
BT-616
Feb 16, 2026
Merged

Improve beamtalk doc HTML output to ExDoc quality (BT-616)#586
jamesc merged 4 commits intomainfrom
BT-616

Conversation

@jamesc
Copy link
Owner

@jamesc jamesc commented Feb 16, 2026

Summary

Upgrades beamtalk doc from basic HTML output to ExDoc/rustdoc-quality documentation with modern UI features.

Linear issue: https://linear.app/beamtalk/issue/BT-616

Key Changes

  • Sidebar navigation — Always-visible class list with active state highlighting, collapsible on mobile
  • Client-side search — JavaScript search across classes and methods with keyboard shortcut (/ to focus)
  • Modern responsive CSS — Catppuccin color palette, dark mode via prefers-color-scheme, mobile-first responsive layout
  • Syntax highlighting — Custom lightweight highlighter for beamtalk code blocks (keywords, strings, numbers, comments, symbols, class names, self)
  • Class hierarchy tree — Interactive tree visualization on index page showing full inheritance structure
  • Method grouping — Clear separation of class methods, instance methods, and inherited methods
  • Source links — Per-method links to GitHub source at exact line number
  • Mobile support — Hamburger menu toggle for sidebar on small screens

Technical Details

  • All assets (CSS, JS) embedded as const strings — zero external dependencies
  • Search index generated as JSON during doc build
  • Source file path and line numbers tracked through ClassInfo/MethodInfo structs
  • All 35 doc tests pass, full CI green (1,304 Rust + 1,422 stdlib + 1,332 Erlang tests)

Generated Output

  • 33 class pages + index + style.css + search.js (984KB total)
  • beamtalk doc lib/ --output docs/api works end-to-end

Summary by CodeRabbit

  • New Features

    • Collapsible sidebar with persistent client-side search and injected search index (search.js)
    • Per-method source links to repository locations with optional line references
    • Class hierarchy tree and full class list on the documentation index
    • Syntax highlighting including beamtalk-specific highlighting
  • Documentation

    • Sidebar and search integrated across index and class pages; responsive layout and header toggle
  • Tests

    • Updated tests covering search output, highlighting, and source-link rendering

jamesc and others added 2 commits February 16, 2026 12:29
- Add sidebar navigation with class list (always visible, collapsible on mobile)
- Add client-side search across classes and methods (JavaScript, no server)
- Modern responsive CSS with dark mode support (Catppuccin color palette)
- Syntax highlighting for beamtalk code blocks in doc comments
- Class hierarchy tree visualization on index page
- Methods grouped by category: class methods, instance methods, inherited
- Source link per method pointing to GitHub repo (lib/ files)
- Mobile responsive with hamburger menu toggle
- Keyboard shortcut (/) to focus search
- All assets embedded as const strings (no external dependencies)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 16, 2026 12:38
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

Doc generator now records per-class source roots/files and per-method line numbers, builds a persistent sidebar and class hierarchy, emits a client-side search index (search.js), adds beamtalk syntax highlighting, and injects source links into generated class/method HTML pages.

Changes

Cohort / File(s) Summary
Doc generator core
crates/beamtalk-cli/src/commands/doc.rs
Data model extended: ClassInfo gains source_file and source_root; MethodInfo gains line_number. Parsing populates source paths and line numbers. Rendering updated to produce sidebar HTML, hierarchy tree, CSS, search.js, and per-method source links; markdown rendering and beamtalk highlighting added; many helpers and signatures updated to accept sidebar_html.
Tests / expectations
crates/beamtalk-cli/tests/*
Tests updated to construct and assert new fields (source_file, source_root, line_number), verify search.js output, beamtalk-highlighted code blocks, and presence of source links in generated HTML.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as "CLI Command"
    participant Parser as "BEAM Parser"
    participant Generator as "Doc Generator"
    participant Assets as "Asset Builder"
    participant Output as "Output Dir"

    User->>CLI: run `beamtalk doc lib/ --output docs/api`
    CLI->>Parser: read BEAM files and doc comments
    Parser->>Parser: extract ClassInfo (incl. source_root/file) and MethodInfo (incl. line_number)
    Parser-->>Generator: classes + metadata
    Generator->>Generator: build sidebar HTML and hierarchy tree
    Generator->>Assets: build search index and templates
    Assets->>Output: write `search.js`, CSS, other assets
    Generator->>Output: write `index.html` and per-class pages (with highlighted code and source links)
    CLI-->>User: static docs generated in output dir
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (32 files):

⚔️ .github/workflows/release.yml (content)
⚔️ Justfile (content)
⚔️ crates/beamtalk-cli/src/commands/build.rs (content)
⚔️ crates/beamtalk-cli/src/commands/build_stdlib.rs (content)
⚔️ crates/beamtalk-cli/src/commands/doc.rs (content)
⚔️ crates/beamtalk-cli/src/commands/manifest.rs (content)
⚔️ crates/beamtalk-cli/src/commands/new.rs (content)
⚔️ crates/beamtalk-cli/src/commands/protocol.rs (content)
⚔️ crates/beamtalk-cli/src/commands/repl/client.rs (content)
⚔️ crates/beamtalk-cli/src/commands/repl/color.rs (content)
⚔️ crates/beamtalk-cli/src/commands/repl/display.rs (content)
⚔️ crates/beamtalk-cli/src/commands/repl/mod.rs (content)
⚔️ crates/beamtalk-cli/src/commands/repl/process.rs (content)
⚔️ crates/beamtalk-cli/src/commands/test.rs (content)
⚔️ crates/beamtalk-cli/src/commands/transcript.rs (content)
⚔️ crates/beamtalk-cli/src/commands/workspace/mod.rs (content)
⚔️ crates/beamtalk-cli/src/main.rs (content)
⚔️ crates/beamtalk-cli/src/repl_startup.rs (content)
⚔️ crates/beamtalk-compiler-port/src/main.rs (content)
⚔️ crates/beamtalk-core/src/codegen/core_erlang/document.rs (content)
⚔️ crates/beamtalk-core/src/semantic_analysis/error.rs (content)
⚔️ crates/beamtalk-core/src/semantic_analysis/scope.rs (content)
⚔️ crates/beamtalk-lsp/src/main.rs (content)
⚔️ crates/beamtalk-lsp/src/server.rs (content)
⚔️ crates/beamtalk-mcp/src/client.rs (content)
⚔️ crates/beamtalk-mcp/src/main.rs (content)
⚔️ crates/beamtalk-mcp/src/server.rs (content)
⚔️ crates/beamtalk-mcp/src/workspace.rs (content)
⚔️ editors/vscode/.gitignore (content)
⚔️ editors/vscode/.vscodeignore (content)
⚔️ editors/vscode/package.json (content)
⚔️ editors/vscode/src/extension.ts (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly references the issue (BT-616) and accurately summarizes the main change: improving beamtalk doc HTML output to ExDoc quality, which aligns with the substantial UI and feature enhancements in the changeset.
Linked Issues check ✅ Passed The pull request implements all primary acceptance criteria from BT-616: sidebar navigation, client-side search, modern responsive CSS with dark mode, beamtalk syntax highlighting, class hierarchy tree, method grouping, per-method source links, and static-file compatibility.
Out of Scope Changes check ✅ Passed All changes in the pull request directly support the objectives outlined in BT-616. The data model augmentation, parsing enhancements, rendering and UI integration, and navigation features are all within scope as specified in the linked issue.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BT-616
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch BT-616
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@crates/beamtalk-cli/src/commands/doc.rs`:
- Around line 1171-1184: The code double-escapes doc comment summaries: the
manual .replace('\\', "\\\\").replace('"', "\\\"") applied when building summary
(from class.doc_comment) is then passed to json_escape(&summary), causing
backslashes to be quadrupled; remove the manual escaping step and assign summary
to the raw first line (e.g., let summary =
class.doc_comment.as_ref().and_then(|d|
d.lines().next()).unwrap_or("").to_string();) so that json_escape(&summary)
alone performs correct escaping before index_entries.push(...) which constructs
the JSON entry using class.name and summary.
- Around line 1382-1391: The loop that advances j over a quoted string can
overflow when a backslash appears as the last character: in the block that
checks if chars[j] == '\\', guard the escape increment so you never advance past
len (e.g., replace the unconditional j += 1 inside the if with a conditional
that only increments when j + 1 < len, or break/stop the loop when the backslash
is the final char), and ensure before using chars[i..j] you verify j <= len;
update the code around variables j, chars, quote, len, and i to perform that
bounds check to avoid panics when a string literal ends with a trailing
backslash.
- Around line 1354-1364: The loop that scans block comments (variables chars, j,
len) stops with the condition j + 1 < len which causes the last character to be
dropped for unterminated comments; change the loop to scan until the end (e.g.,
while j < len && !(chars[j] == '*' && j + 1 < len && chars[j + 1] == '/')) and
then after the loop, if a closing "*/" was found (j + 1 < len) advance j by 2,
otherwise set j = len so the unterminated comment consumes to the end without
losing the final character.
🧹 Nitpick comments (3)
crates/beamtalk-cli/src/commands/doc.rs (3)

1005-1011: Consider making the GitHub URL configurable.

The source link URL is hardcoded to jamesc/beamtalk on the main branch with lib/ prefix. This prevents usage with forks, feature branches, or different directory structures.

Consider accepting a base URL pattern via CLI flag (e.g., --source-url "https://github.com/user/repo/blob/{branch}/{path}#L{line}") or deriving it from git remote config.


1266-1271: Incomplete HTML escaping in "No results" message.

The query is escaped with only replace(/</g,'&lt;'), missing >, &, and quotes. For consistency with the esc() function used elsewhere, consider:

       resultsDiv.className = 'search-results active';
-      resultsDiv.innerHTML = '<h2>Search Results</h2><p>No results for "' +
-        q.replace(/</g,'&lt;') + '"</p>';
+      resultsDiv.innerHTML = '<h2>Search Results</h2><p>No results for "' +
+        esc(q) + '"</p>';
       return;

860-863: Active state pattern works but is fragile.

The string replace approach to add the active class relies on exact HTML structure matching. This works but could break if the sidebar format changes. Consider an alternative like passing the active class name to build_sidebar_html.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR significantly enhances the beamtalk doc command to generate ExDoc/rustdoc-quality HTML documentation with modern UI features including sidebar navigation, client-side search, syntax highlighting, and responsive design.

Changes:

  • Adds sidebar navigation with always-visible class list and active state highlighting
  • Implements JavaScript-based search across classes and methods with keyboard shortcut support (/ to focus)
  • Adds custom syntax highlighter for Beamtalk code blocks with support for keywords, strings, numbers, comments, symbols, and class names
  • Implements class hierarchy tree visualization on the index page
  • Adds source links to GitHub for each method (with line numbers)
  • Introduces modern responsive CSS with Catppuccin color palette and dark mode via prefers-color-scheme
  • Embeds all assets (CSS, JS) as const strings for zero external dependencies

- Fix double escaping bug in search index summary text
- Fix unterminated block comment losing last character in highlighter
- Fix potential panic with malformed string ending in backslash

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/beamtalk-cli/src/commands/doc.rs (1)

957-977: ⚠️ Potential issue | 🟠 Major

Inherited methods currently omit source links.

Line 974 calls write_method_html, which drops source links. The requirement calls for per‑method links; you already have all_classes, so you can attach the parent’s source_file.

🔧 Suggested fix to include parent source links
     for (parent_name, methods) in inherited {
         let _ = writeln!(
             html,
             "<h3>From {}</h3>",
             class_link(parent_name, all_classes)
         );
+        let parent_source = all_classes
+            .get(*parent_name)
+            .and_then(|c| c.source_file.as_deref());
         for method in *methods {
-            write_method_html(html, method, all_classes);
+            write_method_html_with_source(html, method, parent_source);
         }
     }
🤖 Fix all issues with AI agents
In `@crates/beamtalk-cli/src/commands/doc.rs`:
- Around line 169-170: The code currently sets source_file from path.file_name()
which drops subdirectories and later formats links as "lib/{file}", causing
incorrect links for nested or non-lib inputs; update the logic that computes
source_file to store the path relative to the input root (e.g., use
Path::strip_prefix(input_root) or compute a relative path and call
to_string_lossy().into_owned() instead of file_name()), preserve any
subdirectory components, and then change the link-emission code that currently
hard-codes "lib/{file}" to only emit that base when the known root is "lib" or
use a configurable base path (ensure you reference the same source_file variable
and the link-creation block where "lib/{file}" is used).
- Around line 1082-1084: The search input HTML string lacks an accessible label;
update the literal that renders the input (the string containing
id="sidebar-search" and class="sidebar-search") to include an accessible
label—either add an aria-label attribute like aria-label="Search classes" to the
input, or add a visually hidden <label for="sidebar-search">Search
classes</label> alongside it so screen readers announce its purpose; locate the
input by its id "sidebar-search" in the doc rendering code and make the change
there.

- Use .lines().count() for CRLF-safe line number calculation
- Use esc() for complete HTML escaping in search results
- Add aria-label to search input for accessibility
- Preserve relative paths for nested source directories
- Pass source root through to link generation instead of hardcoding lib/

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/beamtalk-cli/src/commands/doc.rs (1)

46-61: ⚠️ Potential issue | 🟡 Minor

Fix source links when docs are generated from a single file path.

If the input path is a single file, source_root becomes the file path and strip_prefix yields an empty relative path, which makes source links malformed (e.g., Foo.bt/ paths). Normalize the root to a directory before passing it into parse_class_info (and use it for README lookup).

🔧 Suggested fix
-    let source_path = Utf8PathBuf::from(path);
+    let source_path = Utf8PathBuf::from(path);
+    let source_root = if source_path.is_file() {
+        source_path
+            .parent()
+            .unwrap_or_else(|| Utf8Path::new("."))
+            .to_path_buf()
+    } else {
+        source_path.clone()
+    };

     // Find .bt source files
     let source_files = find_source_files(&source_path)?;
@@
-    for file in &source_files {
-        if let Some(class_info) = parse_class_info(&source_path, file)? {
+    for file in &source_files {
+        if let Some(class_info) = parse_class_info(&source_root, file)? {
             classes.push(class_info);
         }
     }
@@
-    let readme_path = source_path.join("README.md");
+    let readme_path = source_root.join("README.md");

Also applies to: 170-175, 946-1019

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.

2 participants