Skip to content

Commit 77bd3f0

Browse files
committed
feat: single-file release build via GitHub Actions
P0 #1 — 'download one HTML and go' flow for non-git users. Infrastructure: - scripts/build-single.mjs — Node ESM script that inlines lib/marked.min.js into index.html, adds a version banner in <head>, writes the result as dist/claude-code-chat-viewer-<version>.html (~76 KB). - .github/workflows/release.yml — on push of a v* tag: checkout, build, create a GitHub Release with the single HTML attached and a curated body (download instructions, bundled deps, feature list, privacy note, links to docs/demo). generate_release_notes appends commit changelog. Documentation: - CHANGELOG.md — Keep-a-Changelog format, populated with a complete 0.1.0 section listing every feature shipped so far. - README.md — reworked 'How to open' into three paths: single-file download (preferred), repo clone (for hacking), live online demo. .gitignore: - dist/ excluded (build artifacts, only tracked via release assets).
1 parent 99a4bc9 commit 77bd3f0

5 files changed

Lines changed: 161 additions & 3 deletions

File tree

.github/workflows/release.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: "20"
20+
21+
- name: Build single-file HTML
22+
run: node scripts/build-single.mjs "$GITHUB_REF_NAME"
23+
24+
- name: Create GitHub Release
25+
uses: softprops/action-gh-release@v2
26+
with:
27+
files: dist/claude-code-chat-viewer-*.html
28+
generate_release_notes: true
29+
body: |
30+
## 📥 Download
31+
32+
Grab **`claude-code-chat-viewer-${{ github.ref_name }}.html`** from the assets below. One file. Double-click to open in any modern browser. No install, no build step, no server, no CDN — works offline from the first open.
33+
34+
## What's inside
35+
36+
One bundled dependency:
37+
38+
- [marked](https://github.com/markedjs/marked) v12 — markdown → HTML (~35 KB)
39+
40+
That's it. Everything else is vanilla HTML / CSS / JS. Total file weight: ~76 KB.
41+
42+
## Features
43+
44+
- 🖱️ **Drag & drop** any `.jsonl` file onto the page
45+
- 📖 **Reader mode** — hide tool noise, keep only user + assistant messages
46+
- 📋 **Copy per message** button in each entry
47+
- 🔧 **Friendly tool names** — `📝 Edit file`, `🖥️ Shell`, `📖 Read file`…
48+
- 🎨 **Auto light/dark theme** from your OS, with manual override
49+
- 🌐 **Six UI languages** — English, Русский, Español, Français, 中文, العربية (RTL for Arabic)
50+
- 🔒 **Zero network requests** — Markdown images neutralized, external links sandboxed
51+
- ⚡ **Virtualized rendering** — handles transcripts with 10 000+ records
52+
- 🎁 **Try-with-example** button right in the empty state if you don't have a JSONL handy
53+
54+
## Privacy
55+
56+
The viewer makes zero automatic network requests. Your transcripts stay on your machine. Markdown images in transcripts are shown as inert text (never fetched). External links use `rel="noopener noreferrer nofollow"` and open only on click.
57+
58+
## Where to find your transcripts
59+
60+
```
61+
~/.claude/projects/<project-slug>/<session-uuid>.jsonl
62+
```
63+
64+
where `<project-slug>` is your working directory with `/` replaced by `-`.
65+
66+
Full docs: [README](https://github.com/hitmman55/claude-code-chat-viewer#claude-code-chat-viewer) · Live demo: <https://hitmman55.github.io/claude-code-chat-viewer/>
67+
68+
---

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
!demo.jsonl
44
!package.json
55

6+
dist/
67
node_modules/
78
.DS_Store
89
Thumbs.db

CHANGELOG.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here.
4+
5+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/).
6+
7+
## [Unreleased]
8+
9+
Nothing yet.
10+
11+
## [0.1.0] — 2026-04-22
12+
13+
First public release.
14+
15+
### Added
16+
17+
- Single-file HTML viewer for Claude Code JSONL transcripts.
18+
- Six UI languages with live switcher: English, Русский, Español, Français, 中文, العربية. Arabic auto-switches to RTL.
19+
- Light / dark theme with automatic detection from the OS `prefers-color-scheme`. Manual click pins the choice.
20+
- Drag-and-drop file loading — drop anywhere on the page. Folders and non-file drags are rejected cleanly.
21+
- Reader mode — one toggle that hides all service entries, leaving only real user messages and assistant text.
22+
- Copy-per-message button in every entry header. Clipboard API with `document.execCommand` fallback for `file://` contexts. ✓ confirmation for 1.2 s after copy.
23+
- Friendly tool names — 16 known Claude Code tools get emoji + label (`📖 Read file`, `🖥️ Shell`, `📝 Edit file`, …). Unknown / MCP tools keep `🔧 {raw_name}`.
24+
- Bundled `demo.jsonl` + "Try with example" button in the empty state (online only — `fetch()` is blocked on `file://`).
25+
- Five category filters: thinking, tools, results, system/meta, ui-state. Toggle via a single CSS class, no DOM reflow.
26+
- Streaming JSONL parse via `file.stream()` + `TextDecoderStream` — no full-string load.
27+
- Native virtualization via `content-visibility: auto` on each entry.
28+
- Chunked rendering — 500 records per chunk, "Load more" button for the rest.
29+
- Size caps — 20 KB prose / 5 KB service blocks. Stringification is also bounded.
30+
- `.json` fallback — plain JSON arrays/objects are parsed as records too.
31+
32+
### Security / privacy
33+
34+
- XSS-safe markdown rendering: every text block is HTML-escaped before `marked.parse` so raw HTML can never reach the DOM.
35+
- Markdown images are neutralized — rendered as inert text with visible URL, never fetched. Prevents a crafted transcript from leaking the viewer's IP / usage via tracker images.
36+
- External links restricted to `http(s)` and opened with `rel="noopener noreferrer nofollow" target="_blank"`. Other schemes (`javascript:`, `data:`, `file:`, `mailto:`) are rendered as plain text.
37+
- `localStorage` access wrapped in try/catch — survives strict privacy modes.
38+
39+
### Packaging
40+
41+
- Unlicense (public domain, no attribution required).
42+
- GitHub Pages hosted live demo.
43+
- Single-file release build via CI — one HTML with marked.min.js inlined, available as a release asset.
44+
45+
[Unreleased]: https://github.com/hitmman55/claude-code-chat-viewer/compare/v0.1.0...HEAD
46+
[0.1.0]: https://github.com/hitmman55/claude-code-chat-viewer/releases/tag/v0.1.0

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,15 @@ Each block is a separate row with a colored left bar. No messenger bubbles: this
4949

5050
## How to open
5151

52-
1. Clone or download the repo (ZIP is fine). You need `index.html` + the `lib/` folder.
53-
2. Double-click `index.html` — opens in any modern browser.
54-
3. Click the file picker and pick a `.jsonl` transcript.
52+
Pick whichever fits you:
53+
54+
**A. Single-file download** — easiest. Grab the standalone HTML from the [latest release](https://github.com/hitmman55/claude-code-chat-viewer/releases/latest), double-click it. One file, works offline forever.
55+
56+
**B. Clone the repo** — if you want to hack on it. You need `index.html` + the `lib/` folder.
57+
58+
**C. Live online** — just use <https://hitmman55.github.io/claude-code-chat-viewer/>. Nothing to download.
59+
60+
Once open, click the file picker (or drag-drop, or use the "Try with example" button) and pick a `.jsonl` transcript.
5561

5662
Claude Code transcripts live at:
5763

scripts/build-single.mjs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env node
2+
// Build a standalone single-file HTML by inlining lib/marked.min.js into index.html.
3+
// Output: dist/claude-code-chat-viewer-<version>.html
4+
//
5+
// Usage: node scripts/build-single.js [version]
6+
// version — e.g. "v0.1.0" (defaults to $GITHUB_REF_NAME or "dev")
7+
8+
import fs from "node:fs";
9+
import path from "node:path";
10+
import { fileURLToPath } from "node:url";
11+
12+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13+
const version = process.argv[2] || process.env.GITHUB_REF_NAME || "dev";
14+
const outDir = path.join(repoRoot, "dist");
15+
16+
fs.mkdirSync(outDir, { recursive: true });
17+
18+
const html = fs.readFileSync(path.join(repoRoot, "index.html"), "utf8");
19+
const marked = fs.readFileSync(path.join(repoRoot, "lib/marked.min.js"), "utf8");
20+
21+
const scriptTag = '<script src="lib/marked.min.js"></script>';
22+
if (!html.includes(scriptTag)) {
23+
console.error(`error: could not find ${scriptTag} in index.html`);
24+
process.exit(1);
25+
}
26+
27+
const banner = ` <!-- claude-code-chat-viewer ${version} — https://github.com/hitmman55/claude-code-chat-viewer -->`;
28+
29+
const bundled = html
30+
.replace(scriptTag, `<script>${marked}</script>`)
31+
.replace("<head>", `<head>\n${banner}`);
32+
33+
const outFile = path.join(outDir, `claude-code-chat-viewer-${version}.html`);
34+
fs.writeFileSync(outFile, bundled);
35+
36+
const sizeKb = Math.round(bundled.length / 1024);
37+
console.log(`Built ${path.relative(repoRoot, outFile)}${sizeKb} KB`);

0 commit comments

Comments
 (0)