Fix export-html styling and behavior

- Fix UTF-8 decoding with TextDecoder for base64 session data
- Use toolOutput color for expand hints (not borderAccent)
- Remove '- click to expand' text to match TUI
- Ctrl+O toggles expanded state (not visibility) on tool outputs
- Edit diffs always visible (not affected by Ctrl+O)
- Remove Ctrl+F override to allow browser search
- Replace tabs with 3 spaces in all tool outputs
- Fix syntax highlighting default color to use --text
- Add more hljs token selectors (property, punctuation, operator)
- Compaction uses customMessageBg/Label/Text colors
- Model change uses dim color without background
- Scroll to end on initial load
- Pointer cursor on expandable elements
This commit is contained in:
Mario Zechner 2026-01-01 13:24:01 +01:00
parent 55fd8d9fed
commit fcb700701e

View file

@ -214,6 +214,14 @@
max-width: 800px; max-width: 800px;
} }
/* Help bar */
.help-bar {
font-size: 11px;
color: var(--dim);
margin-bottom: 16px;
opacity: 0.7;
}
/* Header */ /* Header */
.header { .header {
background: var(--container-bg); background: var(--container-bg);
@ -278,6 +286,10 @@
padding: 0; padding: 0;
} }
.assistant-message > .message-timestamp {
padding-left: 16px;
}
.assistant-text { .assistant-text {
padding: 12px 16px; padding: 12px 16px;
} }
@ -289,6 +301,13 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
.thinking-collapsed {
display: none;
padding: 12px 16px;
color: var(--thinkingText);
font-style: italic;
}
/* Tool execution */ /* Tool execution */
.tool-execution { .tool-execution {
padding: 12px 16px; padding: 12px 16px;
@ -305,7 +324,7 @@
} }
.tool-path { .tool-path {
color: var(--borderAccent); color: var(--accent);
word-break: break-all; word-break: break-all;
} }
@ -353,6 +372,7 @@
.tool-output code { .tool-output code {
padding: 0; padding: 0;
background: none; background: none;
color: var(--text);
} }
.tool-output.expandable { .tool-output.expandable {
@ -387,9 +407,7 @@
} }
.expand-hint { .expand-hint {
color: var(--borderAccent); color: var(--toolOutput);
font-style: italic;
margin-top: 4px;
} }
/* Diff */ /* Diff */
@ -397,6 +415,7 @@
margin-top: 12px; margin-top: 12px;
font-size: 11px; font-size: 11px;
overflow-x: auto; overflow-x: auto;
white-space: pre;
} }
.diff-added { color: var(--toolDiffAdded); } .diff-added { color: var(--toolDiffAdded); }
@ -406,8 +425,6 @@
/* Model change */ /* Model change */
.model-change { .model-change {
padding: 8px 16px; padding: 8px 16px;
background: var(--info-bg);
border-radius: 4px;
color: var(--dim); color: var(--dim);
font-size: 11px; font-size: 11px;
} }
@ -417,59 +434,41 @@
font-weight: bold; font-weight: bold;
} }
/* Compaction */ /* Compaction / Branch Summary - matches customMessage colors from TUI */
.compaction { .compaction {
background: var(--info-bg); background: var(--customMessageBg);
border-radius: 4px; border-radius: 4px;
overflow: hidden;
}
.compaction-header {
padding: 12px 16px; padding: 12px 16px;
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
} }
.compaction-header:hover { .compaction-label {
background: var(--selectedBg); color: var(--customMessageLabel);
}
.compaction-toggle {
color: var(--borderAccent);
font-size: 10px;
transition: transform 0.2s;
}
.compaction.expanded .compaction-toggle {
transform: rotate(90deg);
}
.compaction-title {
color: var(--text);
font-weight: bold; font-weight: bold;
} }
.compaction-collapsed {
color: var(--customMessageText);
}
.compaction-content { .compaction-content {
display: none; display: none;
padding: 0 16px 16px; color: var(--customMessageText);
white-space: pre-wrap;
margin-top: 8px;
}
.compaction.expanded .compaction-collapsed {
display: none;
} }
.compaction.expanded .compaction-content { .compaction.expanded .compaction-content {
display: block; display: block;
} }
.compaction-summary {
background: var(--selectedBg);
border-radius: 4px;
padding: 12px;
white-space: pre-wrap;
}
/* System prompt */ /* System prompt */
.system-prompt { .system-prompt {
background: var(--info-bg); background: var(--customMessageBg);
padding: 12px 16px; padding: 12px 16px;
border-radius: 4px; border-radius: 4px;
margin-bottom: 16px; margin-bottom: 16px;
@ -477,12 +476,12 @@
.system-prompt-header { .system-prompt-header {
font-weight: bold; font-weight: bold;
color: var(--warning); color: var(--customMessageLabel);
margin-bottom: 8px; margin-bottom: 8px;
} }
.system-prompt-content { .system-prompt-content {
color: var(--dim); color: var(--customMessageText);
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
font-size: 11px; font-size: 11px;
@ -656,15 +655,17 @@
} }
/* Syntax highlighting */ /* Syntax highlighting */
.hljs { background: transparent; } .hljs { background: transparent; color: var(--text); }
.hljs-comment, .hljs-quote { color: var(--syntaxComment); } .hljs-comment, .hljs-quote { color: var(--syntaxComment); }
.hljs-keyword, .hljs-selector-tag { color: var(--syntaxKeyword); } .hljs-keyword, .hljs-selector-tag { color: var(--syntaxKeyword); }
.hljs-number, .hljs-literal { color: var(--syntaxNumber); } .hljs-number, .hljs-literal { color: var(--syntaxNumber); }
.hljs-string, .hljs-doctag { color: var(--syntaxString); } .hljs-string, .hljs-doctag { color: var(--syntaxString); }
.hljs-title, .hljs-section, .hljs-name { color: var(--syntaxFunction); } .hljs-title, .hljs-section, .hljs-name { color: var(--syntaxFunction); }
.hljs-type, .hljs-class, .hljs-built_in { color: var(--syntaxType); } .hljs-type, .hljs-class, .hljs-built_in { color: var(--syntaxType); }
.hljs-attr, .hljs-variable, .hljs-params { color: var(--syntaxVariable); } .hljs-attr, .hljs-variable, .hljs-params, .hljs-property { color: var(--syntaxVariable); }
.hljs-meta { color: var(--syntaxKeyword); } .hljs-meta { color: var(--syntaxKeyword); }
.hljs-punctuation, .hljs-operator { color: var(--syntaxOperator); }
.hljs-subst { color: var(--text); }
/* Footer */ /* Footer */
.footer { .footer {
@ -783,6 +784,7 @@
</aside> </aside>
<main id="content"> <main id="content">
<div id="header-container"></div> <div id="header-container"></div>
<div class="help-bar">Ctrl+T toggle thinking · Ctrl+O toggle tools · Esc reset</div>
<div id="messages"></div> <div id="messages"></div>
<div class="footer"> <div class="footer">
Generated by {{APP_NAME}} on {{GENERATED_DATE}} Generated by {{APP_NAME}} on {{GENERATED_DATE}}
@ -801,7 +803,14 @@
(function() { (function() {
'use strict'; 'use strict';
const data = JSON.parse(atob(document.getElementById('session-data').textContent)); // Decode base64 with proper UTF-8 handling
const base64 = document.getElementById('session-data').textContent;
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
const data = JSON.parse(new TextDecoder('utf-8').decode(bytes));
const { header, entries, leafId, systemPrompt, tools } = data; const { header, entries, leafId, systemPrompt, tools } = data;
// Build entry index // Build entry index
@ -1328,6 +1337,8 @@
// Format expandable output (matches old export-html.ts exactly) // Format expandable output (matches old export-html.ts exactly)
function formatExpandableOutput(text, maxLines, lang) { function formatExpandableOutput(text, maxLines, lang) {
// Replace tabs with spaces first
text = replaceTabs(text);
const lines = text.split('\n'); const lines = text.split('\n');
const displayLines = lines.slice(0, maxLines); const displayLines = lines.slice(0, maxLines);
const remaining = lines.length - maxLines; const remaining = lines.length - maxLines;
@ -1353,7 +1364,7 @@
let out = '<div class="tool-output expandable" onclick="this.classList.toggle(\'expanded\')">'; let out = '<div class="tool-output expandable" onclick="this.classList.toggle(\'expanded\')">';
out += `<div class="output-preview"><pre><code class="hljs">${previewHighlighted}</code></pre>`; out += `<div class="output-preview"><pre><code class="hljs">${previewHighlighted}</code></pre>`;
out += `<div class="expand-hint">... (${remaining} more lines) - click to expand</div>`; out += `<div class="expand-hint">... (${remaining} more lines)</div>`;
out += '</div>'; out += '</div>';
out += `<div class="output-full"><pre><code class="hljs">${highlighted}</code></pre></div></div>`; out += `<div class="output-full"><pre><code class="hljs">${highlighted}</code></pre></div></div>`;
return out; return out;
@ -1369,7 +1380,7 @@
for (const line of displayLines) { for (const line of displayLines) {
out += `<div>${escapeHtml(replaceTabs(line))}</div>`; out += `<div>${escapeHtml(replaceTabs(line))}</div>`;
} }
out += `<div class="expand-hint">... (${remaining} more lines) - click to expand</div>`; out += `<div class="expand-hint">... (${remaining} more lines)</div>`;
out += '</div>'; out += '</div>';
out += '<div class="output-full">'; out += '<div class="output-full">';
for (const line of lines) { for (const line of lines) {
@ -1493,13 +1504,8 @@
const diffLines = result.details.diff.split('\n'); const diffLines = result.details.diff.split('\n');
html += '<div class="tool-diff">'; html += '<div class="tool-diff">';
for (const line of diffLines) { for (const line of diffLines) {
if (line.startsWith('+')) { const cls = line.match(/^\+/) ? 'diff-added' : line.match(/^-/) ? 'diff-removed' : 'diff-context';
html += `<div class="diff-added">${escapeHtml(line)}</div>`; html += `<div class="${cls}">${escapeHtml(replaceTabs(line))}</div>`;
} else if (line.startsWith('-')) {
html += `<div class="diff-removed">${escapeHtml(line)}</div>`;
} else {
html += `<div class="diff-context">${escapeHtml(line)}</div>`;
}
} }
html += '</div>'; html += '</div>';
} }
@ -1570,7 +1576,10 @@
if (block.type === 'text' && block.text.trim()) { if (block.type === 'text' && block.text.trim()) {
html += `<div class="assistant-text markdown-content">${renderMarkdown(block.text)}</div>`; html += `<div class="assistant-text markdown-content">${renderMarkdown(block.text)}</div>`;
} else if (block.type === 'thinking' && block.thinking.trim()) { } else if (block.type === 'thinking' && block.thinking.trim()) {
html += `<div class="thinking-block">`;
html += `<div class="thinking-text">${escapeHtml(block.thinking)}</div>`; html += `<div class="thinking-text">${escapeHtml(block.thinking)}</div>`;
html += `<div class="thinking-collapsed">Thinking ...</div>`;
html += `</div>`;
} }
} }
@ -1617,14 +1626,10 @@
} }
if (entry.type === 'compaction') { if (entry.type === 'compaction') {
return `<div class="compaction" id="${entryId}"> return `<div class="compaction" id="${entryId}" onclick="this.classList.toggle('expanded')">
<div class="compaction-header" onclick="this.parentElement.classList.toggle('expanded')"> <div class="compaction-label">[compaction]</div>
<span class="compaction-toggle"></span> <div class="compaction-collapsed">Compacted from ${entry.tokensBefore.toLocaleString()} tokens (ctrl+o to expand)</div>
<span class="compaction-title">Context compacted from ${entry.tokensBefore.toLocaleString()} tokens</span> <div class="compaction-content"><strong>Compacted from ${entry.tokensBefore.toLocaleString()} tokens</strong>\n\n${escapeHtml(entry.summary)}</div>
</div>
<div class="compaction-content">
<div class="compaction-summary">${escapeHtml(entry.summary)}</div>
</div>
</div>`; </div>`;
} }
@ -1738,7 +1743,8 @@
if (scrollToTarget) { if (scrollToTarget) {
const targetEl = document.getElementById(`entry-${targetId}`); const targetEl = document.getElementById(`entry-${targetId}`);
if (targetEl) { if (targetEl) {
targetEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Use 'end' to ensure we scroll far enough to see the target
targetEl.scrollIntoView({ behavior: 'smooth', block: 'end' });
// Brief highlight // Brief highlight
targetEl.style.outline = '2px solid var(--accent)'; targetEl.style.outline = '2px solid var(--accent)';
setTimeout(() => { targetEl.style.outline = ''; }, 1500); setTimeout(() => { targetEl.style.outline = ''; }, 1500);
@ -1791,18 +1797,56 @@
// Close sidebar when clicking close button // Close sidebar when clicking close button
document.getElementById('sidebar-close').addEventListener('click', closeSidebar); document.getElementById('sidebar-close').addEventListener('click', closeSidebar);
// Keyboard shortcut: Escape to reset to leaf // Track toggle states
let thinkingExpanded = true;
let toolOutputsExpanded = true;
const toggleThinking = () => {
thinkingExpanded = !thinkingExpanded;
document.querySelectorAll('.thinking-text').forEach(el => {
el.style.display = thinkingExpanded ? '' : 'none';
});
document.querySelectorAll('.thinking-collapsed').forEach(el => {
el.style.display = thinkingExpanded ? 'none' : '';
});
};
const toggleToolOutputs = () => {
toolOutputsExpanded = !toolOutputsExpanded;
// Toggle expanded state on expandable tool outputs
document.querySelectorAll('.tool-output.expandable').forEach(el => {
if (toolOutputsExpanded) {
el.classList.add('expanded');
} else {
el.classList.remove('expanded');
}
});
// Toggle compaction/branch-summary expanded state
document.querySelectorAll('.compaction').forEach(el => {
if (toolOutputsExpanded) {
el.classList.add('expanded');
} else {
el.classList.remove('expanded');
}
});
};
// Keyboard shortcuts
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
searchInput.value = ''; searchInput.value = '';
searchQuery = ''; searchQuery = '';
navigateTo(leafId); navigateTo(leafId);
} }
// Focus search on Ctrl/Cmd+F // Toggle thinking blocks on Ctrl+T
if ((e.ctrlKey || e.metaKey) && e.key === 'f') { if (e.ctrlKey && e.key === 't') {
e.preventDefault(); e.preventDefault();
searchInput.focus(); toggleThinking();
searchInput.select(); }
// Toggle tool outputs on Ctrl+O
if (e.ctrlKey && e.key === 'o') {
e.preventDefault();
toggleToolOutputs();
} }
}); });