Skip to content

Add timeline report: render folded execution as self-contained HTML - #16

Merged
contrasam merged 3 commits into
mainfrom
claude/timeline-html
Jul 25, 2026
Merged

Add timeline report: render folded execution as self-contained HTML#16
contrasam merged 3 commits into
mainfrom
claude/timeline-html

Conversation

@contrasam

Copy link
Copy Markdown
Contributor

Summary

Implements the v0.2 timeline UI exit criterion (spec §12): a new catalyst-timeline module that renders an execution's folded state as a read-only, self-contained HTML report. The report displays execution status, token/cost roll-ups from timelineView(), and a step-by-step trajectory table with latencies and recorded payloads.

Key Changes

  • New module catalyst-timeline with TimelineReport class:

    • TimelineReport.html(ExecutionState) — renders a complete HTML document with inline styles, no external references
    • TimelineReport.writeTo(ExecutionState, Path) — writes the report to a file, creating parent directories as needed
    • All log content (task types, tool names, effect labels, payloads, error strings) is HTML-escaped to prevent injection
  • Design constraints enforced:

    • Read-only and post-hoc: consumes a folded ExecutionState, installs no runtime hook; the log remains the only source of truth
    • Self-contained: no external scripts, stylesheets, images, or fonts; all styles are inline CSS; can be attached to tickets, committed as artifacts, or opened from file:// URLs
    • Deterministic: the same execution always produces the same HTML (pure function of the fold), making reports diffable and safe to version-control
    • Zero dependencies beyond catalyst-core: no templating engine, no web server, no Jackson beyond what core already uses
  • Report structure:

    • Header with execution ID, task type, status badge, attempt/retry counts, and error message (if failed)
    • Summary section with tiles: model calls, tool calls, prompt/completion tokens, cost, boundary latency, wall clock, step count
    • Timeline table with columns: sequence number, boundary kind (color-coded), label, offset from start, latency, and collapsed detail block for payloads
    • Payloads over 2,000 characters are elided to prevent large completions from bloating the page
  • Comprehensive test coverage:

    • TimelineReportTest: unit tests for HTML generation, escaping, determinism, payload elision, file writing
    • TimelineAcceptanceTest: end-to-end test rendering a mixed execution (model calls, tool calls, effects, memory writes) and verifying self-containment and determinism
  • Integration:

    • Added to Demo class with timeline subcommand demonstrating the full workflow
    • CI workflow updated with v0.2 timeline exit demo (verifies self-contained and deterministic properties)
    • Documentation updated in README and ROADMAP

Implementation Details

  • HTML escaping covers the five dangerous characters (&, <, >, ", ') applied uniformly to all interpolated values
  • Status and kind badges use semantic color coding (green for success, red for failure, yellow for warnings, blue for running/model/tool)
  • The timeline table scrolls horizontally within its own container to prevent oversized payloads or labels from forcing page-level horizontal scroll
  • Payload details use HTML <details> elements for collapsible display, with character count and elision indicator
  • Time formatting converts milliseconds to human-readable strings (e.g., "1.23 s" for values ≥ 1 second)

https://claude.ai/code/session_01G54yVgLZGZ3mzBv4kPc9F3

The last v0.2 item. The log already contains everything an execution did, so
a timeline is a fold, not an instrumentation layer — this renders that fold.

Shaped after the OTel exporter rather than as a live view: it consumes an
ExecutionState (itself only a fold of the log) and installs no runtime hook,
so an execution recorded months ago renders exactly as well as a fresh one,
and the log stays the single source of truth. Two consequences are gated in
CI because they are what make a report worth having:

- Self-contained. Inline CSS, no scripts, fonts or images, so a report opens
  from file:// with nothing else present and can be attached to a ticket.
- Deterministic. Being a pure function of the fold, two renders of one
  execution are byte-identical, which is what lets a report be diffed or
  committed as a build artifact.

Everything interpolated is HTML-escaped. Tool names, effect labels and
recorded payloads are log content, and a report is likely to be opened in a
browser by someone other than whoever produced the execution. Oversized
payloads are elided rather than inlined so one large completion cannot swamp
the page.

The module depends on catalyst-core alone — no templating engine, no web
server. Deliberately left out for now: latency bars and a TrajectoryDiff
view. The table is the read-only view the roadmap asked for; visual timing and
diff rendering are polish on top of it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G54yVgLZGZ3mzBv4kPc9F3
@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown

Greptile Summary

Adds a self-contained HTML timeline report for folded execution state.

  • Introduces catalyst-timeline with deterministic report rendering and file output.
  • Displays execution metadata, aggregate metrics, and recorded timeline-step details.
  • Integrates the report into the API demo, CI exit criteria, tests, documentation, and Maven reactor.
  • Correctly preserves Unicode surrogate-pair boundaries while eliding oversized payloads.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported Unicode truncation defect is corrected by backing up cuts that split surrogate pairs and is covered by focused boundary tests.

Important Files Changed

Filename Overview
catalyst-timeline/src/main/java/com/cajunsystems/catalyst/timeline/TimelineReport.java Implements escaped, deterministic HTML rendering and safely truncates payload details at Unicode code-point boundaries.
catalyst-timeline/src/test/java/com/cajunsystems/catalyst/timeline/TimelineReportTest.java Covers rendering, escaping, determinism, elision, file output, failures, and surrogate-boundary preservation.
catalyst-api/src/test/java/com/cajunsystems/catalyst/api/TimelineAcceptanceTest.java Verifies the complete folded-execution reporting workflow and its self-contained output.
catalyst-api/src/main/java/com/cajunsystems/catalyst/api/Demo.java Adds the timeline exit demo using a mixed execution with model, tool, memory, and effect boundaries.
.github/workflows/ci.yml Adds CI assertions for self-contained and deterministic timeline output.
pom.xml Registers and manages the new timeline module within the Maven reactor.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Execution event log] --> B[ExecutionState fold]
    B --> C[TimelineReport.html]
    C --> D[Self-contained HTML document]
    C --> E[TimelineReport.writeTo]
    E --> F[Portable report file]
Loading

Reviews (3): Last reviewed commit: "CI: restore the streaming step's run blo..." | Re-trigger Greptile

…eview)

Truncating a serialized payload at a raw UTF-16 index can cut between the
halves of a surrogate pair. The lone surrogate left behind is unmappable in
UTF-8, so it reaches the page as a replacement character — a visibly
corrupted last character in what is meant to be a faithful record. Emoji turn
up in completions routinely, so the boundary is worth respecting.

The test sweeps padding lengths around the cut rather than guessing which one
straddles it: the payload is wrapped in a JSON envelope before truncation, so
the offset that lands mid-pair is not predictable from the payload alone. My
first attempt fixed the position analytically and passed against the
unfixed code — the sweep fails at pad=1951 without the fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G54yVgLZGZ3mzBv4kPc9F3
@contrasam
contrasam force-pushed the claude/timeline-html branch from c5c735f to 0685de1 Compare July 25, 2026 07:31
The rebase onto main put the streaming and timeline steps' conflict boundary
mid-step, and resolving it left the streaming step with a name and no run:,
with both scripts concatenated under the timeline step. A step with neither
run: nor uses: is invalid, so the workflow failed to start — the run finished
in the same second it was created, with the workflow shown by path rather
than by name, and no build check ever appeared on the PR.

The YAML parsed fine, which is why the check I ran after the rebase missed
it: it verified structure and unknown keys but never asserted the one thing
that mattered, that every step actually has something to execute. Confirmed
the repaired file by running both restored steps verbatim from the parsed
workflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G54yVgLZGZ3mzBv4kPc9F3
@contrasam
contrasam merged commit 109a9ef into main Jul 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants