From 4c135c63c51d1d089074b7a7738d2e67a9cc2cf6 Mon Sep 17 00:00:00 2001 From: alban Date: Tue, 10 Feb 2026 18:06:32 +0100 Subject: [PATCH] Fix gist preview detection to work on any host, not just hardcoded hostnames Replace hostname checks (gisthost.github.io / gistpreview.github.io) with generic query string detection using /^\?([a-f0-9]{20,})(\/|$)/i in both the link rewriting script and the search script. Co-Authored-By: Claude Opus 4.6 --- src/claude_code_transcripts/__init__.py | 15 ++++--- .../templates/search.js | 16 ++------ ...enerateHtml.test_generates_index_html.html | 16 ++------ ...SessionFile.test_jsonl_generates_html.html | 16 ++------ tests/test_generate_html.py | 39 ++++++++++++++++++- 5 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/claude_code_transcripts/__init__.py b/src/claude_code_transcripts/__init__.py index e4854a3..ea41723 100644 --- a/src/claude_code_transcripts/__init__.py +++ b/src/claude_code_transcripts/__init__.py @@ -1141,14 +1141,13 @@ def render_message(log_type, message_json, timestamp): }); """ -# JavaScript to fix relative URLs when served via gisthost.github.io or gistpreview.github.io -# Fixes issue #26: Pagination links broken on gisthost.github.io +# JavaScript to fix relative URLs when served via gist preview hosts. +# Detects gist preview context by the URL query string pattern ?/... +# Fixes issue #26: Pagination links broken on gist preview hosts GIST_PREVIEW_JS = r""" (function() { - var hostname = window.location.hostname; - if (hostname !== 'gisthost.github.io' && hostname !== 'gistpreview.github.io') return; - // URL format: https://gisthost.github.io/?GIST_ID/filename.html - var match = window.location.search.match(/^\?([^/]+)/); + // Detect gist preview context by query string pattern: ?/... + var match = window.location.search.match(/^\?([a-f0-9]{20,})(\/|$)/i); if (!match) return; var gistId = match[1]; @@ -1176,7 +1175,7 @@ def render_message(log_type, message_json, timestamp): } // Use MutationObserver to catch dynamically added content - // gistpreview.github.io may add content after initial load + // Gist preview hosts may add content after initial load var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { @@ -1209,7 +1208,7 @@ def render_message(log_type, message_json, timestamp): startObserving(); // Handle fragment navigation after dynamic content loads - // gisthost.github.io/gistpreview.github.io loads content dynamically, so the browser's + // Gist preview hosts load content dynamically, so the browser's // native fragment navigation fails because the element doesn't exist yet function scrollToFragment() { var hash = window.location.hash; diff --git a/src/claude_code_transcripts/templates/search.js b/src/claude_code_transcripts/templates/search.js index 6bc337c..231c985 100644 --- a/src/claude_code_transcripts/templates/search.js +++ b/src/claude_code_transcripts/templates/search.js @@ -18,21 +18,13 @@ // Show search box (progressive enhancement) searchBox.style.display = 'flex'; - // Gist preview support - detect if we're on gisthost.github.io or gistpreview.github.io - var hostname = window.location.hostname; - var isGistPreview = hostname === 'gisthost.github.io' || hostname === 'gistpreview.github.io'; - var gistId = null; + // Gist preview support - detect by query string pattern: ?/... + var gistMatch = window.location.search.match(/^\?([a-f0-9]{20,})(\/|$)/i); + var isGistPreview = !!gistMatch; + var gistId = gistMatch ? gistMatch[1] : null; var gistOwner = null; var gistInfoLoaded = false; - if (isGistPreview) { - // Extract gist ID from URL query string like ?78a436a8a9e7a2e603738b8193b95410/index.html - var queryMatch = window.location.search.match(/^\?([a-f0-9]+)/i); - if (queryMatch) { - gistId = queryMatch[1]; - } - } - async function loadGistInfo() { if (!isGistPreview || !gistId || gistInfoLoaded) return; try { diff --git a/tests/__snapshots__/test_generate_html/TestGenerateHtml.test_generates_index_html.html b/tests/__snapshots__/test_generate_html/TestGenerateHtml.test_generates_index_html.html index 693c48f..4aab613 100644 --- a/tests/__snapshots__/test_generate_html/TestGenerateHtml.test_generates_index_html.html +++ b/tests/__snapshots__/test_generate_html/TestGenerateHtml.test_generates_index_html.html @@ -221,21 +221,13 @@

Claude Code transcript

// Show search box (progressive enhancement) searchBox.style.display = 'flex'; - // Gist preview support - detect if we're on gisthost.github.io or gistpreview.github.io - var hostname = window.location.hostname; - var isGistPreview = hostname === 'gisthost.github.io' || hostname === 'gistpreview.github.io'; - var gistId = null; + // Gist preview support - detect by query string pattern: ?/... + var gistMatch = window.location.search.match(/^\?([a-f0-9]{20,})(\/|$)/i); + var isGistPreview = !!gistMatch; + var gistId = gistMatch ? gistMatch[1] : null; var gistOwner = null; var gistInfoLoaded = false; - if (isGistPreview) { - // Extract gist ID from URL query string like ?78a436a8a9e7a2e603738b8193b95410/index.html - var queryMatch = window.location.search.match(/^\?([a-f0-9]+)/i); - if (queryMatch) { - gistId = queryMatch[1]; - } - } - async function loadGistInfo() { if (!isGistPreview || !gistId || gistInfoLoaded) return; try { diff --git a/tests/__snapshots__/test_generate_html/TestParseSessionFile.test_jsonl_generates_html.html b/tests/__snapshots__/test_generate_html/TestParseSessionFile.test_jsonl_generates_html.html index e83424a..7da6c68 100644 --- a/tests/__snapshots__/test_generate_html/TestParseSessionFile.test_jsonl_generates_html.html +++ b/tests/__snapshots__/test_generate_html/TestParseSessionFile.test_jsonl_generates_html.html @@ -212,21 +212,13 @@

Claude Code transcript

// Show search box (progressive enhancement) searchBox.style.display = 'flex'; - // Gist preview support - detect if we're on gisthost.github.io or gistpreview.github.io - var hostname = window.location.hostname; - var isGistPreview = hostname === 'gisthost.github.io' || hostname === 'gistpreview.github.io'; - var gistId = null; + // Gist preview support - detect by query string pattern: ?/... + var gistMatch = window.location.search.match(/^\?([a-f0-9]{20,})(\/|$)/i); + var isGistPreview = !!gistMatch; + var gistId = gistMatch ? gistMatch[1] : null; var gistOwner = null; var gistInfoLoaded = false; - if (isGistPreview) { - // Extract gist ID from URL query string like ?78a436a8a9e7a2e603738b8193b95410/index.html - var queryMatch = window.location.search.match(/^\?([a-f0-9]+)/i); - if (queryMatch) { - gistId = queryMatch[1]; - } - } - async function loadGistInfo() { if (!isGistPreview || !gistId || gistInfoLoaded) return; try { diff --git a/tests/test_generate_html.py b/tests/test_generate_html.py index 25c2822..dc9dbc9 100644 --- a/tests/test_generate_html.py +++ b/tests/test_generate_html.py @@ -567,6 +567,41 @@ def test_gist_preview_js_runs_on_dom_content_loaded(self): # The JS should listen for DOMContentLoaded assert "DOMContentLoaded" in GIST_PREVIEW_JS + def test_gist_preview_js_detects_by_query_string_not_hostname(self): + """Test that GIST_PREVIEW_JS detects gist preview context using the URL + query string pattern (?/...) instead of hardcoded hostnames. + + This allows it to work on any gist preview host, not just + gisthost.github.io and gistpreview.github.io. + """ + # Should NOT contain hardcoded hostname checks + assert "gisthost.github.io" not in GIST_PREVIEW_JS + assert "gistpreview.github.io" not in GIST_PREVIEW_JS + # Should use a regex on location.search to detect gist preview context + assert "location.search" in GIST_PREVIEW_JS + + +class TestSearchJsGistDetection: + """Tests for gist preview detection in search.js template.""" + + @pytest.fixture + def search_js_content(self): + search_js_path = ( + Path(__file__).parent.parent + / "src" + / "claude_code_transcripts" + / "templates" + / "search.js" + ) + return search_js_path.read_text(encoding="utf-8") + + def test_search_js_detects_by_query_string_not_hostname(self, search_js_content): + """Test that search.js detects gist preview context using the URL + query string pattern instead of hardcoded hostnames.""" + assert "gisthost.github.io" not in search_js_content + assert "gistpreview.github.io" not in search_js_content + assert "location.search" in search_js_content + class TestCreateGist: """Tests for the create_gist function.""" @@ -728,9 +763,9 @@ def mock_run(*args, **kwargs): assert result.exit_code == 0 assert (output_dir / "index.html").exists() - # Verify JS was injected (checks for both domains for backwards compatibility) + # Verify gist preview JS was injected index_content = (output_dir / "index.html").read_text(encoding="utf-8") - assert "gisthost.github.io" in index_content + assert "location.search" in index_content class TestContinuationLongTexts: