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