diff --git a/claude_code_log/html/templates/components/timeline.html b/claude_code_log/html/templates/components/timeline.html
index b9daa018..3e7b38db 100644
--- a/claude_code_log/html/templates/components/timeline.html
+++ b/claude_code_log/html/templates/components/timeline.html
@@ -350,11 +350,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
diff --git a/claude_code_log/html/templates/transcript.html b/claude_code_log/html/templates/transcript.html
index bf43d6ca..0dd26d3d 100644
--- a/claude_code_log/html/templates/transcript.html
+++ b/claude_code_log/html/templates/transcript.html
@@ -729,12 +729,22 @@
🔍 Search & Filter
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -750,36 +760,6 @@ 🔍 Search & Filter
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -788,18 +768,13 @@ 🔍 Search & Filter
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -849,11 +824,11 @@ 🔍 Search & Filter
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -875,52 +850,6 @@ 🔍 Search & Filter
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr
index f3d159d6..b55cc8ff 100644
--- a/test/__snapshots__/test_snapshot_html.ambr
+++ b/test/__snapshots__/test_snapshot_html.ambr
@@ -4986,11 +4986,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -7145,12 +7145,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -7166,36 +7176,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -7204,18 +7184,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -7265,11 +7240,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -7291,52 +7266,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -13751,11 +13680,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -15805,12 +15734,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -15826,36 +15765,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -15864,18 +15773,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -15925,11 +15829,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -15951,52 +15855,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -32542,11 +32400,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -34697,12 +34555,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -34718,36 +34586,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -34756,18 +34594,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -34817,11 +34650,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -34843,52 +34676,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -48385,11 +48172,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -51107,12 +50894,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -51128,36 +50925,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -51166,18 +50933,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -51227,11 +50989,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -51253,52 +51015,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -72869,11 +72585,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -75140,12 +74856,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -75161,36 +74887,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -75199,18 +74895,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -75260,11 +74951,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -75286,52 +74977,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -84570,11 +84215,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -86847,12 +86492,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -86868,36 +86523,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -86906,18 +86531,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -86967,11 +86587,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -86993,52 +86613,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -93453,11 +93027,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -96010,12 +95584,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -96031,36 +95615,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -96069,18 +95623,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -96130,11 +95679,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -96156,52 +95705,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -102616,11 +102119,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -105120,12 +104623,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -105141,36 +104654,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -105179,18 +104662,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -105240,11 +104718,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -105266,52 +104744,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -111726,11 +111158,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -114175,12 +113607,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -114196,36 +113638,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -114234,18 +113646,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -114295,11 +113702,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -114321,52 +113728,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -120781,11 +120142,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -123058,12 +122419,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -123079,36 +122450,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -123117,18 +122458,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -123178,11 +122514,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -123204,52 +122540,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -129664,11 +128954,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -131746,12 +131036,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -131767,36 +131067,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -131805,18 +131075,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -131866,11 +131131,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -131892,52 +131157,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
@@ -138352,11 +137571,11 @@
return { timelineItems, timelineGroups, timelineEnd, timelineStart };
}
- // The timeline groups a filter toggle governs. Mirrors the toggle
- // expansion in the transcript's applyFilter: the single "Tool" toggle
- // stands for both tool groups, and "User" also covers the
+ // The timeline groups a filter toggle governs: the single "Tool"
+ // toggle stands for both tool groups, and "User" also covers the
// user-initiated bash lines. Every other toggle governs the group of
- // its own name.
+ // its own name. The transcript's applyFilter expands toggles through
+ // this same map (exported below as window.timelineToggleGroups).
const TOGGLE_GROUPS = {
'tool': ['tool_use', 'tool_result'],
'user': ['user', 'bash-input', 'bash-output']
@@ -140383,12 +139602,22 @@
closeToolbarButton.addEventListener('click', toggleFilterToolbar);
}
+ // The cards a filter toggle covers, through the same toggle map
+ // as applyFilter: "User" also counts the bash lines, "Tool" both
+ // tool types.
+ function toggleCardSelector(type, extra = '') {
+ const toggleGroups = window.timelineToggleGroups;
+ return (toggleGroups[type] || [type])
+ .map(t => `.message.${t}:not(.session-header)${extra}`)
+ .join(', ');
+ }
+
// Count messages by type and update button labels
function updateMessageCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const messages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const messages = document.querySelectorAll(toggleCardSelector(type));
const count = messages.length;
const toggle = document.querySelector(`[data-type="${type}"]`);
const countSpan = toggle ? toggle.querySelector('.count') : null;
@@ -140404,36 +139633,6 @@
}
}
});
-
- // Handle combined "user" filter (user + bash-input + bash-output)
- const userMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const userCount = userMessages.length;
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan) {
- userCountSpan.textContent = `(${userCount})`;
- if (userCount === 0) {
- userToggle.style.display = 'none';
- } else {
- userToggle.style.display = 'flex';
- }
- }
-
- // Handle combined "tool" filter (tool_use + tool_result)
- const toolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const toolCount = toolMessages.length;
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan) {
- toolCountSpan.textContent = `(${toolCount})`;
- if (toolCount === 0) {
- toolToggle.style.display = 'none';
- } else {
- toolToggle.style.display = 'flex';
- }
- }
}
// Filter functionality
@@ -140442,18 +139641,13 @@
.filter(toggle => toggle.classList.contains('active'))
.map(toggle => toggle.dataset.type);
- // Expand filter types to their corresponding CSS classes
- const expandedTypes = [];
- activeTypes.forEach(type => {
- if (type === 'tool') {
- expandedTypes.push('tool_use', 'tool_result');
- } else if (type === 'user') {
- // User filter includes bash commands (user-initiated)
- expandedTypes.push('user', 'bash-input', 'bash-output');
- } else {
- expandedTypes.push(type);
- }
- });
+ // Expand filter types to their corresponding CSS classes,
+ // through the map the timeline filters its groups by
+ // (timeline.html), so the two cannot disagree: "Tool" stands
+ // for both tool types, "User" also covers the user-initiated
+ // bash lines, and every other toggle its own type.
+ const toggleGroups = window.timelineToggleGroups;
+ const expandedTypes = activeTypes.flatMap(type => toggleGroups[type] || [type]);
// Show/hide messages based on active toggle buttons
const allMessages = document.querySelectorAll('.message:not(.session-header)');
@@ -140503,11 +139697,11 @@
}
function updateVisibleCounts() {
- const messageTypes = ['assistant', 'sidechain', 'memory', 'system', 'thinking', 'image'];
+ const messageTypes = ['user', 'system', 'assistant', 'thinking', 'tool', 'sidechain', 'memory', 'image'];
messageTypes.forEach(type => {
- const visibleMessages = document.querySelectorAll(`.message.${type}:not(.session-header):not(.filtered-hidden)`);
- const totalMessages = document.querySelectorAll(`.message.${type}:not(.session-header)`);
+ const visibleMessages = document.querySelectorAll(toggleCardSelector(type, ':not(.filtered-hidden)'));
+ const totalMessages = document.querySelectorAll(toggleCardSelector(type));
const visibleCount = visibleMessages.length;
const totalCount = totalMessages.length;
@@ -140529,52 +139723,6 @@
}
}
});
-
- // Handle combined "user" filter separately (includes bash messages)
- const visibleUserMessages = document.querySelectorAll(`.message.user:not(.session-header):not(.filtered-hidden), .message.bash-input:not(.session-header):not(.filtered-hidden), .message.bash-output:not(.session-header):not(.filtered-hidden)`);
- const totalUserMessages = document.querySelectorAll(`.message.user:not(.session-header), .message.bash-input:not(.session-header), .message.bash-output:not(.session-header)`);
- const visibleUserCount = visibleUserMessages.length;
- const totalUserCount = totalUserMessages.length;
-
- const userToggle = document.querySelector(`[data-type="user"]`);
- const userCountSpan = userToggle ? userToggle.querySelector('.count') : null;
-
- if (userCountSpan && totalUserCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleUserCount !== totalUserCount) {
- userCountSpan.textContent = `(${visibleUserCount}/${totalUserCount})`;
- } else {
- userCountSpan.textContent = `(${totalUserCount})`;
- }
- }
-
- // Handle combined "tool" filter separately
- const visibleToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header):not(.filtered-hidden), .message.tool_result:not(.session-header):not(.filtered-hidden)`);
- const totalToolMessages = document.querySelectorAll(`.message.tool_use:not(.session-header), .message.tool_result:not(.session-header)`);
- const visibleToolCount = visibleToolMessages.length;
- const totalToolCount = totalToolMessages.length;
-
- const toolToggle = document.querySelector(`[data-type="tool"]`);
- const toolCountSpan = toolToggle ? toolToggle.querySelector('.count') : null;
-
- if (toolCountSpan && totalToolCount > 0) {
- const activeTypes = Array.from(filterToggles)
- .filter(toggle => toggle.classList.contains('active'))
- .map(toggle => toggle.dataset.type);
-
- const isFiltering = activeTypes.length < filterToggles.length;
-
- if (isFiltering && visibleToolCount !== totalToolCount) {
- toolCountSpan.textContent = `(${visibleToolCount}/${totalToolCount})`;
- } else {
- toolCountSpan.textContent = `(${totalToolCount})`;
- }
- }
}
function toggleFilter(button) {
diff --git a/test/test_timeline_browser.py b/test/test_timeline_browser.py
index 0b616aee..ba16794b 100644
--- a/test/test_timeline_browser.py
+++ b/test/test_timeline_browser.py
@@ -52,6 +52,76 @@
"() => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)))"
)
+# Rewrites the toggle map in place as timeline.html exports it, before the
+# timeline or the page filter reads it: tool_result moves from the "Tool"
+# toggle to the "Assistant" toggle.
+_MOVE_TOOL_RESULT_TO_ASSISTANT = """
+Object.defineProperty(window, 'timelineToggleGroups', {
+ configurable: true,
+ get() { return this.__toggleGroups; },
+ set(map) {
+ map.tool = ['tool_use'];
+ map.assistant = ['assistant', 'tool_result'];
+ this.__toggleGroups = map;
+ },
+});
+"""
+
+# Each timeline item is shown iff its card is: its group is on the timeline
+# and visible, and the item is not hidden one by one. Counts the items
+# compared per group, so a check that compared nothing reads as such.
+_PAGE_AND_TIMELINE_AGREE = """() => {
+ const tl = window.timeline;
+ const shownGroups = new Set(tl.groupsData.get()
+ .filter(g => g.visible !== false).map(g => g.id));
+ const cards = [...document.querySelectorAll('.message:not(.session-header)')];
+ const problems = [];
+ const compared = {};
+ tl.itemsData.get().forEach(item => {
+ const el = cards[item.id];
+ if (!el) { problems.push('item without a card: ' + item.id); return; }
+ const cardShown = !el.classList.contains('filtered-hidden')
+ && !el.classList.contains('search-hidden');
+ const itemShown = shownGroups.has(item.group)
+ && !(item.className || '').split(/\\s+/).includes('timeline-filtered-hidden');
+ compared[item.group] = (compared[item.group] || 0) + 1;
+ if (cardShown !== itemShown) {
+ problems.push((cardShown ? 'page shows, timeline hides ' : 'page hides, timeline shows ')
+ + el.id + ' (' + item.group + ')');
+ }
+ });
+ return { problems, compared };
+}"""
+
+# Each visible toggle's count, as the toggle map says it should read: the
+# cards whose type is one of the toggle's groups, as "(visible/total)" while
+# a toggle is off and some of them are hidden, else "(total)". A toggle with
+# no cards is hidden. Returns the totals and labels too, so a check that
+# counted nothing reads as such.
+_TOGGLE_COUNTS_FOLLOW_THE_MAP = """() => {
+ const groupsOf = window.timelineToggleGroups;
+ const toggles = [...document.querySelectorAll('.filter-toggle')];
+ const filtering = toggles.some(t => !t.classList.contains('active'));
+ const cards = [...document.querySelectorAll('.message:not(.session-header)')];
+ const problems = [], totals = {}, labels = {};
+ toggles.forEach(toggle => {
+ const type = toggle.dataset.type;
+ const groups = groupsOf[type] || [type];
+ const mine = cards.filter(el => groups.some(g => el.classList.contains(g)));
+ const visible = mine.filter(el => !el.classList.contains('filtered-hidden')).length;
+ totals[type] = mine.length;
+ labels[type] = toggle.querySelector('.count').textContent;
+ if (!mine.length) {
+ if (toggle.style.display !== 'none') problems.push(type + ': shown with no cards');
+ return;
+ }
+ const want = filtering && visible !== mine.length
+ ? '(' + visible + '/' + mine.length + ')' : '(' + mine.length + ')';
+ if (labels[type] !== want) problems.push(type + ': ' + labels[type] + ', want ' + want);
+ });
+ return { problems, totals, labels };
+}"""
+
def _wait_for_filters(page: Page) -> None:
"""After a filter toggle (or Select All/None) click: the timeline's groups
@@ -946,6 +1016,107 @@ def test_tool_filter_removes_tool_items_from_timeline(self, page: Page, theme: s
assert page.evaluate(count_items, tool_kinds) == tools_before
assert page.evaluate(count_items, side_kinds) == side_before
+ def _open_with_tool_result_under_assistant(self, page: Page, theme: str) -> None:
+ """Open representative_messages.jsonl with the toggle map rewritten
+ (``_MOVE_TOOL_RESULT_TO_ASSISTANT``), and check the rewrite landed."""
+ messages = load_transcript(Path("test/test_data/representative_messages.jsonl"))
+ with tempfile.NamedTemporaryFile(
+ suffix=".html", delete=False, dir=self.temp_dir
+ ) as f:
+ temp_file = Path(f.name)
+ html = generate_html(
+ messages, "Toggle Map", theme=theme, output_dir=temp_file.parent
+ )
+ temp_file.write_text(html, encoding="utf-8")
+ self.temp_files.append(temp_file)
+
+ page.add_init_script(_MOVE_TOOL_RESULT_TO_ASSISTANT)
+ page.goto(f"file://{temp_file}")
+ # The rewrite reached the map the timeline and the page read.
+ assert page.evaluate(
+ "() => [window.timelineToggleGroups.tool, window.timelineToggleGroups.assistant]"
+ ) == [["tool_use"], ["assistant", "tool_result"]]
+
+ @pytest.mark.browser
+ @pytest.mark.parametrize("theme", ["classic", "minimal"])
+ def test_page_filter_expands_toggles_through_the_timelines_map(
+ self, page: Page, theme: str
+ ):
+ """The page filter and the timeline read one toggle map (#395).
+
+ The map is rewritten as the page exports it, before either reads it:
+ tool_result moves from the "Tool" toggle to the "Assistant" toggle.
+ With Assistant off and Tool on, the timeline hides the tool_result
+ group; a page filter with a copy of its own would still show the
+ tool_result cards, and the two would disagree.
+ """
+ self._open_with_tool_result_under_assistant(page, theme)
+ page.locator("#toggleTimeline").click()
+ self._wait_for_timeline_loaded(page)
+ _wait_for_timeline_groups(page)
+
+ moved_cards = "#transcript .message.tool_result:not(.sidechain)"
+ assert page.locator(moved_cards).count() > 0
+
+ page.locator("#filterMessages").click()
+ page.locator('.filter-toggle[data-type="assistant"]').click()
+ _wait_for_filters(page)
+
+ # The page and the timeline agree, item by item, the moved group
+ # included.
+ agreement = page.evaluate(_PAGE_AND_TIMELINE_AGREE)
+ assert agreement["problems"] == [], agreement["problems"][:10]
+ assert agreement["compared"].get("tool_result", 0) > 0, agreement
+ assert agreement["compared"].get("tool_use", 0) > 0, agreement
+ # And the page follows the map: no Tool toggle covers tool_result now.
+ assert set(
+ page.evaluate(
+ "(sel) => [...document.querySelectorAll(sel)]"
+ ".map(m => m.classList.contains('filtered-hidden'))",
+ moved_cards,
+ )
+ ) == {True}
+
+ @pytest.mark.browser
+ @pytest.mark.parametrize("theme", ["classic", "minimal"])
+ def test_toggle_counts_follow_the_timelines_map(self, page: Page, theme: str):
+ """The toggles' counts read the same toggle map as the filter (#395).
+
+ With tool_result moved from the "Tool" toggle to the "Assistant"
+ toggle (as in the test above), Tool counts only the tool_use cards
+ and Assistant counts the tool_result cards too, both when the page
+ loads and as "(visible/total)" once Assistant is turned off.
+ """
+ self._open_with_tool_result_under_assistant(page, theme)
+ moved = page.locator("#transcript .message.tool_result").count()
+ assert moved > 0
+
+ counts = page.evaluate(_TOGGLE_COUNTS_FOLLOW_THE_MAP)
+ assert counts["problems"] == [], counts
+ assert counts["totals"]["tool"] > 0, counts
+ assert counts["totals"]["assistant"] > moved, counts
+
+ page.locator("#filterMessages").click()
+ page.locator('.filter-toggle[data-type="assistant"]').click()
+ _wait_for_filters(page)
+
+ counts = page.evaluate(_TOGGLE_COUNTS_FOLLOW_THE_MAP)
+ assert counts["problems"] == [], counts
+ # The visible/total form was exercised, not only the totals.
+ assert "/" in counts["labels"]["assistant"], counts
+
+ # With Assistant off, the moved cards are hidden whichever map the
+ # visible count reads. With Assistant on and Tool off they are shown,
+ # so a visible count that bypassed the map would count them under
+ # Tool (and miss them under Assistant).
+ page.locator('.filter-toggle[data-type="assistant"]').click()
+ page.locator('.filter-toggle[data-type="tool"]').click()
+ _wait_for_filters(page)
+
+ counts = page.evaluate(_TOGGLE_COUNTS_FOLLOW_THE_MAP)
+ assert counts["problems"] == [], counts
+ assert "/" in counts["labels"]["tool"], counts
+
@pytest.mark.browser
def test_timeline_filter_individual_message_types(self, page: Page):
"""Test filtering individual message types in timeline."""