style and display fixes

This commit is contained in:
Rob Colbert 2026-05-12 01:02:09 -04:00
parent dd47212471
commit b3c9579890
3 changed files with 134 additions and 139 deletions

View File

@ -76,12 +76,14 @@ const ChatTurn = memo(function ChatTurn({ turn }: ChatTurnProps) {
{/* Error Alert */} {/* Error Alert */}
{turn.errorMessage && turn.status === "error" && ( {turn.errorMessage && turn.status === "error" && (
<div className="max-w-[80%] ml-0 mb-4"> <div className="max-w-[80%] ml-0 mb-2">
<div className="bg-red-600 text-white rounded p-4 border-2 border-red-400"> <div className="bg-red-600 text-white rounded p-3 border-2 border-red-400">
<div className="text-sm font-semibold mb-2 flex items-center gap-2"> <div className="text-xs font-semibold mb-1 flex items-center gap-2">
<span></span> Error <span></span> Error
</div> </div>
<div className="whitespace-pre-wrap">{turn.errorMessage}</div> <div className="whitespace-pre-wrap text-sm">
{turn.errorMessage}
</div>
</div> </div>
</div> </div>
)} )}
@ -89,51 +91,53 @@ const ChatTurn = memo(function ChatTurn({ turn }: ChatTurnProps) {
{/* User Prompt */} {/* User Prompt */}
<div className="max-w-[80%] ml-0 mb-4"> <div className="max-w-[80%] ml-0 mb-4">
<div className="bg-[#4a0000] text-white rounded p-4"> <div className="bg-[#4a0000] text-white rounded p-4">
<div className="text-sm font-semibold mb-2">You</div> <div className="text-xs font-semibold mb-2">You</div>
<div className="whitespace-pre-wrap">{turn.prompts?.user || ""}</div> <div className="whitespace-pre-wrap text-sm">
{turn.prompts?.user || ""}
</div>
</div> </div>
</div> </div>
{/* Agent Response */} {/* Agent Response */}
{(turn.blocks && turn.blocks.length > 0) && ( {turn.blocks && turn.blocks.length > 0 && (
<div className="max-w-[80%] ml-auto mb-4 overflow-x-hidden"> <div className="max-w-[80%] ml-auto mb-4 overflow-x-hidden">
<div className="bg-bg-tertiary border border-border-default rounded p-4 overflow-x-auto"> <div className="bg-bg-tertiary border border-border-default rounded p-3 overflow-x-auto">
<div className="text-sm font-semibold mb-3 text-text-secondary"> <div className="text-xs font-semibold mb-4 text-text-secondary">
Gadget Gadget
</div> </div>
{/* Render blocks in order */} {/* Render blocks in order */}
{turn.blocks.map((block, idx) => { {turn.blocks.map((block, idx) => {
if (block.mode === 'thinking') { if (block.mode === "thinking") {
return ( return (
<div key={idx} className="mb-3"> <div key={idx} className="mb-4">
<div className="text-xs text-text-muted mb-1 font-mono"> <div className="text-xs text-text-muted mb-1 font-mono">
Thinking Thinking
</div> </div>
<div <div
className="p-3 bg-bg-secondary rounded text-sm text-text-muted whitespace-pre-wrap font-mono text-xs" className="gadget-markdown bg-bg-secondary rounded p-2 text-xs text-text-muted font-mono"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: marked.parse(block.content) as string __html: marked.parse(block.content) as string,
}} }}
/> />
</div> </div>
); );
} else if (block.mode === 'responding') { } else if (block.mode === "responding") {
return ( return (
<div key={idx} className="mb-3 overflow-x-auto"> <div key={idx} className="mb-4 overflow-x-auto">
<div <div
className="gadget-markdown" className="gadget-markdown text-[13px]"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: marked.parse(block.content) as string __html: marked.parse(block.content) as string,
}} }}
/> />
</div> </div>
); );
} else if (block.mode === 'tool') { } else if (block.mode === "tool") {
const toolCall = block.content; const toolCall = block.content;
const subagent = toolCall.subagent; const subagent = toolCall.subagent;
return ( return (
<div key={idx} className="mb-3"> <div key={idx} className="tool-call">
<div className="flex items-center gap-2 text-xs font-mono text-text-secondary"> <div className="flex items-center gap-2 text-xs font-mono text-text-secondary">
<span className="text-brand"></span> <span className="text-brand"></span>
<span>{toolCall.name}</span> <span>{toolCall.name}</span>
@ -144,16 +148,14 @@ const ChatTurn = memo(function ChatTurn({ turn }: ChatTurnProps) {
<span className="text-green-500"></span> <span className="text-green-500"></span>
)} )}
</div> </div>
{subagent && ( {subagent && <SubagentDisplay subagent={subagent} />}
<SubagentDisplay subagent={subagent} />
)}
</div> </div>
); );
} }
return null; return null;
})} })}
<div className="mt-2 text-xs text-text-muted"> <div className="mt-1 text-xs text-text-muted">
{startedAt.toLocaleTimeString()} {startedAt.toLocaleTimeString()}
</div> </div>
</div> </div>
@ -163,7 +165,11 @@ const ChatTurn = memo(function ChatTurn({ turn }: ChatTurnProps) {
); );
}); });
function SubagentDisplay({ subagent }: { subagent: NonNullable<ChatTurnBlockTool['content']['subagent']> }) { function SubagentDisplay({
subagent,
}: {
subagent: NonNullable<ChatTurnBlockTool["content"]["subagent"]>;
}) {
const [expanded, setExpanded] = useState(true); const [expanded, setExpanded] = useState(true);
const toggle = useCallback(() => setExpanded((p) => !p), []); const toggle = useCallback(() => setExpanded((p) => !p), []);
@ -179,7 +185,7 @@ function SubagentDisplay({ subagent }: { subagent: NonNullable<ChatTurnBlockTool
}; };
return ( return (
<div className="ml-4 border-l-2 border-brand/20 pl-2"> <div className="ml-3 border-l-2 border-brand/20 pl-1.5 mt-1">
<button <button
onClick={toggle} onClick={toggle}
className="flex items-center gap-1.5 text-xs font-mono text-text-muted hover:text-text-primary transition-colors w-full text-left" className="flex items-center gap-1.5 text-xs font-mono text-text-muted hover:text-text-primary transition-colors w-full text-left"
@ -188,7 +194,8 @@ function SubagentDisplay({ subagent }: { subagent: NonNullable<ChatTurnBlockTool
<span>Subagent</span> <span>Subagent</span>
{subagent.stats && ( {subagent.stats && (
<span className="text-text-muted"> <span className="text-text-muted">
({subagent.toolCalls.length} calls · {formatDuration(subagent.stats.durationMs)}) ({subagent.toolCalls.length} calls ·{" "}
{formatDuration(subagent.stats.durationMs)})
</span> </span>
)} )}
</button> </button>
@ -198,9 +205,11 @@ function SubagentDisplay({ subagent }: { subagent: NonNullable<ChatTurnBlockTool
{/* Thinking */} {/* Thinking */}
{subagent.thinking && ( {subagent.thinking && (
<div> <div>
<div className="text-xs text-text-muted font-mono mb-1">Thinking</div> <div className="text-xs text-text-muted font-mono mb-0.5">
Thinking
</div>
<div <div
className="subagent-compact gadget-markdown bg-bg-secondary rounded p-1.5 text-xs text-text-muted whitespace-pre-wrap font-mono" className="gadget-markdown bg-bg-secondary rounded p-1.5 text-xs text-text-muted font-mono"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: marked.parse(subagent.thinking) as string, __html: marked.parse(subagent.thinking) as string,
}} }}
@ -211,12 +220,15 @@ function SubagentDisplay({ subagent }: { subagent: NonNullable<ChatTurnBlockTool
{/* Subagent Tool Calls */} {/* Subagent Tool Calls */}
{subagent.toolCalls.length > 0 && ( {subagent.toolCalls.length > 0 && (
<div> <div>
<div className="text-xs text-text-muted font-mono mb-1"> <div className="text-xs text-text-muted font-mono mb-0.5">
Tool Calls ({subagent.toolCalls.length}) Tool Calls ({subagent.toolCalls.length})
</div> </div>
<div className="space-y-1"> <div className="space-y-0.5">
{subagent.toolCalls.map((tc, i) => ( {subagent.toolCalls.map((tc, i) => (
<div key={tc.callId || i} className="flex items-center gap-2 text-xs font-mono text-text-secondary"> <div
key={tc.callId || i}
className="flex items-center gap-2 text-xs font-mono text-text-secondary"
>
<span className="text-yellow-500"></span> <span className="text-yellow-500"></span>
<span>{tc.name}</span> <span>{tc.name}</span>
{tc.response && <span className="text-green-500"></span>} {tc.response && <span className="text-green-500"></span>}
@ -229,9 +241,11 @@ function SubagentDisplay({ subagent }: { subagent: NonNullable<ChatTurnBlockTool
{/* Response */} {/* Response */}
{subagent.response && ( {subagent.response && (
<div> <div>
<div className="text-xs text-text-muted font-mono mb-1">Response</div> <div className="text-xs text-text-muted font-mono mb-0.5">
Response
</div>
<div <div
className="subagent-compact gadget-markdown" className="gadget-markdown text-xs"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: marked.parse(subagent.response) as string, __html: marked.parse(subagent.response) as string,
}} }}
@ -246,7 +260,9 @@ function SubagentDisplay({ subagent }: { subagent: NonNullable<ChatTurnBlockTool
<span>{formatTokenCount(subagent.stats.inputTokens)} in</span> <span>{formatTokenCount(subagent.stats.inputTokens)} in</span>
<span>{formatTokenCount(subagent.stats.responseTokens)} out</span> <span>{formatTokenCount(subagent.stats.responseTokens)} out</span>
{subagent.stats.thinkingTokenCount > 0 && ( {subagent.stats.thinkingTokenCount > 0 && (
<span>{formatTokenCount(subagent.stats.thinkingTokenCount)} thinking</span> <span>
{formatTokenCount(subagent.stats.thinkingTokenCount)} thinking
</span>
)} )}
</div> </div>
)} )}

View File

@ -13,8 +13,9 @@
--color-border-default: #2a2a2a; --color-border-default: #2a2a2a;
--color-border-highlight: #3a3a3a; --color-border-highlight: #3a3a3a;
--font-mono: 'Courier New', Courier, monospace; --font-mono: "Fira Code", "Courier New", Courier, monospace;
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-sans:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--spacing-header: 48px; --spacing-header: 48px;
--spacing-status: 32px; --spacing-status: 32px;
@ -54,7 +55,17 @@ button {
font-size: inherit; font-size: inherit;
} }
input, textarea { .tool-call {
margin-top: theme("spacing.4");
margin-bottom: theme("spacing.4");
}
.tool-call + .tool-call {
margin-top: 0;
}
input,
textarea {
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
} }
@ -77,8 +88,13 @@ input, textarea {
} }
@keyframes strobe { @keyframes strobe {
0%, 100% { opacity: 1; } 0%,
50% { opacity: 0.4; } 100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
} }
.strobe { .strobe {
@ -86,20 +102,20 @@ input, textarea {
} }
/* ── Gadget Markdown Styles ─────────────────────────────────── */ /* ── Gadget Markdown Styles ─────────────────────────────────── */
/* Applied to agent response output inside ChatTurn */ /* Dense, technical display — no blog-style whitespace */
.gadget-markdown { .gadget-markdown {
color: var(--color-text-primary); color: var(--color-text-primary);
line-height: 1.7; line-height: 1.5;
font-size: 14px;
white-space: pre-wrap;
word-break: break-word; word-break: break-word;
/* Prevent the markdown root from stretching to fill the scroll container
display:inline-block shrinks it to content width, so the
overflow-x-auto on the parent actually clips and scrolls it. */
display: inline-block; display: inline-block;
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
margin-bottom: 8px;
:last-child {
margin-bottom: 0;
}
} }
.gadget-markdown h1, .gadget-markdown h1,
@ -110,19 +126,37 @@ input, textarea {
.gadget-markdown h6 { .gadget-markdown h6 {
color: #e8e8e8; color: #e8e8e8;
font-weight: 600; font-weight: 600;
line-height: 1.3; line-height: 1.25;
margin-top: 1.5em; margin-top: 0.5rem;
margin-bottom: 0.5em; margin-bottom: 0.25rem;
} }
.gadget-markdown h1 { font-size: 1.4em; border-bottom: 1px solid var(--color-border-default); padding-bottom: 0.3em; } .gadget-markdown h1 {
.gadget-markdown h2 { font-size: 1.2em; border-bottom: 1px solid var(--color-border-default); padding-bottom: 0.25em; } font-size: 1.5em;
.gadget-markdown h3 { font-size: 1.05em; } /* border-bottom: 1px solid var(--color-border-default); */
.gadget-markdown h4 { font-size: 1em; } padding-bottom: 0.2em;
}
.gadget-markdown h2 {
font-size: 1.4em;
/* border-bottom: 1px solid var(--color-border-default); */
padding-bottom: 0.15em;
}
.gadget-markdown h3 {
font-size: 1.3em;
}
.gadget-markdown h4 {
font-size: 1.2em;
}
.gadget-markdown h5 {
font-size: 1.1em;
}
.gadget-markdown h6 {
font-size: 1em;
}
.gadget-markdown p { .gadget-markdown p {
margin-top: 0; margin-top: 0;
margin-bottom: 0.85em; margin-bottom: 0.8rem;
} }
.gadget-markdown a { .gadget-markdown a {
@ -137,23 +171,23 @@ input, textarea {
/* Inline code */ /* Inline code */
.gadget-markdown code:not(pre code) { .gadget-markdown code:not(pre code) {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 0.875em; font-size: 0.85em;
background: #1e1e1e; background: #1e1e1e;
color: #f472b6; color: rgb(219, 219, 7);
padding: 0.15em 0.4em; padding: 0.1em 0.3em;
border-radius: 4px; border-radius: 3px;
border: 1px solid #2d2d2d; border: 1px solid #2d2d2d;
} }
/* Code blocks */ /* Code blocks */
.gadget-markdown pre { .gadget-markdown pre {
line-height: 1rem;
background: #0d0d0d; background: #0d0d0d;
border: 1px solid #2a2a2a; border: 1px solid #2a2a2a;
border-radius: 6px; border-radius: 4px;
padding: 1em 1.2em; padding: 0.5rem 0.7rem;
overflow-x: auto; overflow-x: auto;
margin-bottom: 1em; margin-bottom: 0.5rem;
/* Horizontal scroll fix — parent must have overflow-x-auto or be constrained */
max-width: 100%; max-width: 100%;
width: 100%; width: 100%;
} }
@ -173,14 +207,14 @@ input, textarea {
width: auto; width: auto;
max-width: 100%; max-width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin-bottom: 1em; margin-bottom: 0.5rem;
font-size: 0.9em; font-size: 0.85em;
} }
.gadget-markdown th, .gadget-markdown th,
.gadget-markdown td { .gadget-markdown td {
border: 1px solid #2a2a2a; border: 1px solid #2a2a2a;
padding: 0.5em 0.9em; padding: 0.35em 0.6em;
text-align: left; text-align: left;
} }
@ -200,23 +234,23 @@ input, textarea {
/* Blockquotes */ /* Blockquotes */
.gadget-markdown blockquote { .gadget-markdown blockquote {
border-left: 3px solid #3a3a3a; border-left: 2px solid #3a3a3a;
margin: 0 0 1em 0; margin: 0 0 0.35rem 0;
padding: 0.5em 1em; padding: 0.25rem 0.6rem;
color: var(--color-text-muted); color: var(--color-text-muted);
background: #111111; background: #111111;
border-radius: 0 4px 4px 0; border-radius: 0 3px 3px 0;
} }
/* Lists */ /* Lists */
.gadget-markdown ul, .gadget-markdown ul,
.gadget-markdown ol { .gadget-markdown ol {
margin-bottom: 0.85em; margin-bottom: 0.35rem;
padding-left: 1.5em; padding-left: 1.2rem;
} }
.gadget-markdown li { .gadget-markdown li {
margin-bottom: 0.3em; margin-bottom: 0.15rem;
} }
.gadget-markdown li::marker { .gadget-markdown li::marker {
@ -227,13 +261,13 @@ input, textarea {
.gadget-markdown hr { .gadget-markdown hr {
border: none; border: none;
border-top: 1px solid var(--color-border-default); border-top: 1px solid var(--color-border-default);
margin: 1.5em 0; margin: 0.5rem 0;
} }
/* Images */ /* Images */
.gadget-markdown img { .gadget-markdown img {
max-width: 100%; max-width: 100%;
border-radius: 6px; border-radius: 4px;
border: 1px solid var(--color-border-default); border: 1px solid var(--color-border-default);
} }
@ -243,61 +277,6 @@ input, textarea {
font-weight: 600; font-weight: 600;
} }
.gadget-markdown em { .gadget-markdown em {
color: #c4c4c4; color: #c4c4c4;
} }
/* ── Subagent Compact Markdown ── */
/* Higher-specificity overrides (.subagent-compact.gadget-markdown > .gadget-markdown)
to crush the generous spacing that looks fine at top-level but explodes when nested. */
.subagent-compact.gadget-markdown {
font-size: 12px;
line-height: 1.4;
}
.subagent-compact.gadget-markdown h1,
.subagent-compact.gadget-markdown h2,
.subagent-compact.gadget-markdown h3,
.subagent-compact.gadget-markdown h4,
.subagent-compact.gadget-markdown h5,
.subagent-compact.gadget-markdown h6 {
margin-top: 0.5rem;
margin-bottom: 0.25rem;
line-height: 1.2;
}
.subagent-compact.gadget-markdown p {
margin-top: 0;
margin-bottom: 0.35rem;
}
.subagent-compact.gadget-markdown pre {
margin-top: 0.35rem;
margin-bottom: 0.35rem;
padding: 0.5rem 0.6rem;
}
.subagent-compact.gadget-markdown ul,
.subagent-compact.gadget-markdown ol {
margin-bottom: 0.35rem;
padding-left: 1.1rem;
}
.subagent-compact.gadget-markdown li {
margin-bottom: 0.15rem;
}
.subagent-compact.gadget-markdown blockquote {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
padding: 0.25rem 0.6rem;
}
.subagent-compact.gadget-markdown hr {
margin: 0.4rem 0;
}
.subagent-compact.gadget-markdown table {
margin-bottom: 0.5rem;
}

View File

@ -1009,7 +1009,7 @@ export default function ChatSessionView() {
{/* Chat View (75%) */} {/* Chat View (75%) */}
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
{/* Messages */} {/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4"> <div className="flex-1 overflow-y-auto p-3 space-y-2">
{turns.map((turn) => ( {turns.map((turn) => (
<ChatTurnComponent <ChatTurnComponent
key={turn._id} key={turn._id}