Skip to content

Commit 8e5d6a0

Browse files
committed
tweak: cleanup to markdown renderer
1 parent 1cbf8ff commit 8e5d6a0

File tree

6 files changed

+369
-141
lines changed

6 files changed

+369
-141
lines changed

cli/src/components/message-block.tsx

Lines changed: 166 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ interface ContentWithMarkdownProps {
9797
isStreaming: boolean
9898
codeBlockWidth: number
9999
palette: MarkdownPalette
100+
textColor?: string
101+
textAttributes?: TextAttributes
102+
renderWrapper?: (node: ReactNode, info: { isLettered: boolean }) => ReactNode
100103
}
101104

102105
const ContentWithMarkdown = memo(
@@ -105,15 +108,47 @@ const ContentWithMarkdown = memo(
105108
isStreaming,
106109
codeBlockWidth,
107110
palette,
111+
textColor,
112+
textAttributes,
113+
renderWrapper,
108114
}: ContentWithMarkdownProps) => {
115+
if (!content) {
116+
return null
117+
}
118+
119+
const wrap =
120+
renderWrapper ??
121+
((node: ReactNode) => {
122+
if (node === '' || node === null || node === undefined) {
123+
return null
124+
}
125+
return node
126+
})
127+
128+
const options = {
129+
codeBlockWidth,
130+
palette,
131+
textColor,
132+
textAttributes,
133+
}
134+
135+
if (!isStreaming && hasLetteredItems(content)) {
136+
return wrap(renderLetteredItemsWithBoxes(content, options), {
137+
isLettered: true,
138+
})
139+
}
140+
109141
if (!hasMarkdown(content)) {
110-
return content
142+
return wrap(content, { isLettered: false })
111143
}
112-
const options = { codeBlockWidth, palette }
144+
113145
if (isStreaming) {
114-
return renderStreamingMarkdown(content, options)
146+
return wrap(renderStreamingMarkdown(content, options), {
147+
isLettered: false,
148+
})
115149
}
116-
return renderMarkdown(content, options)
150+
151+
return wrap(renderMarkdown(content, options), { isLettered: false })
117152
},
118153
)
119154

@@ -150,12 +185,22 @@ const PlanBox = memo(
150185
paddingBottom: 1,
151186
}}
152187
>
153-
<text style={{ wrapMode: 'word', fg: theme.foreground }}>
154-
{renderMarkdown(planContent, {
155-
codeBlockWidth: Math.max(10, availableWidth - 8),
156-
palette: markdownPalette,
157-
})}
158-
</text>
188+
<ContentWithMarkdown
189+
content={planContent}
190+
isStreaming={false}
191+
codeBlockWidth={Math.max(10, availableWidth - 8)}
192+
palette={markdownPalette}
193+
textColor={theme.foreground}
194+
renderWrapper={(node, { isLettered }) =>
195+
isLettered ? (
196+
<box style={{ width: '100%' }}>{node}</box>
197+
) : (
198+
<text style={{ wrapMode: 'word', fg: theme.foreground }}>
199+
{node}
200+
</text>
201+
)
202+
}
203+
/>
159204
<BuildModeButtons
160205
theme={theme}
161206
onBuildFast={onBuildFast}
@@ -319,33 +364,35 @@ const ToolBranch = memo(
319364
},
320365
}
321366

322-
const displayContent = (
367+
const textAttributes =
368+
theme.messageTextAttributes && theme.messageTextAttributes !== 0
369+
? theme.messageTextAttributes
370+
: undefined
371+
372+
const renderableDisplayContent = (
323373
<ContentWithMarkdown
324374
content={fullContent}
325375
isStreaming={false}
326376
codeBlockWidth={agentMarkdownOptions.codeBlockWidth}
327377
palette={agentMarkdownOptions.palette}
378+
textColor={theme.foreground}
379+
textAttributes={textAttributes}
380+
renderWrapper={(node, { isLettered }) =>
381+
isLettered ? (
382+
<box style={{ width: '100%' }}>{node}</box>
383+
) : (
384+
<text
385+
fg={theme.foreground}
386+
style={{ wrapMode: 'word' }}
387+
attributes={textAttributes}
388+
>
389+
{node}
390+
</text>
391+
)
392+
}
328393
/>
329394
)
330395

331-
const renderableDisplayContent =
332-
displayContent === null ||
333-
displayContent === undefined ||
334-
displayContent === false ||
335-
displayContent === '' ? null : (
336-
<text
337-
fg={theme.foreground}
338-
style={{ wrapMode: 'word' }}
339-
attributes={
340-
theme.messageTextAttributes && theme.messageTextAttributes !== 0
341-
? theme.messageTextAttributes
342-
: undefined
343-
}
344-
>
345-
{displayContent}
346-
</text>
347-
)
348-
349396
const headerName = displayInfo.name
350397

351398
const handleToggle = useCallback(() => {
@@ -539,24 +586,42 @@ const AgentBody = memo(
539586
const marginBottom = textBlock.marginBottom ?? 0
540587
const explicitColor = textBlock.color
541588
const nestedTextColor = explicitColor ?? theme.foreground
589+
const marginLeft = Math.max(0, indentLevel * 2)
542590
nodes.push(
543-
<text
591+
<ContentWithMarkdown
544592
key={renderKey}
545-
style={{
546-
wrapMode: 'word',
547-
fg: nestedTextColor,
548-
marginLeft: Math.max(0, indentLevel * 2),
549-
marginTop,
550-
marginBottom,
551-
}}
552-
>
553-
<ContentWithMarkdown
554-
content={filteredNestedContent}
555-
isStreaming={isNestedStreamingText}
556-
codeBlockWidth={markdownOptionsForLevel.codeBlockWidth}
557-
palette={markdownOptionsForLevel.palette}
558-
/>
559-
</text>,
593+
content={filteredNestedContent}
594+
isStreaming={isNestedStreamingText}
595+
codeBlockWidth={markdownOptionsForLevel.codeBlockWidth}
596+
palette={markdownOptionsForLevel.palette}
597+
textColor={nestedTextColor}
598+
renderWrapper={(node, { isLettered }) =>
599+
isLettered ? (
600+
<box
601+
style={{
602+
marginLeft,
603+
marginTop,
604+
marginBottom,
605+
width: '100%',
606+
}}
607+
>
608+
{node}
609+
</box>
610+
) : (
611+
<text
612+
style={{
613+
wrapMode: 'word',
614+
fg: nestedTextColor,
615+
marginLeft,
616+
marginTop,
617+
marginBottom,
618+
}}
619+
>
620+
{node}
621+
</text>
622+
)
623+
}
624+
/>,
560625
)
561626
nestedIdx++
562627
break
@@ -793,19 +858,30 @@ const SimpleContent = memo(
793858
? trimTrailingNewlines(content)
794859
: content.trim()
795860

861+
const textAttributes = isUser ? TextAttributes.ITALIC : undefined
862+
796863
return (
797-
<text
864+
<ContentWithMarkdown
798865
key={`message-content-${messageId}`}
799-
style={{ wrapMode: 'word', fg: textColor }}
800-
attributes={isUser ? TextAttributes.ITALIC : undefined}
801-
>
802-
<ContentWithMarkdown
803-
content={normalizedContent}
804-
isStreaming={isStreamingMessage}
805-
codeBlockWidth={codeBlockWidth}
806-
palette={palette}
807-
/>
808-
</text>
866+
content={normalizedContent}
867+
isStreaming={isStreamingMessage}
868+
codeBlockWidth={codeBlockWidth}
869+
palette={palette}
870+
textColor={textColor}
871+
textAttributes={textAttributes}
872+
renderWrapper={(node, { isLettered }) =>
873+
isLettered ? (
874+
<box style={{ width: '100%' }}>{node}</box>
875+
) : (
876+
<text
877+
style={{ wrapMode: 'word', fg: textColor }}
878+
attributes={textAttributes}
879+
>
880+
{node}
881+
</text>
882+
)
883+
}
884+
/>
809885
)
810886
},
811887
)
@@ -869,24 +945,42 @@ const SingleBlock = memo(
869945
const marginBottom = textBlock.marginBottom ?? 0
870946
const explicitColor = textBlock.color
871947
const blockTextColor = explicitColor ?? textColor
948+
const textAttributes = isUser ? TextAttributes.ITALIC : undefined
872949
return (
873-
<text
950+
<ContentWithMarkdown
874951
key={renderKey}
875-
style={{
876-
wrapMode: 'word',
877-
fg: blockTextColor,
878-
marginTop,
879-
marginBottom,
880-
}}
881-
attributes={isUser ? TextAttributes.ITALIC : undefined}
882-
>
883-
<ContentWithMarkdown
884-
content={filteredContent}
885-
isStreaming={isStreamingText}
886-
codeBlockWidth={codeBlockWidth}
887-
palette={markdownPalette}
888-
/>
889-
</text>
952+
content={filteredContent}
953+
isStreaming={isStreamingText}
954+
codeBlockWidth={codeBlockWidth}
955+
palette={markdownPalette}
956+
textColor={blockTextColor}
957+
textAttributes={textAttributes}
958+
renderWrapper={(node, { isLettered }) =>
959+
isLettered ? (
960+
<box
961+
style={{
962+
marginTop,
963+
marginBottom,
964+
width: '100%',
965+
}}
966+
>
967+
{node}
968+
</box>
969+
) : (
970+
<text
971+
style={{
972+
wrapMode: 'word',
973+
fg: blockTextColor,
974+
marginTop,
975+
marginBottom,
976+
}}
977+
attributes={textAttributes}
978+
>
979+
{node}
980+
</text>
981+
)
982+
}
983+
/>
890984
)
891985
}
892986

cli/src/components/message-renderer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ const AgentMessage = memo(
422422
const agentMarkdownOptions = {
423423
codeBlockWidth: agentCodeBlockWidth,
424424
palette: agentPalette,
425+
textColor: theme.foreground,
425426
}
426427
const displayContent = useMemo(() => {
427428
if (!hasMarkdown(rawDisplayContent)) {

cli/src/types/theme-system.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface MarkdownThemeOverrides {
1717
dividerFg?: string
1818
codeMonochrome?: boolean
1919
defaultOptionFg?: string
20+
bodyTextFg?: string
2021
}
2122

2223
/**

cli/src/utils/__tests__/markdown-renderer.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,4 +957,41 @@ c) Third option`
957957
expect(defaultSpans.length).toBeGreaterThanOrEqual(2)
958958
})
959959
})
960+
961+
describe('ordering and styling', () => {
962+
test('keeps trailing paragraphs after lettered sections', () => {
963+
const markdown = `1. Decide on rollout strategy:
964+
- a) (DEFAULT) Gradual enablement with guardrails
965+
- b) Dual-track rollout with feature flags
966+
967+
Please confirm whether you want QA sign-off before deployment.`
968+
969+
const output = renderLetteredItemsWithBoxes(markdown)
970+
const textContent = getAllTextContent(output)
971+
expect(textContent).toContain('Please confirm whether you want QA sign-off before deployment.')
972+
expect(textContent.indexOf('Please confirm whether you want QA sign-off before deployment.')).toBeGreaterThan(
973+
textContent.indexOf('Dual-track rollout'),
974+
)
975+
})
976+
977+
test('applies custom text color and attributes', () => {
978+
const markdown = `1. Pick next step:
979+
- a) Move forward`
980+
981+
const output = renderLetteredItemsWithBoxes(markdown, {
982+
textColor: '#ff0000',
983+
textAttributes: TextAttributes.ITALIC,
984+
})
985+
986+
const textElements = findAllElements(
987+
output,
988+
(el) => el.type === 'text' && el.props.style?.fg === '#ff0000',
989+
)
990+
991+
expect(textElements.length).toBeGreaterThan(0)
992+
textElements.forEach((el) => {
993+
expect(el.props.attributes).toBe(TextAttributes.ITALIC)
994+
})
995+
})
996+
})
960997
})

0 commit comments

Comments
 (0)