diff --git a/src/web/public/app.js b/src/web/public/app.js index b95fa4a3..1c535ddb 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -1110,9 +1110,10 @@ class CodemanApp { /** Render markdown to sanitized HTML, falling back to plain text if marked.js unavailable */ _renderMarkdown(text) { + const src = text || ''; if (typeof marked !== 'undefined' && marked.parse) { try { - const prepared = this._preprocessAsciiArt(text); + const prepared = this._preprocessAsciiArt(src); let html = this._sanitizeHtml(marked.parse(prepared, { breaks: true, gfm: true })); // Wrap tables in a horizontal-scroll container so they overflow gracefully // on mobile without collapsing into block-level cells. @@ -1168,7 +1169,7 @@ class CodemanApp { } catch { /* fall through */ } } // Fallback: escape HTML and preserve whitespace - const escaped = text.replace(/&/g, '&').replace(//g, '>'); + const escaped = src.replace(/&/g, '&').replace(//g, '>'); return `
${escaped}
`; } diff --git a/src/web/public/styles.css b/src/web/public/styles.css index b96277ef..0272c7ae 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -7971,14 +7971,15 @@ kbd { bottom: 0; left: 0; right: 0; - max-height: 85vh; - background: #1a1a2e; - border-top: 1px solid #333; - border-radius: 12px 12px 0 0; + max-height: 88vh; + background: #14141f; + border-top: 1px solid #2a2a3a; + border-radius: 14px 14px 0 0; + box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.45); z-index: 5000; flex-direction: column; transform: translateY(100%); - transition: transform 0.25s ease-out; + transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1); } .response-viewer.visible { @@ -7990,12 +7991,13 @@ kbd { display: flex; align-items: center; justify-content: space-between; - padding: 12px 16px; - border-bottom: 1px solid #333; + padding: 14px 20px; + border-bottom: 1px solid #2a2a3a; flex-shrink: 0; font-size: 14px; font-weight: 600; - color: #e0e0e0; + color: #e8e8ec; + letter-spacing: 0.2px; } .response-viewer-actions { @@ -8078,42 +8080,80 @@ kbd { background: rgba(109, 219, 127, 0.12); } -/* Markdown rendered content inside response viewer */ -.rv-text { - word-break: break-word; - line-height: 1.6; +/* Markdown rendered content inside response viewer. + Prose uses a proportional font for readability; code keeps monospace. */ +.rv-text, +.response-viewer-body > :not(.rv-message) { + word-break: normal; + overflow-wrap: anywhere; + line-height: 1.7; } -.rv-text p { - margin: 0 0 0.6em; +.rv-text p, +.response-viewer-body > p { + margin: 0 0 0.85em; } -.rv-text p:last-child { +.rv-text p:last-child, +.response-viewer-body > p:last-child { margin-bottom: 0; } -.rv-text h1, .rv-text h2, .rv-text h3, .rv-text h4 { - color: #e0e0e0; - margin: 1em 0 0.4em; +.rv-text h1, .rv-text h2, .rv-text h3, .rv-text h4, +.response-viewer-body > h1, .response-viewer-body > h2, +.response-viewer-body > h3, .response-viewer-body > h4 { + color: #f2f2f6; + margin: 1.4em 0 0.5em; line-height: 1.3; + font-weight: 700; + letter-spacing: -0.01em; } -.rv-text h1 { font-size: 1.3em; } -.rv-text h2 { font-size: 1.15em; } -.rv-text h3 { font-size: 1.05em; } +.rv-text h1:first-child, .rv-text h2:first-child, +.response-viewer-body > h1:first-child, .response-viewer-body > h2:first-child { + margin-top: 0; +} -.rv-text code { - background: #2a2a3e; - padding: 1px 5px; - border-radius: 3px; +.rv-text h1, .response-viewer-body > h1 { + font-size: 1.55em; + padding-bottom: 0.3em; + border-bottom: 1px solid #2d2d40; +} +.rv-text h2, .response-viewer-body > h2 { + font-size: 1.3em; + color: #ffd27a; +} +.rv-text h3, .response-viewer-body > h3 { + font-size: 1.13em; + color: #bfc8ff; +} +.rv-text h4, .response-viewer-body > h4 { + font-size: 1em; + color: #c9c9d5; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.rv-text code, +.response-viewer-body > :not(pre) code { + background: #262638; + color: #ffb4a2; + padding: 1px 6px; + border-radius: 4px; + font-family: 'Fira Code', 'JetBrains Mono', 'SF Mono', Menlo, Monaco, monospace; font-size: 0.9em; } -.rv-text pre { - background: #1e1e2e; - border: 1px solid #333; - border-radius: 6px; - padding: 10px 12px; +/* Descendant (not child) combinator: code blocks are wrapped in .rv-code-wrap, + so the latest-response view (markdown rendered straight into the body) nests +
 one level deeper than a direct child. The historical .rv-text path
+   already matched via descendant; keep both in lockstep. */
+.rv-text pre,
+.response-viewer-body pre {
+  background: #0f0f1a;
+  border: 1px solid #2a2a3d;
+  border-radius: 8px;
+  padding: 14px 16px;
   overflow-x: auto;
   margin: 1em 0;
   -webkit-overflow-scrolling: touch;
@@ -8125,7 +8165,7 @@ kbd {
    Preserve indentation (pre-wrap) but allow breaks inside long tokens
    (URLs, paths, identifiers) so they don't overflow. */
 .rv-text pre code,
-.response-viewer-body > pre code {
+.response-viewer-body pre code {
   background: none;
   color: #e6e6f0;
   padding: 0;
@@ -8294,38 +8334,43 @@ kbd {
 .rv-text ul, .rv-text ol,
 .response-viewer-body > ul, .response-viewer-body > ol {
   margin: 0.6em 0;
+  padding-left: 1.5em;
 }
 
-.rv-text pre code {
-  background: none;
-  padding: 0;
-  font-size: 0.85em;
-  line-height: 1.5;
+.rv-text li,
+.response-viewer-body > ul > li, .response-viewer-body > ol > li {
+  margin-bottom: 0.3em;
 }
 
-.rv-text ul, .rv-text ol {
-  margin: 0.4em 0;
-  padding-left: 1.4em;
-}
+.rv-text li > p { margin: 0.2em 0; }
 
-.rv-text li {
-  margin-bottom: 0.2em;
+.rv-text blockquote,
+.response-viewer-body > blockquote {
+  border-left: 3px solid #5c7cfa;
+  background: rgba(92, 124, 250, 0.06);
+  margin: 0.8em 0;
+  padding: 0.5em 14px;
+  color: #b8b8c8;
+  border-radius: 0 6px 6px 0;
 }
 
-.rv-text blockquote {
-  border-left: 3px solid #444;
-  margin: 0.6em 0;
-  padding: 0.3em 0 0.3em 12px;
-  color: #999;
+.rv-text strong,
+.response-viewer-body > p strong,
+.response-viewer-body > li strong {
+  color: #ffffff;
+  font-weight: 700;
 }
 
-.rv-text strong {
-  color: #f0f0f0;
+.rv-text em,
+.response-viewer-body em {
+  color: #e0e0ec;
 }
 
-.rv-text a {
-  color: #5c7cfa;
+.rv-text a,
+.response-viewer-body a {
+  color: #7aa2ff;
   text-decoration: none;
+  border-bottom: 1px solid rgba(122, 162, 255, 0.35);
 }
 
 .rv-text a:hover,
@@ -8403,19 +8448,37 @@ kbd {
 .rv-text hr,
 .response-viewer-body > hr {
   border: none;
-  border-top: 1px solid #333;
-  margin: 1em 0;
+  border-top: 1px solid #2d2d40;
+  margin: 1.5em 0;
 }
 
 .response-viewer-body {
   flex: 1;
   overflow-y: auto;
   -webkit-overflow-scrolling: touch;
-  padding: 16px;
-  font-family: 'Fira Code', 'Cascadia Code', 'JetBrains Mono', 'SF Mono', Monaco, monospace;
-  font-size: 13px;
-  line-height: 1.5;
-  color: #d4d4d4;
+  overscroll-behavior: contain;
+  padding: 20px 22px 28px;
+  /* Proportional font for prose — monospace only for code/pre */
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC',
+    'Hiragino Sans GB', 'Segoe UI', 'Helvetica Neue', Helvetica, Arial,
+    'Noto Sans CJK SC', sans-serif;
+  font-size: 15px;
+  line-height: 1.7;
+  color: #d8d8e0;
+  /* Comfortable reading width on wider viewports */
+  --rv-content-max: 720px;
+}
+
+/* Constrain content width for readability; code blocks can still scroll horizontally */
+.response-viewer-body > * {
+  max-width: var(--rv-content-max);
+  margin-left: auto;
+  margin-right: auto;
+}
+.response-viewer-body > pre,
+.response-viewer-body > table,
+.response-viewer-body > .rv-message {
+  max-width: var(--rv-content-max);
 }
 
 .response-viewer-body:empty::after {