Collapse image pull progress into loading indicator#82
Conversation
📝 WalkthroughWalkthroughThis PR introduces visual progress feedback for container image pulls by adding a new TUI progress component, emitting pull-related events from the container service, suppressing plain text progress output, and enabling Cobra's silent error handling for custom error display. Changes
Sequence DiagramsequenceDiagram
participant Container as Container Service
participant EventSink as Event Sink
participant UIApp as UI App
participant ProgressComp as Pull Progress<br/>Component
participant View as Terminal View
Container->>Container: Start pulling image
Container->>EventSink: Emit spinner start event
Container->>Container: Pull image (with layer updates)
loop For each layer progress update
Container->>EventSink: Emit ProgressEvent (layer ID, status, current, total)
EventSink->>UIApp: Route ProgressEvent
UIApp->>ProgressComp: SetProgress(event)
ProgressComp->>ProgressComp: Update layer state & aggregate %
ProgressComp-->>UIApp: Return updated component + cmd
end
alt Pull succeeds
Container->>EventSink: Emit spinner stop + success
EventSink->>UIApp: Route success event
UIApp->>View: Append success message
else Pull fails
Container->>EventSink: Emit error event + spinner stop
EventSink->>UIApp: Route error event
UIApp->>ProgressComp: Hide()
UIApp->>View: Append error message
end
UIApp->>View: Render (spinner + progress layers + output)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/container/start.go`:
- Around line 97-98: The code is emitting the same "Pulling <image>" twice by
calling both output.EmitSpinnerStart(sink, fmt.Sprintf("Pulling %s", c.Image))
and output.EmitStatus(sink, "pulling", c.Image, ""), which duplicates output in
non-interactive/PlainSink modes; remove the redundant EmitStatus call (or guard
it behind an interactive/terminal check) so that only the spinner start is
emitted here and let the existing ContainerStatusEvent path produce the status
once (locate calls to EmitSpinnerStart and EmitStatus in start.go around the
image pull logic).
In `@internal/output/plain_sink.go`:
- Around line 33-35: Remove the special-case ProgressEvent early return from
PlainSink so sinks no longer own suppression logic; instead update
FormatEventLine(event any) to detect ProgressEvent and return ok=false for it,
then have PlainSink call FormatEventLine and skip writing when ok==false.
Specifically, delete the switch/case that returns for ProgressEvent in
PlainSink, ensure FormatEventLine treats ProgressEvent as non-renderable
(ok=false), and make PlainSink follow the common path of using FormatEventLine's
(text, ok) result to decide whether to emit the line.
In `@internal/ui/components/pull_progress.go`:
- Around line 56-58: The aggregate progress regresses because each update
blindly replaces layer.bytes with the phase-specific values (e.Status,
e.Current, e.Total) causing Extracting phases to reduce totals and
cached/terminal states to contribute zero; change the update logic in the layer
update path (where layer.status, layer.current, layer.total are set) and in
aggregatePercent() to be phase-aware: preserve the maximum observed bytes
per-layer (e.g., track seenTotal/seenCurrent) and treat terminal states like
"Already exists"/"Pull complete" as fully-complete for that layer (use seenTotal
or mark as complete to add its bytes to totalBytes) so totalBytes never goes to
zero and progress never moves backward. Ensure any code paths referenced by
aggregatePercent(), layer.status, layer.current, and layer.total are updated to
use the new seen/complete fields.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 8c79b5a3-90b9-4dc6-81fa-d834c762b7c9
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (9)
cmd/root.gogo.modinternal/container/start.gointernal/output/plain_sink.gointernal/output/plain_sink_test.gointernal/ui/app.gointernal/ui/app_test.gointernal/ui/components/pull_progress.gointernal/ui/components/pull_progress_test.go


Motivation
When pulling a Docker image, the CLI printed every individual layer progress line from Docker (downloading, extracting, etc. for each layer). This flooded the terminal with dozens of rapidly updating lines, making the output noisy and hard to follow.
Changes
PullProgressBubble Tea component backed bybubbles/progressthat aggregates pull progress across all Docker layers into a single animated progress bar. Shows the dominant phase (Downloading/Extracting), completed layer count, and overall byte progress with a Nimbo-themed gradient.ErrorEventinstead of a raw error string, consistent with how other failures (e.g., Docker not available) are displayed.Tests
PullProgresscomponent covering aggregation, layer counting, phase detection, visibility, and edge cases.Screenshots