feat(html): add event permalinks
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
0ab8627447
commit
36c184e7dd
5 changed files with 21 additions and 3 deletions
|
|
@ -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 `<div class="event">` 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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
16
html.go
16
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
|
|||
<span class="input">%s</span>
|
||||
<span class="dur">%s</span>
|
||||
<span class="status">%s</span>
|
||||
<a class="permalink" href="#evt-%d" aria-label="Direct link to this event" onclick="event.stopPropagation()">#</a>
|
||||
</div>
|
||||
<div class="event-body">
|
||||
`,
|
||||
|
|
@ -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);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue