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>
Performance: Replace hljs.highlightAuto() fallback with plain escapeHtml()
for unlabeled code blocks. highlightAuto tries every grammar (~6.7ms/block)
while escapeHtml costs ~0.04ms. With thousands of unlabeled blocks in
typical sessions this dominated render time.
Import shared escapeHtml instead of the local duplicate. Import github-dark
highlight.js theme CSS directly.
Fix highlightSearchText to avoid corrupting HTML entities: split text on
entity patterns (& < etc.) before applying search regex, so searching
for 'amp' does not break & into &<mark>amp</mark>;.
Add unit tests for highlightSearchText covering: plain text matches, empty
queries, avoiding matches inside HTML tags, and preserving HTML entities.
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>
Establish a cohesive dark UI foundation:
- Define CSS custom properties for surfaces (4-level depth hierarchy),
borders, foreground text (3-tier), accent colors, and canvas background
- Add Inter (text) and JetBrains Mono (code) font loading via Google Fonts
- Extend Tailwind with semantic color tokens, typography scale (caption/body/
subheading/heading), box shadows (card, glow), and animations (fade-in,
slide-in, skeleton shimmer)
- Add component-layer utilities: .btn system (primary/secondary/ghost/danger),
.glass frosted overlays, .custom-checkbox, .skeleton loaders
- Add .prose-message typographic styles for rendered markdown content
- Add search minimap CSS (tick marks, viewport indicator)
- Restyle scrollbars for thin dark appearance (WebKit + Firefox)
- Replace hardcoded Tailwind color classes in CATEGORY_COLORS with semantic
tokens (dot/border/text) mapped to the new design system
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>