Skip to content

feat(sozo): rework UI - #3342

Merged
glihm merged 19 commits into
mainfrom
feat-sozo_ui
Sep 19, 2025
Merged

feat(sozo): rework UI#3342
glihm merged 19 commits into
mainfrom
feat-sozo_ui

Conversation

@remybar

@remybar remybar commented Sep 16, 2025

Copy link
Copy Markdown
Contributor

Description

Improve the way sozo inputs and outputs data to the user.

Related issues

#3324

Tests

  • Yes
  • No, because they aren't needed
  • No, because I need help

Added to documentation?

  • README.md
  • Dojo Book
  • No documentation needed

Checklist

  • I've formatted my code (scripts/rust_fmt.sh, scripts/cairo_fmt.sh)
  • I've linted my code (scripts/clippy.sh, scripts/docs.sh)
  • I've commented my code
  • I've requested a review after addressing the comments

Summary by CodeRabbit

  • New Features

    • Added a unified, themable Sozo UI crate with verbosity levels and terminal theme detection; improved 0x-prefixed zero-padded hex formatting.
  • Refactor

    • Plumbed Sozo UI throughout CLI and ops (commands and migration now accept UI references); removed prior MigrationUi and centralized UI output.
    • Migration and utils now render richer, structured world/resource/status displays and tables.
  • Bug Fixes / Improvements

    • Improved transaction/error formatting and provider health messages; deploy APIs now return deployed address alongside results.
  • Chores

    • Updated manifests, examples, and test data; added terminal theme dependency.

@glihm glihm linked an issue Sep 17, 2025 that may be closed by this pull request
@remybar
remybar marked this pull request as ready for review September 18, 2025 18:34
@coderabbitai

coderabbitai Bot commented Sep 18, 2025

Copy link
Copy Markdown
Contributor

ohayo sensei,

Walkthrough

Adds a new themable UI crate sozo-ui, replaces prior scarb-ui/MigrationUi usages across CLI, ops, walnut, xtask, and tests, threads SozoUi through many command and util APIs, updates verbosity/theme detection, changes deploy API to return deployed address, and adds display/format helpers and large manifest/policies data updates.

Changes

Cohort / File(s) Summary
Workspace & deps
Cargo.toml, bin/sozo/Cargo.toml, crates/sozo/ops/Cargo.toml, crates/sozo/walnut/Cargo.toml, crates/sozo/ui/Cargo.toml, xtask/generate-test-db/Cargo.toml
Add crates/sozo/ui to workspace; introduce sozo-ui workspace dependency; replace scarb-ui/scarb-ui.workspace = true with sozo-ui; add terminal-colorsaurus and minor dependency reorderings.
New UI crate
crates/sozo/ui/src/lib.rs, crates/sozo/ui/Cargo.toml
New SozoUi subsystem: SozoVerbosity, SozoUiTheme, printing/block APIs, sectioning/subsections, verbosity gating, constructors, Default, and theme helpers.
CLI bootstrap & args
bin/sozo/src/main.rs, bin/sozo/src/args.rs
Build SozoUi with detected theme via terminal-colorsaurus; map clap verbosity to SozoVerbosity; adjust cli_main signature and error reporting; tracing level alias tweaks.
Command runners (UI threaded)
bin/sozo/src/commands/mod.rs, .../auth.rs, .../call.rs, .../events.rs, .../execute.rs, .../init.rs, .../inspect.rs, .../migrate.rs, .../model.rs, .../version.rs
Thread &SozoUi through run signatures; replace println!/spinners/MigrationUi with SozoUi title/step/print/block APIs; update hex formatting and add Walnut branch; adjust many run signatures.
CLI utils & details
bin/sozo/src/utils.rs
Thread &SozoUi into utilities; move warnings/progress/detailed renders to UI; add table rendering and detail helpers; update get_world_* and contracts_from_* signatures; standardize 66-digit hex formatting.
Migration flow (ops)
crates/sozo/ops/src/migrate/mod.rs, crates/sozo/ops/src/migrate/error.rs
Replace MigrationUi with SozoUi across migrate/upload_metadata and internals; add DeployWorldError(anyhow::Error) variant; centralize UI helpers for transaction/results/resources/permissions and adapt many signatures.
Ops UI removal & tests
crates/sozo/ops/src/lib.rs, crates/sozo/ops/src/migration_ui.rs (deleted), crates/sozo/ops/src/tests/migration.rs
Remove migration_ui module/file and API; update tests/callers to use SozoUi::default() and new signatures.
Walnut
crates/sozo/walnut/src/debugger.rs, .../verification.rs, .../walnut.rs
Replace Ui with SozoUi in imports and public function signatures; internal logic unchanged.
Dojo utils: provider/tx/errors
crates/dojo/utils/src/provider.rs, .../tx/deployer.rs, .../tx/error.rs, .../tx/mod.rs
Simplify provider error message; deploy_via_udc now returns (Felt, TransactionResult) (address + result); richer TransactionExecution error formatting; TransactionResult derives Clone.
Dojo world display
crates/dojo/world/src/diff/mod.rs, .../resource.rs, .../lib.rs
Add Display impl for WorldStatus and ResourceType; add ResourceDiff::status() helper.
Examples & manifests
examples/spawn-and-move/dojo_dev.toml, examples/spawn-and-move/manifest_dev.json, bin/sozo/tests/test_data/policies.json
Pad world_address and refresh many artifact class_hashes/addresses/calldata; large data-only remapping in policies.json.
Xtask & test db
xtask/generate-test-db/src/main.rs, xtask/generate-test-db/Cargo.toml
Use SozoUi::default() for migrations/tests; add sozo-ui workspace dependency.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Term as Terminal
  participant Main as sozo::main
  participant Theme as terminal-colorsaurus
  participant UI as SozoUi
  participant Cmd as commands::run
  participant Sub as Subcommand
  participant U as utils::get_world_diff_and_provider
  participant Prov as RPC

  User->>Term: run `sozo <cmd>`
  Term->>Main: parse args
  Main->>Theme: detect theme
  Theme-->>Main: Light/Dark or error
  Main->>UI: SozoUi::new(theme, verbosity)
  Main->>Cmd: run(command, metadata, &UI)
  Cmd->>Sub: args.run(metadata, &UI)
  Sub->>U: get_world_diff_and_provider(..., &UI)
  U->>Prov: RPC / health_check
  Prov-->>U: status/data
  U-->>Sub: (WorldDiff, Provider, profile)
  Sub->>UI: title/step/result/warn(...)
  Sub-->>Cmd: Result
  Cmd-->>Main: Result
  Main->>UI: error_block(...) on failure
Loading
sequenceDiagram
  autonumber
  participant Mig as sozo_ops::Migration
  participant UI as SozoUi
  participant Depl as deploy_via_udc
  participant Net as RPC / Chain
  participant Meta as UploadService

  UI->>Mig: migrate(&UI)
  Mig->>UI: title "Evaluate project's state"
  Mig->>Depl: deploy_via_udc(...)
  Depl->>Net: send tx
  Net-->>Depl: (address, tx_result/receipt)
  Depl-->>Mig: (Felt, TransactionResult)
  Mig->>UI: step/result with address/status
  Mig->>Meta: upload_metadata(&UI, ...)
  Meta-->>Mig: cid/ok
  Mig->>UI: result "Metadata uploaded."
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed ohayo sensei, the title "feat(sozo): rework UI" is concise and accurately summarizes the primary change in the changeset—reworking the Sozo UI (new sozo-ui crate and plumbing the UI through commands and ops). It is clear, scoped to the main intent, and appropriate for a changelog-style PR title.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-sozo_ui

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
bin/sozo/src/utils.rs (2)

131-135: Fix RPC version check: allow stable versions newer than the RC.
Current logic may reject node versions like 0.9.0 or 0.9.x even though they’re ≥ 0.9.0-rc.2. Switch to a VersionReq minimum and clarify the error.

Apply this diff:

- pub const RPC_SPEC_VERSION: &str = "0.9.0-rc.2";
+ pub const MIN_RPC_SPEC_VERSION_REQ: &str = ">=0.9.0-rc.2";
@@
-    if !is_compatible_version(&spec_version, RPC_SPEC_VERSION)? {
-        return Err(anyhow!(
-            "Unsupported Starknet RPC version: {spec_version}, expected {RPC_SPEC_VERSION}.",
-        ));
+    if !is_compatible_version(&spec_version, MIN_RPC_SPEC_VERSION_REQ)? {
+        return Err(anyhow!(
+            "Unsupported Starknet RPC version: {spec_version}, required {MIN_RPC_SPEC_VERSION_REQ}.",
+        ));
     }
@@
-fn is_compatible_version(provided_version: &str, expected_version: &str) -> Result<bool> {
-    let provided_ver = Version::parse(provided_version)
-        .map_err(|e| anyhow!("Failed to parse provided version '{}': {}", provided_version, e))?;
-    let expected_ver = Version::parse(expected_version)
-        .map_err(|e| anyhow!("Failed to parse expected version '{}': {}", expected_version, e))?;
-
-    // Specific backward compatibility rule: 0.6 is compatible with 0.7.
-    if (provided_ver.major == 0 && provided_ver.minor == 7)
-        && (expected_ver.major == 0 && expected_ver.minor == 6)
-    {
-        return Ok(true);
-    }
-
-    let expected_ver_req = VersionReq::parse(expected_version).map_err(|e| {
-        anyhow!("Failed to parse expected version requirement '{}': {}", expected_version, e)
-    })?;
-
-    Ok(expected_ver_req.matches(&provided_ver))
+fn is_compatible_version(provided_version: &str, expected_req: &str) -> Result<bool> {
+    let provided_ver = Version::parse(provided_version)
+        .map_err(|e| anyhow!("Failed to parse provided version '{}': {}", provided_version, e))?;
+
+    // Optional special-case if a plain version was supplied as the "requirement".
+    if let Ok(expected_ver) = Version::parse(expected_req) {
+        if (provided_ver.major == 0 && provided_ver.minor == 7)
+            && (expected_ver.major == 0 && expected_ver.minor == 6)
+        {
+            return Ok(true);
+        }
+    }
+
+    // Treat input as a semver requirement. If it isn't one, fall back to ">=<version>".
+    let expected_ver_req = VersionReq::parse(expected_req)
+        .or_else(|_| VersionReq::parse(&format!(">={}", expected_req)))
+        .map_err(|e| {
+            anyhow!("Failed to parse expected version requirement '{}': {}", expected_req, e)
+        })?;
+
+    Ok(expected_ver_req.matches(&provided_ver))
 }

Also applies to: 35-36, 445-463


480-492: ohayo sensei — Fix inline &ui.subsection() callsites; pass a stable &SozoUi reference

Inline uses of &ui.subsection() create a temporary that can’t be safely borrowed across an async .await — bind the subsection to a local and pass its reference.

  • Affected: bin/sozo/src/commands/migrate.rs:59 — change to let step_ui = ui.subsection(); then pass &step_ui.
bin/sozo/src/commands/init.rs (3)

121-148: Check git clone exit status and avoid to_str().unwrap() on paths.

Failures currently go unnoticed, and unwrap() will panic on non‑UTF8 paths.

-        ui.print(format!("Cloning project template from {}...", url));
-        Command::new("git")
-            .args([
-                "clone",
-                "--branch",
-                &format!("v{}", version),
-                "--single-branch",
-                "--recursive",
-                url,
-                path.to_str().unwrap(),
-            ])
-            .output()?;
+        ui.print(format!("Cloning project template from {}...", url));
+        let out = Command::new("git")
+            .arg("clone")
+            .arg("--branch").arg(format!("v{}", version))
+            .arg("--single-branch")
+            .arg("--recursive")
+            .arg(url)
+            .arg(path) // accepts AsRef<OsStr>
+            .output()?;
+        anyhow::ensure!(
+            out.status.success(),
+            "git clone failed: {}",
+            String::from_utf8_lossy(&out.stderr)
+        );
...
-        ui.warn(
-            "Couldn't find template for your current sozo version. Getting the latest version
-            instead.",
-        );
-        Command::new("git").args(["clone", "--recursive", url, path.to_str().unwrap()]).output()?;
+        ui.warn("Couldn't find template for your current sozo version. Getting the latest version instead.");
+        let out = Command::new("git")
+            .arg("clone")
+            .arg("--recursive")
+            .arg(url)
+            .arg(path)
+            .output()?;
+        anyhow::ensure!(
+            out.status.success(),
+            "git clone failed: {}",
+            String::from_utf8_lossy(&out.stderr)
+        );

111-119: Check git ls-remote status before parsing tags.

If git fails (e.g., offline), .contains() will just return false and pick “latest,” hiding the real error.

-    let output = Command::new("git").args(["ls-remote", "--tags", url]).output()?;
-
-    let output_str = String::from_utf8(output.stdout)?;
+    let output = Command::new("git").args(["ls-remote", "--tags", url]).output()?;
+    anyhow::ensure!(output.status.success(), "git ls-remote failed: {}", String::from_utf8_lossy(&output.stderr));
+    let output_str = String::from_utf8(output.stdout)?;

150-167: Check all git commands’ exit status in modify_git_history.

If any git invocation fails, the code proceeds silently.

-    let git_output = Command::new("git").args(["rev-parse", "--short", "HEAD"]).output()?.stdout;
-    let commit_hash = String::from_utf8(git_output)?;
+    let head = Command::new("git").args(["rev-parse", "--short", "HEAD"]).output()?;
+    anyhow::ensure!(head.status.success(), "git rev-parse failed: {}", String::from_utf8_lossy(&head.stderr));
+    let commit_hash = String::from_utf8(head.stdout)?;
...
-        Command::new("git").arg("init").output()?;
-        Command::new("git").args(["add", "--all"]).output()?;
+        anyhow::ensure!(Command::new("git").arg("init").status()?.success(), "git init failed");
+        anyhow::ensure!(Command::new("git").args(["add", "--all"]).status()?.success(), "git add failed");
...
-        Command::new("git").args(["commit", "-m", &commit_msg]).output()?;
+        anyhow::ensure!(Command::new("git").args(["commit", "-m", &commit_msg]).status()?.success(), "git commit failed");
crates/sozo/ops/src/migrate/mod.rs (2)

347-401: Avoid syncing when there’s nothing to change.

Same empty‑call risk applies here; also improves logs by stating there’s nothing to do.

Apply:

-        let has_changed = !invoker.calls.is_empty();
-
-        if self.do_multicall() {
-            ui.step(format!("Sync {} permissions", invoker.calls.len()));
-            invoker.multicall().await?;
-        } else {
-            ui.print(format!("Sync {} permissions (sequentially)", invoker.calls.len()));
-            invoker.invoke_all_sequentially().await?;
-        }
-
-        ui.result("Permissions synced.");
-        Ok(has_changed)
+        let has_changed = !invoker.calls.is_empty();
+        if !has_changed {
+            ui.result("No permissions to sync.");
+            return Ok(false);
+        }
+        if self.do_multicall() {
+            ui.step(format!("Sync {} permissions", invoker.calls.len()));
+            invoker.multicall().await?;
+        } else {
+            ui.step(format!("Sync {} permissions (sequentially)", invoker.calls.len()));
+            invoker.invoke_all_sequentially().await?;
+        }
+        ui.result("Permissions synced.");
+        Ok(true)

565-606: Only register resources when there are calls to execute.

Protects against empty multicall and clarifies output.

Apply:

-        if self.do_multicall() {
+        if n_resources == 0 && invoker.calls.is_empty() && deploy_calls.is_empty() {
+            ui.result("No resources to register.");
+        } else if self.do_multicall() {
             ui.step(format!("Register {} resources", n_resources));
             invoker.extend_calls(deploy_calls.values().cloned().collect());
             let txs_results = invoker.multicall().await?;
             ...
-        } else {
+        } else {
             ui.step(format!("Register {} resources (sequentially)", n_resources));
             invoker.invoke_all_sequentially().await?;
             ...
         }
 
-        ui.result("Resources registered.");
+        if n_resources > 0 {
+            ui.result("Resources registered.");
+        }
🧹 Nitpick comments (37)
bin/sozo/src/commands/version.rs (3)

10-10: Plan deprecation path before removal (confirm behavior overlap).

Ohayo sensei — if this subcommand is removed, will sozo --version also surface the Scarb version, or only Sozo’s? If not equivalent, consider a deprecation cycle: hide the subcommand, print a deprecation notice, then drop in the next release.

Apply this optional deprecation notice if you keep it for one cycle:

 impl VersionArgs {
     pub fn run(&self, scarb_metadata: &Metadata) -> Result<()> {
+        eprintln!("Deprecated: use `sozo --version`. This subcommand will be removed in a future release.");

19-21: Propagate child process failures and avoid UTF‑8 panics.

Check status.success() and print stderr on failure; prefer from_utf8_lossy for robustness.

Apply:

-        let output = Command::new(app).args(["--version"]).output()?;
-        println!("{}", String::from_utf8(output.stdout)?);
+        let output = Command::new(app).arg("--version").output()?;
+        if !output.status.success() {
+            bail!("Failed to run Scarb: {}", String::from_utf8_lossy(&output.stderr));
+        }
+        println!("{}", String::from_utf8_lossy(&output.stdout));

14-16: Tiny copy edit in user‑facing error.

Pluralize “instruction” → “instructions.”

-            bail!(
-                "Scarb not found. Find install instruction here: https://docs.swmansion.com/scarb"
-            )
+            bail!(
+                "Scarb not found. See install instructions: https://docs.swmansion.com/scarb"
+            )
crates/dojo/utils/src/provider.rs (2)

1-3: Ohayo, sensei — keep the friendly error while preserving the root cause via Context

The new generic message improves UX but drops diagnostic context. Use anyhow::Context to keep the underlying error in the chain while showing the same user-facing text.

 use starknet::core::types::{BlockId, BlockTag};
 use starknet::providers::Provider;
 use tracing::trace;
+use anyhow::Context;

@@
 pub async fn health_check_provider<P: Provider + Sync + std::fmt::Debug + 'static>(
     provider: P,
 ) -> anyhow::Result<(), anyhow::Error> {
-    match provider.get_block_with_tx_hashes(BlockId::Tag(BlockTag::Latest)).await {
-        Ok(block) => {
-            trace!(
-                latest_block = ?block,
-                "Provider health check."
-            );
-            Ok(())
-        }
-        Err(_) => Err(anyhow::anyhow!("Unhealthy provider. Please check your configuration.")),
-    }
+    let block = provider
+        .get_block_with_tx_hashes(BlockId::Tag(BlockTag::Latest))
+        .await
+        .context("Unhealthy provider. Please check your configuration.")?;
+    trace!(latest_block = ?block, "Provider health check.");
+    Ok(())
 }

Also applies to: 12-21


9-11: Ohayo, sensei — simplify the return type

Idiomatic anyhow: prefer anyhow::Result<()> over anyhow::Result<(), anyhow::Error>.

-pub async fn health_check_provider<P: Provider + Sync + std::fmt::Debug + 'static>(
-    provider: P,
-) -> anyhow::Result<(), anyhow::Error> {
+pub async fn health_check_provider<P: Provider + Sync + std::fmt::Debug + 'static>(
+    provider: P,
+) -> anyhow::Result<()> {
crates/dojo/utils/src/tx/error.rs (2)

61-67: Improve top‑level rendering by threading indentation depth.

Passing an initial depth clarifies nesting downstream.

-fn display_tx_execution_error(error: &TransactionExecutionErrorData) -> String {
-    format!(
-        "Transaction error (index: {})\n{}",
-        error.transaction_index,
-        display_tx_execution_detail(&error.execution_error)
-    )
-}
+fn display_tx_execution_error(error: &TransactionExecutionErrorData) -> String {
+    format!(
+        "Transaction error (index: {})\n{}",
+        error.transaction_index,
+        display_tx_execution_detail(&error.execution_error, 0)
+    )
+}

69-80: Hex‑format addresses, indent nested errors, and drop the unnecessary clone.

Better UX and less allocation.

-fn display_tx_execution_detail(detail: &ContractExecutionError) -> String {
-    match detail {
-        ContractExecutionError::Message(msg) => format!("Message: {}", msg.clone()),
-        ContractExecutionError::Nested(nested) => {
-            format!(
-                "Error in contract at {}\n{}",
-                nested.contract_address,
-                display_tx_execution_detail(&nested.error)
-            )
-        }
-    }
-}
+fn display_tx_execution_detail(detail: &ContractExecutionError, depth: usize) -> String {
+    let indent = "  ".repeat(depth);
+    match detail {
+        ContractExecutionError::Message(msg) => format!("{indent}Message: {msg}"),
+        ContractExecutionError::Nested(nested) => {
+            format!(
+                "{indent}Error in contract at {:#066x}\n{}",
+                nested.contract_address,
+                display_tx_execution_detail(&nested.error, depth + 1)
+            )
+        }
+    }
+}
crates/dojo/world/src/diff/resource.rs (1)

133-141: Consider implementing Display for ResourceDiff for consistency.

WorldStatus and ResourceType already implement Display. Mirroring that here would let UI code format ResourceDiff values uniformly.

Example outside this hunk:

impl std::fmt::Display for ResourceDiff {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.status())
    }
}
crates/dojo/world/src/lib.rs (1)

33-44: Ergonomics: derive Eq/Clone/Copy on ResourceType.

Handy for passing by value and using in sets/maps without allocations. Safe for a small C-like enum.

Outside this hunk:

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ResourceType {
    // ...
}
crates/dojo/world/src/diff/mod.rs (1)

58-66: ohayo sensei — Centralize status labels to prevent drift

Multiple crates hard-code the same status strings (e.g. "Synced", "Not Deployed", "To Upgrade"); centralize them (shared consts or a single Display impl consumed across crates) and add a snapshot/unit test to lock the exact strings.
See: crates/dojo/world/src/diff/mod.rs and bin/sozo/src/commands/inspect.rs (rg shows many "Synced" occurrences).

examples/spawn-and-move/manifest_dev.json (1)

1482-1866: ohayo sensei — Bulk artifact refresh: addresses/class_hashes updated — add quick integrity checks

Automated verification failed with jq/bash errors; run the script below against examples/spawn-and-move/manifest_dev.json and paste the output.

  • Checks: 1) hex shape of addresses/class_hashes (0x + lowercase hex) 2) unique tags across contracts/libraries/models/events/external_contracts 3) presence of each contract.systems entry as a function in its contract ABI
#!/bin/bash
set -euo pipefail
JSON=examples/spawn-and-move/manifest_dev.json

if [ ! -f "$JSON" ]; then
  echo "ERROR: file not found: $JSON" >&2
  exit 2
fi

# 1) Collect hex-like fields
jq -r '
  (
    (.contracts[]? | [.address?, .class_hash?])[],
    (.libraries[]? | [.class_hash?])[],
    (.models[]? | [.class_hash?])[],
    (.events[]? | [.class_hash?])[],
    (.external_contracts[]? | [.address?, .class_hash?])[]
  )
  | .[]
  | select(. != null)
' "$JSON" | sort -u > /tmp/manifest_hex_vals.txt

bad_hex=0
while IFS= read -r v; do
  [ -z "$v" ] && continue
  if ! printf '%s\n' "$v" | grep -qE '^0x[0-9a-f]+$'; then
    echo "BAD_HEX: $v"
    bad_hex=1
  fi
done < /tmp/manifest_hex_vals.txt

if [ $bad_hex -eq 0 ]; then echo "OK: hex formatting"; else echo "FAIL: hex formatting"; fi

# 2) Duplicate tags
jq -r '
  (
    (.contracts[]?.tag),
    (.libraries[]?.tag),
    (.models[]?.tag),
    (.events[]?.tag),
    (.external_contracts[]?.tag)
  )
  | select(. != null)
' "$JSON" | sort | uniq -d > /tmp/manifest_dup_tags.txt || true

if [ -s /tmp/manifest_dup_tags.txt ]; then
  echo "DUP_TAGS:"
  cat /tmp/manifest_dup_tags.txt
  echo "FAIL: duplicate tags"
else
  echo "OK: unique tags"
fi

# 3) Systems -> ABI functions presence
jq -c '
  [.contracts[]? as $c |
    {
      tag: $c.tag,
      missing: [ ($c.systems // [])[]? as $s
                | select( ( ($c.abi // []) | any( .type=="function" and .name==$s ) ) | not )
              ]
    }
  ] | map(select(.missing | length > 0))
' "$JSON" > /tmp/manifest_missing_systems.json

if [ -s /tmp/manifest_missing_systems.json ] && [ "$(jq -r 'length' /tmp/manifest_missing_systems.json)" -gt 0 ]; then
  echo "MISSING_SYSTEMS:"
  jq -r '.[] | "\(.tag): \(.missing | join(", "))"' /tmp/manifest_missing_systems.json
  echo "FAIL: missing systems entrypoints"
else
  echo "OK: systems entrypoints present"
fi
examples/spawn-and-move/dojo_dev.toml (1)

72-72: ohayo sensei — Normalize world_address formatting to avoid brittle string compares

They match numerically, but normalize the hex string across files to avoid fragile string-equality checks.

-world_address = "0x035bab41ae664e0303f0dedf06c068f118c0a5649bdc5c5c07d909446d9beb72"
+world_address = "0x35bab41ae664e0303f0dedf06c068f118c0a5649bdc5c5c07d909446d9beb72"
bin/sozo/Cargo.toml (1)

39-39: Consider housing terminal-colorsaurus inside sozo-ui.
Keeps theming concerns in one place and reduces CLI crate surface.

bin/sozo/src/utils.rs (4)

368-371: Avoid printing credentials embedded in IPFS URLs.
Sanitize userinfo if present before printing.

Apply this diff:

-            local_ui.debug(local_ui.indent(2, format!("url: {}", ipfs_config.url)));
+            local_ui.debug(local_ui.indent(2, format!("url: {}", sanitize_url(&ipfs_config.url))));

Add this helper (and use url::Url; at top if needed):

fn sanitize_url(raw: &str) -> String {
    if let Ok(mut u) = url::Url::parse(raw) {
        let _ = u.set_username("");
        let _ = u.set_password(None);
        u.to_string()
    } else {
        raw.to_string()
    }
}

397-409: Show selectors as 0x-prefixed 66‑wide hex for consistency.
Improves readability alongside other hex fields.

Apply this diff:

-        .map(|(selector, resource)| ResourceDetails {
+        .map(|(selector, resource)| ResourceDetails {
             resource_type: resource.resource_type(),
             tag: resource.tag(),
             status: resource.status(),
-            selector: selector.to_string(),
+            selector: format!("{:#066x}", selector),
         })

468-475: Minor nit: avoid embedded newline in “not found” string.
Prevents double line breaks in output.

Apply this diff:

-    } else {
-        "not found in your PATH\n".to_string()
-    };
+    } else {
+        "not found in your PATH".to_string()
+    };

501-509: Accept “yes” as confirmation too.
Slight UX polish for prompt parsing.

Apply this diff:

-    Ok(input.trim().to_lowercase() == "y")
+    Ok(matches!(input.trim().to_lowercase().as_str(), "y" | "yes"))
crates/sozo/ui/Cargo.toml (1)

6-7: ohayo sensei — Centralize theme/color handling in crates/sozo/ui

Repo contains multiple direct color/theming deps; move terminal-theme detection + helper APIs into crates/sozo/ui and re-export them so downstream crates stop depending on color crates directly.

Detected direct deps (Cargo.toml):

  • root Cargo.toml: colored = "2.0.0" (ln131), console = "0.15.7" (ln133)
  • bin/sozo/Cargo.toml: colored.workspace = true (ln19)
  • crates/sozo/ui/Cargo.toml: colored.workspace = true (ln7)
  • crates/sozo/mcp/Cargo.toml: colored.workspace = true (ln22)
  • crates/sozo/ops/Cargo.toml: colored.workspace = true (ln12)
  • bin/cairo-bench/Cargo.toml: colored.workspace = true (ln14)
  • crates/sozo/walnut/Cargo.toml: console.workspace = true (ln11)

Action: implement detection + re-export in crates/sozo/ui and remove direct colored/console deps from the listed crates.

crates/sozo/ui/src/lib.rs (5)

117-123: Title prints add an unconditional newline; consider avoiding double spacing.

title() always calls new_line() before printing; frequent titles can create extra vertical whitespace. Consider gating the leading newline on previous-write state or de-duplicating consecutive blank lines. Happy to sketch a tiny “last_was_newline” tracker if you want.


125-131: Fix small typos in docs.

  • “withthe” → “within”
  • “erorr” → “error”
-    /// Prints a step withthe current section.
+    /// Prints a step within the current section.
...
-    /// Prints a block of text surrounded by newlines with a specific erorr color.
+    /// Prints a block of text surrounded by newlines with a specific error color.

Also applies to: 189-193


244-279: Safer, clearer color fallback for deep nesting.

When section_level >= section_colors.len(), you drop coloring entirely. Prefer clamping to the last configured color and prefix to keep consistent styling for deep sections.

-    fn get_title_prefix(&self) -> String {
-        if self.section_level < self.theme.title_prefixes.len() {
-            self.theme.title_prefixes[self.section_level].to_string()
-        } else {
-            "".to_string()
-        }
-    }
+    fn get_title_prefix(&self) -> String {
+        let idx = self.section_level.min(self.theme.title_prefixes.len().saturating_sub(1));
+        self.theme.title_prefixes.get(idx).cloned().unwrap_or_default()
+    }
...
-    fn print_with_section_color(&self, text: String) {
-        let text = if self.section_level < self.theme.section_colors.len() {
-            let mut text = text.color(self.theme.section_colors[self.section_level]);
+    fn print_with_section_color(&self, text: String) {
+        let idx = self.section_level.min(self.theme.section_colors.len().saturating_sub(1));
+        let text = if let Some(color) = self.theme.section_colors.get(idx).cloned() {
+            let mut text = text.color(color);

5-13: Verbosity gating relies on enum ordering; lock intent in code.

self.verbosity >= verbosity depends on variant order. Future reordering would silently change behavior. Consider mapping to an explicit numeric level or deriving Ord and adding a unit test asserting the intended order (Quiet < Normal < Verbose < Debug < Trace).

Also applies to: 165-175, 196-208


47-76: Consider honoring NO_COLOR/CI/non‑TTY to disable colors.

Many CLIs disable colors when not a TTY or when NO_COLOR is set. You can integrate colored::control::set_override(false) behind a small SozoUi::auto_color_from_env() helper.

bin/sozo/src/commands/init.rs (2)

93-109: Validate sozo --version exit status.

Guard against non‑zero exit codes.

-    let output = Command::new("sozo")
+    let output = Command::new("sozo")
         .arg("--version")
         .output()
         .context("Failed to execute `sozo --version` command")?;
+    anyhow::ensure!(
+        output.status.success(),
+        "Command `sozo --version` failed: {}",
+        String::from_utf8_lossy(&output.stderr)
+    );

139-143: Trim the warning string (avoid embedded newline/indent).

The current multiline literal embeds a newline and spaces. Use a single‑line string for cleaner output.

-        ui.warn(
-            "Couldn't find template for your current sozo version. Getting the latest version
-            instead.",
-        );
+        ui.warn("Couldn't find template for your current sozo version. Getting the latest version instead.");
crates/sozo/ops/src/migrate/error.rs (1)

41-42: New error variant is fine; consider a From impl for ergonomics.

DeployWorldError(anyhow::Error) mirrors DeployExternalContractError. Add impl From<anyhow::Error> for MigrationError<S> behind a helper if it’s a common path; otherwise LGTM.

crates/sozo/walnut/src/debugger.rs (1)

40-42: Prefer ui.result for link-style status

Using ui.result(format!("Debug with Walnut: {url}")) aligns with other success outputs and improves visual consistency.

-        ui.print(format!("Debug transaction with Walnut: {url}"));
+        ui.result(format!("Debug with Walnut: {url}"));
bin/sozo/src/commands/execute.rs (1)

171-173: Route results through SozoUi instead of println!

Keeps output theming/verbosity consistent.

-        for r in &txs_results {
-            println!("{}", r);
-        }
+        for r in &txs_results {
+            ui.result(r.to_string());
+        }
bin/sozo/src/commands/call.rs (1)

107-109: Error message source can be misleading

When using the local manifest (no --diff), saying “not found in the world diff” is inaccurate. Tailor the message based on source.

-        let contract_address = contract_address
-            .ok_or_else(|| anyhow!("Contract {descriptor} not found in the world diff."))?;
+        let contract_address = contract_address.ok_or_else(|| {
+            let source = if self.diff || local_manifest.is_none() {
+                "world diff"
+            } else {
+                "local manifest"
+            };
+            anyhow!("Contract {descriptor} not found in the {source}.")
+        })?;
bin/sozo/src/main.rs (1)

37-39: Error rendering via UI is consistent

ui.error_block(format!("{err:?}").trim()) is fine; consider including a friendly hint only if it doesn’t leak internals.

bin/sozo/src/commands/migrate.rs (2)

111-121: Avoid mixing colored styling with SozoUi theming

Coloring the address with colored::green() may clash with UI themes. Prefer UI-rendered styling (e.g., ui.result(...) or a themed block).

-        let colored_address = format!("{:#066x}", world_address).green();
-
-        let end_text = if has_changes {
-            format!("Migration successful with world at address {}", colored_address)
-        } else {
-            format!("No changes for world at address {:#066x}", world_address)
-        };
-
-        ui.new_line();
-        ui.block(end_text);
-        ui.new_line();
+        let end_text = if has_changes {
+            format!("Migration successful with world at address {:#066x}", world_address)
+        } else {
+            format!("No changes for world at address {:#066x}", world_address)
+        };
+        ui.new_line();
+        ui.result(end_text);
+        ui.new_line();

135-160: Banner printing via UI is clean; minor debug detail

ui.debug(format!("Provider: {:?}", provider)); is helpful; just ensure no sensitive data can leak in debug logs for shared terminals.

crates/sozo/ops/src/tests/migration.rs (1)

73-76: Consider silencing UI output in tests to reduce noise/flakes.

Using SozoUi::default() may emit styled output; if a silent/quiet verbosity exists, prefer it for CI stability.

Can you confirm if sozo_ui exposes a Silent/Quiet verbosity or builder helper we can use here?

bin/sozo/src/commands/auth.rs (2)

332-339: Route color decisions through SozoUi for consistent theming.

Direct Colorize styling here may conflict with theme/TTY detection. Consider letting SozoUi own the color for these segments.


340-344: Interactive confirm should be UI/flag‑driven to support non‑TTY runs.

Using utils::prompt_confirm may block in CI/non‑interactive shells. Add a non‑interactive flag (e.g., --yes) or expose a SozoUi confirm.

I can draft a follow‑up PR adding a --yes flag and plumbing it through clone_permissions; want me to?

bin/sozo/src/commands/mod.rs (1)

82-103: Fix display label for Walnut command.

Display returns "WalnutVerify"; this should likely be "Walnut" for consistency with the variant/help text.

Apply:

-            #[cfg(feature = "walnut")]
-            Commands::Walnut(_) => write!(f, "WalnutVerify"),
+            #[cfg(feature = "walnut")]
+            Commands::Walnut(_) => write!(f, "Walnut"),
crates/sozo/ops/src/migrate/mod.rs (1)

420-462: Skip class declaration when n_classes == 0.

Prevents unnecessary declarer setup and clearer logs.

Apply:

-        let n_classes = classes.len();
+        let n_classes = classes.len();
+        if n_classes == 0 {
+            ui.trace("No classes to declare.");
+            return Ok(());
+        }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb32e52 and b5e8b63.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • spawn-and-move-db.tar.gz is excluded by !**/*.gz
📒 Files selected for processing (37)
  • Cargo.toml (3 hunks)
  • bin/sozo/Cargo.toml (1 hunks)
  • bin/sozo/src/args.rs (2 hunks)
  • bin/sozo/src/commands/auth.rs (21 hunks)
  • bin/sozo/src/commands/call.rs (5 hunks)
  • bin/sozo/src/commands/events.rs (2 hunks)
  • bin/sozo/src/commands/execute.rs (3 hunks)
  • bin/sozo/src/commands/init.rs (3 hunks)
  • bin/sozo/src/commands/inspect.rs (2 hunks)
  • bin/sozo/src/commands/migrate.rs (7 hunks)
  • bin/sozo/src/commands/mod.rs (2 hunks)
  • bin/sozo/src/commands/model.rs (7 hunks)
  • bin/sozo/src/commands/version.rs (1 hunks)
  • bin/sozo/src/main.rs (1 hunks)
  • bin/sozo/src/utils.rs (5 hunks)
  • crates/dojo/utils/src/provider.rs (1 hunks)
  • crates/dojo/utils/src/tx/deployer.rs (2 hunks)
  • crates/dojo/utils/src/tx/error.rs (3 hunks)
  • crates/dojo/world/src/diff/mod.rs (1 hunks)
  • crates/dojo/world/src/diff/resource.rs (1 hunks)
  • crates/dojo/world/src/lib.rs (1 hunks)
  • crates/sozo/ops/Cargo.toml (1 hunks)
  • crates/sozo/ops/src/lib.rs (0 hunks)
  • crates/sozo/ops/src/migrate/error.rs (1 hunks)
  • crates/sozo/ops/src/migrate/mod.rs (19 hunks)
  • crates/sozo/ops/src/migration_ui.rs (0 hunks)
  • crates/sozo/ops/src/tests/migration.rs (2 hunks)
  • crates/sozo/ui/Cargo.toml (1 hunks)
  • crates/sozo/ui/src/lib.rs (1 hunks)
  • crates/sozo/walnut/Cargo.toml (1 hunks)
  • crates/sozo/walnut/src/debugger.rs (3 hunks)
  • crates/sozo/walnut/src/verification.rs (2 hunks)
  • crates/sozo/walnut/src/walnut.rs (2 hunks)
  • examples/spawn-and-move/dojo_dev.toml (1 hunks)
  • examples/spawn-and-move/manifest_dev.json (15 hunks)
  • xtask/generate-test-db/Cargo.toml (1 hunks)
  • xtask/generate-test-db/src/main.rs (2 hunks)
💤 Files with no reviewable changes (2)
  • crates/sozo/ops/src/lib.rs
  • crates/sozo/ops/src/migration_ui.rs
🧰 Additional context used
🧬 Code graph analysis (18)
bin/sozo/src/commands/events.rs (2)
bin/sozo/src/commands/mod.rs (1)
  • run (106-134)
bin/sozo/src/utils.rs (2)
  • world_diff (398-407)
  • get_world_diff_and_provider (98-153)
bin/sozo/src/commands/init.rs (4)
bin/sozo/src/commands/auth.rs (1)
  • run (141-212)
bin/sozo/src/commands/call.rs (1)
  • run (53-152)
bin/sozo/src/commands/execute.rs (1)
  • run (67-183)
bin/sozo/src/commands/mod.rs (1)
  • run (106-134)
crates/sozo/walnut/src/walnut.rs (3)
bin/sozo/src/commands/auth.rs (1)
  • run (141-212)
bin/sozo/src/commands/execute.rs (1)
  • run (67-183)
bin/sozo/src/commands/mod.rs (1)
  • run (106-134)
crates/dojo/world/src/lib.rs (4)
crates/dojo/world/src/diff/mod.rs (1)
  • fmt (59-65)
bin/sozo/src/commands/inspect.rs (2)
  • fmt (65-73)
  • fmt (183-189)
crates/dojo/world/src/services/ipfs_service.rs (1)
  • fmt (18-21)
crates/dojo/world/src/uri.rs (1)
  • fmt (61-67)
crates/dojo/world/src/diff/mod.rs (2)
bin/sozo/src/commands/inspect.rs (2)
  • fmt (65-73)
  • fmt (183-189)
crates/dojo/world/src/lib.rs (1)
  • fmt (34-43)
xtask/generate-test-db/src/main.rs (1)
crates/sozo/ui/src/lib.rs (1)
  • default (87-89)
bin/sozo/src/commands/inspect.rs (2)
bin/sozo/src/commands/mod.rs (1)
  • run (106-134)
bin/sozo/src/utils.rs (2)
  • world_diff (398-407)
  • get_world_diff_and_provider (98-153)
bin/sozo/src/commands/call.rs (3)
bin/sozo/src/commands/auth.rs (1)
  • run (141-212)
bin/sozo/src/commands/mod.rs (1)
  • run (106-134)
bin/sozo/src/commands/print_env.rs (1)
  • run (24-59)
bin/sozo/src/main.rs (1)
crates/sozo/ui/src/lib.rs (5)
  • trace (173-175)
  • default (87-89)
  • light (48-61)
  • dark (63-76)
  • new (94-96)
bin/sozo/src/args.rs (1)
crates/sozo/ui/src/lib.rs (2)
  • error (156-163)
  • verbose (165-167)
bin/sozo/src/commands/auth.rs (2)
bin/sozo/src/commands/mod.rs (1)
  • run (106-134)
bin/sozo/src/utils.rs (3)
  • world_diff (398-407)
  • get_world_diff_and_account (159-192)
  • get_world_diff_and_provider (98-153)
bin/sozo/src/commands/migrate.rs (2)
crates/sozo/ui/src/lib.rs (2)
  • trace (173-175)
  • new (94-96)
crates/dojo/utils/src/provider.rs (1)
  • health_check_provider (9-22)
bin/sozo/src/commands/execute.rs (9)
bin/sozo/src/commands/auth.rs (1)
  • run (141-212)
bin/sozo/src/commands/call.rs (1)
  • run (53-152)
bin/sozo/src/commands/init.rs (1)
  • run (28-90)
bin/sozo/src/commands/inspect.rs (1)
  • run (37-52)
bin/sozo/src/commands/migrate.rs (1)
  • run (46-124)
bin/sozo/src/commands/model.rs (1)
  • run (134-222)
bin/sozo/src/commands/mod.rs (1)
  • run (106-134)
bin/sozo/src/commands/events.rs (1)
  • run (60-156)
crates/sozo/walnut/src/walnut.rs (1)
  • run (26-33)
bin/sozo/src/utils.rs (7)
crates/dojo/world/src/local/mod.rs (2)
  • deterministic_world_address (100-103)
  • new (83-97)
crates/dojo/world/src/config/environment.rs (3)
  • world_address (26-28)
  • rpc_url (30-32)
  • account_address (34-36)
bin/sozo/src/commands/options/starknet.rs (2)
  • provider (32-50)
  • url (55-67)
bin/sozo/src/commands/options/account/mod.rs (2)
  • account (70-95)
  • account_address (139-152)
crates/dojo/utils/src/tx/deployer.rs (2)
  • is_deployed (107-122)
  • new (34-36)
crates/dojo/world/src/diff/resource.rs (3)
  • name (63-69)
  • namespace (72-78)
  • status (133-141)
crates/sozo/ui/src/lib.rs (1)
  • new (94-96)
crates/sozo/ops/src/migrate/mod.rs (1)
crates/sozo/ui/src/lib.rs (1)
  • trace (173-175)
crates/sozo/ops/src/tests/migration.rs (1)
crates/sozo/ui/src/lib.rs (1)
  • default (87-89)
bin/sozo/src/commands/mod.rs (7)
bin/sozo/src/commands/auth.rs (1)
  • run (141-212)
bin/sozo/src/commands/call.rs (1)
  • run (53-152)
bin/sozo/src/commands/execute.rs (1)
  • run (67-183)
bin/sozo/src/commands/inspect.rs (1)
  • run (37-52)
bin/sozo/src/commands/migrate.rs (1)
  • run (46-124)
bin/sozo/src/commands/model.rs (1)
  • run (134-222)
bin/sozo/src/commands/events.rs (1)
  • run (60-156)
bin/sozo/src/commands/model.rs (2)
bin/sozo/src/commands/mod.rs (1)
  • run (106-134)
bin/sozo/src/utils.rs (1)
  • get_world_diff_and_provider (98-153)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: clippy
🔇 Additional comments (53)
crates/dojo/utils/src/tx/error.rs (2)

3-3: Ohayo, sensei — importing ContractExecutionError is correct.

Needed for the richer nested error formatting introduced below.


18-19: Dynamic Display via thiserror expression looks good; please confirm toolchain support.

Using a function call inside #[error("{}", …)] relies on thiserror’s expression support. Sanity‑check it builds across our MSRV/CI matrix.

crates/dojo/utils/src/tx/deployer.rs (3)

95-98: Receipt tuple return looks good.


102-102: Final tuple return LGTM.


72-72: Ohayo sensei — breaking return-type change handled.
Call site crates/sozo/ops/src/migrate/mod.rs:1122 already destructures let (world_address, res) = deployer.deploy_via_udc(...); ripgrep found no other usages. Optional: add Returns: (contract_address, TransactionResult) to the fn docs.

crates/dojo/world/src/diff/mod.rs (1)

58-66: LGTM: Display mapping reads well and matches CLI phrasing.

The strings ("Not Deployed", "To Upgrade", "Synced") align with sozo inspect outputs.

crates/dojo/world/src/lib.rs (2)

33-44: LGTM: Clear, human-friendly Display for ResourceType.


33-44: Ohayo sensei — Name polish: consider spacing "External Contract".

Minor UX nit: Display currently emits "ExternalContract"; changing to "External Contract" is cosmetic but repo-wide references to 'ExternalContract' exist (e.g., bin/sozo/src/commands/inspect.rs and many files under crates/sozo and crates/dojo/world) — keep the token if UIs/parsers depend on it or update all consumers.

examples/spawn-and-move/manifest_dev.json (1)

3-4: ohayo sensei — world class_hash/address verified against dojo_dev.toml

manifest_dev.json (.world.class_hash, .world.address) are valid hex and .world.address equals examples/spawn-and-move/dojo_dev.toml world_address (script output: OK: world fields look valid).

crates/sozo/ui/Cargo.toml (1)

1-7: Ohayo, sensei — manifest looks clean and workspace-aligned.
Inherits edition/version correctly and scopes deps via workspace. No blockers.

crates/sozo/ops/Cargo.toml (2)

19-23: Ohayo, sensei — UI dependency threading looks consistent.
Adding sozo-ui and early placement of scarb-interop is fine.


8-29: ohayo sensei — Verify/remove colored in crates/sozo/ops/Cargo.toml

Sandbox ripgrep returned no matches in crates/sozo/ops; confirm whether SozoUi now centralizes output styling and remove colored from crates/sozo/ops if unused. Suggested local checks: rg -n --hidden --no-ignore --type=rust "colored::|.color(|.bold(|.red(|.green(|.yellow(|.blue(" crates/ && rg -n "\bcolored\b" Cargo.lock

Cargo.toml (2)

22-23: Ohayo, sensei — workspace wiring for sozo-ui is correct.
Member and workspace dependency entries look good.

Also applies to: 84-85


170-173: Drop scarb-ui if fully migrated

ohayo sensei — automated verification failed in the sandbox; confirm no references remain and remove scarb-ui from Cargo.toml to trim the build graph and avoid drift. Verify locally with: git grep -n -E 'scarb[-]ui|scarb-ui|scarb_ui' || rg --hidden --no-ignore -n -S 'scarb[-]ui|scarb-ui|scarb_ui' --glob '!.git/' --glob '!target/' --glob '!/node_modules/'

bin/sozo/src/utils.rs (3)

76-87: Ohayo, sensei — world address mismatch warning UX is solid.
Great use of ui.warn_block and 66-wide hex formatting.


165-192: Nice step/result flow for world diff and account verification.
Clear UX with subsection scoping; error includes 66‑wide address. Looks good.


112-126: Ohayo sensei — Avoid temporary Arc wrap/unwrap around provider

Have provider_utils accept &impl Provider (or &dyn Provider) so you can pass a reference and remove the Arc/try_unwrap churn to avoid accidental clone leaks.

health_check_provider was not found by the earlier search; verify signature and callsites. Run from repo root: rg -nP 'health_check_provider\s*(' -C2 and inspect bin/sozo/src/utils.rs (lines ~112–126).

bin/sozo/Cargo.toml (1)

34-40: Ohayo, sensei — single sozo-ui entry confirmed.
Found one occurrence at bin/sozo/Cargo.toml:34.

crates/sozo/walnut/Cargo.toml (1)

18-18: Ohayo, sensei — dependency swap LGTM.

Switch to sozo-ui aligns with the new UI stack. No concerns here.

xtask/generate-test-db/Cargo.toml (1)

19-19: Ohayo, sensei — don't remove sozo-ui from the xtask workspace.

xtask/generate-test-db/src/main.rs imports SozoUi (use sozo_ui::SozoUi; at line 15) and calls .migrate(&SozoUi::default()) at line 85, so the workspace entry is required.

Likely an incorrect or invalid review comment.

xtask/generate-test-db/src/main.rs (2)

15-15: LGTM! UI migration looks good, sensei.

The change from MigrationUi to SozoUi aligns perfectly with the PR-wide UI rework.


85-85: LGTM! Default UI usage is appropriate here.

Ohayo! Using SozoUi::default() for the test database generation is the right choice. The default constructor provides dark theme with standard verbosity, which is suitable for this automated task.

crates/sozo/walnut/src/walnut.rs (2)

4-4: LGTM! Import change aligns with UI migration.

The import update from scarb_ui to sozo_ui is consistent with the broader UI rework across the codebase.


26-26: LGTM! Function signature properly updated, sensei.

The signature change to accept ui: &SozoUi is consistent with other command modules in the PR.

bin/sozo/src/commands/events.rs (3)

12-12: LGTM! Import addition follows the pattern.

Ohayo! The SozoUi import is properly placed with other sozo-related imports.


60-60: LGTM! Method signature updated correctly.

The addition of ui: &SozoUi parameter matches the pattern used across all other command modules.


64-65: LGTM! UI parameter properly threaded through.

The ui parameter is correctly passed to utils::get_world_diff_and_provider, maintaining consistency with the new API.

crates/sozo/walnut/src/verification.rs (2)

11-11: LGTM! Import updated for UI migration.

The change from scarb_ui to sozo_ui import is consistent with the overall UI rework.


31-31: LGTM! Function signature properly updated, sensei.

Ohayo! The walnut_verify function now correctly accepts ui: &SozoUi instead of the old UI type, maintaining API consistency across the walnut module.

bin/sozo/src/commands/inspect.rs (3)

10-10: LGTM! Import added correctly.

The SozoUi import follows the established pattern for the UI migration.


37-37: LGTM! Function signature updated appropriately.

The addition of ui: &SozoUi parameter aligns with other command modules' signatures.


43-43: LGTM! UI parameter correctly propagated, sensei.

Ohayo! The ui parameter is properly passed through to utils::get_world_diff_and_provider, maintaining the new API contract.

bin/sozo/src/commands/model.rs (3)

8-8: LGTM! Import follows the pattern.

The SozoUi import is correctly placed with other sozo-related imports.


134-134: LGTM! Method signature properly updated.

The addition of ui: &SozoUi parameter is consistent with the UI migration pattern across all command modules.


145-145: LGTM! UI parameter consistently threaded through all subcommands.

Ohayo sensei! Excellent work ensuring that the ui parameter is passed to utils::get_world_diff_and_provider in all five subcommand branches (ClassHash, ContractAddress, Layout, Schema, and Get). This maintains API consistency throughout the model command implementation.

Also applies to: 155-155, 171-171, 188-188, 206-206

crates/sozo/walnut/src/debugger.rs (2)

30-33: API migration to SozoUi looks good

Switching the parameter to &SozoUi is consistent with the repo-wide UI refactor.


47-49: Static verify signature change is consistent

Accepting &SozoUi here matches how other commands pass UI through async paths.

bin/sozo/src/commands/execute.rs (2)

66-90: Plumbing &SozoUi through Execute.run is correct

Imports, signature, and passing ui into helpers all look consistent with the new UI model.


175-180: Walnut debug call is correct

Guarded by the feature flag and respects Noop internally. 👍

bin/sozo/src/args.rs (2)

43-55: Verify verbosity mapping matches intent

Comment says “-v verbose, -vv debug, -vvv trace”. Ensure clap_verbosity_flag’s defaults map to the desired SozoVerbosity in practice (default likely maps to Warn). Adjust if default ends up too chatty.

Run a quick manual check:

  • sozo (no -v): expect Normal or less chatty UI
  • sozo -v: expect Verbose
  • sozo -vv: expect Debug
  • sozo -vvv: expect Trace

61-68: Logging threshold logic is sound

Using as_trace() >= DEBUG to toggle detailed filters is clean.

bin/sozo/src/main.rs (1)

26-35: Theme detection and fallback look good

Light/Dark mapping with a dark fallback is reasonable.

bin/sozo/src/commands/migrate.rs (2)

46-66: UI-first flow is well-threaded

Passing &SozoUi, using ui.subsection(), and removing spinner coupling is a solid improvement.


96-102: Great warning UX for missing IPFS creds

Clear guidance and link provided through ui.warn_block.

crates/sozo/ops/src/tests/migration.rs (2)

15-15: Ohayo, sensei — SozoUi import swap looks clean.

The new UI abstraction is correctly referenced.


79-80: Ohayo, sensei — upload_metadata UI threading matches new signature.

The test aligns with the ops API change.

bin/sozo/src/commands/auth.rs (3)

430-446: Ohayo, sensei — nice hierarchical output with subsection + headers.

Indentation via permissions_ui improves readability.


497-564: print_diff_permissions uses SozoUi indentation correctly.

Logic and formatting look good; no functional concerns.


141-156: Ohayo, sensei — threading &SozoUi through Auth::run is the right call; verification incomplete.

rg produced no output, so I can't confirm other call sites compile — re-run these and paste the results:

rg -nP --hidden -S '\b(Auth|AuthArgs)\s*::\s*run\s*\(' -C2
rg -nP --hidden -S 'SozoUi' -C2
rg -nP --hidden -S 'list_permissions\([^)]*SozoUi' -n
rg -nP --hidden -S 'update_(writers|owners)\([^)]*SozoUi' -n
bin/sozo/src/commands/mod.rs (2)

6-6: Ohayo, sensei — SozoUi import integration LGTM.


106-134: Ohayo, sensei — passing ui to subcommands is consistent across the board.

Main dispatcher changes read well.

crates/sozo/ops/src/migrate/mod.rs (2)

94-101: Ohayo, sensei — top-level Migrate sectioning is clear.

Title + subsection pattern improves UX traceability.


1096-1154: ohayo sensei — Incorrect: TransactionResult::Receipt doesn't exist; keep handling HashReceipt.

TransactionResult is defined as Noop / Hash / HashReceipt (crates/dojo/utils/src/tx/mod.rs) and invoker/deployer return HashReceipt, so the match in crates/sozo/ops/src/migrate/mod.rs:1131 is correct; only add a new enum variant if you mean to support a different result shape.

Likely an incorrect or invalid review comment.

Comment on lines +131 to +134
ui.print(format!(
"[ {} ]",
output.iter().map(|o| format!("0x{:x}", o)).collect::<Vec<_>>().join(" ")
);
output.iter().map(|o| format!("0x{:#066x}", o)).collect::<Vec<_>>().join(" "),
));

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.

⚠️ Potential issue

Double “0x” prefix bug in output formatting

format!("0x{:#066x}", o) produces 0x0x…. Remove the extra literal.

-                ui.print(format!(
-                    "[ {} ]",
-                    output.iter().map(|o| format!("0x{:#066x}", o)).collect::<Vec<_>>().join(" "),
-                ));
+                ui.print(format!(
+                    "[ {} ]",
+                    output.iter().map(|o| format!("{:#066x}", o)).collect::<Vec<_>>().join(" "),
+                ));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ui.print(format!(
"[ {} ]",
output.iter().map(|o| format!("0x{:x}", o)).collect::<Vec<_>>().join(" ")
);
output.iter().map(|o| format!("0x{:#066x}", o)).collect::<Vec<_>>().join(" "),
));
ui.print(format!(
"[ {} ]",
output.iter().map(|o| format!("{:#066x}", o)).collect::<Vec<_>>().join(" "),
));
🤖 Prompt for AI Agents
In bin/sozo/src/commands/call.rs around lines 131 to 134, the output formatting
uses format!("0x{:#066x}", o) which produces a double "0x" prefix; remove the
extra literal "0x" and rely on the "{:#066x}" specifier (i.e., replace
format!("0x{:#066x}", o) with format!("{:#066x}", o)) so each value is prefixed
once and retains the intended zero-padding.

Comment on lines +159 to 163
let address = format!("{:#066x}", inner.contract_address);
let selector = format!("0x{:#066x}", inner.selector);
let inner_error = format_execution_error(&inner.error);
format!("Error in contract at {address} when calling {selector}:\n {inner_error}",)
}

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.

⚠️ Potential issue

Double “0x” prefix bug in error selector formatting

Same issue when formatting the selector.

-            let address = format!("{:#066x}", inner.contract_address);
-            let selector = format!("0x{:#066x}", inner.selector);
+            let address = format!("{:#066x}", inner.contract_address);
+            let selector = format!("{:#066x}", inner.selector);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let address = format!("{:#066x}", inner.contract_address);
let selector = format!("0x{:#066x}", inner.selector);
let inner_error = format_execution_error(&inner.error);
format!("Error in contract at {address} when calling {selector}:\n {inner_error}",)
}
let address = format!("{:#066x}", inner.contract_address);
let selector = format!("{:#066x}", inner.selector);
let inner_error = format_execution_error(&inner.error);
format!("Error in contract at {address} when calling {selector}:\n {inner_error}",)
}
🤖 Prompt for AI Agents
In bin/sozo/src/commands/call.rs around lines 159 to 163, the selector
formatting adds a duplicate "0x" prefix because the format specifier {:#066x}
already includes "0x"; remove the extra literal "0x" or change the specifier to
{:066x} and prepend a single "0x" so the selector prints with exactly one "0x"
prefix (do the same consistent change if there are other selector/address format
occurrences).

Comment thread bin/sozo/src/utils.rs
Comment on lines +356 to +365
if let Some(http_headers) = env.http_headers.as_ref() {
local_ui.debug(local_ui.indent(1, "http_headers:"));
for header in http_headers {
local_ui.debug(
local_ui.indent(2, format!("name: {}, value: {}", header.name, header.value)),
);
}
} else {
local_ui.debug(local_ui.indent(1, "http_headers: None"));
}

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.

⚠️ Potential issue

Don’t print raw HTTP header values (possible secrets).
Mask header values before output to avoid leaking tokens/keys, even at verbose/debug levels.

Apply this diff:

-            for header in http_headers {
-                local_ui.debug(
-                    local_ui.indent(2, format!("name: {}, value: {}", header.name, header.value)),
-                );
-            }
+            for header in http_headers {
+                local_ui.debug(local_ui.indent(
+                    2,
+                    format!("name: {}, value: {}", header.name, mask_secret(&header.value)),
+                ));
+            }

Add this helper somewhere in this module:

fn mask_secret(value: &str) -> String {
    let v = value.trim();
    if v.is_empty() { return "".into(); }
    // Preserve scheme (e.g., "Bearer", "Basic") if present, mask the rest.
    if let Some(space) = v.find(' ') {
        let (scheme, _) = v.split_at(space);
        return format!("{} {}", scheme, "********");
    }
    // Generic tail-preserving mask.
    let n = v.chars().count();
    let keep = n.min(4);
    let tail: String = v.chars().skip(n - keep).collect();
    format!("{}{}", "*".repeat(n.saturating_sub(keep)), tail)
}
🤖 Prompt for AI Agents
In bin/sozo/src/utils.rs around lines 356 to 365, the debug output prints raw
HTTP header values which may leak secrets; add the provided mask_secret helper
function into this module and update the debug loop to call
mask_secret(&header.value) (preserving scheme handling and empty-string
behavior) when formatting the value for output so that header values are masked
before logging.

Comment on lines +78 to 79
None => return Ok((Felt::ZERO, TransactionResult::Noop)),
};

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.

⚠️ Potential issue

Don’t return Felt::ZERO as a sentinel; return the actual address.

Leaking a magic value invites bugs; you can compute the address here.

-            None => return Ok((Felt::ZERO, TransactionResult::Noop)),
+            None => {
+                let contract_address =
+                    get_contract_address(salt, class_hash, constructor_calldata, deployer_address);
+                return Ok((contract_address, TransactionResult::Noop));
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
None => return Ok((Felt::ZERO, TransactionResult::Noop)),
};
None => {
let contract_address =
get_contract_address(salt, class_hash, constructor_calldata, deployer_address);
return Ok((contract_address, TransactionResult::Noop));
},
};
🤖 Prompt for AI Agents
In crates/dojo/utils/src/tx/deployer.rs around lines 78-79, replace the sentinel
return of Felt::ZERO with the actual deployed contract address: compute the
address using the same deterministic formula used elsewhere in the codebase
(e.g. the helper that takes class_hash, constructor_calldata and salt / deployer
address — typically named something like compute_contract_address or
calculate_contract_address), return that Felt instead of ZERO, and add any
needed imports and error handling so the function returns Ok((computed_address,
TransactionResult::Noop)).

Comment on lines +133 to +141
pub fn status(&self) -> String {
let res = match self {
ResourceDiff::Created(_) => "Created",
ResourceDiff::Updated(_, _) => "Updated",
ResourceDiff::Synced(_, _) => "Synced",
};

res.to_string()
}

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.

🛠️ Refactor suggestion

🧩 Analysis chain

Avoid allocation in status(); return a string slice.

No need to allocate a new String each call. Return &'static str directly.

-    pub fn status(&self) -> String {
-        let res = match self {
-            ResourceDiff::Created(_) => "Created",
-            ResourceDiff::Updated(_, _) => "Updated",
-            ResourceDiff::Synced(_, _) => "Synced",
-        };
-
-        res.to_string()
-    }
+    pub fn status(&self) -> &'static str {
+        match self {
+            ResourceDiff::Created(_) => "Created",
+            ResourceDiff::Updated(_, _) => "Updated",
+            ResourceDiff::Synced(_, _) => "Synced",
+        }
+    }

If any call sites rely on an owned String, they can do .to_string() at the edge. Please grep usages to confirm:


🏁 Script executed:

#!/bin/bash
rg -nP --type=rust '\.status\(\)'

Length of output: 328


🏁 Script executed:

#!/bin/bash
set -euo pipefail

pairs=(
"xtask/generate-test-db/src/main.rs:124"
"bin/sozo/src/utils.rs:404"
"crates/sozo/walnut/src/verification.rs:63"
"crates/dojo/world/abigen/src/main.rs:94"
"crates/dojo/world/abigen/src/main.rs:111"
)

for p in "${pairs[@]}"; do
  file="${p%%:*}"
  line="${p##*:}"
  start=$(( line>6 ? line-6 : 1 ))
  end=$(( line+6 ))
  echo "== $file lines $start-$end =="
  sed -n "${start},${end}p" "$file" || true
  echo
done

Length of output: 2369


Ohayo sensei: Update call site for status() refactor
Change status: resource.status(), to status: resource.status().to_string() in bin/sozo/src/utils.rs:404—other .status() calls (Command::status(), HTTP Response.status()) remain unaffected.

🤖 Prompt for AI Agents
In crates/dojo/world/src/diff/resource.rs lines 133-141 the
ResourceDiff::status() method now returns a String; update the single call site
in bin/sozo/src/utils.rs at around line 404 by replacing status:
resource.status(), with status: resource.status().to_string() so the produced
value is owned String (leave other .status() calls untouched).

Comment on lines 171 to +183
if self.do_multicall() {
ui.update_text_boxed(format!("Uploading {} metadata...", invoker.calls.len()));
ui.step(format!("Upload {} metadata", invoker.calls.len()));
invoker.multicall().await.map_err(|e| anyhow!(e.to_string()))?;
} else {
ui.update_text_boxed(format!(
"Uploading {} metadata (sequentially)...",
invoker.calls.len()
));
ui.step(format!("Upload {} metadata (sequentially)", invoker.calls.len()));
invoker.invoke_all_sequentially().await.map_err(|e| anyhow!(e.to_string()))?;
}

ui.result("Metadata uploaded.");

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.

⚠️ Potential issue

Guard empty metadata calls before multicall.

If invoker.calls is empty, multicall/invoke_all_sequentially may be a no‑op or error depending on Invoker impl. Safer to bail early with a result message.

Apply:

-        if self.do_multicall() {
-            ui.step(format!("Upload {} metadata", invoker.calls.len()));
-            invoker.multicall().await.map_err(|e| anyhow!(e.to_string()))?;
-        } else {
-            ui.step(format!("Upload {} metadata (sequentially)", invoker.calls.len()));
-            invoker.invoke_all_sequentially().await.map_err(|e| anyhow!(e.to_string()))?;
-        }
+        if invoker.calls.is_empty() {
+            ui.result("No metadata changes detected.");
+        } else if self.do_multicall() {
+            ui.step(format!("Upload {} metadata", invoker.calls.len()));
+            invoker.multicall().await.map_err(|e| anyhow!(e.to_string()))?;
+        } else {
+            ui.step(format!("Upload {} metadata (sequentially)", invoker.calls.len()));
+            invoker.invoke_all_sequentially().await.map_err(|e| anyhow!(e.to_string()))?;
+        }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In crates/sozo/ops/src/migrate/mod.rs around lines 174 to 183, guard against
empty invoker.calls before attempting multicall/invoke_all_sequentially: check
if invoker.calls.is_empty(), and if so call ui.result("Metadata uploaded.") and
return early (Ok(())) to avoid invoking a no-op or triggering
implementation-specific errors; otherwise proceed with the existing
do_multicall() branch and calls as before.

Comment on lines 626 to +642
if self.do_multicall() {
let ui_text = format!("Registering {} external contracts...", n_external_contracts);
ui.update_text_boxed(ui_text);
ui.step(format!("Register {} external contracts", n_external_contracts));
invoker.multicall().await?;
} else {
let ui_text = format!(
"Registering {} external contracts (sequentially)...",
n_external_contracts
);
ui.update_text_boxed(ui_text);
ui.step(format!("Register {} external contracts (sequentially)", n_external_contracts));
invoker.invoke_all_sequentially().await?;
}

if !not_upgradeable_contract_names.is_empty() {
let msg = format!(
ui.warn_block(format!(
"The following external contracts are NOT upgradeable as they don't export an \
`upgrade(ClassHash)` function:\n{}",
not_upgradeable_contract_names.join("\n")
);
println!();
println!("{}", msg.as_str().bright_yellow());
println!();
));
}

ui.result("External contracts registered.");

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.

⚠️ Potential issue

Also guard external‑contract registration on empty call list.

Same reasoning; avoids no‑op multicalls.

Apply:

-        if self.do_multicall() {
-            ui.step(format!("Register {} external contracts", n_external_contracts));
-            invoker.multicall().await?;
-        } else {
-            ui.step(format!("Register {} external contracts (sequentially)", n_external_contracts));
-            invoker.invoke_all_sequentially().await?;
-        }
+        if n_external_contracts == 0 {
+            ui.result("No external contracts to register.");
+        } else if self.do_multicall() {
+            ui.step(format!("Register {} external contracts", n_external_contracts));
+            invoker.multicall().await?;
+        } else {
+            ui.step(format!("Register {} external contracts (sequentially)", n_external_contracts));
+            invoker.invoke_all_sequentially().await?;
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if self.do_multicall() {
let ui_text = format!("Registering {} external contracts...", n_external_contracts);
ui.update_text_boxed(ui_text);
ui.step(format!("Register {} external contracts", n_external_contracts));
invoker.multicall().await?;
} else {
let ui_text = format!(
"Registering {} external contracts (sequentially)...",
n_external_contracts
);
ui.update_text_boxed(ui_text);
ui.step(format!("Register {} external contracts (sequentially)", n_external_contracts));
invoker.invoke_all_sequentially().await?;
}
if !not_upgradeable_contract_names.is_empty() {
let msg = format!(
ui.warn_block(format!(
"The following external contracts are NOT upgradeable as they don't export an \
`upgrade(ClassHash)` function:\n{}",
not_upgradeable_contract_names.join("\n")
);
println!();
println!("{}", msg.as_str().bright_yellow());
println!();
));
}
ui.result("External contracts registered.");
if n_external_contracts == 0 {
ui.result("No external contracts to register.");
} else if self.do_multicall() {
ui.step(format!("Register {} external contracts", n_external_contracts));
invoker.multicall().await?;
} else {
ui.step(format!("Register {} external contracts (sequentially)", n_external_contracts));
invoker.invoke_all_sequentially().await?;
}
if !not_upgradeable_contract_names.is_empty() {
ui.warn_block(format!(
"The following external contracts are NOT upgradeable as they don't export an \
`upgrade(ClassHash)` function:\n{}",
not_upgradeable_contract_names.join("\n")
));
}
ui.result("External contracts registered.");
🤖 Prompt for AI Agents
In crates/sozo/ops/src/migrate/mod.rs around lines 625 to 642, the code always
attempts to register external contracts even when the call list is empty; wrap
the registration block in a guard that checks the call count (e.g.,
n_external_contracts > 0 or invoker.has_calls()) and only run ui.step and
invoker.multicall()/invoke_all_sequentially() when there are calls to perform;
if there are zero calls, skip the multicall/serial invocation and emit an
appropriate ui message/result or silently skip so you avoid performing no-op
multicalls.

Comment thread crates/sozo/ui/src/lib.rs
Comment on lines +150 to +163
/// Prints a warning without taking the indentation into account.
pub fn warn<T: Message>(&self, text: T) {
self.do_print(text, SozoVerbosity::Quiet, self.theme.warning_color, false);
}

/// Prints an error without taking the indentation into account.
pub fn error<T: Message>(&self, text: T) {
self.do_print(
format!("error: {}", text.text().trim()),
SozoVerbosity::Quiet,
self.theme.error_color,
false,
);
}

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.

⚠️ Potential issue

Ohayo, sensei — route warnings/errors to stderr (don’t pollute stdout).

Currently warn, error, warn_block, and error_block write to stdout via println!, which breaks piping (e.g., sozo ... | jq) and conventional CLI UX. Errors and warnings should go to stderr.

Patch (minimal change, no internal API churn):

 pub fn warn<T: Message>(&self, text: T) {
-    self.do_print(text, SozoVerbosity::Quiet, self.theme.warning_color, false);
+    if self.verbosity >= SozoVerbosity::Quiet {
+        eprintln!("{}", text.text().color(self.theme.warning_color));
+    }
 }

 pub fn error<T: Message>(&self, text: T) {
-    self.do_print(
-        format!("error: {}", text.text().trim()),
-        SozoVerbosity::Quiet,
-        self.theme.error_color,
-        false,
-    );
+    if self.verbosity >= SozoVerbosity::Quiet {
+        eprintln!("{}", format!("error: {}", text.text().trim()).color(self.theme.error_color));
+    }
 }

And for blocks:

 pub fn warn_block<T: Message>(&self, block: T) {
-    self.new_line();
-    self.do_block(block, SozoVerbosity::Normal, self.theme.warning_color, false);
-    self.new_line();
+    if self.verbosity > SozoVerbosity::Quiet {
+        eprintln!();
+        for line in block.text().lines() {
+            eprintln!("{}", line.color(self.theme.warning_color));
+        }
+        eprintln!();
+    }
 }

 pub fn error_block<T: Message>(&self, block: T) {
-    self.new_line();
-    self.do_block(block, SozoVerbosity::Normal, self.theme.error_color, false);
-    self.new_line();
+    if self.verbosity > SozoVerbosity::Quiet {
+        eprintln!();
+        for line in block.text().lines() {
+            eprintln!("{}", line.color(self.theme.error_color));
+        }
+        eprintln!();
+    }
 }

Also applies to: 182-194

🤖 Prompt for AI Agents
In crates/sozo/ui/src/lib.rs around lines 150-163 (and similarly 182-194), the
warn/error and their _block counterparts currently print to stdout; change these
to write to stderr so warnings/errors don't pollute stdout and break piping.
Replace the println!/print usage with stderr output (e.g., eprintln! or writing
to std::io::stderr()/writeln!) in those methods, keeping the same message
formatting, verbosity, color/theme handling, and boolean flags so no public API
changes are introduced.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 0

🧹 Nitpick comments (3)
bin/sozo/tests/test_data/policies.json (3)

203-205: Normalize method naming style (camelCase vs snake_case).

All methods are snake_case except Line 204 "deployContract". If consumers expect snake_case, this is an inconsistency; if ABI expects camelCase, then the rest may be inconsistent. Please confirm the intended convention and align.

If snake_case is intended, apply:

-    "method": "deployContract"
+    "method": "deploy_contract"

206-206: Add a trailing newline at EOF.

Tiny formatting nit for POSIX tools and diffs.

Apply:

-]
+]
+

1-206: Optional: generate this fixture programmatically from manifest/ABI.

To avoid drift, consider a tiny generator that pulls method names and target addresses from the current dev manifest and emits this JSON.

I can scaffold a small Rust/JS script to emit policies from the manifest if you want.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5e8b63 and 63574aa.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • spawn-and-move-db.tar.gz is excluded by !**/*.gz
📒 Files selected for processing (1)
  • bin/sozo/tests/test_data/policies.json (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-28T23:35:04.367Z
Learnt from: glihm
PR: dojoengine/dojo#2691
File: bin/sozo/tests/test_data/policies.json:7-32
Timestamp: 2024-11-28T23:35:04.367Z
Learning: The file `bin/sozo/tests/test_data/policies.json` is a test file containing policies used by sessions to determine which methods can be called for which contract addresses. There is not much to verify or check in this file.

Applied to files:

  • bin/sozo/tests/test_data/policies.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: clippy
🔇 Additional comments (2)
bin/sozo/tests/test_data/policies.json (2)

1-206: ohayo sensei — test policy list updates look fine overall

Syntactically valid JSON and structure matches prior schema expectations for this fixture.


1-206: ohayo sensei — sanity checks for bin/sozo/tests/test_data/policies.json failed to run here; run these locally
Sandbox produced bash errors so verification couldn't complete. Run the checks below and fix any DUP/BAD/MISSING results.

jq -r '.[] | "(.target)|(.method)"' bin/sozo/tests/test_data/policies.json | sort | uniq -c | awk '$1>1{print "DUP", $0}'

jq -r '.[].target' bin/sozo/tests/test_data/policies.json | sort -u | grep -v -E '^0x[0-9a-f]{64}$' || echo "OK: addresses look valid"

for m in $(jq -r '.[].method' bin/sozo/tests/test_data/policies.json | sort -u); do
rg -n -S -g '!/target/' -g '!/node_modules/' -g '!/dist/' -e "\b$m\b" . || echo "MISSING: $m"
done

for a in $(jq -r '.[].target' bin/sozo/tests/test_data/policies.json | sort -u); do
rg -n -S -g '!/target/' -g '!/node_modules/' -g '!/dist/' -F "$a" . || echo "ADDR_NOT_FOUND: $a"
done

Fix duplicates, invalid addresses, or add missing ABI/manifests if any checks report issues.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 0

♻️ Duplicate comments (3)
crates/sozo/ops/src/migrate/mod.rs (2)

174-180: Guard empty metadata calls before multicall.

When invoker.calls is empty, attempting multicall or sequential invocation might result in unnecessary operations or implementation-specific errors.

Apply this diff to handle empty calls gracefully:

+        if invoker.calls.is_empty() {
+            ui.result("No metadata changes detected.");
+            return Ok(());
+        }
+        
         if self.do_multicall() {
             ui.step(format!("Upload {} metadata", invoker.calls.len()));
             invoker.multicall().await.map_err(|e| anyhow!(e.to_string()))?;
         } else {
             ui.step(format!("Upload {} metadata (sequentially)", invoker.calls.len()));
             invoker.invoke_all_sequentially().await.map_err(|e| anyhow!(e.to_string()))?;
         }

691-697: Also guard external contract registration on empty call list.

Similar to the metadata upload, we should check if there are actually external contracts to register before attempting multicall/sequential invocation.

Apply this diff:

+        if n_external_contracts == 0 {
+            return Ok(has_changed);
+        }
+
         if self.do_multicall() {
             ui.step(format!("Register {} external contracts", n_external_contracts));
             invoker.multicall().await?;
         } else {
             ui.step(format!("Register {} external contracts (sequentially)", n_external_contracts));
             invoker.invoke_all_sequentially().await?;
         }
bin/sozo/src/utils.rs (1)

358-362: Don't print raw HTTP header values (possible secrets).

HTTP headers may contain sensitive authentication tokens or API keys that shouldn't be logged even at debug/verbose levels.

Apply this diff to mask sensitive header values:

             for header in http_headers {
                 local_ui.debug(
-                    local_ui.indent(2, format!("name: {}, value: {}", header.name, header.value)),
+                    local_ui.indent(2, format!("name: {}, value: {}", header.name, mask_secret(&header.value))),
                 );
             }

Add this helper function to the module:

fn mask_secret(value: &str) -> String {
    let v = value.trim();
    if v.is_empty() { return "".into(); }
    // Preserve scheme (e.g., "Bearer", "Basic") if present, mask the rest.
    if let Some(space) = v.find(' ') {
        let (scheme, _) = v.split_at(space);
        return format!("{} {}", scheme, "********");
    }
    // Generic tail-preserving mask.
    let n = v.chars().count();
    let keep = n.min(4);
    let tail: String = v.chars().skip(n - keep).collect();
    format!("{}{}", "*".repeat(n.saturating_sub(keep)), tail)
}
🧹 Nitpick comments (4)
crates/dojo/utils/src/provider.rs (3)

20-22: Ohayo, sensei — keep the friendly message but retain root-cause for debugging

Good privacy win removing provider Debug from user output. However, dropping the source error makes troubleshooting harder. Capture and trace-log the original error while keeping the sanitized message.

Apply this diff:

-        Err(_) => Err(anyhow::anyhow!(
-            "Unhealthy RPC provider. Please check your configuration and that the node is running."
-        )),
+        Err(e) => {
+            trace!(error = ?e, "Provider health check failed");
+            Err(anyhow::Error::new(e).context(
+                "Unhealthy RPC provider. Please check your configuration and that the node is running.",
+            ))
+        },

Add this import at the top of the file:

use anyhow::Context;

Please confirm the UI shows only the top-level message by default and reveals the chain under verbose/debug.


9-11: Drop unused Debug bound on P

std::fmt::Debug is no longer needed after removing provider details from the error. Loosen constraints.

-pub async fn health_check_provider<P: Provider + Sync + std::fmt::Debug + 'static>(
+pub async fn health_check_provider<P: Provider + Sync + 'static>(

11-11: Use anyhow::Result<()> shorthand

Minor ergonomics/readability tweak.

-) -> anyhow::Result<(), anyhow::Error> {
+) -> anyhow::Result<()> {
bin/sozo/src/utils.rs (1)

53-63: Consider documenting the ResourceDetails struct fields.

While this internal struct is straightforward, adding brief doc comments for each field would improve maintainability, especially for the selector field which stores a hex-formatted value.

+/// Internal struct for displaying resource details in table format
 #[derive(Tabled)]
 struct ResourceDetails {
+    /// Type of the resource (Contract, Model, Event, etc.)
     #[tabled(rename = "Resource Type")]
     resource_type: ResourceType,
+    /// Human-readable tag/identifier for the resource
     #[tabled(rename = "Tag")]
     tag: String,
+    /// Hex-formatted selector (0x prefixed)
     #[tabled(rename = "Selector")]
     selector: String,
+    /// Current sync status (Created, Updated, Synced)
     #[tabled(rename = "Status")]
     status: String,
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63574aa and 166a962.

📒 Files selected for processing (4)
  • bin/sozo/src/utils.rs (5 hunks)
  • crates/dojo/utils/src/provider.rs (1 hunks)
  • crates/dojo/utils/src/tx/mod.rs (1 hunks)
  • crates/sozo/ops/src/migrate/mod.rs (28 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
crates/sozo/ops/src/migrate/mod.rs (5)
crates/sozo/ui/src/lib.rs (2)
  • new (94-96)
  • trace (173-175)
crates/dojo/utils/src/tx/deployer.rs (1)
  • new (34-36)
crates/dojo/utils/src/tx/declarer.rs (2)
  • new (52-54)
  • declare (86-147)
crates/dojo/utils/src/tx/waiter.rs (1)
  • new (100-115)
crates/dojo/utils/src/tx/invoker.rs (1)
  • new (28-30)
bin/sozo/src/utils.rs (5)
crates/dojo/world/src/local/mod.rs (2)
  • deterministic_world_address (100-103)
  • new (83-97)
crates/dojo/utils/src/tx/deployer.rs (2)
  • is_deployed (107-122)
  • new (34-36)
bin/sozo/src/commands/options/world.rs (1)
  • address (33-44)
crates/dojo/world/src/diff/resource.rs (1)
  • status (133-141)
crates/sozo/ui/src/lib.rs (1)
  • new (94-96)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: fmt
🔇 Additional comments (8)
crates/dojo/utils/src/tx/mod.rs (1)

62-62: LGTM! Clone trait addition enables tuple return patterns.

Ohayo sensei! The addition of Clone to the TransactionResult enum's derive attributes is well-aligned with the cross-file changes mentioned in the AI summary. This enables the deploy_via_udc function to return a tuple (Felt, TransactionResult) while allowing the TransactionResult to be cloned when needed.

bin/sozo/src/utils.rs (3)

78-86: Well-structured warning message, ohayo sensei!

The warning message provides clear guidance about the address mismatch and actionable next steps. The 66-width hex formatting ensures consistent address display.


398-408: Clean resource table presentation!

The use of Table::new with Style::psql() provides a well-formatted output for resource details. Good choice for readability.


427-430: Good separation of concerns in world details display.

The function nicely delegates to specialized display functions for profile and world diff details, maintaining clean code organization.

crates/sozo/ops/src/migrate/mod.rs (4)

258-295: Excellent contract initialization tracking, sensei!

The init_details HashMap effectively captures initialization arguments for verbose logging, providing clear visibility into contract initialization parameters.


1185-1200: Ohayo! Great error handling for world deployment.

The code properly declares the world class before deployment and captures the transaction result with receipt. The error messaging clearly guides users when world address mismatches occur.


1287-1350: Clean UI helper functions for resource display!

The display_resources_to_sync function provides well-organized verbose output with proper indentation for different resource categories. The consistent structure makes it easy to understand what resources are being synced.


1361-1380: Well-structured permission display, sensei!

The display_permissions function clearly separates writer and owner permissions with proper indentation, making it easy to understand the permission changes being applied.

@glihm glihm 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.

Thank you @remybar for the great work here!

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/sozo/ops/src/migrate/mod.rs (2)

631-671: Fix deploy block-number capture in multicall path (can record wrong block).

Currently you take the first tx’s block for all deploys; deploy calls are appended last, and multicall may split into multiple txs. This can assign an incorrect block to some deployments and break registration.

Apply:

-        if self.do_multicall() {
-            ui.debug("Deploying resources with multicall");
-
-            invoker.extend_calls(deploy_calls.values().cloned().collect());
-
-            let txs_results = invoker.multicall().await?;
-
-            display_transaction_results(&ui, &txs_results, "Register");
-
-            // If some external contracts have been deployed, we need to
-            // get the block number of the multicall tx.
-            // Since the multicall may be split into multiple transactions, we take the block number
-            // of the first transaction.
-            if !deploy_calls.is_empty() {
-                // TODO: @remybar, wondering if here we should
-                // also handle the case when it contains the receipt
-                // already, due to the tx configuration.
-                if let TransactionResult::Hash(tx_hash) = txs_results[0] {
-                    let receipt =
-                        TransactionWaiter::new(tx_hash, &self.world.account.provider()).await?;
-                    let block_number = receipt.block.block_number();
-
-                    deploy_block_numbers =
-                        deploy_calls.keys().map(|name| (name.clone(), block_number)).collect();
-                }
-            }
-        } else {
+        if self.do_multicall() {
+            ui.debug("Register non-deploy resources with multicall");
+
+            // Run non-deploy calls via multicall first.
+            let txs_results = invoker.multicall().await?;
+            display_transaction_results(&ui, &txs_results, "Register");
+
+            // Deploy external contracts sequentially to capture accurate block numbers.
+            for (name, call) in deploy_calls {
+                let tx = invoker.invoke(call).await?;
+                display_transaction_results(&ui, &vec![tx.clone()], "Deploy external");
+                if let TransactionResult::Hash(tx_hash) = tx {
+                    let receipt =
+                        TransactionWaiter::new(tx_hash, &self.world.account.provider()).await?;
+                    let block_number = receipt.block.block_number();
+                    deploy_block_numbers.insert(name, block_number);
+                }
+            }
+        } else {
             let txs_results = invoker.invoke_all_sequentially().await?;
 
             display_transaction_results(&ui, &txs_results, "Register");

1009-1016: Avoid panics: replace unwrap() with proper error propagation.

ByteArray::from_string can fail; don’t panic inside ops library.

Apply:

-            let name = ByteArray::from_string(&library.common.name).unwrap();
-            let version = ByteArray::from_string(&library.version).unwrap();
+            let name = ByteArray::from_string(&library.common.name)?;
+            let version = ByteArray::from_string(&library.version)?;
♻️ Duplicate comments (3)
crates/sozo/ops/src/migrate/mod.rs (3)

174-183: Guard empty metadata callset before invoking multicall/sequential.

Avoid calling invoker when there are 0 calls; emit a friendly result and return early. This was flagged earlier; still applicable.

Apply:

-        if self.do_multicall() {
-            ui.step(format!("Upload {} metadata", invoker.calls.len()));
-            invoker.multicall().await.map_err(|e| anyhow!(e.to_string()))?;
-        } else {
-            ui.step(format!("Upload {} metadata (sequentially)", invoker.calls.len()));
-            invoker.invoke_all_sequentially().await.map_err(|e| anyhow!(e.to_string()))?;
-        }
-
-        ui.result("Metadata uploaded.");
+        if invoker.calls.is_empty() {
+            ui.result("No metadata changes detected.");
+            return Ok(());
+        } else if self.do_multicall() {
+            ui.step(format!("Upload {} metadata", invoker.calls.len()));
+            invoker.multicall().await.map_err(|e| anyhow!(e.to_string()))?;
+        } else {
+            ui.step(format!("Upload {} metadata (sequentially)", invoker.calls.len()));
+            invoker.invoke_all_sequentially().await.map_err(|e| anyhow!(e.to_string()))?;
+        }
+
+        ui.result("Metadata uploaded.");

693-702: Guard external‑contract registration on empty call list.

Skip multicall/sequential when n_external_contracts == 0; show a “no‑op” result. Previously flagged; still needed.

Apply:

-        if self.do_multicall() {
-            ui.step(format!("Register {} external contracts", n_external_contracts));
-            invoker.multicall().await?;
-        } else {
-            ui.step(format!("Register {} external contracts (sequentially)", n_external_contracts));
-            invoker.invoke_all_sequentially().await?;
-        }
+        if n_external_contracts == 0 {
+            ui.result("No external contracts to register.");
+        } else if self.do_multicall() {
+            ui.step(format!("Register {} external contracts", n_external_contracts));
+            invoker.multicall().await?;
+        } else {
+            ui.step(format!("Register {} external contracts (sequentially)", n_external_contracts));
+            invoker.invoke_all_sequentially().await?;
+        }
 
-        if n_external_contracts > 0 {
+        if n_external_contracts > 0 {
             ui.result("External contracts registered.");
         }

Also applies to: 711-713


410-426: Short‑circuit permissions sync when there are no changes.

Avoid invoking invoker with zero calls; print a concise message and return false.

Apply:

-        ui.step(format!("Sync {} permissions", invoker.calls.len()));
-        display_permissions(&ui, &writers_perms, &owners_perms);
-
-        let has_changed = !invoker.calls.is_empty();
+        ui.step(format!("Sync {} permissions", invoker.calls.len()));
+        display_permissions(&ui, &writers_perms, &owners_perms);
+
+        let has_changed = !invoker.calls.is_empty();
+        if !has_changed {
+            ui.result("No permission changes detected.");
+            return Ok(false);
+        }
 
-        if self.do_multicall() {
+        if self.do_multicall() {
             let txs_results = invoker.multicall().await?;
             display_transaction_results(&ui, &txs_results, "Sync permissions");
         } else {
🧹 Nitpick comments (3)
crates/sozo/ops/src/migrate/mod.rs (3)

1024-1025: Don’t panic on unsupported library updates; return a typed error.

Replace panic!("libraries cannot be updated!") with a MigrationError variant (e.g., UnsupportedOperation/InvalidUpdate) so callers can handle it gracefully.

I can wire a concrete error variant in error::MigrationError and update call sites if you want.


441-447: Early exit when there are no classes to declare.

Skip setting up declarers and printing steps when n_classes == 0.

Apply:

         let n_classes = classes.len();
 
-        let ui_text = format!("Declare {} classes", n_classes);
-        ui.step(ui_text);
+        if n_classes == 0 {
+            ui.result("No classes to declare.");
+            return Ok(());
+        }
+
+        ui.step(format!("Declare {} classes", n_classes));

1276-1289: Accept slices, not &Vec, in display_transaction_results.

More idiomatic and flexible API; existing call sites continue to work.

Apply:

-fn display_transaction_results(ui: &SozoUi, txs_results: &Vec<TransactionResult>, action: &str) {
+fn display_transaction_results(ui: &SozoUi, txs_results: &[TransactionResult], action: &str) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 166a962 and a839874.

📒 Files selected for processing (1)
  • crates/sozo/ops/src/migrate/mod.rs (28 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/sozo/ops/src/migrate/mod.rs (4)
crates/sozo/ui/src/lib.rs (2)
  • new (94-96)
  • trace (173-175)
crates/dojo/utils/src/tx/deployer.rs (1)
  • new (34-36)
crates/dojo/utils/src/tx/declarer.rs (2)
  • new (52-54)
  • declare (86-147)
crates/dojo/utils/src/tx/invoker.rs (1)
  • new (28-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: clippy
🔇 Additional comments (2)
crates/sozo/ops/src/migrate/mod.rs (2)

500-503: Nice touch: result only when there’s work.

Emitting “Classes declared.” only when n_classes > 0 is clean.


1169-1256: World deploy/upgrade UI and checks look solid.

Good UX flow, receipt gating, and address display. The mismatch error message is explicit.

@glihm
glihm merged commit 307a96a into main Sep 19, 2025
10 checks passed
@glihm
glihm deleted the feat-sozo_ui branch September 19, 2025 03:35
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.

[BUG] Sozo renders world address incorrectly

2 participants