Commit Graph

21 Commits

Author SHA1 Message Date
150cd0c686 Wire up ErrorBoundary, URL session sync, j/k navigation, and refresh
Wrap SessionViewer in an ErrorBoundary so render errors show a
recovery UI instead of a white screen.

Sync the selected session ID with the URL search param (?session=).
On initial load, load the session from the URL if it exists in the
session list. On session change, update the URL via
history.replaceState without triggering navigation.

Add j/k keyboard navigation to step through filtered messages.
Search focus takes precedence over keyboard focus; both reset when
filters or session changes.

Add a refresh button in the sidebar header that calls the new
refreshSessions hook, with a spinning icon while loading.

Pass sensitiveCount to FilterPanel for the new badge display.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 13:36:18 -05:00
4ec186d45b Skip entrance animation for messages beyond the first 20
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>
2026-01-30 13:36:02 -05:00
957f9bc744 Polish SearchBar with arrow key navigation and conditional controls
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>
2026-01-30 13:35:55 -05:00
10f23ccecc Pretty-print JSON in tool inputs and preformatted blocks
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>
2026-01-30 13:35:45 -05:00
b0b330e0ba Add session list refresh with server cache bypass
useSession now exposes a refreshSessions() callback that fetches
/api/sessions?refresh=1. The sessions route checks for the refresh
query parameter and resets the cache timestamp to zero, forcing a
fresh scan of ~/.claude/projects/ on the next request.

This enables users to pick up new sessions without restarting the
server or waiting for the 30-second cache to expire.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 13:35:37 -05:00
e5c5e470b0 Add ErrorBoundary component with recovery UI
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>
2026-01-30 13:35:30 -05:00
89ee0cb313 Add Tooltip component and show sensitive message count on auto-redact
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>
2026-01-30 13:35:23 -05:00
4027dd65be Persist filter and auto-redact preferences to localStorage
Category toggles and the auto-redact checkbox now survive page
reloads. On mount, useFilters reads from localStorage keys
session-viewer:enabledCategories and session-viewer:autoRedact,
falling back to defaults when storage is empty, corrupted, or
contains invalid category names. Each state change writes back
to localStorage in a useEffect.

Tests cover round-trip persistence, invalid data recovery, corrupted
JSON fallback, and the boolean coercion for auto-redact.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 13:35:08 -05:00
c66ce4ae16 Change message copy button to copy content instead of anchor link
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>
2026-01-30 10:42:30 -05:00
a51c134da7 Harden API layer: encode session IDs and validate export payload
Session fetch (useSession.ts):
- Wrap the session ID in encodeURIComponent before interpolating
  into the fetch URL. Session IDs can contain characters like '+'
  or '/' that would corrupt the path without encoding.

Export route (export.ts):
- Add validation that redactedMessageUuids, when present, is an
  array. Previously only visibleMessageUuids was checked, so a
  malformed redactedMessageUuids value (e.g. a string or object)
  would silently pass validation and potentially cause downstream
  type errors in the HTML exporter.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 09:34:29 -05:00
4c5d6dd4c8 Extend search to match tool input content, not just message body
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>
2026-01-30 09:34:20 -05:00
bba678568a Polish: simplify formatTimestamp and tone down export button
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>
2026-01-30 09:26:35 -05:00
c0e4158b77 Add time gap indicators and hash anchor navigation to SessionViewer
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>
2026-01-30 09:26:27 -05:00
0f3739605c Add language labels and copy-to-clipboard for code blocks
Custom marked renderer wraps fenced code blocks in a .code-block-wrapper
div containing a language label badge (top-right) and a copy button.
The language label shows the fenced language identifier in uppercase.

A delegated click handler on the document root intercepts clicks on
[data-copy-code] buttons, reads the sibling <code> element's text
content, writes it to the clipboard, and shows a "Copied!" / "Failed"
confirmation that auto-reverts after 1.5 seconds. Delegated handling
is necessary because code blocks are rendered via dangerouslySetInnerHTML
and don't participate in React's synthetic event system.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 09:26:18 -05:00
1dc178f59f Overhaul MessageBubble with collapsible sections, diff rendering, and header actions
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>
2026-01-30 09:26:09 -05:00
50b29ff0a2 Display session duration in sidebar and simplify date formatting
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>
2026-01-30 09:25:51 -05:00
9716091ecc Optimize markdown rendering: skip highlightAuto, fix entity-safe highlighting
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 (&amp; &lt; etc.) before applying search regex, so searching
for 'amp' does not break &amp; 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>
2026-01-30 01:10:22 -05:00
6a4e22f1f8 Add search navigation with match cycling, keyboard shortcuts, and minimap
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>
2026-01-30 01:10:08 -05:00
15a312d98c Redesign all client components with dark theme, polish, and UX improvements
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>
2026-01-30 01:09:41 -05:00
40b3ccf33e Add dark design system with CSS custom properties and refined typography
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>
2026-01-30 01:09:24 -05:00
ecd63cd1c3 Add React client: session browser, message viewer, filters, search, redaction, export
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>
2026-01-29 22:56:37 -05:00