-
-
Notifications
You must be signed in to change notification settings - Fork 53
Introduce a "code view" of transcripts (git blame style) #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
btucker
wants to merge
92
commits into
simonw:main
Choose a base branch
from
btucker:feat/code-viewer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add a --code-view flag that generates a Code tab showing files modified during the session: - Tree view of modified files on the left panel - CodeMirror editor on the right with syntax highlighting - Git blame-style gutter showing which transcript message wrote each line - Click blame annotation to jump to the transcript message - Tab navigation between Transcript and Code views Supports both local git repos and public GitHub URLs: - Local repos: uses git show HEAD:filepath - GitHub URLs: fetches via GitHub API Key additions: - FileOperation and FileState dataclasses for tracking file changes - extract_file_operations() to parse Write/Edit tool calls - get_initial_file_content() for git/GitHub integration - reconstruct_file_with_blame() for blame attribution algorithm - CodeMirror 6 integration with custom blame gutter extension - Tab bar in index.html and page.html templates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Escape `</` and `<!--` sequences in embedded JSON to prevent the browser from prematurely terminating the <script> block when file contents contain these HTML-significant patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove git file fetching functionality that was causing performance issues. The code viewer now shows: - For new files (first op is Write): full content with blame attribution - For existing files (first op is Edit): diff-only view showing changes Removed: - get_initial_file_content, _get_file_from_local_git, _get_file_from_github - session_cwd extraction (no longer needed) - mode parameter from generate_code_view_html - Debug print statements - Related tests for git fetching Full file reconstruction from git can be added as a future enhancement. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Shows "Page X/Y: rendering message N/M..." when a page has more than 50 messages. Uses carriage return to update in place and clears when done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Major redesign of the code view: - Three resizable panes: file tree, code editor, and transcript - Uses temporary git repo to track file changes for accurate blame - Click code lines to scroll transcript to the relevant message - Full transcript in right pane (same as Transcript tab) - Draggable separators between all panels UI improvements: - Tab styling connects to header border (active tab "emerges" from content) - File status indicators (green dot for added, orange for modified) - Header stays full-width on both tabs for stable navigation - Transcript content centered at 800px max-width 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace the global source_repo_path parameter with automatic per-file git repository detection. For Edit operations on files not yet in the temp repo, we now walk up from each file's directory to find its git repo and fetch initial content from HEAD. Falls back to reading from disk if no git repo is found. This handles cases where files span multiple git repositories and removes the need for the --repo CLI flag when files already exist in git repos on disk. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For large transcripts, having all messages in the DOM at once can cause slow renders and high memory usage. This adds chunked rendering: - Store messages as a JS array instead of embedding all HTML in DOM - Render first 50 messages initially - Use IntersectionObserver to load more as user scrolls down - scrollToMessage() ensures target message is rendered before scrolling - Build ID-to-index map for fast message lookup This keeps the initial DOM small while still supporting full transcript navigation when clicking on blame sections in the code viewer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Lines that existed before the session (no msg_id) now have a plain white background instead of being colored. Only lines that were actually written or edited during the session get blame colors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Defensive CSS to prevent markdown content from breaking out of containers if it contains malformed HTML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The chunked rendering broke message truncation because the truncation initialization only ran on page load. Now we initialize truncation for newly rendered messages after each chunk is added to the DOM. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When fetching initial content for Edit operations, we now commit it first (with empty metadata) before applying the edit. This allows git blame to correctly attribute unchanged lines to the initial commit (which has no msg_id) and only the actually changed lines to the edit commit. Previously, all lines in an edited file were attributed to the first edit operation, even lines that weren't changed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests verify that: - Write operations attribute all lines to that operation - Edit operations only attribute changed lines, not context - Multiple edits to the same file are tracked separately 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
A thin vertical strip next to the editor shows colored markers for each blame range, making it easy to see where changes are in long files. Clicking a marker scrolls to that location and highlights it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Refactors the code viewer functionality into a separate module: - Moves ~1000 lines of code viewer logic to code_view.py - Reduces __init__.py from ~3100 to ~2433 lines - Includes FileOperation, FileState, CodeViewData, BlameRange dataclasses - Includes all git-based blame attribution functions - Removes obsolete escape_json_for_script test (fixed in python-markdown) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Extract CSS to templates/styles.css (~260 lines) - Improves maintainability with proper CSS formatting - Allows future CSS tooling integration 2. Add group_operations_by_file() helper in code_view.py - Consolidates duplicate file grouping logic - Used by build_file_states() and generate_code_view_html() 3. Add buildRangeColorMap() helper in code_view.js - Pre-computes color assignments for blame ranges - Ensures consistent colors between editor decorations and minimap - Eliminates duplicate color tracking logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Escape </ sequences in JSON data embedded in <script> tags to prevent premature script tag closing when file content contains </script>. Adds test to verify the escaping works correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…f any edit fails or is malformed, our reconstruction diverges from reality. Subsequent edits also fail because their old_string doesn't match our corrupted state.
- Strip common top-level directories from file tree paths for cleaner display - Fix blame highlighting not appearing by using StateField instead of ViewPlugin - Remove final sync step that was destroying blame attribution - Add tests for common prefix stripping behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ValueError to the exception handler to gracefully handle cases where the temporary git repo has no commits yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When viewing sessions from remote URLs (like gists), the local filesystem isn't available to read initial file content before edits. This change extracts the originalFile field from toolUseResult in JSONL sessions and uses it as a fallback when reconstructing file state. - Add original_content field to FileOperation dataclass - Extract originalFile from toolUseResult entries in extract_file_operations() - Use original_content as fallback in build_file_history_repo() when file doesn't exist locally - Add test coverage for originalFile extraction 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The JSONL parser was stripping out the toolUseResult field, which contains the originalFile content needed for code reconstruction in remote sessions. This caused code.html to have empty fileData when processing JSONL URLs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Ensures the toolUseResult field (containing originalFile content) is preserved when parsing JSONL files, which is needed for remote session code reconstruction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Skill expansion messages have isMeta=True in JSONL sessions. These are system-injected, not user-typed prompts. They should be treated as continuations of the previous conversation for prompt numbering purposes. This fixes the code viewer showing all blame as "prompt simonw#2" when a skill was invoked - now all operations are correctly attributed to prompt #1 (the original user request to invoke the skill). - Preserve isMeta field in JSONL parsing - Treat isMeta entries like isCompactSummary (continuations) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously each Write/Edit operation got a unique color based on its msg_id. Now all operations from the same prompt get the same color, making it easier to visually identify which prompt caused which changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace the stats/summary in tooltips with the assistant text that immediately preceded the tool call. This provides more useful context about what Claude was thinking when making each change. - Capture assistant text blocks before tool_use - Display as "Assistant context" in tooltip - Truncate long assistant text to 500 chars - Add CSS styling for tooltip-assistant section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use gh gist edit --add to add files to same gist in batches instead of creating multiple separate gists - Add descriptive gist names like "claude-code-transcripts local session-name" - Simplify code by removing DATA_GIST_IDS handling since we only have one data gist now 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Generate index-data.json with index items HTML for large sessions - Update index.html template to load from JSON on gistpreview - Strip inline content from index.html when uploading to gist - Increase batch size limits (10MB, 100 files) for adding files to gists - Add tests for index-data.json generation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of stripping content with regex after rendering, modify the templates to conditionally exclude content when use_page_data_json or use_index_data_json is true. This is cleaner and more reliable. Also update tests to expect the new create + edit gist pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Large sessions now always include content in the generated HTML files, allowing local viewing without JavaScript. Content is stripped from HTML during inject_gist_preview_js when uploading to gist with a data gist ID, keeping gist files small. - Always pass use_*_data_json=False to templates (include content) - Add _strip_container_content helper for HTML content stripping - Strip content and inject loader JS in inject_gist_preview_js only - Move PAGE_DATA_LOADER_JS and INDEX_DATA_LOADER_JS to Python constants - Templates stay clean - loader JS only added during gist upload - Add test for large sessions including content in local HTML 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The gh gist edit --add command with multiple --add flags was silently failing to add some files. Change to add files one at a time to ensure all files are uploaded reliably. Also add verbose logging to show which files are being uploaded. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GitHub's API returns HTTP 409 conflicts when updating gists too rapidly. Add retry logic with exponential backoff (2s, 4s, 6s) for 409 errors, plus a 0.5s delay between file additions to avoid rate limiting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Since files are now added one at a time with delays, the batch grouping logic was no longer needed and just added confusing output. Removed _batch_files_for_gist and related constants. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… get styles/js to work
For large transcripts (12K+ messages), auto-scrolling to messages would render thousands of DOM nodes, causing the browser to hang for several seconds on file switches and blame clicks. Changes: - Remove auto-scroll to transcript on file load (just highlight code) - Remove auto-scroll on blame click (tooltip shows message on hover) - Remove auto-scroll in navigateToBlame (user already sees transcript) - Keep progressive rendering infrastructure for future use - Update tests to match new behavior The tooltip on hover still shows the full message content, and users can manually scroll the transcript if needed. This keeps the UI responsive even for very large transcripts. Performance improvement: - Before: 5+ second hang when switching files or clicking blame - After: Instant response (<50ms) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move computation of user prompt numbers and color indices from the client-side JavaScript to the server-side Python during git repo build. This eliminates client-side walking of data for each blame block. Changes: - build_msg_to_user_html now returns 3 values including msg_to_prompt_num - generate_code_view_html computes color_index per unique context_msg_id - Each blame range now includes prompt_num and color_index fields - Simplified buildRangeMaps JS to use pre-computed values directly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Restored the call to scrollToMessage() in the blame click handler. This was previously removed for performance but is needed for proper navigation between code and transcript views. Added e2e test to verify clicking a blame block scrolls to and highlights the corresponding message in the transcript panel. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of rendering all messages between the current view and the target when navigating to a distant blame block, the transcript now "teleports" to the target location by clearing the DOM and rebuilding from the user prompt containing the target message. Key changes: - Track windowStart/windowEnd instead of just renderedCount - Add findUserPromptIndex() to locate conversation boundaries - Add teleportToMessage() to jump to distant messages instantly - Add bi-directional lazy loading (scroll up loads earlier messages) - Add top sentinel for upward IntersectionObserver - Move bottom sentinel inside transcript-content div This keeps DOM size small regardless of where in the transcript the user navigates, while still allowing smooth scrolling in both directions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes: 1. Auto-select first blame block on file load - now that teleportation makes transcript navigation instant, re-enable auto-scrolling 2. Add prompt # to user message headers - shows "User simonw#1", "User simonw#2" etc. for each user prompt (excluding tool result messages) 3. Add prompt # to pinned user message - shows "#N message text..." 4. Fix pinned user prompt overlap - hide the pinned header when the next user message would overlap with it, rather than showing both 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The pinned message was flashing because hiding it changed offsetHeight to 0, which shifted the threshold calculations and caused unstable show/hide behavior. Fix by caching the pinned message height and using the cached value when the element is hidden. Also simplified the threshold logic to use a single pinnedAreaBottom value for both determining what to pin and detecting overlap. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed pinned user message format from: Label: "User prompt:" Content: "#1 message text..." To: Label: "User Prompt #1" Content: "message text..." This puts the prompt number in the header where it's more prominent. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When teleporting to a distant message, the initial chunk was only rendering CHUNK_SIZE messages from the user prompt. If the target message (e.g., an assistant edit) was beyond that chunk, it wouldn't be in the DOM and the scroll would fail or scroll to the wrong place. Fixed by ensuring initialEnd is at least the target message index, so the target is always rendered after teleporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix URL fragment handling to scroll code and select blame block on load - Call scrollToMessage when navigating from hash to update transcript - Use setTimeout to wait for editor initialization when loading new file - Change selected blame highlight color from blue to bright yellow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use bright yellow highlight color for transcript message border (matches the blame block highlight color) - Skip blame highlighting on initial file load, but still scroll the editor and transcript to the first blame - Fix navigateToBlame timing when loading a different file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Shows progress for: - Replaying file operations (when > 20 operations) - Processing files for blame data (when > 5 files) Uses carriage return for in-place updates, similar to page rendering. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes leftover characters when switching from operations to files phase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds "Prompt" to match the pinned message label format. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The pinned user prompt header was showing a different prompt number than the tooltip when clicking blame blocks. This was caused by inconsistent counting between server-side (tooltip) and client-side (pinned header): - Server-side build_msg_to_user_html() was skipping continuation conversations, but the HTML renderer included them in the count - Client-side getPromptNumber() was excluding messages with class="continuation" but this didn't match the server logic Fix: - Remove continuation skip in build_msg_to_user_html() - Update _collect_conversation_messages() to only return current conv - Update client-side getPromptNumber() to include continuation messages - Change tooltip heading from "#X" to "User Prompt #X" for consistency Also fixes: - Race condition when loading file from URL hash (check hash first) - CSS selector bug in updatePinnedUserMessage() - Ensure user prompt is loaded when teleporting to distant messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two issues fixed: 1. Prompt number mismatch: Client-side getPromptNumber was counting ALL user messages including tool_results, but server only counts real prompts (messages with "User Prompt #N" label). Fixed by checking for ">User Prompt #" in message HTML instead of just class="message user". 2. Pinned click not working: After teleportation, the onclick handler held a stale reference to a DOM element that was removed. Fixed by storing the message ID and looking up the element on click, with fallback to scrollToMessage if element is not in DOM. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of computing prompt numbers client-side (which was error-prone with off-by-one issues), include the canonical prompt_num in the messagesData JSON from the server. The server already computes prompt numbers correctly when rendering HTML, so we just add that to the message data payload. Client-side getPromptNumber() now reads directly from messagesData[i].prompt_num. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Based on code quality review, addressed three issues: 1. Remove duplicate imports - removed redundant `import re` (appeared twice inside functions) and `import json` (inside function, already at module level) 2. Add proper is_recursive field to FileOperation instead of reusing replace_all field for delete operations. This was a code smell where a boolean meant for "replace all occurrences" was being used for "is recursive delete". 3. Consolidate read_blob_content and read_blob_bytes into single read_blob(decode=True/False) function, keeping backwards-compatible aliases for the original function names. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Merged upstream's JSON URL functionality, removing our duplicate implementation of is_url and fetch_session_from_url in favor of upstream's is_url and fetch_url_to_tempfile. Kept our code-view options (--code-view, --exclude-deleted-files) while adopting upstream's URL handling logic and variable naming. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Author
|
Quick update that I got inspired to spin off all the git pieces of this into it's own project: https://github.com/btucker/agentgit Still very much a WIP, but I think there's potential. I'm going to keep this PR open as a draft for the moment, but come back and slim it down using agentgit as a library |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
When reviewing work aided by coding agents I've often wanted to explore the chronological transcript side-by-side with the code. I find having the provanence easily explorable is valuable to keeping my mental model aligned with the codebase. I've been messing with different UI concepts around this & was inspired by the release of claude-code-transcripts to bring these ideas here.
Here's an example of what this looks like, the complete sessions of building this feature
Or a more reasonably sized session, care of @simonw: https://gistpreview.github.io/?df987f5892de7a02f003944d88211533/code.html
Along the way building this, "we" (Claude Code & I) discovered an infinite loop bug in python-markdown. The discovery & fix are memorialized in this session. Humorously, this lead to the discovery of another bug in this PR if the transcript contains unterminated HTML comments.
In a nutshell, this PR adds a new flag,
--code-viewthat works forlocal,json, &webgeneration. Under the hood, this is how it works:Extract Operations
- Parses session loglines for Write, Edit, and Bash tool calls
- Creates FileOperation objects with file path, content/diff, timestamp, and message ID
- Detects rm commands in Bash calls → OP_DELETE operations
Build Temp Git Repo
- Creates a temporary git repository
- Replays operations chronologically as commits:
- Each commit stores metadata (tool_id, msg_id, timestamp) in the commit message
Generate Blame Data
- Runs git blame on each file in the final repo state
- Groups consecutive lines by commit → BlameRange objects
- Maps ranges back to the original prompt/message that made the change
Render HTML
- File tree: Built from files that exist in final repo state (deleted files excluded)
- Code panel: Uses CodeMirror 6 with custom decorations for blame highlighting
- Transcript panel: Full conversation with click-to-navigate links
- Blame ranges link code lines ↔ transcript messages bidirectionally
The UI is far from perfect. I tried to stay true to not introducing any frontend frameworks with the exception of CodeMirror (which is loaded from
esm.sh). We did some work on performance optimizations for huge sessions. For smaller sessions, it's much more nimble. For example:$ uv run claude-code-transcripts json https://gist.githubusercontent.com/simonw/bfe117b6007b9d7dfc5a81e4b2fd3d9a/raw/31e9df7c09c8a10c6fbd257aefa47dfa3f7863e5/3f5f590c-2795-4de2-875a-aa3686d523a1.jsonl --code-view --gist Fetching session from URL... Warning: Could not auto-detect GitHub repo. Commit links will be disabled. Generated page-001.html Generated /private/var/folders/sl/rhfr008x7s56dc6bsbnwh3qh0000gn/T/claude-session-tmp9n674xpt/index.html (2 prompts, 1 pages) Generated code.html (7 files) Output: /private/var/folders/sl/rhfr008x7s56dc6bsbnwh3qh0000gn/T/claude-session-tmp9n674xpt Creating GitHub gist... Gist: https://gist.github.com/btucker/c0fdb0e0a763a983a04a5475bb63954e Preview: https://gistpreview.github.io/?c0fdb0e0a763a983a04a5475bb63954e/index.html-> See resulting code view
I ran into an issue that gistpreview relies listing all files in the gist. This fails if you exceed the size limit as then every file after you hit the limit is blank. To work around this I adopted an approach of putting data files in a separate gist. These can then be loaded via the raw API & doesn't have the same limitations. This was a pre-existing issue, but exacerbated by the code view. Search is also updated to make use of these data files instead of the HTML if available. This ended up being a large part of the PR.
Also added are e2e tests with python-playwright. This was very helpful as the complexity of the code view UI increased.
This is obviously a huge, wide-ranging PR and no hard feelings if you do not want to accept it into the main codebase. It could also be reasonably split up into 3 changes:
adding support for json to be a url(already added in main)