Commit Graph

8 Commits

Author SHA1 Message Date
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
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
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
4b13e7eeb9 Add session duration computation to discovery pipeline
Extend SessionEntry with an optional duration field (milliseconds)
computed from the delta between created and modified timestamps.
The computeDuration helper handles missing or invalid dates gracefully,
returning 0 for any edge case. This enables downstream UI to show
how long each session lasted without additional API calls.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 09:25:44 -05:00
0e89924685 Redesign HTML exporter with dark theme, timestamps, and performance fixes
Visual overhaul of exported HTML to match the new client dark design:
- Replace category-specific CSS classes with inline border/dot/text styles
  from a CATEGORY_STYLES map matching client-side colors
- Add message header layout with category dot, label, and timestamp
- Add Inter font family, refined prose typography, and proper code styling
- Add print-friendly media query
- Redesign redacted divider with SVG eye-slash icon and red accent
- Add SVG icons to session header metadata (project, date, message count)
- Fix singular/plural for '1 message' vs 'N messages'

Performance: Skip markdown parsing for hook_progress, tool_result, and
file_snapshot categories (structured data). Render as preformatted text
instead, avoiding expensive marked.parse() on large JSON blobs (~300ms each).

Replace local escapeHtml with shared/escape-html module. Add formatTimestamp
helper. Add cast safety comment for marked.parse() sync usage.

Update test to verify singular message count ('1 message' not '1 messages').

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 01:10:35 -05:00
eb8001dbf1 Harden session discovery with path validation and parallel I/O
Security: Reject session paths containing '..' traversal segments or
non-.jsonl extensions before resolving them. This prevents a malicious
sessions-index.json from tricking the viewer into reading arbitrary files.

Performance: Process all project directories concurrently with Promise.all
instead of sequentially awaiting each one. Each directory's stat + readFile
is independent I/O that benefits from parallelism.

Add test case verifying that traversal paths and non-JSONL paths are rejected
while valid paths pass through.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 01:08:57 -05:00
96da009086 Remove hardcoded Tailscale IP, bind server and Vite to localhost only
The Express server was binding to both 127.0.0.1 and a specific Tailscale IP
(100.84.4.113), creating two separate http.Server instances. Simplify to a
single localhost binding. Also update Vite dev server to use 127.0.0.1 instead
of the Tailscale IP and disable the HMR error overlay which obscures the UI
during development.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 01:08:46 -05:00
090d69a97a Add Express server: session discovery, JSONL parser, HTML exporter, API routes
Server-side implementation for reading, parsing, and exporting Claude
Code session logs:

session-discovery.ts:
- Walks ~/.claude/projects/ directories, reads sessions-index.json from
  each project folder, supports both the current {version, entries}
  format and the legacy raw array format
- Aggregates sessions across all projects, sorted by most recently
  modified first
- Gracefully handles missing directories, corrupt index files, and
  missing entries

session-parser.ts:
- Parses Claude Code JSONL session files line-by-line into normalized
  ParsedMessage objects
- Handles the full real-world format: type="user" (string or
  ContentBlock array content), type="assistant" (text, thinking,
  tool_use blocks), type="progress" (hook events with structured data
  fields), type="summary" (summary text field), type="file-history-
  snapshot", and silently skips type="system" (turn_duration metadata)
  and type="queue-operation" (internal)
- Detects <system-reminder> tags in user messages and reclassifies them
  as system_message category
- Resilient to malformed JSONL lines (skips with continue, no crash)

html-exporter.ts:
- Generates self-contained HTML exports with no external dependencies —
  all CSS (layout, category-specific colors, syntax highlighting) is
  inlined in a <style> block
- Dark theme (GitHub dark palette) with category-specific left border
  colors and backgrounds matching the Claude Code aesthetic
- Renders markdown content via marked + highlight.js with syntax
  highlighting, inserts "content redacted" dividers where redacted
  messages were removed
- Outputs a complete <!DOCTYPE html> document with session metadata
  header (project name, date, message count)

routes/sessions.ts:
- GET /api/sessions — returns all discovered sessions with 30-second
  in-memory cache to avoid re-scanning the filesystem on every request
- GET /api/sessions/:id — looks up session by ID from cache, parses
  the JSONL file, returns parsed messages

routes/export.ts:
- POST /api/export — accepts ExportRequest body, validates required
  fields, generates HTML via the exporter, returns as a downloadable
  attachment with sanitized filename

index.ts:
- Express app factory (createApp) with 50MB JSON body limit, health
  check endpoint, session and export routers, and static file serving
  for the built client
- Dual-bind to localhost and Tailscale IP (100.84.4.113) for local +
  tailnet access, with optional browser-open on startup via
  SESSION_VIEWER_OPEN_BROWSER env var
- Auto-start guard: only calls startServer() when run directly, not
  when imported by tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 22:56:10 -05:00