Skip to content

refactor: add icp-events and report progress through it in four operations - #709

Draft
raymondk wants to merge 5 commits into
mainfrom
fm/icp-split-inc1-events
Draft

refactor: add icp-events and report progress through it in four operations#709
raymondk wants to merge 5 commits into
mainfrom
fm/icp-split-inc1-events

Conversation

@raymondk

Copy link
Copy Markdown
Collaborator

Stacked PR. Base is fm/icp-split-inc0-cleanups, not main. Review after that one lands.

Why

Progress reporting is what welds crates/icp-cli/src/operations/ to the binary: six of those files import crate::progress and a seventh drives indicatif directly. Nothing else in the crate split can start until that dependency is inverted, so this goes first and the API here has to carry the remaining conversions.

What

New crate crates/icp-events — progress and user-facing notices as data. Depends on serde and futures only; not on icp, not on an async runtime, and not on anything terminal-shaped (indicatif, dialoguer, clap, console).

  • EventTaskStarted/TaskMessage/TaskPosition/TaskFinished, StepStarted/StepOutput/StepFinished, and Notice { level, message }
  • TaskKind — closed enum carrying the three shapes that exist today: Spinner, Steps { output_label } (the MultiStepProgressBar shape), Bytes { total } (what snapshot_transfer.rs uses)
  • Reporter / TaskTask::run(fut, success, error) replaces ProgressManager::execute_with_progress, the ~6-line idiom the four converted operations shared. A dropped Task still emits TaskFinished { Neutral }, so an early return can't leave a sink holding a bar that spins forever.
  • EventSink, DiscardSink, RecordingSink, CancelToken

Notice, Steps and Bytes are deliberately unused by the four operations converted here. They exist because the follow-up work items need them: Notice for the 29 user-facing info!/warn!/error! calls in operations/ (INFO-level tracing is product output in this CLI — logging.rs installs a UserLayer that prints Level::INFO to stderr unprefixed), and Steps/Bytes for build.rs/sync.rs/snapshot_transfer.rs. Designing them now avoids a redesign later.

crates/icp-cli/src/events.rsIndicatifSink, the only place that knows about both events and indicatif. Styles, STEADY_TICK and byte_style are hoisted into progress.rs and shared by both renderers so they cannot drift while both exist.

Four operations convertedinstall.rs, settings.rs, binding_env_vars.rs, candid_compat.rs now take a &Reporter instead of debug: bool. Each deploy.rs call site builds its own reporter with events::indicatif_reporter(ctx.debug), mirroring the previous one-ProgressManager-per-operation lifetime; a single shared MultiProgress across the whole deploy would have kept finished bars live across the info! lines printed between operations and changed the output.

Terminal output is unchanged — how that was verified

events.rs has a rendering_equivalence module. A recording indicatif::TermLike captures what indicatif actually draws; each scenario is driven through both the old ProgressManager path and the new IndicatifSink path against the same recording terminal, and the frame sequences are compared:

  • spinner success, spinner failure
  • the candid_compat skip (which used finish_with_message, leaving the running style — so Outcome::Neutral must call finish_with_message too, not set_message + finish, or it inserts an extra redraw)
  • multi-canister bar ordering
  • byte-transfer bars against snapshot_transfer::create_transfer_progress_bar

Only two things are normalized away: the blank padding row indicatif writes to fill the terminal line (a function of the frame it follows), and the timer-driven spinner animation glyph (advances on wall-clock time, so it differs run to run). Prefix, final tick glyph, colour codes and message are compared byte for byte.

These tests were mutation-checked — reverting the neutral-finish call sequence, and changing the label prefix from [name] to name, each fail them — and run repeatedly to confirm they aren't flaky. Per-operation tests additionally pin the exact message strings and the exact multi-step message framing.

Testability

Making the operations unit-testable was a primary goal, not a side effect. Each of the four is now tested by running it for real against a RecordingSink and asserting on the resulting Vec<Event> — exact event sequences for the artifact-miss and skip paths, per-canister task ordering, and empty-input silence. Shared fixtures are in operations/test_support.rs. No test touches the network; the whole unit suite runs in well under a second.

Deliberately not in this PR

Each is owned by a separate work item:

  • build.rs, sync.rs and snapshot_transfer.rs still use progress.rs / indicatif directly
  • progress.rs is not deleted — it still has users, in commands/ as well as operations/
  • the 29 user-facing tracing calls inside operations/ are still tracing calls
  • no port signature in crates/icp changed

Stability

The event model is intentionally not semver-stable: publish = false, 0.x in lockstep with icp-cli, all enums #[non_exhaustive], TaskKind closed rather than an open string. The event stream does not drive --json--json keeps meaning the command's final result, and no flag or feature is added that would change that.

Checks

cargo build, cargo fmt --check, cargo clippy --workspace --all-targets, and the icp-events (34 + doctest), icp-cli (85) and icp (236) unit suites are clean on the rebased branch. Integration suites run in CI.

🤖 Generated with Claude Code

Base automatically changed from fm/icp-split-inc0-cleanups to main August 13, 2026 13:40
…tions

Progress reporting is what welds `crates/icp-cli/src/operations/` to the binary:
six of those files import `crate::progress` and a seventh drives `indicatif`
directly. This inverts the dependency so the rest of the crate split can proceed.

New crate `crates/icp-events` expresses progress and user-facing notices as data:
`Event`, `Reporter`, `Task`, `EventSink`, `CancelToken`. It depends only on serde
and futures — not on `icp`, not on an async runtime, and on nothing terminal-shaped.
`TaskKind` carries the three shapes the CLI uses today (spinner, multi-step with
streamed command output, byte position) and `Event::Notice` carries the user-facing
`info!`/`warn!`/`error!` output, so converting those later needs no redesign.

`crates/icp-cli/src/events.rs` adds `IndicatifSink`, the one place that knows about
both events and `indicatif`. It takes its styles and tick interval from
`crate::progress` so the two renderers cannot drift while both exist.

`install.rs`, `settings.rs`, `binding_env_vars.rs` and `candid_compat.rs` now take a
`&Reporter` instead of `debug: bool`. `progress.rs` stays for `build.rs`, `sync.rs`
and `snapshot_transfer.rs`, which a separate change owns.

The event model is deliberately not semver-stable: `publish = false`, `0.x`, all
enums `#[non_exhaustive]`, `TaskKind` closed. Events do not drive `--json`.
Copilot AI balanced review requested due to automatic review settings August 13, 2026 18:54
@raymondk
raymondk force-pushed the fm/icp-split-inc1-events branch from f754450 to f8bf02e Compare August 13, 2026 18:54

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

Introduces an event-based progress abstraction to decouple CLI operations from terminal rendering.

Changes:

  • Adds icp-events with events, reporting, recording, and cancellation APIs.
  • Adds an indicatif event sink with rendering-equivalence tests.
  • Migrates four deploy operations to Reporter with event-sequence tests.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
.claude/CLAUDE.md Documents the new crate.
.claude/architecture.md Documents progress architecture.
Cargo.toml Registers icp-events.
Cargo.lock Locks the new crate.
crates/icp-events/Cargo.toml Configures crate dependencies.
crates/icp-events/src/cancel.rs Implements cancellation tokens.
crates/icp-events/src/event.rs Defines the event model.
crates/icp-events/src/lib.rs Exposes the public API.
crates/icp-events/src/reporter.rs Implements reporters and tasks.
crates/icp-events/src/sink.rs Implements event sinks.
crates/icp-cli/Cargo.toml Adds the crate dependency.
crates/icp-cli/src/commands/deploy.rs Supplies reporters to operations.
crates/icp-cli/src/events.rs Renders events with indicatif.
crates/icp-cli/src/main.rs Registers the events module.
crates/icp-cli/src/operations/binding_env_vars.rs Migrates environment-variable progress.
crates/icp-cli/src/operations/candid_compat.rs Migrates compatibility-check progress.
crates/icp-cli/src/operations/install.rs Migrates installation progress.
crates/icp-cli/src/operations/mod.rs Registers test support.
crates/icp-cli/src/operations/settings.rs Migrates settings progress.
crates/icp-cli/src/operations/snapshot_transfer.rs Reuses byte-bar styling.
crates/icp-cli/src/operations/test_support.rs Adds shared operation fixtures.
crates/icp-cli/src/progress.rs Exposes shared progress styles.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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