Skip to content

Conversation

@jbrinkman
Copy link
Owner

@jbrinkman jbrinkman commented Aug 5, 2025

Overview

This PR implements issue #49 by reorganizing the HTML report structure from type-first to change-category-first organization with collapsible type groups.

Changes Made

🔧 Core Implementation

  • HtmlFormatterScriban.cs: Modified PrepareChangeSections to use GroupChangesByType method for hierarchical organization
  • main-layout.scriban: Updated template with collapsible type groups and clickable section headers
  • styles.css: Enhanced with type group collapsibility, hover effects, and consistent styling
  • scripts.js: Added toggleTypeGroup functionality with session storage persistence

🎨 User Experience Improvements

  • Hierarchical Structure: Change categories (Added, Removed, Modified, etc.) as top-level sections
  • Collapsible Type Groups: Types within each category can be expanded/collapsed (default: collapsed)
  • Full Header Clickability: Section and type headers are fully clickable, not just arrow buttons
  • Consistent Hover Effects: All interactive headers show visual feedback on hover
  • Fixed Navigation: Summary cards properly navigate to corresponding sections
  • Session Persistence: Expanded/collapsed state preserved during browser session

🧪 Testing & Quality

  • Fixed CLI Workflow Tests: Corrected exit code expectations (changed from 2 to 1 for breaking changes)
  • All Tests Passing: 425/425 tests successful
  • Real-world Validation: Tested with StackExchange.Redis vs Valkey.Glide assemblies (886 differences)

Before & After

Before: Types were primary groupings with change types as sub-sections
After: Change categories are primary with collapsible type groups within each category

Technical Details

  • Maintains backward compatibility with existing configuration and API
  • No breaking changes to public interfaces
  • Enhanced template system with better separation of concerns
  • Improved accessibility with proper click targets and visual feedback

Testing

# All tests pass
dotnet test # 425/425 successful

# Real-world validation
dotnet run -- compare StackExchange.Redis.dll Valkey.Glide.dll --config config.json

Resolves #49

Summary by CodeRabbit

  • New Features

    • HTML reports now group API changes by containing type, providing clearer organization and summaries for each type.
    • Type groups in the report display change counts, breaking change badges, and allow toggling visibility for easier navigation.
    • Individual changes show element type, name, descriptions, and signature differences with interactive toggles.
  • Style

    • Enhanced visual styling for type groups, badges, and change categories to improve readability and highlight breaking changes.
  • Bug Fixes

    • Adjusted CLI integration tests to expect the correct exit code when breaking changes are detected.
  • Tests

    • Updated test assertions to match the new type-based grouping and display format in HTML reports.

- Implement hierarchical change category organization with collapsible type groups
- Change from type-first to change-category-first organization
- Add collapsible type groups that default to collapsed state
- Fix summary navigation links to properly navigate to sections
- Update HTML template structure:
  * Modify HtmlFormatterScriban.cs to use GroupChangesByType method
  * Update main-layout.scriban with collapsible type groups and navigation
  * Enhance styles.css with type group collapsibility and hover effects
  * Add scripts.js toggle functionality with session storage persistence
- Make section headers fully clickable (not just arrow buttons)
- Add consistent hover effects for section and type headers
- Fix CLI workflow tests to expect correct exit codes
- All 425 tests passing

Resolves #49
@coderabbitai
Copy link

coderabbitai bot commented Aug 5, 2025

Walkthrough

The changes overhaul the HTML report generation for API differences by grouping changes under their containing types rather than by change category. Supporting code, templates, JavaScript, and CSS are refactored or extended to implement collapsible, type-centric sections, update summary cards, and adjust test assertions to the new structure. Tests and templates are updated accordingly.

Changes

Cohort / File(s) Change Summary
Grouping Logic & Formatter
src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs
Introduces GroupChangesByType and supporting helpers to group API changes by containing type, replacing previous grouping by change category. Updates all usages to use the new grouping logic and structures the change data for type-centric rendering.
HTML Templates
src/DotNetApiDiff/Reporting/HtmlTemplates/main-layout.scriban, src/DotNetApiDiff/Reporting/HtmlTemplates/change-group.scriban, src/DotNetApiDiff/Reporting/HtmlTemplates/type-group.scriban
Refactors templates to render changes grouped by type, introduces a new type-group.scriban template, updates main layout and group rendering to support type summaries, badges, and collapsible UI. Removes redundant conditional logic and improves clarity and hierarchy of rendered content.
JavaScript Interactivity
src/DotNetApiDiff/Reporting/HtmlTemplates/scripts.js
Adds functions to support collapsing/expanding type groups, manages state via sessionStorage, and initializes new UI interactions for type-centric sections, mirroring existing section toggling logic.
CSS Styling
src/DotNetApiDiff/Reporting/HtmlTemplates/styles.css
Adds and updates styles for type-centric layout, headers, badges, collapsible groups, and change category coloring, ensuring visual clarity and improved user experience for the new grouping.
Integration Tests
tests/DotNetApiDiff.Tests/Integration/CliWorkflowTests.cs
Updates test assertions to expect exit code 1 (not 2) for breaking changes, aligning with the updated CLI behavior.
Formatter Output Tests
tests/DotNetApiDiff.Tests/Reporting/HtmlFormatterScribanTests.cs
Updates test assertions to expect type-centric group headers and fully qualified element names in the generated HTML, reflecting the new grouping and display logic.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant HtmlFormatterScriban
    participant Templates
    participant Browser

    User->>CLI: Run API diff with HTML output
    CLI->>HtmlFormatterScriban: Generate HTML report
    HtmlFormatterScriban->>HtmlFormatterScriban: GroupChangesByType(differences)
    HtmlFormatterScriban->>Templates: Render grouped changes (per type)
    Templates-->>CLI: HTML report (type-centric)
    CLI->>User: Outputs HTML report
    User->>Browser: Opens HTML report
    Browser->>Browser: Renders collapsible type sections, badges, etc.
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
HTML reports group all changes by the containing type (#49)
Within each type, changes are sub-grouped by category (additions, removals, modifications, breaking) (#49)
Types are sorted alphabetically (#49)
Each type section shows a summary of change counts (#49)
Visual hierarchy clearly distinguishes types from change categories (#49)
Large reports remain readable and navigable (#49)
Console, JSON, and Markdown formats remain unchanged (#49)
Configuration option to toggle organization method (optional enhancement) (#49) No configuration option for organization method was added; only type-centric grouping is implemented.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
None found

Possibly related issues

Poem

In the warren of code, where the changes abound,
Now each type has a folder—see how they're found!
With badges and toggles, reports leap to new heights,
Browsing is easy, through days and through nights.
🐇✨
Oh, what a delight, for devs and for bunnies—
Grouped by their types, the changes are sunny!

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/issue-49-reorganize-html-report

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 1

🔭 Outside diff range comments (1)
src/DotNetApiDiff/Reporting/HtmlTemplates/change-group.scriban (1)

1-56: Consider consolidating duplicate template code.

This template appears to duplicate much of the structure from type-group.scriban. Both templates render type groups with collapsible sections and signature details.

To improve maintainability:

  1. Consider using a single template with conditional logic
  2. Or extract common parts into shared partial templates

This would follow the DRY principle and make future updates easier to maintain.

🧹 Nitpick comments (3)
src/DotNetApiDiff/Reporting/HtmlTemplates/scripts.js (2)

175-213: Review the type group ID extraction approach.

The initializeTypeGroups function uses regex to extract type group IDs from onclick attributes, which could be fragile if the HTML structure changes. Consider using data attributes instead for more robust element identification.

Consider using data attributes for type group IDs instead of parsing onclick attributes:

// Instead of parsing onclick, use a data attribute
-        const onclickAttr = typeHeader.getAttribute('onclick');
-        const match = onclickAttr?.match(/toggleTypeGroup\('([^']+)'\)/);
-        const typeGroupId = match ? match[1] : null;
+        const typeGroupId = typeHeader.getAttribute('data-type-group-id');

Then update the HTML template to include data-type-group-id attributes on type headers.


175-213: Consider using data attributes instead of parsing onclick.

The function works correctly and follows the established pattern, but the regex extraction from onclick attributes is fragile and tightly coupled to HTML structure.

Consider refactoring to use data attributes for better maintainability:

// Instead of extracting from onclick:
-const onclickAttr = typeHeader.getAttribute('onclick');
-const match = onclickAttr?.match(/toggleTypeGroup\('([^']+)'\)/);
-const typeGroupId = match ? match[1] : null;

// Use a data attribute:
+const typeGroupId = typeGroup.getAttribute('data-type-group-id');

This would require updating the HTML template to include data-type-group-id attributes but would make the code more robust and easier to maintain.

src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs (1)

235-254: Consider removing unused GroupChanges method.

The original GroupChanges method is no longer used after the migration to type-based grouping. Consider removing it to reduce code maintenance burden and eliminate confusion.

Since GroupChanges is no longer used, consider removing it:

-    private object[] GroupChanges(List<ApiDifference> changes)
-    {
-        return changes.GroupBy(c => c.ElementType)
-            .OrderBy(g => g.Key)
-            .Select(g => new
-            {
-                key = g.Key,
-                count = g.Count(),
-                changes = g.OrderBy(c => c.ElementName).Select(c => new
-                {
-                    element_name = c.ElementName,
-                    description = c.Description,
-                    is_breaking_change = c.IsBreakingChange,
-                    has_signatures = !string.IsNullOrEmpty(c.OldSignature) || !string.IsNullOrEmpty(c.NewSignature),
-                    old_signature = HtmlEscape(c.OldSignature),
-                    new_signature = HtmlEscape(c.NewSignature),
-                    details_id = $"details-{Guid.NewGuid():N}"
-                }).ToArray()
-            }).ToArray();
-    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70a6209 and a6a2872.

📒 Files selected for processing (8)
  • src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs (6 hunks)
  • src/DotNetApiDiff/Reporting/HtmlTemplates/change-group.scriban (1 hunks)
  • src/DotNetApiDiff/Reporting/HtmlTemplates/main-layout.scriban (2 hunks)
  • src/DotNetApiDiff/Reporting/HtmlTemplates/scripts.js (2 hunks)
  • src/DotNetApiDiff/Reporting/HtmlTemplates/styles.css (2 hunks)
  • src/DotNetApiDiff/Reporting/HtmlTemplates/type-group.scriban (1 hunks)
  • tests/DotNetApiDiff.Tests/Integration/CliWorkflowTests.cs (10 hunks)
  • tests/DotNetApiDiff.Tests/Reporting/HtmlFormatterScribanTests.cs (3 hunks)
🔇 Additional comments (32)
tests/DotNetApiDiff.Tests/Reporting/HtmlFormatterScribanTests.cs (3)

74-74: LGTM!

Test assertion correctly updated to validate the new type-centric grouping with folder icon.


93-93: LGTM!

Using fully qualified element names improves clarity by showing the containing type.


108-108: LGTM!

Test correctly validates type-based grouping for removed items.

tests/DotNetApiDiff.Tests/Integration/CliWorkflowTests.cs (1)

141-142: Exit code standardization looks good, but consider documenting this breaking change.

The change from exit code 2 to 1 for breaking changes aligns with standard CLI conventions where exit code 1 indicates general errors/failures. However, this is a breaking change for existing CI/CD pipelines that may be checking for exit code 2.

Consider:

  1. Documenting this change prominently in release notes
  2. Adding a migration guide for users who need to update their CI/CD scripts

Also applies to: 166-167, 201-202, 236-237, 367-368, 425-426, 448-449, 471-472, 539-540, 617-618

src/DotNetApiDiff/Reporting/HtmlTemplates/type-group.scriban (1)

1-13: LGTM!

Type header section is well-structured with proper pluralization and clear visual indicators.

src/DotNetApiDiff/Reporting/HtmlTemplates/main-layout.scriban (3)

59-83: LGTM!

Simplified summary cards with consistent clickability improve user experience.


155-156: Ensure collision-resistant ID generation for type groups

The current sanitization in src/DotNetApiDiff/Reporting/HtmlTemplates/main-layout.scriban:155-156
uses only .Replace(" ", "-"), .Replace(".", "-"), and .Replace("`", "-") to build IDs from section.change_type and group.key. This can lead to:

  • Collisions (e.g. “Namespace.Type” and “Namespace-Type” both → “Namespace-Type”)
  • Broken IDs for other characters (<, >, ,, [], etc.)
  • No uniqueness guarantee across groups

Please review and verify:

  • All possible group.key values (including generic names like List<T>)
  • Whether there’s any existing utility for sanitizing or hashing these IDs
  • The feasibility of introducing a shared helper (e.g. hash-based or collision-checked) to generate and track unique IDs

102-107: Potential double-click event issue.

Having onclick handlers on both the parent div (line 102) and the child button (lines 104-106) could cause the toggle to fire twice when clicking the button.

Consider either:

  1. Remove the onclick from the button since the parent div handles it
  2. Add event.stopPropagation() to the button's click handler
  3. Remove the button element entirely and style the parent div as clickable
-<div class="section-header" onclick="toggleSection('breaking-changes')">
+<div class="section-header" onclick="toggleSection('breaking-changes')" role="button" tabindex="0">
     <h2>⚠️ Breaking Changes</h2>
-    <button class="section-toggle collapsed">
-        <span class="toggle-icon">▶</span>
-    </button>
+    <span class="section-toggle collapsed">
+        <span class="toggle-icon">▶</span>
+    </span>
 </div>

Likely an incorrect or invalid review comment.

src/DotNetApiDiff/Reporting/HtmlTemplates/scripts.js (6)

73-94: LGTM! Type group toggle functionality follows established patterns.

The toggleTypeGroup function correctly mirrors the existing section toggle pattern with proper DOM manipulation, CSS class management, and session storage persistence. The function handles edge cases by checking for null elements before proceeding.


96-111: LGTM! Session storage helpers are consistent.

The setTypeGroupState and getTypeGroupState functions follow the same pattern as the existing section storage helpers, using appropriate key prefixes ("type-group-" vs "section-") to avoid conflicts and properly handling storage exceptions.


170-173: Good integration of type groups initialization.

The addition of initializeTypeGroups() call to the existing initializeSections() function ensures type groups are properly initialized when the page loads.


73-94: LGTM! Type group toggle logic is well-implemented.

The function correctly mirrors the section toggle pattern with proper state management and defensive programming. The early return when elements aren't found prevents runtime errors.


96-111: LGTM! Consistent session storage implementation.

The type group storage helpers follow the same pattern as section storage with appropriate key prefixes and error handling.


170-173: LGTM! Proper integration of type group initialization.

Adding the call to initializeTypeGroups() ensures type groups are properly initialized on page load, following the same pattern as sections.

src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs (9)

175-175: LGTM! Consistent migration to type-based grouping.

All change sections (added, removed, modified, moved, excluded) have been consistently updated to use GroupChangesByType instead of the previous GroupChanges method, ensuring uniform type-centric organization across all change categories.

Also applies to: 188-188, 201-201, 214-214, 228-228


256-283: Well-structured type-based grouping with comprehensive metadata.

The GroupChangesByType method provides excellent organization and rich metadata:

  • Groups changes by containing type with proper sorting
  • Includes breaking change counts and flags for each type
  • Provides both display names and full names for flexibility
  • Orders changes within each type group logically

The data structure supports the enhanced UI requirements effectively.


285-312: Robust type extraction with proper edge case handling.

The ExtractContainingType method correctly handles various scenarios:

  • Type-level changes return the type name directly
  • Member-level changes extract the containing type from the full name
  • Nested types (with '+' separator) are preserved
  • Malformed names fall back gracefully

The logic appears sound for typical .NET naming conventions.


314-333: Clean display name extraction methods.

Both GetTypeDisplayName and GetMemberDisplayName methods properly extract simple names from fully qualified names by finding the last dot separator. The logic is straightforward and handles edge cases appropriately.


175-175: LGTM! Systematic migration to type-based grouping.

All change sections consistently use the new GroupChangesByType method, ensuring uniform presentation across all change categories.

Also applies to: 188-188, 201-201, 214-214, 228-228


256-283: LGTM! Well-structured type-based grouping implementation.

The method correctly implements the core requirement of grouping changes by containing type. The rich metadata structure including breaking change counts and display names provides excellent data for the templates.


285-312: LGTM! Robust type extraction with good fallback handling.

The method correctly handles the common cases for extracting containing types, including nested types and reasonable fallbacks for edge cases. The logic appropriately handles both type-level and member-level changes.


314-319: LGTM! Simple and effective type name extraction.

The method correctly extracts the simple type name for display purposes with appropriate handling of types without namespaces.


321-333: LGTM! Consistent member name extraction logic.

The method correctly handles both type-level and member-level changes, providing appropriate display names for each context.

src/DotNetApiDiff/Reporting/HtmlTemplates/styles.css (9)

66-72: Good enhancement to section header interactivity.

The addition of cursor pointer and hover effects to section headers improves user experience by providing clear visual feedback for interactive elements. The transition effect is smooth and professional.


672-691: Well-designed type section styling.

The type section styles provide excellent visual hierarchy with:

  • Gradient background for headers creating depth
  • Consistent left border using brand color (#007bff)
  • Proper typography and spacing for type summaries
  • Clear visual distinction for breaking change indicators

693-791: Comprehensive and consistent type group styling.

The type group styles effectively support the new functionality:

  • Proper interactive states with hover effects and transitions
  • Consistent toggle button styling matching section toggles
  • Smooth collapse/expand animations using CSS transitions
  • Appropriate padding and spacing for readability

The CSS follows established patterns from the existing section styling.


793-862: Excellent visual categorization and change item styling.

The change category and item styles provide clear visual organization:

  • Distinct background colors for each change type (added: green tint, removed: red tint, etc.)
  • Consistent border-left color coding matching the summary cards
  • Proper spacing and typography hierarchy
  • Clean element type badges with appropriate sizing

The color scheme aligns well with the existing design system.


743-749: CSS & JS Toggle Icon Consistency Confirmed

I’ve verified that scripts.js always sets the icon to ▶ and styles.css applies rotate(0deg) when collapsed and rotate(90deg) when expanded for both .section-toggle and .type-toggle. The visual state changes purely via CSS rotation, so everything is aligned and no code changes are needed.


66-72: LGTM! Good UX enhancement for interactive headers.

The cursor pointer and hover transition clearly indicate the interactive nature of section headers, improving user experience.


672-692: LGTM! Well-designed foundation for type-centric layout.

The type-section styles establish clear visual hierarchy with appropriate spacing, gradients, and color accents that align with the existing design system.


693-772: LGTM! Comprehensive and accessible type group styling.

The styling provides excellent visual hierarchy, interactive feedback, and accessibility considerations. The consistency with existing toggle patterns and proper focus handling demonstrates attention to both design and UX principles.


773-862: LGTM! Complete and well-organized change item styling.

The styling provides excellent organization for changes within type groups, with appropriate color coding, smooth transitions, and clear visual hierarchy. The element type badges add helpful context while maintaining clean design.

Comment on lines +35 to +36
<button class="toggle-btn" onclick="toggleSignature('{{ change.details_id }}')">
<span class="toggle-icon">▼</span> View Signature Details
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Ensure proper HTML escaping and improve accessibility.

Two concerns to address:

  1. XSS Prevention: Ensure that change.details_id, change.old_signature, and change.new_signature are properly HTML-escaped in the Scriban template engine to prevent XSS attacks.

  2. Accessibility: The toggle button should include ARIA attributes for screen readers.

Consider updating the toggle button:

-<button class="toggle-btn" onclick="toggleSignature('{{ change.details_id }}')">
+<button class="toggle-btn" onclick="toggleSignature('{{ change.details_id }}')" aria-expanded="false" aria-controls="{{ change.details_id }}">

Also applies to: 43-43, 49-49

🤖 Prompt for AI Agents
In src/DotNetApiDiff/Reporting/HtmlTemplates/type-group.scriban around lines
35-36, 43, and 49, ensure that the variables change.details_id,
change.old_signature, and change.new_signature are properly HTML-escaped using
Scriban's escape functions to prevent XSS vulnerabilities. Additionally, enhance
the toggle button's accessibility by adding appropriate ARIA attributes such as
aria-expanded and aria-controls to indicate the toggle state and the controlled
element for screen readers.

@jbrinkman jbrinkman closed this Aug 8, 2025
@jbrinkman
Copy link
Owner Author

This was included as part of PR #51

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.

Improve HTML Report Organization: Group Changes by Type Instead of Change Category

2 participants