@@ -324,13 +325,6 @@ const hasBody = computed(() =>
/* Expandable sections */
.expand-section { border-top: 1px solid rgba(255, 255, 255, 0.04); }
-.expand-content {
- margin: 0; padding: 0.35rem 0.6rem;
- font-size: 11px; line-height: 1.45; color: var(--text-secondary);
- white-space: pre-wrap; word-break: break-word;
- max-height: 200px; overflow-y: auto;
- background: transparent; font-family: 'SF Mono', 'Fira Code', monospace;
-}
.result-section :deep(.tool-result) { border: none; border-radius: 0; margin-top: 0; }
diff --git a/frontend/src/components/transcript-debug/toolCards/WriteCard.vue b/frontend/src/components/transcript-debug/toolCards/WriteCard.vue
index 188ba1f..62d62d8 100644
--- a/frontend/src/components/transcript-debug/toolCards/WriteCard.vue
+++ b/frontend/src/components/transcript-debug/toolCards/WriteCard.vue
@@ -1,8 +1,8 @@
@@ -51,9 +49,7 @@ const lineCount = computed(() => content.value.split('\n').length)
-
+
@@ -140,21 +136,4 @@ const lineCount = computed(() => content.value.split('\n').length)
.toggle-btn:hover { opacity: 0.8; }
.toggle-btn.active { opacity: 1; color: rgba(34, 197, 94, 0.8); }
-/* Content */
-.content-section {
- border-top: 1px solid rgba(255, 255, 255, 0.04);
-}
-
-.content-pre {
- margin: 0;
- padding: 0.35rem 0.6rem;
- font-size: 11px;
- line-height: 1.45;
- color: var(--text-secondary);
- white-space: pre-wrap;
- word-break: break-all;
- max-height: 250px;
- overflow-y: auto;
- font-family: 'SF Mono', 'Fira Code', monospace;
-}
diff --git a/frontend/src/utils/markdown.ts b/frontend/src/utils/markdown.ts
index 5374e4f..fccc8c0 100644
--- a/frontend/src/utils/markdown.ts
+++ b/frontend/src/utils/markdown.ts
@@ -233,11 +233,14 @@ export function parseMarkdown(md: string): string {
// Extract fenced code blocks first (protect from inline parsing)
const codeBlocks: string[] = []
let text = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang: string, code: string) => {
- const highlighted = highlightCode(code.replace(/\n$/, ''), lang || undefined)
+ const rawCode = code.replace(/\n$/, '')
+ const highlighted = highlightCode(rawCode, lang || undefined)
const langLabel = lang ? `${esc(lang)}` : ''
+ const encoded = rawCode.replace(/&/g, '&').replace(/"/g, '"')
+ const copyBtn = ``
const idx = codeBlocks.length
codeBlocks.push(
- `${langLabel}
${highlighted}
`
+ `${langLabel}${copyBtn}
${highlighted}
`
)
return `\x00CODE${idx}\x00`
})
@@ -413,14 +416,46 @@ export const MARKDOWN_STYLES = `
letter-spacing: 0.5px;
}
+.md-content .md-copy-btn {
+ position: absolute;
+ top: 0;
+ right: 0;
+ font-size: 10px;
+ padding: 0.2em 0.5em;
+ color: var(--text-muted, #94a3b8);
+ background: rgba(255,255,255,0.06);
+ border: none;
+ border-bottom-left-radius: 4px;
+ cursor: pointer;
+ font-family: inherit;
+ opacity: 0;
+ transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;
+ z-index: 1;
+}
+
+.md-content .md-code-block:hover .md-copy-btn { opacity: 1; }
+.md-content .md-copy-btn:hover { color: var(--text-primary, #e2e8f0); background: rgba(255,255,255,0.1); }
+
+/* Hide lang label when copy button is visible */
+.md-content .md-code-block:hover .code-lang { opacity: 0; }
+
.md-content .md-pre {
margin: 0;
padding: 0.6em 0.75em;
font-size: 12px;
- line-height: 1.55;
+ line-height: 1.2;
overflow-x: auto;
- font-family: 'SF Mono', 'Fira Code', monospace;
+ font-family: 'Consolas', 'Lucida Console', 'SF Mono', 'Fira Code', monospace;
color: var(--text-secondary);
+ letter-spacing: 0;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.md-content .md-pre code {
+ white-space: pre;
+ word-spacing: 0;
+ tab-size: 4;
}
.md-content .md-blockquote {