Skip to content

Commit d1e51f5

Browse files
btuckerclaude
andcommitted
Add prompt numbers to user messages and improve pinned header behavior
Changes: 1. Auto-select first blame block on file load - now that teleportation makes transcript navigation instant, re-enable auto-scrolling 2. Add prompt # to user message headers - shows "User #1", "User simonw#2" etc. for each user prompt (excluding tool result messages) 3. Add prompt # to pinned user message - shows "#N message text..." 4. Fix pinned user prompt overlap - hide the pinned header when the next user message would overlap with it, rather than showing both 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 66f5803 commit d1e51f5

File tree

5 files changed

+93
-18
lines changed

5 files changed

+93
-18
lines changed

src/claude_code_transcripts/__init__.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ def is_tool_result_message(message_data):
10501050
)
10511051

10521052

1053-
def render_message(log_type, message_json, timestamp):
1053+
def render_message(log_type, message_json, timestamp, prompt_num=None):
10541054
if not message_json:
10551055
return ""
10561056
try:
@@ -1063,7 +1063,8 @@ def render_message(log_type, message_json, timestamp):
10631063
if is_tool_result_message(message_data):
10641064
role_class, role_label = "tool-reply", "Tool reply"
10651065
else:
1066-
role_class, role_label = "user", "User"
1066+
role_class = "user"
1067+
role_label = f"User #{prompt_num}" if prompt_num else "User"
10671068
elif log_type == "assistant":
10681069
content_html = render_assistant_message(message_data)
10691070
role_class, role_label = "assistant", "Assistant"
@@ -1639,6 +1640,9 @@ def generate_html(
16391640
# Collect messages per page for potential page-data.json
16401641
page_messages_dict = {}
16411642

1643+
# Track prompt number across all pages
1644+
prompt_num = 0
1645+
16421646
for page_num in range(1, total_pages + 1):
16431647
start_idx = (page_num - 1) * PROMPTS_PER_PAGE
16441648
end_idx = min(start_idx + PROMPTS_PER_PAGE, total_convs)
@@ -1657,7 +1661,19 @@ def generate_html(
16571661
end="",
16581662
flush=True,
16591663
)
1660-
msg_html = render_message(log_type, message_json, timestamp)
1664+
# Track prompt number for user messages (not tool results)
1665+
current_prompt_num = None
1666+
if log_type == "user" and message_json:
1667+
try:
1668+
message_data = json.loads(message_json)
1669+
if not is_tool_result_message(message_data):
1670+
prompt_num += 1
1671+
current_prompt_num = prompt_num
1672+
except json.JSONDecodeError:
1673+
pass
1674+
msg_html = render_message(
1675+
log_type, message_json, timestamp, current_prompt_num
1676+
)
16611677
if msg_html:
16621678
# Wrap continuation summaries in collapsed details
16631679
if is_first and conv.get("is_continuation"):
@@ -2232,6 +2248,9 @@ def generate_html_from_session_data(
22322248
# Collect messages per page for potential page-data.json
22332249
page_messages_dict = {}
22342250

2251+
# Track prompt number across all pages
2252+
prompt_num = 0
2253+
22352254
for page_num in range(1, total_pages + 1):
22362255
start_idx = (page_num - 1) * PROMPTS_PER_PAGE
22372256
end_idx = min(start_idx + PROMPTS_PER_PAGE, total_convs)
@@ -2249,7 +2268,19 @@ def generate_html_from_session_data(
22492268
f"\rPage {page_num}/{total_pages}: rendering message {msg_count}/{total_page_messages}...",
22502269
nl=False,
22512270
)
2252-
msg_html = render_message(log_type, message_json, timestamp)
2271+
# Track prompt number for user messages (not tool results)
2272+
current_prompt_num = None
2273+
if log_type == "user" and message_json:
2274+
try:
2275+
message_data = json.loads(message_json)
2276+
if not is_tool_result_message(message_data):
2277+
prompt_num += 1
2278+
current_prompt_num = prompt_num
2279+
except json.JSONDecodeError:
2280+
pass
2281+
msg_html = render_message(
2282+
log_type, message_json, timestamp, current_prompt_num
2283+
)
22532284
if msg_html:
22542285
# Wrap continuation summaries in collapsed details
22552286
if is_first and conv.get("is_continuation"):

src/claude_code_transcripts/templates/code_view.js

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -838,14 +838,17 @@ async function init() {
838838
currentBlameRanges = fileInfo.blame_ranges || [];
839839
createEditor(codeContent, content, currentBlameRanges, path);
840840

841-
// Auto-select the first blame range: highlight in editor only
842-
// Don't auto-scroll transcript - that would render thousands of messages
843-
// User can click the blame range to navigate to the message
841+
// Auto-select the first blame range and scroll transcript to it
842+
// With windowed rendering + teleportation, this is now fast
844843
const firstOpIndex = currentBlameRanges.findIndex(r => r.msg_id);
845844
if (firstOpIndex >= 0) {
846845
const firstOpRange = currentBlameRanges[firstOpIndex];
847846
scrollEditorToLine(firstOpRange.start);
848847
highlightRange(firstOpIndex, currentBlameRanges, currentEditor);
848+
// Scroll transcript to the corresponding message
849+
if (firstOpRange.msg_id) {
850+
scrollToMessage(firstOpRange.msg_id);
851+
}
849852
}
850853
}, 10);
851854
}
@@ -1114,6 +1117,26 @@ async function init() {
11141117
return text;
11151118
}
11161119

1120+
// Get the prompt number for a user message by counting user messages before it
1121+
function getPromptNumber(messageEl) {
1122+
const msgId = messageEl.id;
1123+
if (!msgId) return null;
1124+
1125+
const msgIndex = msgIdToIndex.get(msgId);
1126+
if (msgIndex === undefined) return null;
1127+
1128+
// Count user messages from start up to this message
1129+
let promptNum = 0;
1130+
for (let i = 0; i <= msgIndex && i < messagesData.length; i++) {
1131+
const msg = messagesData[i];
1132+
if (msg.html && msg.html.includes('class="message user"') &&
1133+
!msg.html.includes('class="continuation"')) {
1134+
promptNum++;
1135+
}
1136+
}
1137+
return promptNum;
1138+
}
1139+
11171140
function updatePinnedUserMessage() {
11181141
if (!pinnedUserMessage || !transcriptContent || !transcriptPanel) return;
11191142
if (isInitializing || isScrollingToTarget) return; // Skip during scrolling to avoid repeated updates
@@ -1131,17 +1154,35 @@ async function init() {
11311154
const topThreshold = panelRect.top + headerHeight + pinnedHeight + 10;
11321155

11331156
let messageToPin = null;
1157+
let nextUserMessage = null;
1158+
11341159
for (const msg of userMessages) {
1135-
if (msg.getBoundingClientRect().bottom < topThreshold) {
1160+
const msgRect = msg.getBoundingClientRect();
1161+
if (msgRect.bottom < topThreshold) {
11361162
messageToPin = msg;
11371163
} else {
1164+
// This is the first user message that's visible
1165+
nextUserMessage = msg;
11381166
break;
11391167
}
11401168
}
11411169

1170+
// Hide pinned if the next user message would overlap with the pinned area
1171+
// (i.e., its top is within the pinned header zone)
1172+
if (messageToPin && nextUserMessage) {
1173+
const nextRect = nextUserMessage.getBoundingClientRect();
1174+
const pinnedBottomThreshold = panelRect.top + headerHeight + pinnedHeight + 5;
1175+
if (nextRect.top < pinnedBottomThreshold) {
1176+
// Next user message is overlapping - hide the pinned
1177+
messageToPin = null;
1178+
}
1179+
}
1180+
11421181
if (messageToPin && messageToPin !== currentPinnedMessage) {
11431182
currentPinnedMessage = messageToPin;
1144-
pinnedUserContent.textContent = extractUserMessageText(messageToPin);
1183+
const promptNum = getPromptNumber(messageToPin);
1184+
const promptLabel = promptNum ? `#${promptNum}` : '';
1185+
pinnedUserContent.textContent = `${promptLabel} ${extractUserMessageText(messageToPin)}`.trim();
11451186
pinnedUserMessage.style.display = 'block';
11461187
pinnedUserMessage.onclick = () => {
11471188
messageToPin.scrollIntoView({ behavior: 'smooth', block: 'start' });

tests/__snapshots__/test_generate_html/TestGenerateHtml.test_generates_page_001_html.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ <h1><a href="index.html" style="color: inherit; text-decoration: none;">Claude C
354354

355355
<div id="page-messages">
356356

357-
<div class="message user" id="msg-2025-12-24T10-00-00-000Z"><div class="message-header"><span class="role-label">User</span><a href="#msg-2025-12-24T10-00-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:00:00.000Z" data-timestamp="2025-12-24T10:00:00.000Z">2025-12-24T10:00:00.000Z</time></a></div><div class="message-content">
357+
<div class="message user" id="msg-2025-12-24T10-00-00-000Z"><div class="message-header"><span class="role-label">User #1</span><a href="#msg-2025-12-24T10-00-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:00:00.000Z" data-timestamp="2025-12-24T10:00:00.000Z">2025-12-24T10:00:00.000Z</time></a></div><div class="message-content">
358358
<div class="user-content"><p>Create a simple Python function to add two numbers</p></div></div></div>
359359
<div class="message assistant" id="msg-2025-12-24T10-00-05-000Z"><div class="message-header"><span class="role-label">Assistant</span><a href="#msg-2025-12-24T10-00-05-000Z" class="timestamp-link"><time datetime="2025-12-24T10:00:05.000Z" data-timestamp="2025-12-24T10:00:05.000Z">2025-12-24T10:00:05.000Z</time></a></div><div class="message-content">
360360
<div class="thinking"><div class="thinking-label">Thinking</div><p>The user wants a simple addition function. I should:
@@ -404,7 +404,7 @@ <h1><a href="index.html" style="color: inherit; text-decoration: none;">Claude C
404404
remote:
405405
To github.com:example/project.git
406406
def5678..abc1234 main -&gt; main</pre></div><button class="expand-btn">Show more</button></div></div></div></div>
407-
<div class="message user" id="msg-2025-12-24T10-01-00-000Z"><div class="message-header"><span class="role-label">User</span><a href="#msg-2025-12-24T10-01-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:01:00.000Z" data-timestamp="2025-12-24T10:01:00.000Z">2025-12-24T10:01:00.000Z</time></a></div><div class="message-content">
407+
<div class="message user" id="msg-2025-12-24T10-01-00-000Z"><div class="message-header"><span class="role-label">User #2</span><a href="#msg-2025-12-24T10-01-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:01:00.000Z" data-timestamp="2025-12-24T10:01:00.000Z">2025-12-24T10:01:00.000Z</time></a></div><div class="message-content">
408408
<div class="user-content"><p>Now edit the file to add a subtract function</p></div></div></div>
409409
<div class="message assistant" id="msg-2025-12-24T10-01-05-000Z"><div class="message-header"><span class="role-label">Assistant</span><a href="#msg-2025-12-24T10-01-05-000Z" class="timestamp-link"><time datetime="2025-12-24T10:01:05.000Z" data-timestamp="2025-12-24T10:01:05.000Z">2025-12-24T10:01:05.000Z</time></a></div><div class="message-content">
410410
<div class="tool-use" data-tool-id="toolu_glob_001"><div class="tool-header"><span class="tool-icon"></span> Glob</div><div class="truncatable"><div class="truncatable-content"><pre class="json">{
@@ -437,7 +437,7 @@ <h1><a href="index.html" style="color: inherit; text-decoration: none;">Claude C
437437
&#34;output_mode&#34;: &#34;content&#34;
438438
}</pre></div><button class="expand-btn">Show more</button></div></div></div></div>
439439
<div class="message tool-reply" id="msg-2025-12-24T10-01-30-000Z"><div class="message-header"><span class="role-label">Tool reply</span><a href="#msg-2025-12-24T10-01-30-000Z" class="timestamp-link"><time datetime="2025-12-24T10:01:30.000Z" data-timestamp="2025-12-24T10:01:30.000Z">2025-12-24T10:01:30.000Z</time></a></div><div class="message-content"><div class="tool-result"><div class="truncatable"><div class="truncatable-content"><pre>/project/math_utils.py:6:def subtract(a: int, b: int) -&gt; int:</pre></div><button class="expand-btn">Show more</button></div></div></div></div>
440-
<div class="message user" id="msg-2025-12-24T10-02-00-000Z"><div class="message-header"><span class="role-label">User</span><a href="#msg-2025-12-24T10-02-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:02:00.000Z" data-timestamp="2025-12-24T10:02:00.000Z">2025-12-24T10:02:00.000Z</time></a></div><div class="message-content">
440+
<div class="message user" id="msg-2025-12-24T10-02-00-000Z"><div class="message-header"><span class="role-label">User #3</span><a href="#msg-2025-12-24T10-02-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:02:00.000Z" data-timestamp="2025-12-24T10:02:00.000Z">2025-12-24T10:02:00.000Z</time></a></div><div class="message-content">
441441
<div class="user-content"><p>Run the tests again</p></div></div></div>
442442
<div class="message assistant" id="msg-2025-12-24T10-02-05-000Z"><div class="message-header"><span class="role-label">Assistant</span><a href="#msg-2025-12-24T10-02-05-000Z" class="timestamp-link"><time datetime="2025-12-24T10:02:05.000Z" data-timestamp="2025-12-24T10:02:05.000Z">2025-12-24T10:02:05.000Z</time></a></div><div class="message-content">
443443
<div class="tool-use bash-tool" data-tool-id="toolu_bash_004">
@@ -456,7 +456,7 @@ <h1><a href="index.html" style="color: inherit; text-decoration: none;">Claude C
456456
<pre><code class="language-python">def example():
457457
return 42
458458
</code></pre></div></div></div>
459-
<div class="message user" id="msg-2025-12-24T10-03-00-000Z"><div class="message-header"><span class="role-label">User</span><a href="#msg-2025-12-24T10-03-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:03:00.000Z" data-timestamp="2025-12-24T10:03:00.000Z">2025-12-24T10:03:00.000Z</time></a></div><div class="message-content">
459+
<div class="message user" id="msg-2025-12-24T10-03-00-000Z"><div class="message-header"><span class="role-label">User #4</span><a href="#msg-2025-12-24T10-03-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:03:00.000Z" data-timestamp="2025-12-24T10:03:00.000Z">2025-12-24T10:03:00.000Z</time></a></div><div class="message-content">
460460
<div class="user-content"><p>Fix the issue and commit</p></div></div></div>
461461
<div class="message assistant" id="msg-2025-12-24T10-03-05-000Z"><div class="message-header"><span class="role-label">Assistant</span><a href="#msg-2025-12-24T10-03-05-000Z" class="timestamp-link"><time datetime="2025-12-24T10:03:05.000Z" data-timestamp="2025-12-24T10:03:05.000Z">2025-12-24T10:03:05.000Z</time></a></div><div class="message-content"><div class="file-tool edit-tool" data-tool-id="toolu_edit_002">
462462
<div class="file-tool-header edit-header"><span class="file-tool-icon">✏️</span> Edit <span class="file-tool-path">test_math.py</span> <span class="edit-replace-all">(replace all)</span></div>
@@ -475,7 +475,7 @@ <h1><a href="index.html" style="color: inherit; text-decoration: none;">Claude C
475475
<div class="message tool-reply" id="msg-2025-12-24T10-03-20-000Z"><div class="message-header"><span class="role-label">Tool reply</span><a href="#msg-2025-12-24T10-03-20-000Z" class="timestamp-link"><time datetime="2025-12-24T10:03:20.000Z" data-timestamp="2025-12-24T10:03:20.000Z">2025-12-24T10:03:20.000Z</time></a></div><div class="message-content"><div class="tool-result"><div class="truncatable"><div class="truncatable-content"><div class="commit-card"><a href="https://github.com/example/project/commit/def5678"><span class="commit-card-hash">def5678</span> Add subtract function and fix tests</a></div><pre>2 files changed, 10 insertions(+), 1 deletion(-)</pre></div><button class="expand-btn">Show more</button></div></div></div></div>
476476
<div class="message assistant" id="msg-2025-12-24T10-03-25-000Z"><div class="message-header"><span class="role-label">Assistant</span><a href="#msg-2025-12-24T10-03-25-000Z" class="timestamp-link"><time datetime="2025-12-24T10:03:25.000Z" data-timestamp="2025-12-24T10:03:25.000Z">2025-12-24T10:03:25.000Z</time></a></div><div class="message-content">
477477
<div class="assistant-text"><p>Done! The subtract function is now working and committed.</p></div></div></div><details class="continuation"><summary>Session continuation summary</summary>
478-
<div class="message user" id="msg-2025-12-24T10-04-00-000Z"><div class="message-header"><span class="role-label">User</span><a href="#msg-2025-12-24T10-04-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:04:00.000Z" data-timestamp="2025-12-24T10:04:00.000Z">2025-12-24T10:04:00.000Z</time></a></div><div class="message-content">
478+
<div class="message user" id="msg-2025-12-24T10-04-00-000Z"><div class="message-header"><span class="role-label">User #5</span><a href="#msg-2025-12-24T10-04-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:04:00.000Z" data-timestamp="2025-12-24T10:04:00.000Z">2025-12-24T10:04:00.000Z</time></a></div><div class="message-content">
479479
<div class="user-content"><p>This is a session continuation summary from a previous context. The user was working on a math utilities library.</p></div></div></div></details>
480480
</div>
481481

tests/__snapshots__/test_generate_html/TestGenerateHtml.test_generates_page_002_html.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ <h1><a href="index.html" style="color: inherit; text-decoration: none;">Claude C
354354

355355
<div id="page-messages">
356356

357-
<div class="message user" id="msg-2025-12-24T10-05-00-000Z"><div class="message-header"><span class="role-label">User</span><a href="#msg-2025-12-24T10-05-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:05:00.000Z" data-timestamp="2025-12-24T10:05:00.000Z">2025-12-24T10:05:00.000Z</time></a></div><div class="message-content">
357+
<div class="message user" id="msg-2025-12-24T10-05-00-000Z"><div class="message-header"><span class="role-label">User #6</span><a href="#msg-2025-12-24T10-05-00-000Z" class="timestamp-link"><time datetime="2025-12-24T10:05:00.000Z" data-timestamp="2025-12-24T10:05:00.000Z">2025-12-24T10:05:00.000Z</time></a></div><div class="message-content">
358358
<div class="user-content"><p>Add a multiply function too</p></div></div></div>
359359
<div class="message assistant" id="msg-2025-12-24T10-05-05-000Z"><div class="message-header"><span class="role-label">Assistant</span><a href="#msg-2025-12-24T10-05-05-000Z" class="timestamp-link"><time datetime="2025-12-24T10:05:05.000Z" data-timestamp="2025-12-24T10:05:05.000Z">2025-12-24T10:05:05.000Z</time></a></div><div class="message-content"><div class="file-tool edit-tool" data-tool-id="toolu_edit_003">
360360
<div class="file-tool-header edit-header"><span class="file-tool-icon">✏️</span> Edit <span class="file-tool-path">math_utils.py</span></div>

tests/test_code_view_e2e.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,11 +304,14 @@ def test_pinned_user_message_on_scroll(self, code_view_page: Page):
304304
# Pinned header should be visible with content from the first user message
305305
expect(pinned).to_be_visible()
306306
pinned_text = pinned_content.text_content()
307-
# The pinned text should be a truncated prefix of the user message
307+
# The pinned text should have format "#N message text..."
308308
assert len(pinned_text) > 0, "Pinned content should not be empty"
309+
assert pinned_text.startswith("#"), "Pinned text should start with prompt number"
310+
# Strip the prompt number prefix (e.g., "#1 ") to compare with message text
311+
pinned_text_without_num = re.sub(r"^#\d+\s*", "", pinned_text)
309312
assert (
310-
first_user_text.startswith(pinned_text[:50])
311-
or pinned_text in first_user_text
313+
first_user_text.startswith(pinned_text_without_num[:50])
314+
or pinned_text_without_num in first_user_text
312315
), f"Pinned text '{pinned_text[:50]}...' should match user message"
313316

314317
def test_pinned_user_message_click_scrolls_back(self, code_view_page: Page):

0 commit comments

Comments
 (0)