Skip to content

Commit c67ac60

Browse files
authored
fix(chat): empty assistant bubble for plain LLM responses (#290)
@cacheplane/partial-markdown@0.3 does not flush trailing text on finish() unless the buffer ends with a newline. Plain LLM responses typically omit the trailing newline, so the parser produced a document with zero children — the assistant bubble shell rendered but the message text was missing entirely. Reproduce on main: send 'Say hello in one sentence.' to gpt-5-mini. Backend logs show success (1.8s); agent.messages() carries 'Hello — nice to meet you!'; the DOM shows the bubble structure (checkpoint marker, action buttons) but no text. Fix: push a sentinel newline before calling finish() in both branches of the root() computed (content-changed-then-flush and streaming-flipped- without-new-content). The newline only ever exists inside the parser buffer; the original content() signal is unchanged. Two new regression tests cover plain text without trailing newline in both branches. Found while walking the smoke checklist post-#288.
1 parent f6a61e6 commit c67ac60

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

libs/chat/src/lib/streaming/streaming-markdown.component.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,32 @@ describe('ChatStreamingMdComponent', () => {
7373
expect(fixture.nativeElement.querySelector('p')).toBeNull();
7474
expect(fixture.nativeElement.querySelector('h1')).toBeNull();
7575
});
76+
77+
it('renders a paragraph for plain text WITHOUT a trailing newline (LLM-response shape)', () => {
78+
// Regression: @cacheplane/partial-markdown@0.3 does not flush trailing
79+
// text on finish() unless the buffer ends with '\n'. LLM responses
80+
// typically omit the trailing newline. The component must push a
81+
// sentinel newline before finish() so the message renders.
82+
const fixture = TestBed.createComponent(HostComponent);
83+
fixture.componentInstance.content.set('Hello — nice to meet you!');
84+
fixture.componentInstance.streaming.set(false);
85+
fixture.detectChanges();
86+
const p = fixture.nativeElement.querySelector('p');
87+
expect(p).toBeTruthy();
88+
expect(p.textContent?.trim()).toBe('Hello — nice to meet you!');
89+
});
90+
91+
it('renders plain text when streaming flips to false (mirrored else-branch)', () => {
92+
// The else-if branch (no content change, streaming flipped to false)
93+
// must also push a sentinel newline before finish().
94+
const fixture = TestBed.createComponent(HostComponent);
95+
fixture.componentInstance.content.set('Plain answer.');
96+
fixture.componentInstance.streaming.set(true);
97+
fixture.detectChanges();
98+
fixture.componentInstance.streaming.set(false);
99+
fixture.detectChanges();
100+
const p = fixture.nativeElement.querySelector('p');
101+
expect(p).toBeTruthy();
102+
expect(p.textContent?.trim()).toBe('Plain answer.');
103+
});
76104
});

libs/chat/src/lib/streaming/streaming-markdown.component.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,20 @@ export class ChatStreamingMdComponent {
9999
if (c.length > 0) this.parser.push(c);
100100
}
101101
if (!isStreaming && !this.finished) {
102+
// @cacheplane/partial-markdown@0.3 does not flush trailing text on
103+
// finish() unless the buffer ends with a newline. Plain LLM
104+
// responses often omit the trailing newline, which causes the
105+
// parser to emit a document with zero children — i.e. the message
106+
// renders empty. Push a sentinel newline first to force the open
107+
// paragraph closed before we finalize.
108+
if (!c.endsWith('\n')) this.parser.push('\n');
102109
this.parser.finish();
103110
this.finished = true;
104111
}
105112
this.prior = c;
106113
} else if (!isStreaming && !this.finished) {
107114
// Streaming flipped to false without new content; ensure parser is finalized.
115+
if (!this.prior.endsWith('\n')) this.parser.push('\n');
108116
this.parser.finish();
109117
this.finished = true;
110118
}

0 commit comments

Comments
 (0)