Skip to content

feat: implement smooth typewriter-style streaming with batched writes#1

Merged
assagman merged 5 commits intomainfrom
dx/smooth-streaming
Jan 16, 2026
Merged

feat: implement smooth typewriter-style streaming with batched writes#1
assagman merged 5 commits intomainfrom
dx/smooth-streaming

Conversation

@assagman
Copy link
Owner

Summary

Implements a typewriter-style renderer for streaming output that maintains smooth ~33ms UI update cadence, even when upstream delivers large bursts. Also reduces UI jank by batching transcript writes instead of flushing per-chunk.

Changes

  • Add render scheduler with pending text buffer and repeating timer
  • Implement incremental buffer updates (avoid full-buffer rewrites)
  • Support adaptive character consumption per tick (256-2048 chars)
  • Batch transcript writes with size threshold (~4KB) and time-based flush
  • Fix header ordering for proper streaming display
  • Preserve completion state correctly in handle_response

Breaking Changes

None

Testing

  • Manual testing with various streaming response sizes
  • Verify smooth rendering with burst upstream data
  • Confirm transcript writes are batched correctly

- Add timer-based render queue in response.lua for 33ms cadence updates
- Replace per-char string concat with newline-aware chunk processing
- Implement incremental buffer updates to avoid full redraws
- Add batched transcript writes with size/time thresholds (4KB/250ms)
- Replace flush_response_buffer with maybe_flush for streaming chunks

This eliminates bulk text dumps when LLM bursts data, creating a
steady typewriter effect while reducing disk IO jank.

Signed-off-by: assagman <ahmetsercansagman@gmail.com>
Avoid calling append_text for completion markers since it resets
is_streaming and is_complete flags. Instead, add separator and
completion text directly to state.lines.

Signed-off-by: assagman <ahmetsercansagman@gmail.com>
- Add flush_pending_sync() to process pending text before headers
- Fix race condition where prompt text appeared after Response header
- Reduce chars/tick (12-128) for more natural typewriter feel
- Increase timer frequency to 60fps (16ms) for smoother updates
- Adjust adaptive thresholds for gradual speed-up with backlog

Signed-off-by: assagman <ahmetsercansagman@gmail.com>
Copilot AI review requested due to automatic review settings January 16, 2026 11:00
@gemini-code-assist
Copy link

Summary of Changes

Hello @assagman, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience for streaming output by introducing a sophisticated typewriter-style rendering mechanism and optimizing transcript persistence. The changes aim to provide a consistently smooth UI update cadence, even when faced with large data bursts, and to eliminate UI jank caused by frequent disk I/O during streaming. This results in a more fluid and responsive interaction with streaming content.

Highlights

  • Smooth Typewriter-Style Streaming: Implements a typewriter-style renderer for streaming output that maintains a smooth ~33ms UI update cadence, even when upstream delivers large bursts.
  • Batched Transcript Writes: Reduces UI jank by batching transcript writes with a size threshold (~4KB) and time-based flush (~250ms), instead of flushing per-chunk.
  • Render Scheduler: Adds a render scheduler with a pending text buffer and a repeating timer to manage incremental UI updates.
  • Incremental Buffer Updates: Introduces incremental buffer updates to avoid full-buffer rewrites, improving rendering performance.
  • Adaptive Character Consumption: Supports adaptive character consumption per tick (256-2048 characters) to balance smoothness with catching up quickly on large backlogs.
  • Header Ordering and Completion State: Fixes header ordering for proper streaming display and ensures completion state is preserved correctly in handle_response.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements smooth typewriter-style streaming for response rendering by introducing a timer-based render scheduler and batched transcript writes. The implementation addresses UI jank from large burst uploads and frequent disk I/O.

Changes:

  • Add timer-based render scheduler with adaptive character consumption (12-128 chars per 16ms tick)
  • Replace per-chunk transcript flushes with size/time-based batching (~4KB threshold, 250ms interval)
  • Implement incremental buffer updates to avoid full redraws during streaming

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
lua/ghost/response.lua Adds render timer, pending chunks queue, incremental buffer updates, and smooth typewriter rendering
lua/ghost/transcript.lua Implements batched writes with ResponseBuffer structure and maybe_flush function
lua/ghost/receiver.lua Switches from flush_response_buffer to maybe_flush_response_buffer for streaming
.opencode/plans/1768551208297-calm-island.md Planning document describing the implementation approach
Comments suppressed due to low confidence (1)

lua/ghost/response.lua:522

  • The update_tool_call function now calls update_buffer() which triggers a full redraw instead of leveraging incremental updates. For smooth rendering during streaming, tool call updates should also flush pending chunks first and potentially use incremental buffer updates. However, there's no test coverage for tool call updates during active streaming to verify this behavior works correctly.
function M.update_tool_call(tool_id, tool_name, status, kind)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a sophisticated typewriter-style rendering mechanism for streaming AI responses, which significantly improves the user experience by providing smooth, continuous output. It also cleverly batches transcript writes to reduce I/O-related UI jank. The implementation is well-structured, using a timer-based scheduler and incremental buffer updates. My review focuses on improving maintainability by reducing code duplication and magic numbers, and addresses one potential bug in the rendering logic to ensure its robustness.

- Fix rendered_current_line empty string handling bug (HIGH)
- Replace duplicated flush logic with flush_pending_sync() call
- Deduplicate current line completion in handle_response()
- Extract magic numbers to named constants for readability
- Extract reset_state() helper to reduce clear()/close() duplication
- Add clarifying comment for constants documentation
- Default flush=true in cleanup_session() to prevent data loss

Signed-off-by: assagman <ahmetsercansagman@gmail.com>
Signed-off-by: assagman <ahmetsercansagman@gmail.com>
@assagman assagman merged commit e0a32ff into main Jan 16, 2026
@assagman assagman deleted the dx/smooth-streaming branch January 16, 2026 14:30
assagman added a commit that referenced this pull request Jan 20, 2026
Extract shared helper functions for message and plan handlers to eliminate duplication. This improves code maintainability and readability while keeping the dispatch table declarative.

Closes review threads #1 and #2 in PR #8

Signed-off-by: assagman <ahmetsercansagman@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments