Three new components for rendering tool progress events inline:
ProgressBadge — Attaches below tool_call messages. Shows color-coded
pills counting events by subtype (hook/bash/mcp/agent). Expands on
click to show either a timestamped event log (mixed subtypes) or the
full AgentProgressView (all-agent subtypes). Lazy-renders markdown
only when expanded via useMemo.
ProgressGroup — Standalone progress divider for orphaned progress
events in the message stream. Centered pill-style summary with event
count, subtype breakdown, and time range. Expands to show the same
timestamped log format as ProgressBadge.
AgentProgressView — Rich drill-down view for agent sub-conversations.
Parses agent_progress JSON via parseAgentEvents into a structured
activity feed with:
- Header showing quoted prompt, agent ID, turn count, and time range
- Per-event rows with timestamp, SVG icon (10 tool-specific icons),
short tool name, and summarized description
- Click-to-expand drill-down rendering tool inputs as JSON code blocks,
tool results with language-detected syntax highlighting (after
stripping cat-n line numbers), and text responses as markdown
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Messages at index 20+ no longer receive the animate-fade-in class or
animationDelay inline style. This avoids scheduling hundreds of CSS
animations on large sessions where the stagger would be invisible
anyway (the earlier cap of 300ms max delay was already clamping them).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Arrow Up/Down keys now cycle through search matches while the input
is focused, matching the behavior of browser find-in-page. Navigation
buttons gain active:scale-95 press feedback and explicit sizing.
The right-side control region is now conditionally rendered: keyboard
hint (/) shows when empty, match count + nav + clear show when active.
A visual divider separates navigation arrows from the clear button.
The match count badge highlights the current position number with a
distinct weight.
Tests cover empty state visibility, active search state rendering,
arrow key and button navigation, and clear button behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tool call inputs and structured data categories (hook_progress,
file_snapshot) now attempt JSON.parse + JSON.stringify(_, null, 2)
before escaping to HTML. Non-JSON content passes through unchanged.
The detection fast-paths by checking the first non-whitespace
character for { or [ before attempting parse.
Also renames the copy state variable from linkCopied to contentCopied
to match the current behavior of copying message content rather than
anchor links.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
React class component that catches render errors in child trees and
displays a styled error panel with the exception message and a
'Try again' button that resets state. Follows the existing design
system with red accent colors and gradient icon background.
Tests verify normal rendering, error capture with message display,
and the presence of the recovery button.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduce a reusable Tooltip component with delayed hover reveal,
viewport-aware horizontal nudging, and smooth CSS entrance animation.
Supports top/bottom positioning via a data-side attribute.
FilterPanel now wraps the auto-redact checkbox in a Tooltip that
explains what auto-redaction detects. When sensitive messages exist
in the current view, a red pill badge displays the count next to
the label, giving users immediate visibility into how many messages
contain detectable secrets before toggling auto-redact on.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The per-message copy button previously copied a URL with a hash
anchor (#msg-{uuid}) for deep linking. Replace this with copying
the actual message content: for tool_call messages it copies the
tool name and input; for all other categories it copies the text
body. This is more immediately useful — users copying message
content is far more common than sharing anchor links.
Button title updated from "Copy link to message" to "Copy message
content" to match the new behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The search index (app.tsx) and the per-message match check
(SessionViewer.tsx) now also search msg.toolInput when present.
This means searching for a file path, command, or argument that
appears in a tool call's input will correctly highlight and
navigate to that message, rather than dimming it as a non-match.
Both locations use the same compound condition so the match index
and the visual dimming/focus logic stay in sync.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace try/catch with isNaN guard in the HTML exporter's
formatTimestamp, matching the same cleanup applied client-side.
Downgrade the export button from btn-primary to btn-secondary so it
doesn't compete visually with the main content area. The primary blue
gradient was overly prominent for a utility action.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Time gaps:
- Insert a horizontal divider with duration label ("12m later",
"1h 30m later") between consecutive visible messages separated
by more than 5 minutes
- Computed during the display list build pass alongside redacted
dividers, so no additional traversal is needed
Hash anchor navigation:
- Each message div now has id="msg-{uuid}" for deep linking
- On load, if the URL contains a #msg-* hash, scroll that message
into view with smooth centering and a 3-second highlight ring
- Works with the copy-link feature added to MessageBubble headers
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Collapsible blocks:
- Thinking, tool_call, and tool_result messages start collapsed by default
- Chevron toggle in the header expands/collapses content
- Collapsed preview shows line count (thinking), tool name (tool_call),
or first line truncated to 120 chars (tool_result)
- Collapsed blocks skip expensive markdown/highlight rendering entirely
Diff rendering:
- Detect unified diff content via hunk header + add/delete line heuristics
(requires both @@ headers AND +/- lines to avoid false positives on
YAML or markdown lists with leading dashes)
- Render diffs with color-coded line classes: green additions, red
deletions, blue hunk headers, and muted meta/header lines
- Add full diff-view CSS with background tints and block-level spans
Header actions (appear on hover):
- Copy link button: copies a #msg-{uuid} anchor URL to clipboard with
a checkmark confirmation animation
- Redaction toggle button: replaces the previous whole-card onClick
handler with an explicit eye-slash icon button, colored red when
selected — more discoverable and less accident-prone
Style adjustments:
- Raise dimmed message opacity from 0.2/0.45 to 0.35/0.65 for better
readability during search filtering
- Fix btn-secondary hover border using explicit rgba value instead of
Tailwind opacity modifier (which was generating invalid CSS)
- Position copy button below language label when both are present
- Simplify formatTimestamp with isNaN guard instead of try/catch
- Use fixed h-10 header height for consistent vertical alignment
- Brighten user and assistant message backgrounds (bg-surface-overlay)
to visually distinguish them from other message types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Show human-readable session duration (e.g. "23m", "1h 15m") in the
session list metadata row when duration > 0. Add formatSessionDuration
helper that handles sub-minute, minute-only, and hour+minute ranges.
Also replace try/catch in formatDate with an isNaN guard on the parsed
Date, which is more idiomatic and avoids swallowing unrelated errors.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement full-featured search navigation across the session viewer:
App.tsx: Compute matchIndices (filtered message indices matching the query),
track currentMatchPosition state, and provide goToNext/goToPrev callbacks.
Add scroll tracking via ResizeObserver + scroll events for minimap viewport.
Restructure toolbar layout with centered search bar and right-aligned export.
Pass focusedIndex to SessionViewer for scroll-into-view behavior.
SearchBar: Redesign as a unified search container with integrated match count
badge, prev/next navigation arrows, clear button, and keyboard hint (/).
Add keyboard shortcuts: Enter/Shift+Enter for next/prev, Ctrl+G/Ctrl+Shift+G
for navigation, Escape to clear and blur. Show 'X/N' position indicator and
'No results' state.
SessionViewer: Add data-msg-index attributes for scroll targeting via
querySelector instead of individual refs. Memoize displayItems list. Add
MessageSkeleton component for loading state. Add empty state illustrations
with icons and descriptive text. Apply staggered fade-in animations and
search-match-focused outline to the active match.
SearchMinimap: New component rendering match positions as amber ticks on a
narrow strip overlaying the scroll area's right edge. Includes viewport
position indicator and click-to-jump behavior.
Add unit tests for SearchMinimap: empty/zero states, tick rendering,
active tick styling, viewport indicator, and click handler.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MessageBubble: Replace border-left colored bars with rounded cards featuring
accent strips, category dot indicators, and timestamp display. Use shared
escapeHtml. Render tool_result, hook_progress, and file_snapshot as
preformatted text instead of markdown (avoids expensive marked.parse on
large JSON/log blobs).
ExportButton: Add state machine (idle/exporting/success/error) with animated
icons, gradient backgrounds, and auto-reset timers. Replace alert() with
inline error state.
FilterPanel: Add collapsible panel with category dot colors, enable count
badge, custom checkbox styling, and smooth animations.
SessionList: Replace text loading state with skeleton placeholders. Add
empty state illustration with descriptive text. Style session items as
rounded cards with hover/selected states, glow effects, and staggered
entry animations. Add project name decode explanation comment.
RedactedDivider: Add eye-slash SVG icon, red accent color, and styled
dashed lines replacing plain text divider.
useFilters: Remove unused exports (setAllCategories, setPreset, undoRedaction,
clearAllRedactions, selectAllVisible, getMatchCount) to reduce hook surface
area. Match counting moved to App component for search navigation.
SessionList.test: Update assertions for skeleton loading state and expanded
empty state text.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Full React 18 client application for interactive session browsing:
app.tsx:
- Root component orchestrating session list, viewer, filters, search,
redaction controls, and export — wires together useSession and
useFilters hooks
- Keyboard navigation: j/k or arrow keys for message focus, Escape to
clear search and redaction selection, "/" to focus search input
- Derives filtered messages, match count, visible UUIDs, and category
counts via useMemo to avoid render-time side effects
hooks/useSession.ts:
- Manages session list and detail fetching state (loading, error,
data) with useCallback-wrapped fetch functions
- Auto-loads session list on mount
hooks/useFilters.ts:
- Category filter state with toggle, set-all, and preset support
- Text search with debounced query propagation
- Manual redaction workflow: select messages, confirm to move to
redacted set, undo individual or all redactions, select-all-visible
- Auto-redact toggle for the sensitive-redactor module
- Returns memoized object to prevent unnecessary re-renders
components/SessionList.tsx:
- Two-phase navigation: project list → session list within a project
- Groups sessions by project, shows session count and latest modified
date per project, auto-drills into the correct project when a session
is selected externally
- Formats project directory names back to paths (leading dash → /)
components/SessionViewer.tsx:
- Renders filtered messages with redacted dividers inserted where
manually redacted messages were removed from the visible sequence
- Loading spinner, empty state for no session / no filter matches
- Scrolls focused message into view via ref
components/MessageBubble.tsx:
- Renders individual messages with category-specific Tailwind border
and background colors
- Markdown rendering via marked + highlight.js, with search term
highlighting that splits HTML tags to avoid corrupting attributes
- Click-to-select for manual redaction, visual selection indicator
- Auto-redact mode applies sensitive-redactor to content before render
- dangerouslySetInnerHTML is safe here: content is from local
user-owned JSONL files, not untrusted external input
components/FilterPanel.tsx:
- Checkbox list for all 9 message categories with auto-redact toggle
components/SearchBar.tsx:
- Debounced text input (200ms) with match count display
- "/" keyboard shortcut to focus, × button to clear
components/ExportButton.tsx:
- POSTs current session + visible/redacted UUIDs + auto-redact flag
to /api/export, downloads the returned HTML blob as a file
components/RedactedDivider.tsx:
- Dashed-line visual separator indicating redacted content gap
lib/types.ts:
- Re-exports shared types via @shared path alias for client imports
lib/constants.ts:
- Tailwind CSS class mappings per message category (border + bg colors)
lib/markdown.ts:
- Configured marked + highlight.js instance with search highlighting
that operates on text segments only (preserves HTML tags intact)
styles/main.css:
- Tailwind base/components/utilities, custom scrollbar, highlight.js
overrides, search highlight mark, redaction selection outline,
message dimming for non-matching search results
index.html + main.tsx:
- Vite entry point mounting React app into #root with StrictMode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>