From 36c184e7ddd587470f30376dad58dc3ddae877ea Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 04:47:53 +0000 Subject: [PATCH] feat(html): add event permalinks Co-Authored-By: Virgil --- docs/architecture.md | 3 ++- docs/history.md | 2 +- html.go | 16 +++++++++++++++- html_test.go | 2 ++ kb/Rendering.md | 1 + 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index b6f188d..dbf552b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -239,10 +239,11 @@ Success or failure of a `tool_use` event is indicated by a Unicode check mark (U Each event is rendered as a `
` containing: -- `.event-header`: always visible; shows timestamp, tool label, truncated input (120 chars), duration, and status icon. +- `.event-header`: always visible; shows timestamp, tool label, truncated input (120 chars), duration, status icon, and a permalink anchor. - `.event-body`: hidden by default; shown on click via the `toggle(i)` JavaScript function which toggles the `open` class. The arrow indicator rotates 90 degrees (CSS `transform: rotate(90deg)`) when the panel is open. Output text in `.event-body` is capped at 400px height with `overflow-y: auto`. +If the page loads with an `#evt-N` fragment, that event is opened automatically and scrolled into view. Input label semantics vary per tool: diff --git a/docs/history.md b/docs/history.md index 479285c..dfa8ebc 100644 --- a/docs/history.md +++ b/docs/history.md @@ -76,5 +76,5 @@ The following have been identified as potential improvements but are not current - **Parallel search**: fan out `ParseTranscript` calls across goroutines with a result channel to reduce wall time for large directories. - **Persistent index**: a lightweight SQLite index or binary cache per session file to avoid re-parsing on every `Search` or `ListSessions` call. - **Additional tool types**: the parser's `extractToolInput` fallback handles any unknown tool by listing its JSON keys. Dedicated handling could be added for `WebFetch`, `WebSearch`, `NotebookEdit`, and other tools that appear in Claude Code sessions. -- **HTML export options**: configurable truncation limits, optional full-output display, and per-event direct links (anchor IDs already exist as `evt-{i}`). +- **HTML export options**: configurable truncation limits and optional full-output display remain open; per-event direct links are now available via `#evt-{i}` permalinks. - **VHS alternative**: a pure-Go terminal animation renderer to eliminate the `vhs` dependency for MP4 output. diff --git a/html.go b/html.go index fa5c2ee..431ee5e 100644 --- a/html.go +++ b/html.go @@ -71,6 +71,8 @@ body { background: var(--bg); color: var(--fg); font-family: var(--font); font-s .event-header .input { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .event-header .dur { color: var(--dim); font-size: 11px; min-width: 50px; text-align: right; } .event-header .status { font-size: 14px; min-width: 20px; text-align: center; } +.event-header .permalink { color: var(--dim); font-size: 12px; min-width: 16px; text-align: center; text-decoration: none; } +.event-header .permalink:hover { color: var(--accent); } .event-header .arrow { color: var(--dim); font-size: 10px; transition: transform 0.15s; min-width: 16px; } .event.open .arrow { transform: rotate(90deg); } .event-body { display: none; padding: 12px; background: var(--bg); border-top: 1px solid var(--border); } @@ -160,6 +162,7 @@ body { background: var(--bg); color: var(--fg); font-family: var(--font); font-s %s %s %s +
`, @@ -174,7 +177,8 @@ body { background: var(--bg); color: var(--fg); font-family: var(--font); font-s html.EscapeString(toolLabel), html.EscapeString(truncate(evt.Input, 120)), durStr, - statusIcon)) + statusIcon, + i)) if evt.Input != "" { label := "Command" @@ -227,12 +231,22 @@ function filterEvents() { el.classList.toggle('hidden', !show); }); } +function openHashEvent() { + const hash = window.location.hash; + if (!hash || !hash.startsWith('#evt-')) return; + const el = document.getElementById(hash.slice(1)); + if (!el) return; + el.classList.add('open'); + el.scrollIntoView({block: 'start'}); +} document.addEventListener('keydown', e => { if (e.key === '/' && document.activeElement.tagName !== 'INPUT') { e.preventDefault(); document.getElementById('search').focus(); } }); +window.addEventListener('hashchange', openHashEvent); +document.addEventListener('DOMContentLoaded', openHashEvent); diff --git a/html_test.go b/html_test.go index f18b82d..1dd9c69 100644 --- a/html_test.go +++ b/html_test.go @@ -73,6 +73,8 @@ func TestHTML_RenderHTMLBasicSession_Good(t *testing.T) { assert.Contains(t, html, "Claude") // assistant event label assert.Contains(t, html, "Bash") assert.Contains(t, html, "Read") + assert.Contains(t, html, `href="#evt-0"`) + assert.Contains(t, html, "openHashEvent") // Should contain JS for toggle and filter assert.Contains(t, html, "function toggle") assert.Contains(t, html, "function filterEvents") diff --git a/kb/Rendering.md b/kb/Rendering.md index 7d897ae..12cb836 100644 --- a/kb/Rendering.md +++ b/kb/Rendering.md @@ -15,6 +15,7 @@ go-session provides two output formats for visualising parsed sessions: a self-c - Yellow: User messages - Grey: Assistant responses - Red border: Failed tool calls +- **Permalinks** on each event card for direct `#evt-N` links ### Usage