Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/components/src/components/ai-gui/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ File-by-file ownership and coverage pointers: [README.md](README.md).
- Conversation font size is a bounded integer pixel value. Scale body, headings,
dense monospace, terminal output, and collapsed height through
`conversation-font-size-classes.ts`; settings own legacy preset migration.
Keep Streamdown in streaming mode, but never enable word-level `animated`.
Streamdown mode is streaming while growing or a fence is open; never word-level `animated`.
- A Mermaid diagram opens in `mermaid-diagram-viewer.tsx`, never Streamdown's own
full-screen overlay (`controls.mermaid.fullscreen` stays off). Keep three
properties: controls padded by the `--safe-area-*` variables rather than a fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { useResolvedTheme } from '../../theme-provider';
import type { ConversationFontSize } from '@/atoms/settings';
import { useTaskImageUrl } from '@/hooks/use-task-image';
import { MarkdownDiffBlock } from './markdown-diff-block';
import { resolveMarkdownStreamdownMode } from './markdown-streamdown-mode';
import { createMarkdownMermaidConfig, createMarkdownMermaidPlugin } from './markdown-mermaid';
import { MermaidDiagramViewer, type MermaidDiagramSelection } from './mermaid-diagram-viewer';

Expand Down Expand Up @@ -1409,7 +1410,7 @@ export const MarkdownRenderer = memo(function MarkdownRenderer({
// remount when raw-HTML mode or Mermaid theme changes so sanitized
// rendering and diagram colors update correctly.
key={streamdownKey}
mode="streaming"
mode={resolveMarkdownStreamdownMode(isStreaming, normalizedText)}
className="space-y-0"
controls={STREAMDOWN_CONTROLS}
isAnimating={isStreaming}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function markdownHasUnclosedFence(text: string): boolean {
let open = false;
for (const line of text.split('\n')) {
if (line.startsWith('```')) open = !open;
Comment thread
slashdevcorpse marked this conversation as resolved.
Outdated
}
return open;
}

export function resolveMarkdownStreamdownMode(
isStreaming: boolean,
text = ''
): 'static' | 'streaming' {
if (isStreaming || markdownHasUnclosedFence(text)) return 'streaming';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Track list context across continuation lines

When a cancelled response opens a fence on a continuation line of an existing list, such as 1. explanation\n ```ts\nconst x = 1, markdownFenceAt sees four leading spaces without knowing they include the list-item indent and returns no fence. This makes the new resolver choose static mode and skip incomplete-Markdown repair even though the fence remains open. This is fresh evidence beyond the prior container-prefix comment: direct-marker forms are now handled, but ordinary continuation-line forms are still missed. Preserve list-container state while scanning.

AGENTS.md reference: packages/components/src/components/ai-gui/AGENTS.md:L96-L96

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this. Open-fence detection now uses the same per-line CommonMark scanner as normalizeTexMathDelimiters. Same-line list/blockquote markers (10. ```tex, > ```tex) and 0–3 space continuation fences already stay on Streamdown streaming. A 4-space continuation after 1. explanation is an indented code block at document scope unless we carry list indent across lines. That is a full list parser, not this #421 slice. Interrupted mid-list continuation fences are an accepted freeze-path edge case (.github/codex-review.md).

return 'static';
}
17 changes: 17 additions & 0 deletions packages/components/tests/markdown-streamdown-mode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';

import { resolveMarkdownStreamdownMode } from '../src/components/ai-gui/markdown-streamdown-mode';

describe('resolveMarkdownStreamdownMode', () => {
it('uses static mode for finished complete markdown', () => {
expect(resolveMarkdownStreamdownMode(false, 'Hello **world**.')).toBe('static');
});

it('uses streaming mode while a turn is still growing', () => {
expect(resolveMarkdownStreamdownMode(true, 'Hello **wor')).toBe('streaming');
});

it('keeps streaming mode for a finished turn with an unclosed fence', () => {
expect(resolveMarkdownStreamdownMode(false, '```ts\nconst x = 1;\n')).toBe('streaming');
});
});
Loading