Skip to content

fix(Markdown): implement sticky-height streaming to fix layout jump#75

Merged
remarkablemark merged 3 commits into
masterfrom
fix/layout
May 15, 2026
Merged

fix(Markdown): implement sticky-height streaming to fix layout jump#75
remarkablemark merged 3 commits into
masterfrom
fix/layout

Conversation

@remarkablemark
Copy link
Copy Markdown
Member

@remarkablemark remarkablemark commented May 15, 2026

What is the motivation for this pull request?

Bug fix: streaming assistant messages cause transcript content to jump upward when live markdown causes content to reflow to fewer lines.

What is the current behavior?

When streaming an assistant message, markdown rendering can cause content to reflow upward, making later transcript items and the input area jump unexpectedly.

What is the new behavior?

Streaming assistant messages now keep a maximum observed rendered height and append temporary blank rows when live markdown causes content to reflow upward, so later transcript content no longer jumps during streaming.

Also refactors src/components/Messages/utils.ts into focused modules:

  • src/components/Messages/parsing.ts
  • src/components/Messages/streaming.ts
  • src/components/Messages/styles.ts
  • src/components/Messages/layout.ts

plan.md

Checklist:

Streaming assistant messages now keep a maximum observed rendered height
and add temporary blank rows when live markdown causes content to reflow
upward, so later transcript content no longer jumps.

Factored the line/height math into `src/components/Messages/layout.ts`
and exported the markdown terminal renderer from
`src/components/Markdown/Markdown.tsx` via `src/components/Markdown/index.ts`.

---

Preserve Live Markdown and Prevent Transcript Jump

Summary:

Keep live markdown rendering for streaming assistant messages and prevent surrounding layout from jumping upward by giving the active streaming message a sticky minimum height.

The fix should preserve the current live-rendered markdown/code behavior, but when a rerender would make the streaming message shorter because content reflowed upward, the component should pad the message so it keeps its previous on-screen height until the stream finishes.

Implementation Changes:

- Add a dedicated wrapper around the active streaming assistant message in `src/components/Messages/Messages.tsx`.
- Track a sticky height for the current streaming message instance:
  - compute the rendered line count of the streaming message after markdown/code rendering
  - store the maximum line count seen so far for that in-flight message
  - if the newest rendered output is shorter than the stored maximum, append blank lines so the wrapper still occupies the stored maximum height
- Apply sticky-height behavior only to `streamingMessage`.
  - Do not change committed transcript items rendered through `Static`
  - Do not change user/system message behavior
- Reset sticky height when any of these happen:
  - the streaming message is committed and removed
  - a different streaming message starts
  - `sessionId` changes
  - terminal width changes
- Keep the existing live markdown/code rendering path intact:
  - assistant prose still renders through `Markdown`
  - completed code fences still render through `CodeBlock`
  - existing streaming inline markdown behavior stays unchanged

Measurement Strategy:

- Measure rendered height from the final display string that Ink will wrap, not from raw markdown source.
- Add a shared line-count utility for terminal output that:
  - strips ANSI escape sequences before width measurement
  - splits on explicit newlines
  - counts wrapped rows using the current available content width
  - treats empty lines as occupying one row
- Use the same available width assumptions already used by `Markdown`:
  - terminal columns from `useStdout()`
  - minus horizontal margins used for assistant message containers
- For code blocks:
  - count visible rows from the rendered code block content plus its surrounding border/padding rows
  - use the same width calculation the `CodeBlock` container effectively occupies
- Keep the measurement utility local to the message-rendering subsystem unless another component clearly needs it.

Public Interfaces / Behavior:

- No CLI/API/type changes.
- User-visible behavior:
  - live markdown rendering remains enabled during streaming
  - when inline markdown completion causes content to reflow upward, later transcript content and the input area should no longer jump upward during that stream
  - temporary blank space may appear below the active streaming assistant message until the stream completes
  - once committed, the final message renders normally with no sticky padding retained

Assumptions:

- Live markdown during streaming is a hard requirement and should not be reduced.
- The priority is preventing upward movement of surrounding layout, not preventing the streamed text itself from reflowing internally.
- Temporary blank space under the active streaming message is acceptable during streaming if it removes transcript/input jump.
- Terminal-width changes should invalidate prior sticky height rather than trying to preserve it across different wrap widths.
- src/components/Messages/parsing.ts
- src/components/Messages/streaming.ts
- src/components/Messages/styles.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/components/Markdown/Markdown.tsx 100.00% <ø> (ø)
src/components/Markdown/render.ts 100.00% <100.00%> (ø)
src/components/Messages/Messages.tsx 100.00% <100.00%> (ø)
src/components/Messages/layout.ts 100.00% <100.00%> (ø)
src/components/Messages/parsing.ts 100.00% <100.00%> (ø)
src/components/Messages/streaming.ts 100.00% <100.00%> (ø)
src/components/Messages/styles.ts 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@remarkablemark remarkablemark self-assigned this May 15, 2026
@remarkablemark remarkablemark added the bug Something isn't working label May 15, 2026
Refactored the markdown renderer into `src/components/Markdown/render.ts`,
leaving `src/components/Markdown/Markdown.tsx` as the thin Ink component wrapper.
@remarkablemark remarkablemark enabled auto-merge May 15, 2026 05:53
@remarkablemark remarkablemark merged commit 1603941 into master May 15, 2026
16 checks passed
@remarkablemark remarkablemark deleted the fix/layout branch May 15, 2026 05:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant