Skip to content

Commit 81aa170

Browse files
jack-kerouacclaude
andcommitted
Fix index cards appearing out of order when timestamps are non-monotonic
Sort index timeline items by conversation index instead of timestamp, so cards always appear in their original prompt order (simonw#1 before simonw#2) even when sub-second timestamps happen to be non-monotonic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3d201a2 commit 81aa170

File tree

2 files changed

+67
-6
lines changed

2 files changed

+67
-6
lines changed

src/claude_code_transcripts/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,16 +1437,16 @@ def generate_html(json_path, output_dir, github_repo=None):
14371437
item_html = _macros.index_item(
14381438
prompt_num, link, conv["timestamp"], rendered_content, stats_html
14391439
)
1440-
timeline_items.append((conv["timestamp"], "prompt", item_html))
1440+
timeline_items.append((i, conv["timestamp"], item_html))
14411441

14421442
# Add commits as separate timeline items
14431443
for commit_ts, commit_hash, commit_msg, page_num, conv_idx in all_commits:
14441444
item_html = _macros.index_commit(
14451445
commit_hash, commit_msg, commit_ts, _github_repo
14461446
)
1447-
timeline_items.append((commit_ts, "commit", item_html))
1447+
timeline_items.append((conv_idx, commit_ts, item_html))
14481448

1449-
# Sort by timestamp
1449+
# Sort by original conversation order (preserves prompt sequence)
14501450
timeline_items.sort(key=lambda x: x[0])
14511451
index_items = [item[2] for item in timeline_items]
14521452

@@ -1911,16 +1911,16 @@ def generate_html_from_session_data(session_data, output_dir, github_repo=None):
19111911
item_html = _macros.index_item(
19121912
prompt_num, link, conv["timestamp"], rendered_content, stats_html
19131913
)
1914-
timeline_items.append((conv["timestamp"], "prompt", item_html))
1914+
timeline_items.append((i, conv["timestamp"], item_html))
19151915

19161916
# Add commits as separate timeline items
19171917
for commit_ts, commit_hash, commit_msg, page_num, conv_idx in all_commits:
19181918
item_html = _macros.index_commit(
19191919
commit_hash, commit_msg, commit_ts, _github_repo
19201920
)
1921-
timeline_items.append((commit_ts, "commit", item_html))
1921+
timeline_items.append((conv_idx, commit_ts, item_html))
19221922

1923-
# Sort by timestamp
1923+
# Sort by original conversation order (preserves prompt sequence)
19241924
timeline_items.sort(key=lambda x: x[0])
19251925
index_items = [item[2] for item in timeline_items]
19261926

tests/test_generate_html.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,3 +1665,64 @@ def test_search_total_pages_available(self, output_dir):
16651665

16661666
# Total pages should be embedded for JS to know how many pages to fetch
16671667
assert "totalPages" in index_html or "total_pages" in index_html
1668+
1669+
1670+
class TestIndexCardOrder:
1671+
"""Tests that index cards preserve original prompt order."""
1672+
1673+
def test_index_cards_follow_prompt_order_not_timestamp(self, output_dir):
1674+
"""Cards in index.html must appear in prompt order, even when
1675+
an earlier prompt has a later timestamp than the next prompt.
1676+
1677+
Regression test: when two prompts share near-identical timestamps
1678+
(sub-second apart) the earlier prompt can have a *later* timestamp
1679+
due to message ordering in the JSON, causing the index to show
1680+
#2 before #1.
1681+
"""
1682+
# Prompt 1 has a LATER timestamp than prompt 2
1683+
session_data = {
1684+
"loglines": [
1685+
{
1686+
"type": "user",
1687+
"timestamp": "2025-06-01T12:00:00.500Z",
1688+
"message": {"role": "user", "content": "First prompt"},
1689+
},
1690+
{
1691+
"type": "assistant",
1692+
"timestamp": "2025-06-01T12:00:01.000Z",
1693+
"message": {
1694+
"role": "assistant",
1695+
"content": [{"type": "text", "text": "Reply to first"}],
1696+
},
1697+
},
1698+
{
1699+
"type": "user",
1700+
"timestamp": "2025-06-01T12:00:00.100Z",
1701+
"message": {"role": "user", "content": "Second prompt"},
1702+
},
1703+
{
1704+
"type": "assistant",
1705+
"timestamp": "2025-06-01T12:00:00.600Z",
1706+
"message": {
1707+
"role": "assistant",
1708+
"content": [{"type": "text", "text": "Reply to second"}],
1709+
},
1710+
},
1711+
]
1712+
}
1713+
session_file = output_dir / "session.jsonl"
1714+
with open(session_file, "w") as f:
1715+
for line in session_data["loglines"]:
1716+
f.write(json.dumps(line) + "\n")
1717+
1718+
generate_html(session_file, output_dir)
1719+
1720+
index_html = (output_dir / "index.html").read_text(encoding="utf-8")
1721+
1722+
# Find the positions of #1 and #2 in the HTML
1723+
pos_1 = index_html.index("#1</span>")
1724+
pos_2 = index_html.index("#2</span>")
1725+
assert pos_1 < pos_2, (
1726+
f"Card #1 (pos {pos_1}) should appear before card #2 (pos {pos_2}) "
1727+
"in the index, regardless of timestamps"
1728+
)

0 commit comments

Comments
 (0)