Skip to content

Conversation

@cboos
Copy link
Collaborator

@cboos cboos commented Dec 31, 2025

Summary

Address PR review feedback for the Markdown export feature, plus TUI enhancements for on-demand generation and embedded viewing.

TUI Enhancements

  • On-demand generation: Skip upfront HTML generation in TUI mode; generate session files only when requested via shortcuts
  • New shortcuts: h (HTML), m (Markdown), v (embedded viewer)
  • Force-regenerate shortcuts: Hidden H/M/V shortcuts to force regeneration even if file is up-to-date
  • Embedded Markdown viewer: Built-in viewer with table of contents (press v)
    • Customized ToC: 1/3 width, 3-level expansion, cleaned labels (no roman numerals, stripped message type prefixes)

Bug Fixes

  • Fix session file count showing 0 for Markdown format (was hardcoded to .html)
  • Fix timing lambdas that could raise UnboundLocalError on exception
  • Add error handling for image export file operations (graceful degradation)
  • Fix type checker errors in tui.py (pyright + ty)

Code Quality

  • Unify file extension computation using get_file_extension() across cli.py, converter.py, tui.py
  • Extract shared regex patterns in snapshot serializers
  • Hoist strip_error_tags import to module scope in test file
  • Simplify redundant conditional after assert
  • Add minimal docstrings to MarkdownRenderer methods

CLI Improvements

  • Make --clear-output format-aware (clears .md or .html based on --format)
  • Keep --clear-html as backward-compatible alias

Documentation

  • Update README with Markdown export features and new TUI shortcuts
  • Add "Markdown Output Features" section
  • Update style guide script docstring

Test plan

  • Unit tests pass (just test)
  • Type checkers pass (just ty, pyright)
  • Manual TUI testing: h/m/v shortcuts, H/M/V force-regenerate
  • Verify embedded Markdown viewer with ToC
  • Test --clear-output with both HTML and Markdown formats

Summary by CodeRabbit

  • New Features

    • Added support for Markdown output alongside HTML
    • Introduced new keyboard shortcuts in the TUI for quick actions
    • Added ability to export Markdown files to browser
    • Introduced embedded Markdown viewer modal
  • Bug Fixes

    • Enhanced error handling for image export operations
  • Documentation

    • Updated documentation to reflect Markdown output capabilities

✏️ Tip: You can customize this high-level summary in your review settings.

cboos and others added 22 commits December 31, 2025 17:38
Adds concise docstrings showing typical output format to satisfy
docstring coverage requirements (50 methods documented).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The CLI success message was hard-coded to count session-*.html files,
which always reported 0 session files when using --output-format md.
Now derives extension from output_format to count the correct files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Initialize ctx and session_nav before their respective with blocks
so timing lambdas can safely access them even if an exception occurs.
Previously, if _render_messages raised, the lambda would try to access
ctx before assignment, masking the original exception.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Wrap referenced mode file operations in try/except to handle:
- PermissionError from mkdir or write_bytes
- OSError from disk full conditions
- binascii.Error/ValueError from malformed base64 data

Returns None on failure to trigger placeholder rendering,
maintaining graceful degradation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Rename _clear_html_files to _clear_output_files with file_ext parameter
- Add --clear-output as preferred option name (--clear-html still works)
- Clear .md files when --format=md, .html files otherwise
- Update help strings to be format-agnostic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- New MarkdownViewerScreen modal with scrollable content
- Uses Textual's built-in Markdown widget for rendering
- Press ESC or 'q' to dismiss and return to session list
- Reads session Markdown file and displays inline

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add _ensure_session_file() helper that checks if file exists and is
  up-to-date, regenerating if needed (works for both HTML and Markdown)
- Update action_export_selected() (h) to generate HTML on-demand
- Update action_export_markdown() (m) to generate Markdown on-demand
- Update action_view_markdown() (v) to generate Markdown on-demand
- Use load_directory_transcripts and get_renderer() for generation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Replace convert_jsonl_to_html() with ensure_fresh_cache() in TUI startup
- TUI now only updates the cache, not output files
- Users generate HTML/Markdown on-demand via h/m/v shortcuts
- Faster TUI startup, especially for large projects

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Replace Markdown widget with MarkdownViewer for built-in navigation
- Enable show_table_of_contents=True for outline navigation
- Update footer hint to mention 't' for ToC toggle
- Increase viewer size to 95% for better readability
- Remove unused VerticalScroll import

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add CSS to limit ToC width to max 60 columns (~1/3 of viewer)
- Add on_mount hook to collapse tree beyond depth 3 after loading
  (root + children + grandchildren visible, matching HTML fold state)
- Import Tree widget for proper type resolution

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Factor out version comment and tmp path patterns into module-level
constants (_VERSION_PATTERN, _TMP_PATH_PATTERN) to prevent drift
between HTML and Markdown serializers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Strip Textual's default level indicators (Ⅰ, Ⅱ, etc.) from ToC tree
labels for a cleaner outline view.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Use str.replace() instead of startswith() so message type prefixes
("User: ", "Assistant: ", "Thinking: ") are stripped even when they
appear after the emoji icon in ToC labels.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Markdown titles:
- Add excerpts to Sub-assistant titles (was just "Sub-assistant")
- Handle sidechain thinking messages correctly

ToC label cleanup:
- Strip "Sub-assistant: " prefix (like User/Assistant/Thinking)
- Simplify "Task (details): " to "Task: " (details are redundant)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add hidden bindings (show=False) for uppercase H, M, V shortcuts
- H: Force regenerate HTML and open in browser
- M: Force regenerate Markdown and open in browser
- V: Force regenerate Markdown and view in embedded viewer
- Refactor export actions into shared helpers (_export_to_browser,
  _view_markdown_embedded) with force parameter
- Add force parameter to _ensure_session_file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Cast Tree query result to Tree[Any] to satisfy pyright
- Make action_dismiss async with proper signature to match Screen base class

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Update description to mention both HTML and Markdown formats
- Add TUI shortcuts: m (Markdown), v (embedded viewer), H/M/V (force regenerate)
- Add .md files to output file listings
- Add new "Markdown Output Features" section

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Reduces repetition by moving the import from inside each test method
to the module level.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Replace inline "md" if ... else "html" patterns with get_file_extension()
- Update cli.py (2 places), converter.py (1 place), and tui.py (1 place)
- Add get_file_extension import to cli.py and tui.py

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Remove redundant conditional that can never take the else path
since output_path is guaranteed non-None after the assert.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

📝 Walkthrough

Walkthrough

The PR introduces Markdown as a first-class export format alongside HTML. Changes span format-driven CLI logic, enhanced TUI with embedded Markdown viewing capabilities, updated rendering and image-export pipelines, improved error handling, and comprehensive documentation updates.

Changes

Cohort / File(s) Summary
Markdown Rendering Implementation
claude_code_log/markdown/renderer.py, claude_code_log/image_export.py
Added extensive docstrings and formatting logic for various message types (system, user text, slash commands, thinking, assistant, tool results, tasks); refactored code block, blockquote, and collapsible rendering; introduced graceful error handling in image export with try/except wrapping for OSError, binascii.Error, and ValueError to return None on failure instead of propagating errors.
Format-Driven CLI & Conversion
claude_code_log/cli.py, claude_code_log/converter.py
Replaced HTML-specific cache pre-build with generic ensure_fresh_cache(); renamed _clear_html_files() to _clear_output_files() with file-extension parameter; added --clear-output flag; updated all messaging to reference format-derived extensions; made index file extension selection consistent via get_file_extension(); fixed single-file conversion output_dir derivation.
TUI Markdown Viewing & Export
claude_code_log/tui.py
Added MarkdownViewerScreen modal class for embedded Markdown viewing; introduced _export_to_browser(), _view_markdown_embedded(), and _ensure_session_file() helper methods in SessionBrowser; added six public action methods (action_export_selected, action_export_markdown, action_view_markdown, action_force_export_html, action_force_export_markdown, action_force_view_markdown) for export/view workflows.
Rendering Pipeline Safety
claude_code_log/renderer.py
Introduced safe initialization of rendering context (ctx = None) before usage; added defensive checks for ctx availability in log messages.
Documentation & Build Tooling
README.md, scripts/generate_style_guide.py
Updated README to document Markdown support as first-class format, expanded Quick Actions section with explicit key bindings, added Markdown Output Features section; updated style-guide generator docstring to reflect both HTML and Markdown output.
Test Infrastructure Updates
test/snapshot_serializers.py, test/test_markdown_helpers.py, test/__snapshots__/test_snapshot_markdown.ambr
Centralized version and temp-path normalization patterns in serializers; moved strip_error_tags import to module level; updated snapshot content with formatting spacing adjustments and added parameterized decorator code example.

Sequence Diagram(s)

sequenceDiagram
    participant User as TUI User
    participant SessionBrowser
    participant FileSystem
    participant Renderer as Renderer/Converter
    participant Viewer as MarkdownViewerScreen

    User->>SessionBrowser: Select session & action<br/>(export/view markdown)
    activate SessionBrowser
    
    SessionBrowser->>SessionBrowser: _ensure_session_file(session_id, format, force)
    alt Force regeneration OR file missing
        SessionBrowser->>FileSystem: Load transcripts
        FileSystem-->>SessionBrowser: Transcript data
        SessionBrowser->>Renderer: get_renderer(format)
        Renderer-->>SessionBrowser: Markdown renderer instance
        SessionBrowser->>Renderer: Render session content
        Renderer-->>SessionBrowser: Markdown output
        SessionBrowser->>FileSystem: Write session-{id}.md
        FileSystem-->>SessionBrowser: Path to written file
    else File exists AND not forcing
        SessionBrowser->>FileSystem: Locate existing file
        FileSystem-->>SessionBrowser: Path to file
    end
    
    alt View embedded
        SessionBrowser->>SessionBrowser: _view_markdown_embedded()
        SessionBrowser->>Viewer: MarkdownViewerScreen(content)
        activate Viewer
        Viewer-->>User: Display in modal
        User->>Viewer: Read/scroll/close
        Viewer-->>SessionBrowser: Modal closed
        deactivate Viewer
    else Export to browser
        SessionBrowser->>SessionBrowser: _export_to_browser(format)
        SessionBrowser->>FileSystem: Open file in browser
        FileSystem-->>User: Browser displays file
    end
    
    deactivate SessionBrowser
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Add Markdown renderer #71: Adds overlapping Markdown rendering infrastructure (MarkdownRenderer, get_file_extension plumbing, converter/cli signatures) with direct code-level interactions.
  • Consolidate Rendering Architecture #74: Modifies the same rendering and image-export pipelines (markdown/html renderers, image_export error handling, renderer dispatch logic).
  • Polish User Messages #60: Alters renderer message-processing logic (slash-command handling, paired-message rendering) that intersects with changes to markdown/renderer.py.

Poem

🐰 Hops with joy through Markdown lands,
No more HTML-only commands!
Export, view, embed with care,
Format choice floats everywhere,
Sessions shine in .md and more!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main changes: integrating review feedback and adding Markdown preview functionality to the TUI.
Docstring Coverage ✅ Passed Docstring coverage is 96.47% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

🧹 Nitpick comments (5)
claude_code_log/image_export.py (1)

49-67: Robust error handling with graceful degradation.

The exception handling comprehensively covers filesystem failures, permission issues, and base64 decoding errors. Returning None to trigger placeholder rendering is appropriate.

Consider adding logging to improve observability and debugging:

🔎 Suggested enhancement: Add warning log for failed exports
         try:
             # Create images subdirectory
             images_dir = output_dir / "images"
             images_dir.mkdir(exist_ok=True)
 
             # Generate filename based on media type
             ext = _get_extension(image.source.media_type)
             filename = f"image_{counter:04d}{ext}"
             filepath = images_dir / filename
 
             # Decode and write image
             image_data = base64.b64decode(image.source.data)
             filepath.write_bytes(image_data)
 
             return f"images/{filename}"
-        except (OSError, binascii.Error, ValueError):
+        except (OSError, binascii.Error, ValueError) as e:
             # Graceful degradation: return None to trigger placeholder rendering
             # Covers: PermissionError (mkdir/write), disk full, malformed base64
+            import logging
+            logging.warning(f"Failed to export image {counter:04d}: {e}")
             return None
claude_code_log/tui.py (4)

274-276: Move import to module level.

The import re statement is inside the method. For consistency with Python conventions and to avoid repeated imports on each call, move it to the top of the file with other imports.

🔎 Suggested fix

At the top of the file (around line 4), add:

 import os
+import re
 import webbrowser

Then remove the import from the method:

 def _clean_toc_labels(self, node: Any) -> None:
     """Recursively clean tree node labels for a cleaner ToC."""
-    import re
-
     # Unicode roman numerals used by Textual's MarkdownTableOfContents

254-272: Consider logging ToC customization failures.

The broad except clause silently ignores all exceptions. While the comment explains why (ToC might not be ready), this could hide unexpected errors during development or if the Textual API changes.

🔎 Suggested improvement

Consider adding minimal logging for debugging:

         except Exception:
-            pass  # ToC might not be ready yet, or tree structure differs
+            # ToC might not be ready yet, or tree structure differs
+            # Uncomment for debugging: self.log.warning(f"ToC customization failed: {e}")
+            pass

278-286: Consider extracting hardcoded strings as class constants.

The roman numerals and message prefixes are hardcoded in the method. Extracting them as class-level constants would improve maintainability if these need to be adjusted.

🔎 Suggested improvement
 class MarkdownViewerScreen(ModalScreen[None]):
     """Modal screen for viewing Markdown content with table of contents."""
+    
+    # ToC label cleaning patterns
+    ROMAN_NUMERALS = "ⅠⅡⅢⅣⅤⅥ"
+    MESSAGE_PREFIXES = ("User: ", "Assistant: ", "Thinking: ", "Sub-assistant: ")

     CSS = """

Then reference them in the method:

-        # Unicode roman numerals used by Textual's MarkdownTableOfContents
-        roman_numerals = "ⅠⅡⅢⅣⅤⅥ"
-        # Message type prefixes that add clutter in ToC context
-        clutter_prefixes = (
-            "User: ",
-            "Assistant: ",
-            "Thinking: ",
-            "Sub-assistant: ",
-        )
+        roman_numerals = self.ROMAN_NUMERALS
+        clutter_prefixes = self.MESSAGE_PREFIXES

645-674: Consider more specific exception handling.

The broad except Exception clause on Line 673 catches all exceptions. While this provides a good user experience (showing a notification), it makes debugging harder since different error types (file I/O, rendering, etc.) are treated the same.

This is acceptable for a TUI application where graceful degradation is important, but consider at least logging the exception type during development.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f230fe3 and c8f7e87.

📒 Files selected for processing (11)
  • README.md
  • claude_code_log/cli.py
  • claude_code_log/converter.py
  • claude_code_log/image_export.py
  • claude_code_log/markdown/renderer.py
  • claude_code_log/renderer.py
  • claude_code_log/tui.py
  • scripts/generate_style_guide.py
  • test/__snapshots__/test_snapshot_markdown.ambr
  • test/snapshot_serializers.py
  • test/test_markdown_helpers.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use -n auto flag for parallel test execution with pytest in Python projects (e.g., uv run pytest -n auto -m "not (tui or browser)" -v)
Use ruff format for code formatting in Python projects
Use ruff check --fix for linting and automatic fixes in Python projects
Use uv run pyright or uv run ty check for type checking in Python projects

Files:

  • scripts/generate_style_guide.py
  • claude_code_log/image_export.py
  • claude_code_log/converter.py
  • test/test_markdown_helpers.py
  • test/snapshot_serializers.py
  • claude_code_log/renderer.py
  • claude_code_log/markdown/renderer.py
  • claude_code_log/tui.py
  • claude_code_log/cli.py
claude_code_log/renderer.py

📄 CodeRabbit inference engine (CLAUDE.md)

When adding new message types or modifying CSS class generation, ensure the timeline's message type detection logic is updated accordingly and that the filter is applied consistently to messages in both the main transcript and timeline

Files:

  • claude_code_log/renderer.py
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: daaain/claude-code-log PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T16:37:39.485Z
Learning: Implement interactive TUI (Terminal User Interface) for browsing Claude Code sessions with real-time navigation, summaries, and quick actions
📚 Learning: 2025-12-31T16:37:39.485Z
Learnt from: CR
Repo: daaain/claude-code-log PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T16:37:39.485Z
Learning: Document detailed architecture and implementation details in dev-docs/ directory, including rendering pipeline, message types, and CSS class combinations

Applied to files:

  • scripts/generate_style_guide.py
📚 Learning: 2025-12-31T16:37:39.485Z
Learnt from: CR
Repo: daaain/claude-code-log PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T16:37:39.485Z
Learning: Applies to claude_code_log/renderer.py : When adding new message types or modifying CSS class generation, ensure the timeline's message type detection logic is updated accordingly and that the filter is applied consistently to messages in both the main transcript and timeline

Applied to files:

  • scripts/generate_style_guide.py
  • claude_code_log/renderer.py
  • claude_code_log/markdown/renderer.py
📚 Learning: 2025-12-31T16:37:39.485Z
Learnt from: CR
Repo: daaain/claude-code-log PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T16:37:39.485Z
Learning: Implement interactive TUI (Terminal User Interface) for browsing Claude Code sessions with real-time navigation, summaries, and quick actions

Applied to files:

  • README.md
  • claude_code_log/tui.py
📚 Learning: 2025-12-09T23:52:47.578Z
Learnt from: daaain
Repo: daaain/claude-code-log PR: 59
File: test/test_cache.py:135-165
Timestamp: 2025-12-09T23:52:47.578Z
Learning: SQLite supports NULLS FIRST and NULLS LAST in ORDER BY since v3.30.0 (Oct 2019). Do not flag SQL that uses these clauses as an error when reviewing Python tests or code that interacts with SQLite. If reviewing SQL strings, verify the target SQLite version supports NULLS FIRST/LAST and ensure the syntax is used correctly for the database in use.

Applied to files:

  • test/test_markdown_helpers.py
  • test/snapshot_serializers.py
📚 Learning: 2025-12-31T16:37:39.485Z
Learnt from: CR
Repo: daaain/claude-code-log PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T16:37:39.485Z
Learning: Applies to claude_code_log/templates/components/timeline.html : When adding new message types or modifying CSS class generation in renderer.py, ensure the timeline's message type detection logic in timeline.html is updated accordingly to maintain feature parity

Applied to files:

  • claude_code_log/renderer.py
🧬 Code graph analysis (3)
test/test_markdown_helpers.py (1)
claude_code_log/utils.py (1)
  • strip_error_tags (339-356)
claude_code_log/tui.py (2)
claude_code_log/converter.py (2)
  • get_file_extension (37-42)
  • load_directory_transcripts (282-311)
claude_code_log/renderer.py (2)
  • get_renderer (2279-2305)
  • get (121-132)
claude_code_log/cli.py (2)
claude_code_log/converter.py (2)
  • ensure_fresh_cache (553-601)
  • get_file_extension (37-42)
test/test_cache.py (1)
  • cache_manager (43-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: test (windows-latest, 3.12)
  • GitHub Check: test (windows-latest, 3.14)
  • GitHub Check: test (ubuntu-latest, 3.10)
  • GitHub Check: test (ubuntu-latest, 3.12)
  • GitHub Check: test (windows-latest, 3.11)
  • GitHub Check: test (ubuntu-latest, 3.11)
  • GitHub Check: test (windows-latest, 3.13)
  • GitHub Check: test (windows-latest, 3.10)
  • GitHub Check: test (ubuntu-latest, 3.13)
  • GitHub Check: test (ubuntu-latest, 3.14)
🔇 Additional comments (25)
claude_code_log/renderer.py (1)

571-578: LGTM! Fixes timing-lambda UnboundLocalError.

The initialization of ctx and session_nav before their use in log_timing lambdas is correct defensive programming. Since log_timing evaluates the lambda in its finally block (as shown in the relevant code snippet from renderer_timings.py), exceptions occurring before the assignments on lines 575 and 582 would previously cause UnboundLocalError. The if ctx else 0 check on line 573 properly handles this edge case.

This addresses the "fix timing-lambda UnboundLocalError" item from the PR objectives.

claude_code_log/image_export.py (1)

8-8: LGTM!

The binascii import is necessary for catching base64 decoding errors in the exception handler.

scripts/generate_style_guide.py (1)

5-7: LGTM!

The docstring update correctly reflects the script's actual behavior, which generates both HTML and Markdown style guides as shown in the generate_style_guide() function.

test/test_markdown_helpers.py (1)

9-9: LGTM!

Hoisting the import to module level is a good refactor that reduces redundancy and follows Python best practices. The import path is correct.

test/__snapshots__/test_snapshot_markdown.ambr (1)

74-74: LGTM!

The snapshot updates correctly reflect the formatting changes in the Markdown renderer. The extra space after emoji icons (✏️ Edit, ✏️ MultiEdit) aligns with the updated title methods in markdown/renderer.py.

Also applies to: 237-237, 398-398, 529-529

test/snapshot_serializers.py (1)

9-19: Good refactor centralizing normalization patterns.

Extracting shared patterns to module-level constants improves maintainability and ensures consistency between HTML and Markdown serializers.

README.md (1)

3-3: LGTM!

Documentation updates are comprehensive and accurately reflect the new Markdown export capabilities, TUI shortcuts, and file output structure.

Also applies to: 84-91, 121-122, 153-154, 197-207

claude_code_log/converter.py (2)

528-529: LGTM!

Using output_path.parent consistently for output_dir ensures image exports are placed in the correct directory regardless of how output_path was derived.


1170-1171: LGTM!

Using get_file_extension(output_format) for the index file maintains consistency with the format-driven output approach throughout the codebase.

claude_code_log/markdown/renderer.py (4)

252-257: LGTM!

The docstrings follow a consistent format (→ notation) that clearly documents the output shape of each formatter method.


643-652: LGTM!

The double space after emoji icons (✏️ Write, ✏️ Edit, ✏️ MultiEdit) provides consistent visual separation. The snapshot tests confirm this formatting.


691-709: LGTM!

The sidechain handling correctly differentiates sub-assistant responses from main-chain assistant messages. The excerpt extraction from paired messages provides meaningful titles.


718-725: LGTM!

Sidechain assistant messages now get the "🔗 Sub-assistant" prefix, which helps users distinguish between main-chain and sub-agent responses in the rendered Markdown.

claude_code_log/cli.py (5)

47-53: LGTM!

Using ensure_fresh_cache instead of convert_jsonl_to_html is the right approach for TUI startup - it populates the cache without generating output files unnecessarily. The explicit return None on error is correctly handled by callers.


295-360: Good refactor making output file clearing format-aware.

The renamed _clear_output_files function now correctly handles both HTML and Markdown files based on the file_ext parameter. The logic is consistent across all three modes (all projects, single directory, single file).


406-411: LGTM!

The --clear-output option with --clear-html as an alias maintains backward compatibility while providing a more accurate name for the format-aware behavior.


575-582: LGTM!

The clear output logic correctly derives the file extension from the output format and uses it for clearing files.


655-656: LGTM!

Session file counting now correctly uses the format-derived extension instead of hardcoded .html.

claude_code_log/tui.py (7)

8-8: LGTM: Imports are well-organized and appropriate.

All new imports are used in the implementation and support the Markdown viewing functionality.

Also applies to: 13-13, 19-19, 21-21, 26-31


190-248: LGTM: Modal screen structure is well-designed.

The MarkdownViewerScreen class provides a clean modal interface with appropriate CSS styling, bindings, and layout composition. The header, viewer, and footer are properly organized.


350-363: LGTM: Well-designed keyboard bindings.

The binding scheme is intuitive with lowercase keys for normal operations and hidden uppercase variants for force regeneration. This provides power-user functionality without cluttering the UI.


676-701: Embedded viewer implementation looks solid.

The method properly handles file generation, error cases, and screen navigation. The force parameter integration is clean.


703-725: LGTM: Clean action method implementations.

The action methods are simple delegators that follow the Textual framework pattern correctly. The separation between public actions and private implementation methods is appropriate.


819-881: Session file generation logic is well-structured.

The method properly checks for outdated files, handles regeneration, and builds appropriate session titles. The integration with cache_manager and renderer is clean.


908-924: LGTM: Help text clearly documents new functionality.

The updated help text effectively communicates the new Markdown-related shortcuts to users.

Comment on lines +882 to +885
except Exception:
return None

return None
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unreachable code.

Lines 884-885 are unreachable because all code paths in the try-except block above (lines 847-883) already return a value. The final return None will never execute.

🔎 Suggested fix
             if session_content:
                 session_file.write_text(session_content, encoding="utf-8")
                 return session_file
         except Exception:
             return None
-
-        return None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception:
return None
return None
except Exception:
return None
🤖 Prompt for AI Agents
In claude_code_log/tui.py around lines 882 to 885, the final `return None` is
unreachable because every path in the preceding try/except (lines ~847–883)
already returns; remove the redundant `return None` (lines 884–885) so the
function has no dead code and update any surrounding comments if needed.

@cboos
Copy link
Collaborator Author

cboos commented Dec 31, 2025

From coderabbit's review on:

claude_code_log/cli.py (1) 359-437: CLI multi-format support is wired correctly; minor wording/flag semantics are HTML-centric (optional)

To my addition:

For the TUI, IIRC there's the "h" shortcut for generating the HTML; if "m" doesn't do anything yet, it would be nice to use that to produce the markdown. Do that in a separate commit.

And then, I got the idea: "Couldn't we view it directly?"... Excerpt:


🤷 User: Just out of curiosity...

Just out of curiosity... is there a good Markdown renderer for the console (or already integrated with TUI, for that matter?) that we might use here as an embedded "viewer"?

🤖 Assistant: Great question!

Thinking...

The user is asking about Markdown renderers for the console that could be used to display Markdown content directly in the TUI, instead of opening it in a browser. Let me think about what options exist:

  1. Rich - The Python library that Textual is built on top of has excellent Markdown rendering capabilities. It can render Markdown directly to the terminal with syntax highlighting, tables, etc.

  2. Textual itself - Textual has a Markdown widget built-in that can render Markdown content. This would be the most natural choice since we're already using Textual for the TUI.

  3. mdcat - A standalone command-line tool that renders Markdown with syntax highlighting, images (in some terminals), etc.

  4. glow - A popular terminal Markdown viewer from Charm (the same folks behind Bubble Tea, the Go equivalent of Textual).

  5. bat - While primarily for code, it can also render Markdown with syntax highlighting.

Since we're already using Textual, the Markdown widget would be the most natural integration. Let me search for how Textual handles Markdown.

Great question! Since we're already using Textual, there's a built-in Markdown widget that could render content directly in the TUI:

from textual.widgets import Markdown

# Can display markdown directly in the TUI
md_widget = Markdown(markdown_content)

Textual's Markdown widget supports:

  • Headers, lists, emphasis
  • Code blocks with syntax highlighting
  • Tables, blockquotes
  • Links (clickable)

Other options outside Textual:

  • Rich (Textual's foundation) - rich.markdown.Markdown renders to terminal
  • glow - Charm's standalone terminal Markdown viewer (very polished)
  • mdcat - Another CLI Markdown renderer

For your TUI, the Textual Markdown widget would integrate most naturally. You could add a third mode - instead of opening in browser, show an embedded preview panel. Something like pressing v for "view" to toggle a split view showing the rendered Markdown.

Want me to prototype adding an embedded Markdown viewer to the TUI? It could be a nice enhancement - preview without leaving the terminal.


Of course, it's not 100% perfect yet, notably there's a crash when we follow the "Back to combined transcript" link, and loading takes quite some time for long sessions (> 1k messages) to the point it would probably require paging support, etc. But besides that, I think it's nice already!

- **Message Excerpts**: Section titles include message previews for quick navigation
- **Code Preservation**: Syntax highlighting hints via fenced code blocks
- **Embedded Viewer**: TUI includes built-in Markdown viewer with table of contents
- **Image Support**: Configurable image handling (placeholder, embedded base64, or referenced files)
Copy link
Owner

Choose a reason for hiding this comment

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

Is this true? Which is the default?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You mean, for the Image Support? Default is embedded base64 as before for HTML, but that's quite annoying for Markdown, so there the default is 'referenced' (command-line help should tell about these defaults).

Copy link
Owner

@daaain daaain left a comment

Choose a reason for hiding this comment

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

Looks good, just had a question

@daaain
Copy link
Owner

daaain commented Jan 2, 2026

Of course, it's not 100% perfect yet, notably there's a crash when we follow the "Back to combined transcript" link, and loading takes quite some time for long sessions (> 1k messages) to the point it would probably require paging support, etc. But besides that, I think it's nice already!

I'm adding paging in the cache branch!

@cboos
Copy link
Collaborator Author

cboos commented Jan 2, 2026

Of course, it's not 100% perfect yet, notably there's a crash when we follow the "Back to combined transcript" link, and loading takes quite some time for long sessions (> 1k messages) to the point it would probably require paging support, etc. But besides that, I think it's nice already!

I'm adding paging in the cache branch!

Nice ;-) For the paging separation, I had the idea (see #73) to use the "User (compacted conversation)" messages as separator, as I think they provide very natural boundaries. Then, when your restart a page, you'll begin with the summary of what has happened before.

@cboos cboos merged commit fc7aad4 into main Jan 2, 2026
26 of 28 checks passed
@daaain
Copy link
Owner

daaain commented Jan 2, 2026

Nice ;-) For the paging separation, I had the idea (see #73) to use the "User (compacted conversation)" messages as separator, as I think they provide very natural boundaries. Then, when your restart a page, you'll begin with the summary of what has happened before.

The current implementation is on session boundaries, so the page sizes will be a bit different, but you never get a broken up session.

@coderabbitai coderabbitai bot mentioned this pull request Jan 3, 2026
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.

3 participants