Skip to content

Commit bf98abe

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 316fd09 commit bf98abe

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
@@ -1638,3 +1638,64 @@ def test_search_total_pages_available(self, output_dir):
16381638

16391639
# Total pages should be embedded for JS to know how many pages to fetch
16401640
assert "totalPages" in index_html or "total_pages" in index_html
1641+
1642+
1643+
class TestIndexCardOrder:
1644+
"""Tests that index cards preserve original prompt order."""
1645+
1646+
def test_index_cards_follow_prompt_order_not_timestamp(self, output_dir):
1647+
"""Cards in index.html must appear in prompt order, even when
1648+
an earlier prompt has a later timestamp than the next prompt.
1649+
1650+
Regression test: when two prompts share near-identical timestamps
1651+
(sub-second apart) the earlier prompt can have a *later* timestamp
1652+
due to message ordering in the JSON, causing the index to show
1653+
#2 before #1.
1654+
"""
1655+
# Prompt 1 has a LATER timestamp than prompt 2
1656+
session_data = {
1657+
"loglines": [
1658+
{
1659+
"type": "user",
1660+
"timestamp": "2025-06-01T12:00:00.500Z",
1661+
"message": {"role": "user", "content": "First prompt"},
1662+
},
1663+
{
1664+
"type": "assistant",
1665+
"timestamp": "2025-06-01T12:00:01.000Z",
1666+
"message": {
1667+
"role": "assistant",
1668+
"content": [{"type": "text", "text": "Reply to first"}],
1669+
},
1670+
},
1671+
{
1672+
"type": "user",
1673+
"timestamp": "2025-06-01T12:00:00.100Z",
1674+
"message": {"role": "user", "content": "Second prompt"},
1675+
},
1676+
{
1677+
"type": "assistant",
1678+
"timestamp": "2025-06-01T12:00:00.600Z",
1679+
"message": {
1680+
"role": "assistant",
1681+
"content": [{"type": "text", "text": "Reply to second"}],
1682+
},
1683+
},
1684+
]
1685+
}
1686+
session_file = output_dir / "session.jsonl"
1687+
with open(session_file, "w") as f:
1688+
for line in session_data["loglines"]:
1689+
f.write(json.dumps(line) + "\n")
1690+
1691+
generate_html(session_file, output_dir)
1692+
1693+
index_html = (output_dir / "index.html").read_text(encoding="utf-8")
1694+
1695+
# Find the positions of #1 and #2 in the HTML
1696+
pos_1 = index_html.index("#1</span>")
1697+
pos_2 = index_html.index("#2</span>")
1698+
assert pos_1 < pos_2, (
1699+
f"Card #1 (pos {pos_1}) should appear before card #2 (pos {pos_2}) "
1700+
"in the index, regardless of timestamps"
1701+
)

0 commit comments

Comments
 (0)