Skip to content

fancy devenv prompt#1183

Open
winder wants to merge 9 commits into
mainfrom
will/fancy-prompt
Open

fancy devenv prompt#1183
winder wants to merge 9 commits into
mainfrom
will/fancy-prompt

Conversation

@winder

@winder winder commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Description

demo

Testing

Checklist

  • Breaking changes documented in changelog (see changelog directory)
  • Cross link related PRs (in this or other repositories)
  • just lint fix - no new lint errors
  • just generate - mocks and protobufs are up to date

Base automatically changed from will/split-protocol_contracts to main June 19, 2026 13:20
@winder winder force-pushed the will/fancy-prompt branch from a7fcdd6 to 6ca4285 Compare June 19, 2026 21:36

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: can this be used/extended for the test logs as well? For example, we currently have a consistent spam of "failed to get verifier result for messageID" until we get a success, would be nice to clean that up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, I don't think so. Tests should use something else. The way we split test running and success checking into separate CI steps is pretty weird, I think they deserve some specific attention.

A couple options:

  1. Use 'gotestsum'. It has a bunch of features for splitting up human/machine readable results.
  2. For the "failed to get verifier result for messageID" logs, we should probably improve the logging.
  3. Generally, I'd like to see much more precision in the test results. The pass/fail nature of our existing assertions mean that any failure requires a full fledged investigation to figure out whats wrong.

winder added 9 commits June 29, 2026 09:46
Introduces a Reporter interface in the runtime package plus bubbletea,
simple, and noop implementations so ccv up/test/restart render a live
spinner TUI on a terminal and plain one-line output in CI or verbose
mode. Redirects build/env noise to a log file and prints an environment
summary table when the run completes.
Adds a mutex-protected Status() string method to the protocol_contracts,
committeeccv, and committeeccv_clnode components so the bubbletea TUI
reporter can display fine-grained progress during the two longest phases.
The status advances through named steps (e.g. "deploying chain X of N",
"configuring lanes") so operators know exactly where a slow run is stuck.
…tions

Save the active profile to ~/.ccv/active_config whenever ccv up or ccv
test successfully loads a profile. Bare `ccv up` (no --profile arg) now
reads that saved path before falling back to standard.profile, so the
last profile used is automatically reused.
Handle tea.KeyCtrlC in the model's Update function so the TUI exits
cleanly when the user interrupts. After prog.Run() returns, os.Exit(130)
kills the background environment goroutine and signals the conventional
Ctrl-C exit code to the shell.
Add two-line rendering for in-progress components (phase prefix, aligned
duration, status on a second line with └── indent) and a bright-green
style to distinguish active from completed. Include aggregator and indexer
URLs with labels in the final summary report.
Bind lipgloss styles to the real terminal renderer so colors survive
stdout redirection; render checkmarks and component names in green with
default-color phase/duration, remove the fake service from the summary,
and append total startup time to the final report.
Record a timestamp on each status entry so the TUI can display how long
each sub-step took, both while active and in the completed log. Surface
the verbose log file path in the post-run summary.
Rename Statuser to StatusGetter to avoid misspell autocorrection, fix
an unused-write lint error in simpleReporter.OnFinish, and format all
TUI durations to 1 decimal place with fixed 7-char width to prevent
text bouncing.
Add a dim parenthesised cumulative run time (e.g. "(1m27.0s)") after
each active component's per-component elapsed in the TUI. Rewrite
fmtDur to use seconds-only output with zero-padded sub-minute seconds
so durations don't shift text as they cross character-width boundaries.
@winder winder force-pushed the will/fancy-prompt branch from 6ca4285 to 09c21a4 Compare June 29, 2026 13:47
@github-actions

Copy link
Copy Markdown

Code coverage report:

Package main will/fancy-prompt Diff
github.com/smartcontractkit/chainlink-ccv/aggregator 49.57% 49.57% +0.00%
github.com/smartcontractkit/chainlink-ccv/bootstrap 52.96% 52.96% +0.00%
github.com/smartcontractkit/chainlink-ccv/cli 65.13% 65.13% +0.00%
github.com/smartcontractkit/chainlink-ccv/cmd 15.44% 15.44% +0.00%
github.com/smartcontractkit/chainlink-ccv/common 55.38% 55.38% +0.00%
github.com/smartcontractkit/chainlink-ccv/executor 46.47% 46.47% +0.00%
github.com/smartcontractkit/chainlink-ccv/indexer 35.86% 35.82% -0.04%
github.com/smartcontractkit/chainlink-ccv/integration 46.25% 46.25% +0.00%
github.com/smartcontractkit/chainlink-ccv/pkg 84.62% 84.62% +0.00%
github.com/smartcontractkit/chainlink-ccv/pricer 0.00% 0.00% +0.00%
github.com/smartcontractkit/chainlink-ccv/protocol 63.06% 63.06% +0.00%
github.com/smartcontractkit/chainlink-ccv/verifier 34.99% 34.99% +0.00%
Total 46.70% 46.70% +0.00%

Comment thread build/devenv/cli/ccv.go
// newEnvFn is set by PersistentPreRunE (or applyProfile) based on the
// --env-mode flag. The Reporter receives component-level events in phased
// mode; legacy mode wraps the monolith with stage-level events only.
var newEnvFn func(r devenvruntime.Reporter) error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wanted to ensure the TUI is fully decoupled from the devenv, thats accomplished with the Reporter interface. The environment notifies the reporter when something interesting happens. There are several reporters in this PR:

  • Verbose: use the same output as today.
  • Fancy: full TUI with dynamic output.
  • Simple: line-based output, this is the default for non-TTY environments.

Comment on lines +68 to 74
// Status implements the devenvruntime.StatusGetter optional interface so the TUI
// reporter can poll for fine-grained progress during the long deploy loop.
func (p *component) Status() string {
p.mu.Lock()
defer p.mu.Unlock()
return p.status
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Status() string is an optional function that the runtime will call if present on the component. Many components run in a couple seconds or less, so status isn't really necessary. Others run for several minutes, this is how the runtime gets more information about what they're up to.

type tuiModel struct {
styles tuiStyles
log []string // completed / stage lines
active map[string]*activeComp

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Currently, the runtime only support a single active component. The TUI supports having multiple.

Comment thread build/devenv/cli/ccv.go
Comment on lines +236 to +240
if saved := getActiveConfig(); strings.HasSuffix(strings.TrimSpace(saved), ".profile") {
profilePath = strings.TrimSpace(saved)
} else {
profilePath = "standard.profile"
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a small random fix. The "active" config is stored in the home directory but we were ignoring it.

If you run "ccv up --profile phased.profile", a subsequent call to "ccv restart" will stop the current environment and bring the phased profile up again. Without the fix it was reverting to the standard profile.

Comment thread build/devenv/cli/ccv.go
// the fancy reporter should render to (the saved real-terminal fd), and a
// cleanup func that restores the original fds. In verbose mode nothing is
// redirected and the returned writer is os.Stderr.
func redirectToLogFile(verbose bool, prefix string) (term *os.File, cleanup func(), logPath string, err error) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Presumably a lot of this can be done by the logger. It would be nice to initialize the logger to a file rather than all this redirect stuff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're not expected to understand this file.

I tried to hide the entirety of BubbleTEA in here. It uses "The Elm Architecture" to create an event loop for the TUI. All state changes happen through the Update(event) function which returns an "immutable" copy of the tuiModel. The TUI is then generated from the model via View() string.

Everything else is related to the Reporter interface which is a crude shim that injects outside events into the Update function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the simple summary that will be displayed for non-TTY environments.

Comment on lines +20 to +28
func New(verbose bool, out io.Writer) devenvruntime.Reporter {
if verbose {
return devenvruntime.NoopReporter{}
}
if f, ok := out.(*os.File); ok && term.IsTerminal(f.Fd()) {
return newBubbletearReporter(out)
}
return newSimpleReporter(out)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Extra fancy term stuff.

@winder winder marked this pull request as ready for review June 29, 2026 15:06
@winder winder requested a review from a team as a code owner June 29, 2026 15:06
@winder winder requested review from Copilot and makramkd June 29, 2026 15:06

Copilot AI 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.

Pull request overview

Adds a new “fancy” devenv progress UI by introducing a runtime-level reporting interface and implementing both a line-based reporter and a Bubble Tea TUI reporter, then wiring it into phased environment startup and the ccv CLI (including log redirection + post-run summaries).

Changes:

  • Introduce devenvruntime.Reporter (plus NoopReporter) and emit component lifecycle events from the phased runtime.
  • Add build/devenv/reporter implementations (simple + Bubble Tea) and a summary renderer that parses env-out.toml.
  • Update ccv CLI to select a reporter, redirect logs to a file by default (unless --verbose), and show a post-run summary; add optional per-component status polling via StatusGetter.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
build/devenv/runtime/reporter.go Adds the Reporter interface and NoopReporter.
build/devenv/runtime/environment.go Adds reporter hooks (OnStart/OnFinish) and a new reporter parameter to environment constructors.
build/devenv/runtime/environment_test.go Updates helper to pass a reporter into NewEnvironmentWithRegistry.
build/devenv/runtime/component.go Adds StatusGetter optional interface for fine-grained progress updates.
build/devenv/reporter/summary.go Adds env summary rendering by parsing env-out.toml.
build/devenv/reporter/simple.go Implements a line-based reporter.
build/devenv/reporter/factory.go Adds reporter selection based on verbosity + TTY detection.
build/devenv/reporter/bubbletea.go Implements Bubble Tea TUI reporter with status polling and stage/component views.
build/devenv/go.mod Adds Bubble Tea / Lip Gloss / term dependencies.
build/devenv/go.sum Adds checksums for new dependencies.
build/devenv/environment_phased.go Adds NewPhasedEnvironmentWithReporter and plumbs reporter into runtime startup.
build/devenv/components/protocol_contracts/component.go Adds mutex-protected status updates during long deploy loops.
build/devenv/components/committeeccv/component.go Adds mutex-protected status updates and threads setStatus through phase 3 core.
build/devenv/components/committeeccv/component_clnode.go Adds mutex-protected status updates and threads setStatus through phase 3 core.
build/devenv/cli/ccv.go Integrates reporter selection, redirects logs to file by default, adds --verbose, and prints summaries for up/restart.

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

Comment on lines +40 to 43
func NewEnvironmentWithRegistry(ctx context.Context, rawConfig map[string]any, r *Registry, effectExecutor EffectExecutor, logger zerolog.Logger, reporter Reporter) (map[string]any, error) {
if effectExecutor == nil {
effectExecutor = noopEffectExecutor{}
}
Comment on lines 94 to +100
if p1, ok := comp.(Phase1Component); ok {
reporter.OnStart(phase, key, comp)
start := time.Now()
out, effects, err := p1.RunPhase1(ctx, rawConfig, rawConfig[key])
out, effects, runErr := p1.RunPhase1(ctx, rawConfig, rawConfig[key])
compTimings.Record(phase, key, start, time.Now())
if err != nil {
return nil, fmt.Errorf("phase1 %s: %w", key, err)
reporter.OnFinish(phase, key, runErr)
if runErr != nil {
Comment on lines +14 to +16
// - verbose=true → NoopReporter (caller keeps raw zerolog output)
// - TTY detected → BubbletearReporter (animated TUI)
// - otherwise → SimpleFancyReporter (line-based progress)
Comment on lines +16 to +19
func printSummary(out io.Writer, outTomlPath, logFilePath string, elapsed time.Duration) {
if outTomlPath == "" {
return
}
Comment on lines +21 to +25
abs, err := resolveTomlPath(outTomlPath)
if err != nil {
fmt.Fprintf(out, "\nenv output: %s (not found)\n", outTomlPath)
return
}
Comment on lines +27 to +31
var raw map[string]any
if _, err := toml.DecodeFile(abs, &raw); err != nil {
fmt.Fprintf(out, "\nenv output: %s (parse error: %v)\n", abs, err)
return
}
Comment on lines +370 to +373
if tm, ok := m.(tuiModel); ok {
if tm.cancelled {
os.Exit(130)
}
Comment thread build/devenv/cli/ccv.go
Comment on lines +599 to +603
term, cleanup, _, err := redirectToLogFile(verbose, "ccv-test")
if err != nil {
return err
}

// Stage 1: optional image build.
if buildEnabled {
progress(fmt.Sprintf("building images (just %s)...", buildTarget))
buildCmd := exec.Command("just", buildTarget)
buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr
if err := buildCmd.Run(); err != nil {
return fmt.Errorf("just %s failed: %w", buildTarget, err)
defer cleanup()
Comment thread build/devenv/cli/ccv.go
Comment on lines +742 to +747
realStdoutFd, _ := syscall.Dup(int(os.Stdout.Fd()))
realStderrFd, _ := syscall.Dup(int(os.Stderr.Fd()))
realTerm := os.NewFile(uintptr(realStderrFd), "real_stderr")

_ = syscall.Dup2(int(lf.Fd()), int(os.Stdout.Fd()))
_ = syscall.Dup2(int(lf.Fd()), int(os.Stderr.Fd()))
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.

3 participants