Skip to content

fix(cli): eliminate flicker in transfer progress renderer#2647

Merged
Skarlso merged 3 commits into
open-component-model:mainfrom
jakobmoellerdev:fix/transfer-progress-flicker
May 27, 2026
Merged

fix(cli): eliminate flicker in transfer progress renderer#2647
Skarlso merged 3 commits into
open-component-model:mainfrom
jakobmoellerdev:fix/transfer-progress-flicker

Conversation

@jakobmoellerdev

Copy link
Copy Markdown
Member

Problem

ocm transfer component-version flickers visibly during transfer. Two independent causes:

1. Dual render paths racing each other

HandleEvent() called renderLocked() synchronously on every node state change, while the animation goroutine independently fires renderLocked() every 80ms. Rapid node completions (e.g. 3 nodes finishing within 5ms) triggered 3 full clearLines() + redraw cycles in quick succession, colliding with the background ticker and producing visible cursor jumping.

2. Partial-frame writes

Each render frame was written to the terminal as multiple separate fmt.Fprint calls — clear lines, then header, then each event line, then the progress bar. The terminal could paint partial frames between syscalls, causing a visible flash.

Fix

Event ingestion decoupled from rendering (bar_visualizer.go): HandleEvent() now only mutates state. The animation ticker is the sole render driver — at most 10 redraws/sec regardless of event rate. The 100ms lag between a node completing and it appearing on screen is imperceptible.

Single-write frames (bar_visualizer.go): The full frame (cursor movements, header, events, bar) is assembled into a strings.Builder and flushed with a single io.WriteString call. The terminal sees a complete frame or nothing.

Animation interval (animation.go): 80ms → 100ms. 12.5 redraws/sec was imperceptible over 10 and added unnecessary syscall pressure.

Test plan

  • go test ./cli/internal/render/progress/bar/... passes
  • Run ocm transfer component-version <source> <target> against a registry with several components — no flicker during node completions, smooth spinner animation

@jakobmoellerdev jakobmoellerdev requested a review from a team as a code owner May 27, 2026 06:40
@netlify

netlify Bot commented May 27, 2026

Copy link
Copy Markdown

Deploy Preview for ocm-website canceled.

Name Link
🔨 Latest commit 0e638b7
🔍 Latest deploy log https://app.netlify.com/projects/ocm-website/deploys/6a1701eb08e163000732c2dc

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 60c274c8-5112-42f1-aaaa-93873a33969b

📥 Commits

Reviewing files that changed from the base of the PR and between 73382c8 and 0e638b7.

📒 Files selected for processing (3)
  • cli/internal/render/progress/bar/animation.go
  • cli/internal/render/progress/bar/bar_visualizer.go
  • cli/internal/render/progress/bar/bar_visualizer_test.go

📝 Walkthrough

Walkthrough

This PR refactors the progress bar visualization component to use internal buffering instead of direct streaming writes. A new strings.Builder accumulates each animation frame, which is then flushed in a single write to reduce partial-frame flicker. Event completion rendering is deferred to the animation ticker, and the spinner animation interval is adjusted from 80ms to 100ms.

Changes

Progress Bar Rendering Refactor

Layer / File(s) Summary
Buffer infrastructure and rendering coordination
cli/internal/render/progress/bar/bar_visualizer.go
Adds an internal strings.Builder field (buf) to accumulate rendered output. Updates HandleEvent documentation to clarify that the animation ticker is the sole rendering driver, and removes the immediate re-render call when events transition to Completed or Failed.
Frame rendering implementation
cli/internal/render/progress/bar/bar_visualizer.go
Rewrites renderLocked to compose entire frames into the internal buffer using renamed write* helpers (writeClearLines, writeLogBuffer, writeHeader, writeEvents). Changes the progress bar output target to write into the builder instead of the direct writer. Updates failure summary helper to use the builder output path.
Completion and frame flush
cli/internal/render/progress/bar/bar_visualizer.go
Updates the End method to reset the internal buffer, write the final status line, events, and progress bar into the buffer, then flush the composed frame once to the output. Emits failure details after the atomic frame write.
Test suite updates
cli/internal/render/progress/bar/bar_visualizer_test.go
Updates all test cases to call the renamed render methods (writeEvents, writeLogBuffer, writeBar, writeFailureSummary) and to read rendered output from the visualizer's internal buffer rather than separate return values. Test expectations remain on output formatting and content.
Animation spinner interval adjustment
cli/internal/render/progress/bar/animation.go
Increases the spinner ticker interval in RunAnimation from 80ms to 100ms, reducing the frequency of onSpin invocations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • matthiasbruns
  • Skarlso
  • fabianburth

Poem

🐰 A frame buffered whole, no flicker in sight,
Atomic writes bringing animation so bright,
The ticker now gentle at hundred milliseconds per beat,
Progress bars rendering smooth and complete!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the kind/bugfix Bug label May 27, 2026
Two causes of flicker in the bar visualizer:

1. HandleEvent() called renderLocked() on every node
   state change, racing the 80ms animation ticker. Rapid
   node completions (e.g. 3 in <80ms) caused 3 full
   clear+redraw cycles in quick succession. Fix: events
   now mutate state only; the animation ticker is the
   sole render driver.

2. Each render frame was written as multiple separate
   fmt.Fprint calls (clear lines, header, events, bar),
   letting the terminal paint partial frames between
   syscalls. Fix: the full frame is built into a
   strings.Builder and flushed in a single io.WriteString.

Also bumps the animation interval from 80ms to 100ms
(12.5 → 10 redraws/sec) since the higher rate is
imperceptible and adds unnecessary syscall pressure.

On-behalf-of: @SAP <jakob.moeller@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
On-behalf-of: @SAP <jakob.moeller@sap.com>
@github-actions github-actions Bot added the size/m Medium label May 27, 2026
@jakobmoellerdev jakobmoellerdev force-pushed the fix/transfer-progress-flicker branch from 173b0ed to c54d94f Compare May 27, 2026 06:40

@piotrjanik piotrjanik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh, it really helped. Looks much nicer :D GJ!

@matthiasbruns matthiasbruns left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm

@Skarlso Skarlso enabled auto-merge (squash) May 27, 2026 14:38
@Skarlso Skarlso merged commit d15958c into open-component-model:main May 27, 2026
22 of 23 checks passed
skarlso-release-bot Bot pushed a commit to Skarlso/open-component-model that referenced this pull request May 27, 2026
…nent-model#2647)

Co-authored-by: Gergely Bräutigam <gergely.brautigam@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com> d15958c
piotrjanik pushed a commit to piotrjanik/open-component-model that referenced this pull request May 28, 2026
…nent-model#2647)

Co-authored-by: Gergely Bräutigam <gergely.brautigam@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
matthiasbruns pushed a commit to matthiasbruns/open-component-model that referenced this pull request May 28, 2026
…nent-model#2647)

Co-authored-by: Gergely Bräutigam <gergely.brautigam@sap.com>
Signed-off-by: Jakob Möller <contact@jakob-moeller.com>
Signed-off-by: Matthias Bruns <git@matthiasbruns.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants