Skip to content

Add event sink system instead of plain CLI printing#25

Merged
silv-io merged 3 commits intomainfrom
silv-io/event-sink-system
Feb 13, 2026
Merged

Add event sink system instead of plain CLI printing#25
silv-io merged 3 commits intomainfrom
silv-io/event-sink-system

Conversation

@silv-io
Copy link
Member

@silv-io silv-io commented Feb 13, 2026

Motivation

Currently, the CLI prints directly to stdout throughout the codebase. This becomes problematic when integrating with bubbletea for TUI rendering, since bubbletea owns stdout and you can't print to it while the TUI is running.

By routing all output through a Sink interface, we can:

  • Use PlainSink for non-interactive CLI mode (writes to stdout)
  • Swap in a future TUISink that sends events to bubbletea's message loop via Program.Send()

The event types (LogEvent, ProgressEvent, etc.) map naturally to bubbletea's tea.Msg types, making the future integration straightforward.

Changes

  • Added internal/output package with typed events (LogEvent, WarningEvent, ContainerStatusEvent, ProgressEvent) and a Sink interface
  • Used Go generics with union type constraints for compile-time event type safety
  • Replaced direct fmt.Print/log.Printf calls in container/start.go, auth/auth.go, auth/login.go, and command handlers with sink-based emission
  • Updated all commands (start, login, logout) to create and pass a PlainSink

Tests

  • Added unit tests for PlainSink covering all event types and formatting variations
  • Updated existing auth_test.go to use SinkFunc for capturing emitted events and asserting on them
  • All existing tests pass; the sink abstraction is transparent to test behavior

# Conflicts:
#	internal/container/start.go
@silv-io silv-io force-pushed the silv-io/event-sink-system branch from 9c5acf8 to 59b1505 Compare February 13, 2026 14:59
@silv-io silv-io marked this pull request as ready for review February 13, 2026 15:43
@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Adds a new event-based output subsystem (internal/output) and adapts auth, container startup, and CLI commands to emit structured events via sinks (PlainSink) instead of direct prints/callbacks; also adds .idea to .gitignore and updates CLAUDE.md with output routing notes.

Changes

Cohort / File(s) Summary
Output System Foundation
internal/output/events.go, internal/output/plain_sink.go, internal/output/plain_sink_test.go
Introduce event types and generic Emit API, Sink interface and SinkFunc, PlainSink implementation rendering to io.Writer, and comprehensive unit tests covering event rendering and error handling.
Authentication Refactor
internal/auth/auth.go, internal/auth/login.go, internal/auth/auth_test.go
Auth now accepts an output.Sink; browser login components carry a sink; all user-facing messages use EmitLog/EmitWarning; tests capture events via SinkFunc instead of log output.
Container Startup Refactor
internal/container/start.go
Start and awaitStartup now accept an output.Sink (replacing onProgress callback) and emit ContainerStatus/Progress/Warning events through the sink.
CLI Integration
cmd/login.go, cmd/logout.go, cmd/root.go, cmd/start.go
Commands construct a PlainSink (stdout) and pass it into auth.New and runStart/Start; removed inline progress callbacks and direct console prints.
Docs & Config
CLAUDE.md, .gitignore
CLAUDE.md updated with output routing and internal/output namespace notes; .gitignore adds .idea.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as Command (login)
    participant Sink as PlainSink
    participant Auth as Auth
    participant Output as internal/output

    CLI->>Sink: NewPlainSink(os.Stdout)
    CLI->>Auth: New(sink)
    Auth->>Output: EmitLog(sink, "Authentication required...")
    Output->>Sink: emit(LogEvent)
    Sink->>Sink: write to stdout
    Auth->>Output: EmitLog(sink, "Login successful.")
    Output->>Sink: emit(LogEvent)
    Sink->>Sink: write to stdout
Loading
sequenceDiagram
    participant CLI as Command (start)
    participant RunStart as runStart
    participant Sink as PlainSink
    participant Container as Container.Start
    participant Output as internal/output

    CLI->>RunStart: runStart(ctx, rt)
    RunStart->>Sink: NewPlainSink(os.Stdout)
    RunStart->>Container: Start(ctx, rt, sink)
    Container->>Output: EmitStatus(sink, "pulling", ...)
    Output->>Sink: emit(ContainerStatusEvent)
    Container->>Output: EmitProgress(sink, ...)
    Output->>Sink: emit(ProgressEvent)
    Container->>Output: EmitStatus(sink, "ready", ...)
    Output->>Sink: emit(ContainerStatusEvent)
    RunStart-->>CLI: return success/error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main architectural change: replacing direct CLI printing with an event sink system for better output routing and future TUI integration.
Description check ✅ Passed The description provides clear context about the motivation (TUI integration with bubbletea), the architectural approach (Sink interface with typed events), implementation details, and testing coverage, all directly related to the changeset.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch silv-io/event-sink-system

No actionable comments were generated in the recent review. 🎉


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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@cmd/login.go`:
- Line 28: Remove the duplicate login message: auth.GetToken() already emits
"Login successful." to the provided sink, so delete or comment out the explicit
call output.EmitLog(sink, "Login successful.") in cmd/login.go (or otherwise
avoid emitting the same message after calling auth.GetToken()). Ensure no other
duplicate EmitLog calls remain that use the same sink for the success message.

In `@internal/output/plain_sink.go`:
- Around line 20-63: The fmt.Fprint* calls in PlainSink.emit,
PlainSink.emitStatus, and PlainSink.emitProgress currently ignore all write
errors; update these methods to capture the returned error from each fmt call
and surface it (e.g., store the first non-nil error in a field on PlainSink like
lastErr and/or return an error from the Sink methods), and update the Sink
interface or add an Error() accessor so callers can observe write failures;
ensure every call site in emit, emitStatus, and emitProgress checks the returned
error and records/returns it rather than discarding it.
🧹 Nitpick comments (1)
internal/container/start.go (1)

59-68: Consider thread-safety of sink usage from goroutine.

The progress channel is consumed in a goroutine that calls EmitProgress. While PlainSink wraps an io.Writer which is typically safe for concurrent writes at the OS level, the formatting in PlainSink.emit may interleave if multiple goroutines emit simultaneously.

For the current single-container sequential pull pattern this is fine, but if parallelized later, consider synchronization.

Copy link
Collaborator

@carole-lavillonniere carole-lavillonniere left a comment

Choose a reason for hiding this comment

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

Lovely

@silv-io silv-io merged commit ef7b02a into main Feb 13, 2026
10 checks passed
@silv-io silv-io deleted the silv-io/event-sink-system branch February 13, 2026 16:40
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.

2 participants