Skip to content

feat(onboard): clean-slate rewrite — schema-driven, idempotent, DRY#5960

Merged
singlerider merged 56 commits into
zeroclaw-labs:masterfrom
singlerider:feat/onboard-rewrite
Apr 26, 2026
Merged

feat(onboard): clean-slate rewrite — schema-driven, idempotent, DRY#5960
singlerider merged 56 commits into
zeroclaw-labs:masterfrom
singlerider:feat/onboard-rewrite

Conversation

@singlerider
Copy link
Copy Markdown
Collaborator

@singlerider singlerider commented Apr 21, 2026

Summary

  • Base branch: master

  • What changed and why:

    • Replace the 8,534-line onboard/wizard.rs monolith + 3,898-line ratatui twin with a thin schema-driven orchestrator that drives config.set_prop everywhere, pre-populates from the loaded Config, and only writes changed values (idempotent). Closes the drift-class bugs catalogued in [Feature]: Clean-slate rewrite of zeroclaw onboard (DRY, schema-driven, idempotent) #5951 (Sonnet 4.6 gap, etc.).
    • OnboardUi trait in zeroclaw-config::traits (async, alongside ChannelConfig) — one prompt surface, three backends (dialoguer TermUi, headless QuickUi, ratatui RatatuiUi). No new workspace crates.
    • Refactor Provider trait to own its defaults: five default_* methods (default_temperature, default_max_tokens, default_timeout_secs, default_base_url, default_wire_api) with named BASELINE_* constants in zeroclaw-api::provider. Signatures change temperature: f64temperature: Option<f64> end-to-end; callers stop computing defaults, providers unwrap once against their family default. Per-family overrides declared as named consts (no magic numbers).
    • Six-region Ratatui layout (banner + breadcrumb + log + help + input + nav hints). Per-prompt-type hints always visible. Provider defaults prefilled into prompts (timeout-secs (default: 120) + pre-filled input so Enter accepts). Advanced-settings gate in Providers ("Configure advanced settings? [y/N]" default N) so most users breeze through auth + model + done.
    • Docstrings rewritten on every onboard-touched config field (ModelProviderConfig, WorkspaceConfig, HardwareConfig, MemoryConfig, TunnelConfig) — no more one-line restatements of the field name. The UI surfaces defaults separately, so docstrings no longer duplicate them.
    • CONTRIBUTING.md "How to Add a New Provider" section rewritten with correct import paths, the new trait surface, the DRY rule for default_* overrides, list_models guidance for onboard integration, and the right factory dispatch location.
  • Scope boundary (what this PR does NOT change):

  • Blast radius:

    • zeroclaw-api::Provider trait signature change cascades to every provider impl (18 files in zeroclaw-providers/) and every caller (agent loop, delegate tool, swarm, gateway, channels/orchestrator, memory consolidation, main.rs, live tests). All updated; workspace compiles with -D warnings.
    • zeroclaw onboard CLI: legacy --*-only flags still parse with deprecation warning; new positional subcommands (zeroclaw onboard providers, … channels, …) are the forward path.
    • No runtime behavior change for existing configured installs — defaults fall back to the same 0.7 / 4096 / 120 the callers used to compute inline.
  • Linked issue(s):

Validation Evidence (required)

cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo check --workspace --all-targets
  • Commands run and tail output:

    • cargo fmt --all -- --check → clean (exit 0).
    • cargo clippy --workspace --all-targets -- -D warningsFinished \dev` profile ... in 45.72s` with no warnings.
    • cargo check --workspace --all-targetsFinished \dev` profile ... in 17.24s`.
    • cargo test --workspace → all green, 0 failures across every crate.
  • Beyond CI — what did you manually verified:

    • Walked zeroclaw onboard in ratatui mode through Providers → Anthropic: auth-phase heading fires, model picker pulls from models.dev (Sonnet 4.6, Opus 4.7 present), advanced-settings gate defaults to N, accepting it writes providers.models.anthropic.* via set_prop.
    • Re-ran zeroclaw onboard on the configured install: section skip gates fire ("Providers is already configured. Reconfigure?") via onboard_state.completed_sections + per-section meaningful-config heuristic.
  • New test coverage landed on this branch (tied to the AC list in [Feature]: Clean-slate rewrite of zeroclaw onboard (DRY, schema-driven, idempotent) #5951):

    • onboard::tests (13 cases) — section_has_signal per-section correctness; skip_if_configured decision matrix under marker / signal / --force; mark_completed dedupe; full double-run idempotency for workspace, channels, and providers (asserts config.toml byte-identical across two runs).
    • src/main.rs::tests (4 cases) — resolve_onboard_target() covers the --*-only legacy flag deprecation shim: precedence of positional subcommand over legacy flag, per-flag target routing, and the warning pair emitted whenever a legacy flag is set.
    • Providers: fixed 4 pre-existing inert flatten_system_messages tests surfaced by the workspace test run.
    • One real UX bug caught: section_has_signal("channels") treated the default-true channels.cli bool as user-driven signal, which silently skipped the Channels section on every fresh install. Fixed to require a nested channels.<x>.<y> field.
  • Deferred follow-up tests: per-channel field-walk smoke tests (picking a specific channel and asserting its fields land via set_prop) — the current channels_done_selection test only exercises the "pick Done immediately" path.

Security & Privacy Impact (required)

  • New permissions, capabilities, or file system access scope? No.
  • New external network calls? No (models.dev fetch was already added; no new endpoints).
  • Secrets / tokens / credentials handling changed? Yesui.secret(has_current=true) now defaults to "keep current" instead of silently re-prompting, preventing accidental key wipes. --force is the explicit re-entry path.
  • PII, real identities, or personal data in diff, tests, fixtures, or docs? No.

Compatibility (required)

  • Backward compatible? Partially. Config format is 100% backward compatible — same TOML, same keys, no migration. The Provider trait signature (internal Rust surface) changed from f64Option<f64>, but this only matters for out-of-tree provider impls (none known). The zeroclaw onboard CLI gained positional subcommands; legacy --channels-only / --providers-only / etc. still parse for one release with a deprecation warning pointing at the new subcommand.
  • Config / env / CLI surface changed? Yes. CLI subcommand restructure is additive with one-release deprecation shim. Config schema gains [onboard_state] opaque meta-state (empty on fresh installs, auto-populated as sections complete).
  • Upgrade steps: git pull && cargo build --release. No action required from existing users.

Rollback (required for risk: medium and risk: high)

  • Fast rollback command/path: git revert <merge-sha>. No data migration; config file format unchanged, so reverting does not strand any user data. [onboard_state] would remain in config files that were updated during the new-binary run but is #[serde(default)]-tolerant of absence on the old binary.
  • Feature flags or config toggles: None. The old wizard.rs is deleted in the same PR; no runtime switch to fall back to.
  • Observable failure symptoms: zeroclaw onboard panics or set_prop errors in logs; cargo test -p zeroclaw-config -p zeroclaw-providers -p zeroclaw-runtime failing in CI.

Supersede Attribution

N/A.

i18n Follow-Through

N.A. — no user-facing documentation strings were added; rewritten docstrings are internal Rust /// comments surfaced only in the onboard UI (which is not i18n'd today) and rustdoc. CONTRIBUTING.md is English-only per repo convention.


Labels: please set risk: medium, size: xl, scope: core, scope: onboard, scope: provider.

@github-actions github-actions Bot added docs Auto scope: docs/markdown/template files changed. dependencies Auto scope: dependency manifest/lock/policy changed. core Auto scope: root src/*.rs files changed. config Auto scope: src/config/** changed. onboard Auto scope: src/onboard/** changed. provider Auto scope: src/providers/** changed. tests Auto scope: tests/** changed. labels Apr 21, 2026
@singlerider
Copy link
Copy Markdown
Collaborator Author

@WareWolf-MoonWall requesting a review. It's still in Draft form, but it's very testable and seems to work decently well end-to-end in both CLI and TUI mode (they use the same source of truth). Tests need to be written, but check out that beautiful red line diff.

@JordanTheJet
Copy link
Copy Markdown
Collaborator

PR review — agent build/compile verification

Comprehension summary. PR #5960 replaces the 8,534-line wizard.rs + 3,898-line ratatui twin with a thin schema-driven orchestrator (crates/zeroclaw-runtime/src/onboard/mod.rs, ~906 lines) that drives every mutation through Config::set_prop and pre-populates prompts from the loaded Config. It introduces an async OnboardUi trait (zeroclaw-config/src/traits.rs) with three backends (TermUi via dialoguer, QuickUi headless, RatatuiUi in zeroclaw-tui). It also refactors Provider so every impl owns its family defaults via five default_* methods, changing temperature: f64 → Option<f64> end-to-end (18 provider files + every caller in agent loop, delegate, swarm, gateway, channels, memory). Net diff: 63 files, +3,147 / −13,773. Blast radius: zeroclaw-api::Provider trait signature cascades through every provider + every caller; zeroclaw onboard CLI restructure with deprecated --*-only flags shim; zeroclaw models / zeroclaw doctor models explicitly removed (stubbed with bail! message).

Status note. PR is in draft and touches several high-risk paths per AGENTS.md (crates/zeroclaw-runtime/src/**, crates/zeroclaw-tools/src/**, crates/zeroclaw-gateway/src/**). The autonomous agent normally skips drafts and high-risk PRs. Proceeding this run with build/compile verification only, at user request — no merge readiness verdict.

Validation battery run locally on d114f2ea in isolated worktree:

  • cargo fmt --all -- --check → exit 0
  • cargo clippy --all-targets -- -D warnings → clean, no warnings (3m 16s)
  • cargo build → clean, 0 warnings (4m 11s)
  • cargo test (root package) → 820 passed / 0 failed / 7 ignored across 7 test binaries
  • cargo test --workspace4 failures in zeroclaw-providers — see R3 finding below; remaining 3,916+ tests pass across the other 12 test binaries.

CI on the PR is green (all 12 Quality Gate checks including Build x86_64-linux, aarch64-darwin, x86_64-windows, Test, Security, Benchmarks Compile).

Security & performance assessment.

  • Security: The secret(has_current=true) behavior at crates/zeroclaw-runtime/src/onboard/ui/term.rs:49-67 defaults to keep current instead of silently re-prompting — a strict improvement against accidental key wipes. --force is the explicit re-entry path. No new network calls, no new permissions, no new capabilities. Access-control surface unchanged.
  • Performance: Net removal of ~10,626 lines of Rust from the binary. No new runtime deps in the core path (models.dev fetch uses existing reqwest). ratatui + crossterm already present. Architecturally aligned with the "thin is sacred" constraint.

Findings

[question] R3 — 4 pre-existing test failures surfaced. cargo test --workspace fails on four tests in crates/zeroclaw-providers/src/compatible.rs (lines 3134, 3155, 3748, 3770):

compatible::tests::flatten_system_messages_merges_into_first_user --- FAILED
compatible::tests::flatten_system_messages_inserts_user_when_missing --- FAILED
compatible::tests::flatten_system_messages_merges_into_first_user_and_removes_system_roles --- FAILED
compatible::tests::flatten_system_messages_inserts_synthetic_user_when_no_user_exists --- FAILED
test result: FAILED. 765 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out

All four call OpenAiCompatibleProvider::flatten_system_messages(&input, false) and assert flattening, but the function at compatible.rs:302-305 returns messages.to_vec() unchanged when merge=false. Root cause: pre-existing on mastergit diff origin/master..HEAD -- crates/zeroclaw-providers/src/compatible.rs shows this PR only changes temperature: f64 → Option<f64> signatures; it does not touch flatten_system_messages or its tests. The function gained the merge parameter in #5762 (commit 9ca90b1b, already on master), and the tests don't match. This is not a PR-caused regression but the PR body explicitly declared "Intentionally skipped: cargo test" and running it would have surfaced this. Flagging for two separate follow-ups: (a) fix or delete the broken flatten_system_messages_* tests on master, independent of this PR; (b) future PRs should run cargo test --workspace even when the scope seems localized.

[suggestion] Model-id fallback hints are one release behind. model_id_hint at crates/zeroclaw-runtime/src/onboard/mod.rs:632-647 returns \"claude-opus-4-6\" for anthropic and \"anthropic/claude-opus-4.6\" for openrouter. Opus 4.7 is the current flagship; Sonnet 4.6 is the current recommended default for routine workloads. These hints only surface when the live models.dev fetch fails, so it's not critical — but it's the single string a user copy-pastes in that failure mode.

[suggestion] Deprecation error messages should name the replacement. src/main.rs:1635-1640 (zeroclaw models) and src/main.rs:1684-1688 (zeroclaw doctor models) bail with "temporarily disabled during the onboard rewrite — see #5951" but don't redirect. Suggest: "Use zeroclaw onboard providers for the live model picker, or check providers.models.<name>.model in your config." Muscle-memory users hitting the old command will otherwise page into the issue tracker.

[suggestion] Legacy --*-only deprecation shim: five-probe stress not requested, but noting the surface. The shim at src/main.rs:1039-1071 prints warning: --channels-only is deprecated; use zeroclaw onboard channels instead to stderr and still runs the correct section. Happy path verified via onboard_cli_quick_and_channels_only_conflict + onboard_cli_positional_sections_parse. The deprecation handler is a pre-clap flag check so it fires before clap validates; the five-probe matrix (bare, missing-positional, unknown-flag, …) is N/A because these are boolean flags with no positional args — straightforward.

[suggestion] Missing labels. PR body requests risk: medium, size: xl, scope: core, scope: onboard, scope: provider. Currently has auto-scope labels only (docs, dependencies, core, config, onboard, provider, tests). Maintainer should apply risk: medium and size: xl before merge for queue-hygiene and reviewer-routing per reviewer-playbook.md §2.

[question] CLI functionality removal vs. "temporarily disabled". zeroclaw models + zeroclaw doctor models subcommand implementations (234 lines of run_models + helpers) are deleted from crates/zeroclaw-runtime/src/doctor/mod.rs, not feature-flagged. If a user has a script hitting zeroclaw models, it will error out on upgrade. PR body says this is intentional and tracked in #5951 — maintainer should confirm the v0.7.4 changelog flags this as a breaking user-facing change.


Architectural observations

  • Removes ~10.6k lines net; adds no new runtime deps in core. Strictly aligned with the "thin is sacred" constraint.
  • Trait-driven pluggability preserved. OnboardUi is a clean extension point for future backends (gateway-WebSocket browser onboarder mentioned in crates/zeroclaw-config/src/traits.rs:142-154 rationale).
  • Provider default-ownership refactor is a DRY win. Named BASELINE_* constants at crates/zeroclaw-api/src/provider.rs:298-316 give every provider a typed fallback without hardcoded magic numbers at every call site. Anthropic overrides to 1.0 (anthropic.rs:14), Ollama overrides to its own constant (ollama.rs:637). Matches CONTRIBUTING.md guidance to only override when the family genuinely differs.
  • Section-level skip gate via onboard_state.completed_sections (crates/zeroclaw-config/src/schema.rs:477-483) — opaque meta-state, #[serde(default)]-tolerant, safe across rollback.
  • Zero unwrap() / expect() in the new onboard/* modules (grep clean).

Verdict — Needs deeper maintainer review

This PR builds and passes every lint/build gate cleanly on this reviewer's hardware. No PR-caused test failures; the four provider test failures are pre-existing on master. Architecture is sound and consistent with the project's stated constraints. However:

  1. PR is draft. Autonomous approval is not appropriate for a draft.
  2. Touches multiple high-risk paths (crates/zeroclaw-runtime/**, crates/zeroclaw-tools/**, crates/zeroclaw-gateway/**) per AGENTS.md "Risk Tiers."
  3. User-facing CLI removals (zeroclaw models, zeroclaw doctor models) need maintainer sign-off on the deprecation plan before the next release.
  4. Pre-existing flatten_system_messages_* test breakage on master is a separate concern that should be fixed or deleted — unrelated to this PR but surfaced by running its validation battery in full.

@JordanTheJet — requesting maintainer review for merge readiness, label assignment (risk: medium, size: xl), and the v0.7.4 breaking-change call on the removed models subcommands.

Thanks @singlerider for the thorough PR body, validation evidence, and scoped-out rollback discipline.


Generated by the ZeroClaw PR review agent. Build + test evidence captured locally at /tmp/pr-5960-{clippy,build,test,test-ws}.log.

@singlerider
Copy link
Copy Markdown
Collaborator Author

singlerider commented Apr 21, 2026

The CLI and TUI both have identical UX and sources of truth.

TUI

Detects and honors previous configurations

image

Fetches available models, if failed, allows user to enter themselves; nothing hard-coded

image

Allows configuring "advanced" settings, allows for going back in nav with Esc

image

Better help text for users to understand every single setting, auto-populate defaults

image

CLI

Recognizes previous configs

image

Allows for onboard-ing specific sections

image

Allows overwriting previously-set values

image

Help text and default values

image

JordanTheJet
JordanTheJet previously approved these changes Apr 21, 2026
Copy link
Copy Markdown
Collaborator

@JordanTheJet JordanTheJet left a comment

Choose a reason for hiding this comment

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

Review — feat(onboard): clean-slate rewrite

Net −10,626 lines, trait-driven, schema-fed, idempotent. This is the cleanest way out of the wizard-drift mess #5951 catalogued, and the execution lives up to the PR body.

Reviewed at HEAD 03b29ace. I read the full diff, cross-checked the new orchestrator against the local worktree, and ran validation on a fresh PR checkout. The entire protocol battery passes:

  • cargo fmt --all -- --check → clean
  • cargo clippy --workspace --all-targets -- -D warnings → 0 warnings
  • cargo test -p zeroclaw-runtime --lib onboard:: → 13/13 passed
  • cargo test -p zeroclaw-providers --lib flatten_system_messages → 4/4 passed (the R3 finding from JordanTheJet's earlier agent pass is now fully RESOLVED ✅ via commit 5eccff3e)

What's great

The orchestrator is genuinely thin. crates/zeroclaw-runtime/src/onboard/mod.rs is 1,203 lines (including 250+ lines of tests) replacing an 8,534-line monolith, and every mutation funnels through persist(cfg, path, value)cfg.set_prop(path, value)cfg.save().await. No direct struct-field assignment anywhere in the flow. That's the DRY invariant the #5951 ADR demanded, and it's actually enforced by construction — not by convention.

Nav / SkipNav state machine is the right abstraction. Two tiny enums propagate Esc upward — local rewind inside a section, section-level rewind inside run_all, program exit from the first prompt. Clean, trivially testable, and it eliminates the ad-hoc "go back" callback noise the old wizard accrued.

Persist-after-every-write is the correct UX call. onboard/mod.rs:126-134 — Ctrl+C mid-flow leaves prior answers on disk and re-running onboard picks up where the user bailed. The idempotency tests (providers_second_run_no_flags_is_idempotent_on_disk, channels_done_selection_is_idempotent_on_disk, workspace_double_run_is_idempotent_on_disk) assert byte-identical config.toml across two runs, which is the strongest guarantee you can give against the drift class.

BASELINE_* constants plus per-family default_*() trait overrides. crates/zeroclaw-api/src/provider.rs:298-362 gives every caller a typed, named fallback without magic numbers at the call site. The rule in CONTRIBUTING.md ("override ONLY where your family differs from baselines — DRY violation otherwise") is exactly the right norm to lay down in the same PR that establishes the shape.

OnboardUi trait placement. Living in zeroclaw-config::traits next to ChannelConfig respects the existing dependency graph (channels/runtime/tui already depend on zeroclaw-config) and means no new crate. Async-by-default is the right forward bet given the gateway/WebSocket backend reasoning in crates/zeroclaw-config/src/traits.rs:142-157.

Skip-gate design. onboard/mod.rs:321-377 — OR of marker AND signal, with --force bypass. The section_has_signal switch correctly handles the per-section nuances: memory/tunnel are marker-only because their defaults (sqlite/none) are valid user choices indistinguishable from untouched state, and the channels.cli: bool false-positive is explicitly documented and guarded by "require a nested channels.<x>.<y>" at onboard/mod.rs:362-370. The code comment names why the naïve starts_with("channels.") doesn't work — exactly the kind of reasoning that'll save the next contributor.

secret(has_current=true) defaults to keep-current. onboard/ui/term.rs:55-65 and the idempotency contract in traits.rs:172-185. Strict improvement against accidental key wipe on reconfigure; --force is the explicit re-entry. Worth the PR by itself.

Zero unwrap() / expect() in production onboard/* code. Verified by grep — every match is inside #[cfg(test)].

Docstring rewrite discipline. The /// comments on ModelProviderConfig, WorkspaceConfig, HardwareConfig, MemoryConfig, TunnelConfig fields no longer restate the field name. Defaults are surfaced separately in the prompt label ("timeout-secs (default: 120)"), so the docstring says why the field exists not what it is. That's the contribution-culture norm (FND-005 §"specificity > generic") applied to config docs.

Findings

🔵 TermUi::editor comment and return disagree. crates/zeroclaw-runtime/src/onboard/ui/term.rs:104-108:

// Editor close-without-save returns None — treat as Back so users
// who bail out of $EDITOR can rewind instead of accepting the
// unchanged buffer silently.
match Editor::new().edit(&buffer)? {
    Some(edited) => Ok(Answer::Value(edited)),
    None => Ok(Answer::Value(fallback)),  // ← returns Value, not Back
}

The comment promises Answer::Back; the code returns Answer::Value(fallback) — exactly the "accept unchanged buffer silently" case the comment says it's preventing. Zero behavioral impact today because OnboardUi::editor has no callers in the current diff (ui.editor( grep is empty), so this is a "fix the comment or the code before the first caller lands" marker, not a block.

🟡 CLI deprecation surface is one release of muscle memory. The --*-only flag shim at src/main.rs:1089-1121 prints a clean warning: --<flag>-only is deprecated; use zeroclaw onboard <section> instead to stderr and still routes to the right section. The zeroclaw models bail now names the replacement ("use zeroclaw onboard providers for the live model picker, or check providers.models.<name>.model in your config.toml") — that's the R1 from JordanTheJet's earlier pass, RESOLVED ✅. Flagging only that the v0.7.4 release notes should call this out under CLI changes so users grep-updating scripts have a single place to discover the rename.

🟡 Provider trait signature break cascades are fully contained in-tree but change the Beta contract. temperature: f64 → Option<f64> affects every Provider impl in crates/zeroclaw-providers/src/**, every caller (agent loop, delegate, swarm, gateway, channels orchestrator, memory consolidation, live tests), and benches/agent_benchmarks.rs. All updated; workspace builds with -D warnings. For in-tree callers this is a non-event. Out-of-tree Provider implementors (none known to me) would need to migrate — and because zeroclaw-providers is Beta per AGENTS.md §Stability Tiers, "breaking changes permitted in MINOR with changelog notes" applies. Worth a line in the v0.7.4 changelog explicitly naming the trait break.

🔵 Minor — prompt_field label decoration. onboard/mod.rs:207-212 builds "<short> (default: <d>)" for the unset+has-default case but the "Default: <d>. Press Enter to accept." tail in the help text is redundant once the label carries the default inline. Cosmetic — users will understand either way, and the help text is only shown when ui.note(_) is called. Leave as-is unless the ratatui layout starts to feel cluttered; this is preference.

Architectural callouts

  • Ratatui backend is drawing-only. crates/zeroclaw-tui/src/onboarding.rs implements OnboardUi with a six-region layout (banner, breadcrumb, log, help, input, hints) and every flow decision lives above the trait. That's the separation the ADR demanded, and it makes a future gateway-WebSocket OnboardUi drop-in — which is the stated design goal for the async signature.
  • Channels/tunnel section picker derives from schema via init_defaults probe. onboard/mod.rs:710-722 — feature-gated channels drop out of the picker automatically because they never appear in prop_fields() after the probe. No hard-coded channel list to drift from reality.
  • doctor models was restored in commit ecd71d16. The earlier concern about the run_models deletion is now moot; Commands::Doctor { doctor_command: Some(DoctorCommands::Models { … }) } still routes to doctor::run_models(&config, provider.as_deref(), use_cache) on the PR head.

Scope / risk

  • 13 tests directly exercise acceptance criteria from #5951 (section_has_signal_*, skip_gate_*, mark_completed_is_dedupe_safe, double-run idempotency across workspace/channels/providers, flags-driven providers path).
  • Config file format is 100% backward compatible — same TOML, same keys, no migration. [onboard_state] is #[serde(default)], so rolling back to the old binary on a config written by the new binary is safe.
  • No new network calls, no new permissions, no new FS scope. models.dev fetch was already in-tree pre-PR.

Verdict — APPROVE ✅

This is the scorched-earth rewrite #5951 asked for, executed cleanly. Architecture matches the ADR, validation is green across fmt / clippy / the targeted test slices, idempotency is proven in tests (not just asserted in prose), and all four open findings from JordanTheJet's earlier agent pass are resolved on HEAD. The editor comment/code mismatch is the only real nit, and it's in unused code.

Per AGENTS.md §Risk Tiers this touches crates/zeroclaw-runtime/src/**, crates/zeroclaw-tools/src/**, and crates/zeroclaw-gateway/src/** — the high-risk triad — but the change is surgical at each boundary (temperature arg widened, onboard entry points swapped, doctor::run_models rewired), not a policy shift. Labels in the PR body request risk: medium, size: xl, scope: core/onboard/provider — please apply those before merge for queue hygiene.

Thanks for the PR body discipline, @singlerider — the linked-issue map, the explicit scope boundary, the acceptance-criteria test list, and the screenshot pair made review genuinely faster. This is the shape good high-risk PRs take.

— WareWolf-MoonWall

JordanTheJet
JordanTheJet previously approved these changes Apr 21, 2026
Copy link
Copy Markdown
Collaborator

@JordanTheJet JordanTheJet left a comment

Choose a reason for hiding this comment

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

Review — feat(onboard): clean-slate rewrite

Net −10,626 lines, trait-driven, schema-fed, idempotent. This is the cleanest way out of the wizard-drift mess #5951 catalogued, and the execution lives up to the PR body.

Reviewed at HEAD 03b29ace. I read the full diff, cross-checked the new orchestrator against the local worktree, and ran validation on a fresh PR checkout. The entire protocol battery passes:

  • cargo fmt --all -- --check → clean
  • cargo clippy --workspace --all-targets -- -D warnings → 0 warnings
  • cargo test -p zeroclaw-runtime --lib onboard:: → 13/13 passed
  • cargo test -p zeroclaw-providers --lib flatten_system_messages → 4/4 passed (the R3 finding from JordanTheJet's earlier agent pass is now fully RESOLVED ✅ via commit 5eccff3e)

What's great

The orchestrator is genuinely thin. crates/zeroclaw-runtime/src/onboard/mod.rs is 1,203 lines (including 250+ lines of tests) replacing an 8,534-line monolith, and every mutation funnels through persist(cfg, path, value)cfg.set_prop(path, value)cfg.save().await. No direct struct-field assignment anywhere in the flow. That's the DRY invariant the #5951 ADR demanded, and it's actually enforced by construction — not by convention.

Nav / SkipNav state machine is the right abstraction. Two tiny enums propagate Esc upward — local rewind inside a section, section-level rewind inside run_all, program exit from the first prompt. Clean, trivially testable, and it eliminates the ad-hoc "go back" callback noise the old wizard accrued.

Persist-after-every-write is the correct UX call. onboard/mod.rs:126-134 — Ctrl+C mid-flow leaves prior answers on disk and re-running onboard picks up where the user bailed. The idempotency tests (providers_second_run_no_flags_is_idempotent_on_disk, channels_done_selection_is_idempotent_on_disk, workspace_double_run_is_idempotent_on_disk) assert byte-identical config.toml across two runs, which is the strongest guarantee you can give against the drift class.

BASELINE_* constants plus per-family default_*() trait overrides. crates/zeroclaw-api/src/provider.rs:298-362 gives every caller a typed, named fallback without magic numbers at the call site. The rule in CONTRIBUTING.md ("override ONLY where your family differs from baselines — DRY violation otherwise") is exactly the right norm to lay down in the same PR that establishes the shape.

OnboardUi trait placement. Living in zeroclaw-config::traits next to ChannelConfig respects the existing dependency graph (channels/runtime/tui already depend on zeroclaw-config) and means no new crate. Async-by-default is the right forward bet given the gateway/WebSocket backend reasoning in crates/zeroclaw-config/src/traits.rs:142-157.

Skip-gate design. onboard/mod.rs:321-377 — OR of marker AND signal, with --force bypass. The section_has_signal switch correctly handles the per-section nuances: memory/tunnel are marker-only because their defaults (sqlite/none) are valid user choices indistinguishable from untouched state, and the channels.cli: bool false-positive is explicitly documented and guarded by "require a nested channels.<x>.<y>" at onboard/mod.rs:362-370. The code comment names why the naïve starts_with("channels.") doesn't work — exactly the kind of reasoning that'll save the next contributor.

secret(has_current=true) defaults to keep-current. onboard/ui/term.rs:55-65 and the idempotency contract in traits.rs:172-185. Strict improvement against accidental key wipe on reconfigure; --force is the explicit re-entry. Worth the PR by itself.

Zero unwrap() / expect() in production onboard/* code. Verified by grep — every match is inside #[cfg(test)].

Docstring rewrite discipline. The /// comments on ModelProviderConfig, WorkspaceConfig, HardwareConfig, MemoryConfig, TunnelConfig fields no longer restate the field name. Defaults are surfaced separately in the prompt label ("timeout-secs (default: 120)"), so the docstring says why the field exists not what it is. That's the contribution-culture norm (FND-005 §"specificity > generic") applied to config docs.

Findings

🔵 TermUi::editor comment and return disagree. crates/zeroclaw-runtime/src/onboard/ui/term.rs:104-108:

// Editor close-without-save returns None — treat as Back so users
// who bail out of $EDITOR can rewind instead of accepting the
// unchanged buffer silently.
match Editor::new().edit(&buffer)? {
    Some(edited) => Ok(Answer::Value(edited)),
    None => Ok(Answer::Value(fallback)),  // ← returns Value, not Back
}

The comment promises Answer::Back; the code returns Answer::Value(fallback) — exactly the "accept unchanged buffer silently" case the comment says it's preventing. Zero behavioral impact today because OnboardUi::editor has no callers in the current diff (ui.editor( grep is empty), so this is a "fix the comment or the code before the first caller lands" marker, not a block.

🟡 CLI deprecation surface is one release of muscle memory. The --*-only flag shim at src/main.rs:1089-1121 prints a clean warning: --<flag>-only is deprecated; use zeroclaw onboard <section> instead to stderr and still routes to the right section. The zeroclaw models bail now names the replacement ("use zeroclaw onboard providers for the live model picker, or check providers.models.<name>.model in your config.toml") — that's the R1 from JordanTheJet's earlier pass, RESOLVED ✅. Flagging only that the v0.7.4 release notes should call this out under CLI changes so users grep-updating scripts have a single place to discover the rename.

🟡 Provider trait signature break cascades are fully contained in-tree but change the Beta contract. temperature: f64 → Option<f64> affects every Provider impl in crates/zeroclaw-providers/src/**, every caller (agent loop, delegate, swarm, gateway, channels orchestrator, memory consolidation, live tests), and benches/agent_benchmarks.rs. All updated; workspace builds with -D warnings. For in-tree callers this is a non-event. Out-of-tree Provider implementors (none known to me) would need to migrate — and because zeroclaw-providers is Beta per AGENTS.md §Stability Tiers, "breaking changes permitted in MINOR with changelog notes" applies. Worth a line in the v0.7.4 changelog explicitly naming the trait break.

🔵 Minor — prompt_field label decoration. onboard/mod.rs:207-212 builds "<short> (default: <d>)" for the unset+has-default case but the "Default: <d>. Press Enter to accept." tail in the help text is redundant once the label carries the default inline. Cosmetic — users will understand either way, and the help text is only shown when ui.note(_) is called. Leave as-is unless the ratatui layout starts to feel cluttered; this is preference.

Architectural callouts

  • Ratatui backend is drawing-only. crates/zeroclaw-tui/src/onboarding.rs implements OnboardUi with a six-region layout (banner, breadcrumb, log, help, input, hints) and every flow decision lives above the trait. That's the separation the ADR demanded, and it makes a future gateway-WebSocket OnboardUi drop-in — which is the stated design goal for the async signature.
  • Channels/tunnel section picker derives from schema via init_defaults probe. onboard/mod.rs:710-722 — feature-gated channels drop out of the picker automatically because they never appear in prop_fields() after the probe. No hard-coded channel list to drift from reality.
  • doctor models was restored in commit ecd71d16. The earlier concern about the run_models deletion is now moot; Commands::Doctor { doctor_command: Some(DoctorCommands::Models { … }) } still routes to doctor::run_models(&config, provider.as_deref(), use_cache) on the PR head.

Scope / risk

  • 13 tests directly exercise acceptance criteria from #5951 (section_has_signal_*, skip_gate_*, mark_completed_is_dedupe_safe, double-run idempotency across workspace/channels/providers, flags-driven providers path).
  • Config file format is 100% backward compatible — same TOML, same keys, no migration. [onboard_state] is #[serde(default)], so rolling back to the old binary on a config written by the new binary is safe.
  • No new network calls, no new permissions, no new FS scope. models.dev fetch was already in-tree pre-PR.

Verdict — APPROVE ✅

This is the scorched-earth rewrite #5951 asked for, executed cleanly. Architecture matches the ADR, validation is green across fmt / clippy / the targeted test slices, idempotency is proven in tests (not just asserted in prose), and all four open findings from JordanTheJet's earlier agent pass are resolved on HEAD. The editor comment/code mismatch is the only real nit, and it's in unused code.

Per AGENTS.md §Risk Tiers this touches crates/zeroclaw-runtime/src/**, crates/zeroclaw-tools/src/**, and crates/zeroclaw-gateway/src/** — the high-risk triad — but the change is surgical at each boundary (temperature arg widened, onboard entry points swapped, doctor::run_models rewired), not a policy shift. Labels in the PR body request risk: medium, size: xl, scope: core/onboard/provider — please apply those before merge for queue hygiene.

Thanks for the PR body discipline, @singlerider — the linked-issue map, the explicit scope boundary, the acceptance-criteria test list, and the screenshot pair made review genuinely faster. This is the shape good high-risk PRs take.

— WareWolf-MoonWall

@JordanTheJet JordanTheJet dismissed stale reviews from themself April 21, 2026 16:16

Dismissing — posted under wrong voice (WareWolf-MoonWall signature, but authored by JordanTheJet). Re-posting with correct attribution.

Copy link
Copy Markdown
Collaborator

@WareWolf-MoonWall WareWolf-MoonWall left a comment

Choose a reason for hiding this comment

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

Review — feat(onboard): clean-slate rewrite

Reviewed at HEAD 03b29ace. Full diff read, cross-checked against local source. Jordan's commendations are well-earned and I won't echo them in full — I'll name the ones that especially stand out, then get to my findings.


What landed well

The thin-dispatcher principle is enforced by construction. Every mutation routes through persist(cfg, path, value)cfg.set_prop()cfg.save(). No direct struct-field assignment anywhere in the flow. The DRY invariant #5951 demanded isn't just documented — it's architecturally enforced.

Nav / SkipNav as the navigation contract. Two tiny enums carry the Esc signal upward cleanly — no callbacks, no shared state, no implicit returns. The result is a flow graph you can reason about on a whiteboard. This abstraction is what makes 1,203 lines feel manageable.

Idempotency proven in tests, not prose. providers_second_run_no_flags_is_idempotent_on_disk, channels_done_selection_is_idempotent_on_disk, workspace_double_run_is_idempotent_on_disk all assert byte-identical config.toml across two runs. That's the gold standard for this class of guarantee — property verified at the actual storage layer, not just asserted in the PR body.

BASELINE_* constants and per-family default_*(). Named constants at crates/zeroclaw-api/src/provider.rs:298-316, each family overrides only where it genuinely differs (Anthropic → 1.0, Ollama → deterministic), and CONTRIBUTING.md now names the rule. No more magic numbers scattered through every caller.

secret(has_current=true) defaults to keep-current. Silently wiping an API key on reconfigure was a real pain point. This is a strict UX improvement that stands alone — it ships as a side effect of a much larger change and that's fine.

Zero unwrap()/expect() in production onboard/* code. Verified by grep. Every match is inside #[cfg(test)]. That's the discipline level this code needed.


Findings

🔴 TermUi::editor comment and return disagree — fix required.

crates/zeroclaw-runtime/src/onboard/ui/term.rs lines 103–110:

// Editor close-without-save returns None — treat as Back so users
// who bail out of $EDITOR can rewind instead of accepting the
// unchanged buffer silently.
match Editor::new().edit(&buffer)? {
    Some(edited) => Ok(Answer::Value(edited)),
    None => Ok(Answer::Value(fallback)),   // ← returns Value, not Back
}

The comment says "treat as Back." The code returns Answer::Value(fallback) — precisely the "accept unchanged buffer silently" case the comment explicitly says it's preventing.

Jordan rated this 🔵 on the grounds that ui.editor( has no callers in the current diff. That is true today. But a comment that promises the opposite of what the code delivers is a trap for the first contributor who calls this method. The Answer::Back contract is the backbone of the entire navigation model — every section function builds its flow graph around it. Having one method lie about whether it honors that contract is exactly the kind of subtle breakage that causes hard-to-trace UX regressions when the first caller lands, and the "editor" method is the most likely place for a caller to appear next (StringArray fields, system-prompt edit flows).

The fix is a single line:

None => Ok(Answer::Back),

I want that in before this merges, not on a follow-up.


🟡 Per-channel smoke tests are an open acceptance criterion from #5951 — open a tracking ticket.

AC #6 from issue #5951: "Per-channel onboard smoke tests with a scripted mock OnboardUi green." The PR body is honest about this: "Deferred follow-up tests: per-channel field-walk smoke tests (picking a specific channel and asserting its fields land via set_prop) — the current channels_done_selection test only exercises the 'pick Done immediately' path."

"Deferred" is fine. But it needs a ticket, not just a sentence in PR prose. Please open a follow-up issue before merge — a one-liner referencing this PR and naming the missing paths is enough. Without a tracking issue this gap disappears into history and the next contributor has no way to know the channel section wasn't fully tested.


🔵 Milestone required — maintainer action.

Both active milestone scope boundaries explicitly exclude zeroclaw-runtime changes:

  • v0.7.4: No changes to zeroclaw-runtime, zeroclaw-gateway, zeroclaw-tools, or security paths.
  • v0.7.5: Pipeline and tooling only. No new feature work. No runtime changes.

This PR touches crates/zeroclaw-runtime/src/** throughout. It cannot land before v0.7.5 ships. A post-v0.7.5 milestone (v0.7.6, or whatever the team designates for the next runtime release) needs to be set before merge.


🔵 Risk label — make a conscious call (maintainer action).

Per AGENTS.md §Risk Tiers, crates/zeroclaw-runtime/src/** is classified high-risk. The PR body requests risk: medium, which Jordan endorsed, and the rationale is reasonable: the onboard path is not src/security/ (the "especially" sub-tier), and the security surface of this PR is actually narrowed — no new permissions, no new network calls, and secret behavior is strictly safer. A conscious team decision either way is fine. What isn't fine is leaving the PR unlabeled — please apply risk: medium or risk: high explicitly alongside size: xl before merge.


🔵 zeroclaw models removal — add a changelog line.

The bail! in Commands::Models { model_command: _ } is the right move and the message already names the replacement (zeroclaw onboard providers + config.toml path). This is a user-visible breaking change and should appear in CHANGELOG-next.md under a "CLI changes" heading so users and tool authors can find it during upgrades.


🔵 Team decision check-in from #5951 — one item needs a response.

My comment on #5951 raised three 🔵 team decisions as prerequisites before implementation:

  • OnboardUi placement: landed in zeroclaw-config::traits alongside ChannelConfig — resolved.
  • Strangler Fig single-PR approach: Jordan's approval and the PR proceeding to HEAD constitute the team's implicit sign-off on the non-incremental approach — resolved.
  • ⚠️ jokemanfire's #5940 overlap: I asked singlerider to respond to jokemanfire in the #5951 thread before #5940 gets more work. Please confirm that happened or close #5940 with a note explaining the supersede. jokemanfire put real effort into that PR; they deserve a direct response.

Scope and compatibility

The Provider trait signature change (temperature: f64 → Option<f64>) cascades through 18 provider files, every caller in the agent loop, delegate, swarm, gateway, channels orchestrator, memory consolidation, benchmarks, and live tests. All updated; workspace builds clean with -D warnings. For in-tree callers this is a non-event. Out-of-tree Provider implementors (none known) would need to migrate — zeroclaw-providers is Beta tier, so "breaking changes permitted in MINOR with changelog notes" applies. Worth a named entry in the v0.7.4 changelog alongside the CLI change above.

Config file format: 100% backward compatible. Same TOML, same keys, no migration. [onboard_state] is #[serde(default)] — a config written by the new binary is safe to read by the old binary.


Verdict

One 🔴 to resolve (single-line editor fix), one deferred-test ticket to open (channel smoke tests), two maintainer actions (milestone assignment, explicit risk label), one thread check-in (jokemanfire/#5940), and one changelog entry. The architecture is sound, the ADR acceptance criteria are covered in tests, and the provider default-ownership refactor is a genuine quality improvement that will pay dividends for every provider added going forward. After the editor fix this is ready for maintainer sign-off and a milestone.

— WareWolf-MoonWall

@WareWolf-MoonWall WareWolf-MoonWall added the risk: medium Auto risk: src/** or dependency/config changes. label Apr 22, 2026
@JordanTheJet
Copy link
Copy Markdown
Collaborator

Hands-on testing notes

Built the branch and walked through the onboard flow a few times on my machine — both CLI (dialoguer) and TUI (ratatui) backends. Posting raw testing impressions separately from my formal review so @singlerider has the UX-side signal alongside the code-side one.

What feels good

The TUI is the polished story. It's genuinely beautiful — the six-region layout anchors the input at the bottom while the rolling log keeps context, and navigation feels intuitive without needing to read docs. This is the onboarding I'd point a new user at.

Every entry point I tried works exactly as advertised.

  • zeroclaw onboard → CLI from beginning ✅
  • zeroclaw onboard --tui → TUI from beginning ✅
  • zeroclaw onboard channels → CLI jumps to channels ✅
  • zeroclaw onboard --tui channels → TUI jumps to channels ✅

Gateway comes up clean. No onboarding side-effects on the HTTP surface.

Issues I hit

🔴 Daemon won't stay quiet — Mochat poll loop spins on a builder error. After zeroclaw onboard + zeroclaw daemon, the daemon loop repeats this line forever, roughly every few seconds:

WARN zeroclaw_channels::mochat: Mochat: poll request error: builder error

The daemon itself keeps running (gateway responds), but the log is unusable and something downstream of reqwest::ClientBuilder or URL parsing is throwing every tick. I don't know yet if this PR caused it (mochat channel code isn't in the diff) or if the new onboard flow is writing a mochat default that trips the builder. Either way, worth surfacing before merge — right now a fresh onboard → daemon produces a broken-looking console out of the box, and that's the first-run experience for anyone trying this branch.

If it's pre-existing on master, happy to file a separate issue — just want to make sure it's not getting missed because the CI gate and the unit tests don't exercise the onboard → daemon handoff.

🟡 Live model fetch errors on some providers — fallback works but the UX is rough. Minimax (I think — going from memory, may have been a couple others too) fails the models.dev catalog fetch during onboard providers, and the CLI drops into manual model-id entry. The manual path works fine, but the error message doesn't hint at why the fetch failed (region routing? provider not in the catalog? network?), so users are left guessing whether to retry or just type something in. Might be worth a one-line "Catalog lookup failed for <provider> — typing a model id manually" with the underlying reqwest error elided into a tracing::debug.

🟡 CLI advanced-settings walk is really long. I went through onboard providers → advanced gate = Y once, and it dragged. The per-field docstrings are good (genuinely helpful text), but there are a lot of fields and many of them are mutually-exclusive to one provider family:

Wire protocol flavor: "responses" for OpenAI's Codex/Responses API, "chat_completions" for everything else…
Default: chat_completions. Press Enter to accept.
wire-api (default: chat_completions): chat_completions

When true, the client pulls credentials from OPENAI_API_KEY or ~/.codex/auth.json…
requires-openai-auth no

Azure OpenAI resource name (the <resource> part of <resource>.openai.azure.com). Azure-only…
azure-openai-resource:
azure-openai-deployment:
azure-openai-api-version:
…

For someone setting up Anthropic, walking past azure-openai-* fields is pure noise. For someone on Azure, the requires-openai-auth question is irrelevant. The advanced gate already defaults to N, which is the right call — but once a user opts in, the flow doesn't filter by the provider they just picked.

🟡 TUI drops advanced settings entirely. The advanced gate I'm complaining about in the CLI isn't in the TUI main flow at all, from what I could tell. That's a backend inconsistency — same run_all(..) should produce the same reachable state regardless of which OnboardUi is wired up. Either both have the advanced walk, or neither does.

Suggestion

🔵 Drop the advanced walk from the main onboard flow entirely. My take after walking both backends: advanced users will edit config.toml directly (or hit zeroclaw config set providers.models.<name>.<field> <value> if that path exists) — they don't need a 20-prompt walk for it. Keep onboard focused on the 90% path: provider → API key → model → done. Document the advanced fields in docs/reference/ for the users who need them.

This also cleanly resolves the TUI/CLI inconsistency above — if neither backend has an advanced walk, there's nothing to keep in sync.

Net

First-run UX is good up through the model picker, the TUI is genuinely delightful, and the 90% onboarding path is tight. The advanced-settings tail is where it frays, and the daemon warning is the one thing I'd want eyes on before this ships — the rest I can live with in a follow-up.

Previously `prompt_model` silently swallowed `list_models()` failures
(`.await.ok()`), and the user-visible fallback note was generic — users
couldn't tell whether the catalog lookup failed for a transient reason
worth retrying or because the provider genuinely doesn't expose a
no-auth listing.

Two changes:
- Capture both the `create_provider` error and the `list_models` error
  with explicit `match` arms; log each at `tracing::debug!` with the
  provider name and underlying cause, so operators can diagnose without
  surfacing a reqwest dump to end users.
- Rewrite the fallback note to name the provider: "Catalog lookup
  failed for <provider> — enter a model id manually (see the provider's
  docs for the exact format)." — gives users the context they need to
  decide between retrying, consulting the provider, or hand-entering.

Addresses JordanTheJet's 🟡 on cryptic live-fetch errors during the
onboard providers walk.
…iderConfig

A fresh provider entry created via `zeroclaw onboard` (or any path that
instantiates `ModelProviderConfig::default()` and serializes it) wrote
provider-family-irrelevant fields to TOML on disk:

    [providers.models.anthropic]
    requires_openai_auth = false
    merge_system_into_user = false

Both fields are plain `bool` with struct-level default `false`, so they
serialized unconditionally. Same issue for `name`, `base_url`, and
`wire_api` — all `Option<String>` without `skip_serializing_if`,
which would write whenever a caller set them to `Some("")` or to
their common-default string.

Add `skip_serializing_if = "Option::is_none"` to the three Options and
`skip_serializing_if = "is_false"` (with a small local helper) to the
two bools, so a fresh-config entry for e.g. Anthropic only carries
fields that actually apply.

Existing configs round-trip unchanged: the `#[serde(default)]` markers
still populate the same default values on deserialize, so dropping the
redundant `= false` / `= ""` lines from disk doesn't alter runtime
behavior.
The advanced-settings walk previously prompted every non-excluded field
under `providers.models.<provider>.*`, regardless of which provider the
user selected. Walking `azure-openai-resource`/`deployment`/`api-version`
for someone picking Anthropic is pure noise; walking `wire-api` and
`requires-openai-auth` for a non-OpenAI-family provider is the same.

Add a small `provider_family_excludes()` helper that extends the
advanced-walk exclusion list based on the selected provider:
  - `azure_openai` only: `azure-openai-resource`, `azure-openai-deployment`,
    `azure-openai-api-version`
  - `openai` / `openai_codex` only: `wire-api`, `requires-openai-auth`

Hardcoded for now; a `#[configurable(family = "...")]` macro attribute
would let the `Configurable` derive emit this automatically. Tracked as
a follow-up per the zeroclaw-labs#5960 scope boundary.

Addresses JordanTheJet's 🟡 on the long advanced walk surfacing fields
from unrelated provider families.
Previously the only test covering the Channels section was
`channels_done_selection_is_idempotent_on_disk`, which picks "Done"
immediately and never enters any channel's field walk. That left the
per-channel init_defaults + set_prop path entirely untested — the
"picked X, fields are actually writable" guarantee lived only in
manual QA.

Two new smoke tests:

- `channels_telegram_selection_writes_entry` — picks Telegram via a
  sequenced "Channel" answer, scripts the required bot-token secret,
  then exits with "Done". Asserts the telegram subsection initialized
  and the token round-tripped through set_prop.

- `channels_mochat_selection_persists_enabled_url_and_token` — picks
  Mochat, flips the enabled bool through confirm, scripts api-url +
  api-token, exits. Doubles as a regression guard for the orchestrator's
  new mochat enabled-gate: enabled=true must reach persistence.

`QuickUi` gains a `with_sequence()` helper so a single prompt label
(like "Channel") can return distinct answers across calls. The
existing `with()` single-answer map is preserved as a fallback for
exhausted sequences. Covers AC #6 from issue zeroclaw-labs#5951 (per-channel
onboard smoke tests with a scripted mock OnboardUi).
Cover the changes landed in the preceding PR-zeroclaw-labs#5960 follow-up commits:
  - `zeroclaw onboard` defaults to TUI; `--cli` forces terminal prompts;
    `--tui` deprecated no-op (Breaking Changes + Agent & Runtime)
  - advanced-walk provider-family filtering
  - models.dev fetch error UX — named provider in fallback note, debug
    tracing of underlying error
  - `zeroclaw models` restored via doctor::run_models routing
  - TermUi::editor Answer::Back contract fix
  - channels.mochat orchestrator enabled-gate (matches WeCom/ClawdTalk)
  - ModelProviderConfig skip_serializing_if cleanup — fresh provider
    entries no longer write family-irrelevant default flags
…check, completions help

Three Windows-unsafe patterns in src/main.rs:

1. zeroclaw-desktop locator (~line 1898) read `HOME` directly via
   `std::env::var_os`, which is unset on Windows. Replaced with
   `directories::UserDirs::new()` (workspace dep, already used in
   zeroclaw-config). Also try .exe-suffixed name on Windows since
   ~/.cargo/bin/zeroclaw-desktop on Windows is actually
   .cargo\bin\zeroclaw-desktop.exe. .local/bin (XDG) is Unix-only.

2. Daemon-from-home warning (~line 1477) used substring matches on
   '.cargo/bin' and '/home/' — fails on Windows due to backslash paths
   and absent /home prefix. Switched to a Path::starts_with check
   against the user's home dir, with a platform-appropriate install-
   location hint (/usr/local/bin on Linux, Homebrew prefix on macOS,
   Program Files on Windows).

3. Completions long_about showed only Unix shell examples. Added
   PowerShell examples ($PROFILE, Invoke-Expression). The
   CompletionShell::PowerShell variant already exists; just surface
   it in the help text.

Onboarding wizard itself (crates/zeroclaw-runtime/src/onboard, the TUI)
audited and clean — no hardcoded Unix paths or HOME reads.
$SHELL is a Unix convention and is not set on Windows. The doctor was
emitting a warning every run on Windows even though the platform has a
working default shell. Check %ComSpec% (path to cmd.exe) as a fallback
so Windows users see 'shell: C:\\Windows\\system32\\cmd.exe' instead
of a spurious '$SHELL not set'.
AutonomyConfig::default() hardcoded Unix forbidden_paths (/etc, /root,
/home, /usr, /bin, /sbin, /lib, /opt, /boot, /dev, /proc, /sys, /var,
/tmp + ~/.ssh, ~/.gnupg, ~/.aws, ~/.config) and a Unix-only allowed_commands
list. The wizard wrote these to ~/.zeroclaw/config.toml regardless of
target OS, leaving Windows users with a config full of paths that don't
exist on their system and a command allowlist missing dir/type/where/
findstr/more.

policy.rs already had cfg-guarded default_allowed_commands() and
default_forbidden_paths() helpers used by SecurityPolicy::default().
Promote them to pub(crate) and reuse them in AutonomyConfig::default()
so the on-disk defaults match the runtime policy and the host platform.
- Drop `providers.fallback` auto-persist and the empty-entry save; the
  entry is now seeded in memory only and dropped on back-out paths so
  config.toml never gains an empty `[providers.models.<name>]`.
- Populate trait defaults on fresh provider entries via a shared
  `provider_trait_defaults()` helper (single source for both onboard
  pre-populate and the advanced-settings walk).
- Add a "Done" exit to the provider picker so first-time users can
  skip the section without picking one (matches the channels UX).
- StringArray prompts: bidirectional with config.toml — accept either
  `alice,bob` or `["alice", "bob"]`. Reject malformed brackets via
  `parses_as_string_array` (TOML round-trip, no JSON). Help text
  surfaces the format. Clear the note slot on success so error
  messages don't linger.
- Bump ollama default_temperature 0.0 -> 0.8 to match the upstream
  Modelfile default.
@singlerider
Copy link
Copy Markdown
Collaborator Author

I did some runs and tried to break it. I found some issues and fixed them in 5532018. Merging when CI passes. This should be considered experimental in 0.7.4.

@singlerider singlerider merged commit 4112432 into zeroclaw-labs:master Apr 26, 2026
12 checks passed
github-actions Bot pushed a commit that referenced this pull request Apr 26, 2026
…t, DRY (#5960)

- 0c622e6 feat(onboard): scorched-earth delete old wizard + TUI twin (#5951)
- 3dabdb1 feat(config): add async OnboardUi trait in traits.rs (#5951)
- 65e9809 feat(onboard): add TermUi — dialoguer OnboardUi backend (#5951)
- 95b8db4 feat(onboard): add QuickUi — headless OnboardUi backend (#5951)
- bcd3df1 feat(onboard): scaffold orchestrator — Section, Flags, run() dispatch…
- d14cd46 feat(onboard): implement workspace section (#5951)
- 043dd8a feat(onboard): implement memory section; drop Project variant (#5951)
- 7b5a4b3 feat(onboard): DRY up memory + implement tunnel via existing sources…
- 26251b7 feat(onboard): add generic prompt_field + prompt_fields_under helpers…
- 1ada5fb feat(onboard): implement hardware section via prop_fields only (#5951)
- fe836a7 feat(onboard): implement providers section via list_providers() + pro…
- c22d8c5 feat(onboard): TermUi select uses FuzzySelect for searchable menus (#…
- b397324 feat(providers): no-auth live model listing via models.dev + native p…
- ff4183a feat(onboard): wire live model picker into providers section (#5951)
- 415fe0b feat(onboard): implement channels section via prop_fields + free-text…
- acb4658 feat(tui): thin RatatuiUi implementing OnboardUi (#5951)
- b7a99db feat(cli): wire zeroclaw onboard to the new orchestrator (#5951)
- 2710fbc chore(onboard): pass cargo fmt + clippy --workspace -D warnings gate…
- e11191f fix(config): route get_prop/set_prop through HashMap<String, T> + Ter…
- 3f8f30b feat(onboard): human-readable prompts from doc comments + consolidate…
- 8fee0f7 fix(tui): wrap prompt text + grow prompt bar to fit (#5951)
- 8979fdd fix(onboard): show field docstring as context above the prompt, not i…
- 045388d docs(config): rewrite WorkspaceConfig + HardwareConfig doc comments f…
- b27c4ea fix(onboard): channels menu derives full list from schema via probe c…
- eb97efc feat(tui): restore ZeroClaw ASCII banner at top of RatatuiUi (#5951)
- 514a77c fix(onboard): reset context panel between sections, note=replace (#5951)
- c9c47d5 feat(onboard): persist config after every write so mid-flow aborts r…
- fc5ad62 fix(tui): select list uses ratatui List + ListState for scroll-aware…
- 59512f1 feat(onboard): section-level skip gate for already-configured section…
- 8abab39 feat(onboard): track completed sections via onboard_state.completed_s…
- 576ac39 fix(onboard): surface HashMap fields, Back nav, Markdown-style phase…
- 38d55f4 refactor(providers): Option<f64> temperature + per-family default_* o…
- 0a43a14 feat(onboard): six-region TUI layout, provider-defaults prefill, adva…
- 1f50f68 fix(cli): wrap final_temperature in Some for Provider::simple_chat (#…
- 1954cf1 test(onboard): skip-gate + idempotency coverage, fix channels signal…
- 696e95d test(onboard): cover legacy flag shim and provider flag path (#5951)
- f0a2923 fix(onboard): bool prompts and multi-line help text rendering (#5951)
- e13d06e fix(onboard): flatten bool is_set so default annotations reach the l…
- 2188306 feat(onboard): paint status/warn immediately so web-fetch wait is vi…
- 5c2b3fd fix(onboard,doctor): restore zeroclaw doctor models; drop hardcoded m…
- 651573f fix(onboard): rustfmt line-length in doctor summary counters
- f3e0e48 fix(onboard): honor Answer::Back contract on editor close-without-save
- 10c5b7e fix(channels): gate Mochat registration on channels.mochat.enabled
- 53ab2a3 feat(cli): restore zeroclaw models via doctor::run_models routing
- 73be86e feat(onboard): default to TUI; add --cli and TUI init fallback
- 063b23a feat(onboard): improve models.dev fetch error UX
- be4688a refactor(config): omit default-valued flags from serialized ModelProv…
- e727afa feat(onboard): filter advanced-walk fields by provider family
- bd12473 test(onboard): per-channel smoke tests for telegram + mochat
- ba0bbbc docs(changelog): document onboard + provider config + CLI changes
- 6abb3ce style: remove extra blank line in src/config/mod.rs (cargo fmt)
- 50a9bab fix(main): handle Windows paths in desktop locator, daemon-from-home…
- e14b30e fix(doctor): fall back to %ComSpec% on Windows when checking shell
- 72447ac fix(config): make AutonomyConfig defaults Windows-aware
- a0f0eea docs(extension-examples): update Provider example to Option<f64> temp…
- 5532018 fix(onboard): no eager writes; complete provider flow before any persist 4112432
github-actions Bot pushed a commit to drowning-in-codes/zeroclaw that referenced this pull request Apr 26, 2026
…t, DRY (zeroclaw-labs#5960)

- 0c622e6 feat(onboard): scorched-earth delete old wizard + TUI twin (zeroclaw-labs#5951)
- 3dabdb1 feat(config): add async OnboardUi trait in traits.rs (zeroclaw-labs#5951)
- 65e9809 feat(onboard): add TermUi — dialoguer OnboardUi backend (zeroclaw-labs#5951)
- 95b8db4 feat(onboard): add QuickUi — headless OnboardUi backend (zeroclaw-labs#5951)
- bcd3df1 feat(onboard): scaffold orchestrator — Section, Flags, run() dispatch…
- d14cd46 feat(onboard): implement workspace section (zeroclaw-labs#5951)
- 043dd8a feat(onboard): implement memory section; drop Project variant (zeroclaw-labs#5951)
- 7b5a4b3 feat(onboard): DRY up memory + implement tunnel via existing sources…
- 26251b7 feat(onboard): add generic prompt_field + prompt_fields_under helpers…
- 1ada5fb feat(onboard): implement hardware section via prop_fields only (zeroclaw-labs#5951)
- fe836a7 feat(onboard): implement providers section via list_providers() + pro…
- c22d8c5 feat(onboard): TermUi select uses FuzzySelect for searchable menus (#…
- b397324 feat(providers): no-auth live model listing via models.dev + native p…
- ff4183a feat(onboard): wire live model picker into providers section (zeroclaw-labs#5951)
- 415fe0b feat(onboard): implement channels section via prop_fields + free-text…
- acb4658 feat(tui): thin RatatuiUi implementing OnboardUi (zeroclaw-labs#5951)
- b7a99db feat(cli): wire zeroclaw onboard to the new orchestrator (zeroclaw-labs#5951)
- 2710fbc chore(onboard): pass cargo fmt + clippy --workspace -D warnings gate…
- e11191f fix(config): route get_prop/set_prop through HashMap<String, T> + Ter…
- 3f8f30b feat(onboard): human-readable prompts from doc comments + consolidate…
- 8fee0f7 fix(tui): wrap prompt text + grow prompt bar to fit (zeroclaw-labs#5951)
- 8979fdd fix(onboard): show field docstring as context above the prompt, not i…
- 045388d docs(config): rewrite WorkspaceConfig + HardwareConfig doc comments f…
- b27c4ea fix(onboard): channels menu derives full list from schema via probe c…
- eb97efc feat(tui): restore ZeroClaw ASCII banner at top of RatatuiUi (zeroclaw-labs#5951)
- 514a77c fix(onboard): reset context panel between sections, note=replace (zeroclaw-labs#5951)
- c9c47d5 feat(onboard): persist config after every write so mid-flow aborts r…
- fc5ad62 fix(tui): select list uses ratatui List + ListState for scroll-aware…
- 59512f1 feat(onboard): section-level skip gate for already-configured section…
- 8abab39 feat(onboard): track completed sections via onboard_state.completed_s…
- 576ac39 fix(onboard): surface HashMap fields, Back nav, Markdown-style phase…
- 38d55f4 refactor(providers): Option<f64> temperature + per-family default_* o…
- 0a43a14 feat(onboard): six-region TUI layout, provider-defaults prefill, adva…
- 1f50f68 fix(cli): wrap final_temperature in Some for Provider::simple_chat (#…
- 1954cf1 test(onboard): skip-gate + idempotency coverage, fix channels signal…
- 696e95d test(onboard): cover legacy flag shim and provider flag path (zeroclaw-labs#5951)
- f0a2923 fix(onboard): bool prompts and multi-line help text rendering (zeroclaw-labs#5951)
- e13d06e fix(onboard): flatten bool is_set so default annotations reach the l…
- 2188306 feat(onboard): paint status/warn immediately so web-fetch wait is vi…
- 5c2b3fd fix(onboard,doctor): restore zeroclaw doctor models; drop hardcoded m…
- 651573f fix(onboard): rustfmt line-length in doctor summary counters
- f3e0e48 fix(onboard): honor Answer::Back contract on editor close-without-save
- 10c5b7e fix(channels): gate Mochat registration on channels.mochat.enabled
- 53ab2a3 feat(cli): restore zeroclaw models via doctor::run_models routing
- 73be86e feat(onboard): default to TUI; add --cli and TUI init fallback
- 063b23a feat(onboard): improve models.dev fetch error UX
- be4688a refactor(config): omit default-valued flags from serialized ModelProv…
- e727afa feat(onboard): filter advanced-walk fields by provider family
- bd12473 test(onboard): per-channel smoke tests for telegram + mochat
- ba0bbbc docs(changelog): document onboard + provider config + CLI changes
- 6abb3ce style: remove extra blank line in src/config/mod.rs (cargo fmt)
- 50a9bab fix(main): handle Windows paths in desktop locator, daemon-from-home…
- e14b30e fix(doctor): fall back to %ComSpec% on Windows when checking shell
- 72447ac fix(config): make AutonomyConfig defaults Windows-aware
- a0f0eea docs(extension-examples): update Provider example to Option<f64> temp…
- 5532018 fix(onboard): no eager writes; complete provider flow before any persist 4112432
github-actions Bot pushed a commit to nagyist/zeroclaw that referenced this pull request Apr 26, 2026
…t, DRY (zeroclaw-labs#5960)

- 0c622e6 feat(onboard): scorched-earth delete old wizard + TUI twin (zeroclaw-labs#5951)
- 3dabdb1 feat(config): add async OnboardUi trait in traits.rs (zeroclaw-labs#5951)
- 65e9809 feat(onboard): add TermUi — dialoguer OnboardUi backend (zeroclaw-labs#5951)
- 95b8db4 feat(onboard): add QuickUi — headless OnboardUi backend (zeroclaw-labs#5951)
- bcd3df1 feat(onboard): scaffold orchestrator — Section, Flags, run() dispatch…
- d14cd46 feat(onboard): implement workspace section (zeroclaw-labs#5951)
- 043dd8a feat(onboard): implement memory section; drop Project variant (zeroclaw-labs#5951)
- 7b5a4b3 feat(onboard): DRY up memory + implement tunnel via existing sources…
- 26251b7 feat(onboard): add generic prompt_field + prompt_fields_under helpers…
- 1ada5fb feat(onboard): implement hardware section via prop_fields only (zeroclaw-labs#5951)
- fe836a7 feat(onboard): implement providers section via list_providers() + pro…
- c22d8c5 feat(onboard): TermUi select uses FuzzySelect for searchable menus (#…
- b397324 feat(providers): no-auth live model listing via models.dev + native p…
- ff4183a feat(onboard): wire live model picker into providers section (zeroclaw-labs#5951)
- 415fe0b feat(onboard): implement channels section via prop_fields + free-text…
- acb4658 feat(tui): thin RatatuiUi implementing OnboardUi (zeroclaw-labs#5951)
- b7a99db feat(cli): wire zeroclaw onboard to the new orchestrator (zeroclaw-labs#5951)
- 2710fbc chore(onboard): pass cargo fmt + clippy --workspace -D warnings gate…
- e11191f fix(config): route get_prop/set_prop through HashMap<String, T> + Ter…
- 3f8f30b feat(onboard): human-readable prompts from doc comments + consolidate…
- 8fee0f7 fix(tui): wrap prompt text + grow prompt bar to fit (zeroclaw-labs#5951)
- 8979fdd fix(onboard): show field docstring as context above the prompt, not i…
- 045388d docs(config): rewrite WorkspaceConfig + HardwareConfig doc comments f…
- b27c4ea fix(onboard): channels menu derives full list from schema via probe c…
- eb97efc feat(tui): restore ZeroClaw ASCII banner at top of RatatuiUi (zeroclaw-labs#5951)
- 514a77c fix(onboard): reset context panel between sections, note=replace (zeroclaw-labs#5951)
- c9c47d5 feat(onboard): persist config after every write so mid-flow aborts r…
- fc5ad62 fix(tui): select list uses ratatui List + ListState for scroll-aware…
- 59512f1 feat(onboard): section-level skip gate for already-configured section…
- 8abab39 feat(onboard): track completed sections via onboard_state.completed_s…
- 576ac39 fix(onboard): surface HashMap fields, Back nav, Markdown-style phase…
- 38d55f4 refactor(providers): Option<f64> temperature + per-family default_* o…
- 0a43a14 feat(onboard): six-region TUI layout, provider-defaults prefill, adva…
- 1f50f68 fix(cli): wrap final_temperature in Some for Provider::simple_chat (#…
- 1954cf1 test(onboard): skip-gate + idempotency coverage, fix channels signal…
- 696e95d test(onboard): cover legacy flag shim and provider flag path (zeroclaw-labs#5951)
- f0a2923 fix(onboard): bool prompts and multi-line help text rendering (zeroclaw-labs#5951)
- e13d06e fix(onboard): flatten bool is_set so default annotations reach the l…
- 2188306 feat(onboard): paint status/warn immediately so web-fetch wait is vi…
- 5c2b3fd fix(onboard,doctor): restore zeroclaw doctor models; drop hardcoded m…
- 651573f fix(onboard): rustfmt line-length in doctor summary counters
- f3e0e48 fix(onboard): honor Answer::Back contract on editor close-without-save
- 10c5b7e fix(channels): gate Mochat registration on channels.mochat.enabled
- 53ab2a3 feat(cli): restore zeroclaw models via doctor::run_models routing
- 73be86e feat(onboard): default to TUI; add --cli and TUI init fallback
- 063b23a feat(onboard): improve models.dev fetch error UX
- be4688a refactor(config): omit default-valued flags from serialized ModelProv…
- e727afa feat(onboard): filter advanced-walk fields by provider family
- bd12473 test(onboard): per-channel smoke tests for telegram + mochat
- ba0bbbc docs(changelog): document onboard + provider config + CLI changes
- 6abb3ce style: remove extra blank line in src/config/mod.rs (cargo fmt)
- 50a9bab fix(main): handle Windows paths in desktop locator, daemon-from-home…
- e14b30e fix(doctor): fall back to %ComSpec% on Windows when checking shell
- 72447ac fix(config): make AutonomyConfig defaults Windows-aware
- a0f0eea docs(extension-examples): update Provider example to Option<f64> temp…
- 5532018 fix(onboard): no eager writes; complete provider flow before any persist 4112432
github-actions Bot pushed a commit to justjuangui/zeroclaw that referenced this pull request Apr 26, 2026
…t, DRY (zeroclaw-labs#5960)

- 0c622e6 feat(onboard): scorched-earth delete old wizard + TUI twin (zeroclaw-labs#5951)
- 3dabdb1 feat(config): add async OnboardUi trait in traits.rs (zeroclaw-labs#5951)
- 65e9809 feat(onboard): add TermUi — dialoguer OnboardUi backend (zeroclaw-labs#5951)
- 95b8db4 feat(onboard): add QuickUi — headless OnboardUi backend (zeroclaw-labs#5951)
- bcd3df1 feat(onboard): scaffold orchestrator — Section, Flags, run() dispatch…
- d14cd46 feat(onboard): implement workspace section (zeroclaw-labs#5951)
- 043dd8a feat(onboard): implement memory section; drop Project variant (zeroclaw-labs#5951)
- 7b5a4b3 feat(onboard): DRY up memory + implement tunnel via existing sources…
- 26251b7 feat(onboard): add generic prompt_field + prompt_fields_under helpers…
- 1ada5fb feat(onboard): implement hardware section via prop_fields only (zeroclaw-labs#5951)
- fe836a7 feat(onboard): implement providers section via list_providers() + pro…
- c22d8c5 feat(onboard): TermUi select uses FuzzySelect for searchable menus (#…
- b397324 feat(providers): no-auth live model listing via models.dev + native p…
- ff4183a feat(onboard): wire live model picker into providers section (zeroclaw-labs#5951)
- 415fe0b feat(onboard): implement channels section via prop_fields + free-text…
- acb4658 feat(tui): thin RatatuiUi implementing OnboardUi (zeroclaw-labs#5951)
- b7a99db feat(cli): wire zeroclaw onboard to the new orchestrator (zeroclaw-labs#5951)
- 2710fbc chore(onboard): pass cargo fmt + clippy --workspace -D warnings gate…
- e11191f fix(config): route get_prop/set_prop through HashMap<String, T> + Ter…
- 3f8f30b feat(onboard): human-readable prompts from doc comments + consolidate…
- 8fee0f7 fix(tui): wrap prompt text + grow prompt bar to fit (zeroclaw-labs#5951)
- 8979fdd fix(onboard): show field docstring as context above the prompt, not i…
- 045388d docs(config): rewrite WorkspaceConfig + HardwareConfig doc comments f…
- b27c4ea fix(onboard): channels menu derives full list from schema via probe c…
- eb97efc feat(tui): restore ZeroClaw ASCII banner at top of RatatuiUi (zeroclaw-labs#5951)
- 514a77c fix(onboard): reset context panel between sections, note=replace (zeroclaw-labs#5951)
- c9c47d5 feat(onboard): persist config after every write so mid-flow aborts r…
- fc5ad62 fix(tui): select list uses ratatui List + ListState for scroll-aware…
- 59512f1 feat(onboard): section-level skip gate for already-configured section…
- 8abab39 feat(onboard): track completed sections via onboard_state.completed_s…
- 576ac39 fix(onboard): surface HashMap fields, Back nav, Markdown-style phase…
- 38d55f4 refactor(providers): Option<f64> temperature + per-family default_* o…
- 0a43a14 feat(onboard): six-region TUI layout, provider-defaults prefill, adva…
- 1f50f68 fix(cli): wrap final_temperature in Some for Provider::simple_chat (#…
- 1954cf1 test(onboard): skip-gate + idempotency coverage, fix channels signal…
- 696e95d test(onboard): cover legacy flag shim and provider flag path (zeroclaw-labs#5951)
- f0a2923 fix(onboard): bool prompts and multi-line help text rendering (zeroclaw-labs#5951)
- e13d06e fix(onboard): flatten bool is_set so default annotations reach the l…
- 2188306 feat(onboard): paint status/warn immediately so web-fetch wait is vi…
- 5c2b3fd fix(onboard,doctor): restore zeroclaw doctor models; drop hardcoded m…
- 651573f fix(onboard): rustfmt line-length in doctor summary counters
- f3e0e48 fix(onboard): honor Answer::Back contract on editor close-without-save
- 10c5b7e fix(channels): gate Mochat registration on channels.mochat.enabled
- 53ab2a3 feat(cli): restore zeroclaw models via doctor::run_models routing
- 73be86e feat(onboard): default to TUI; add --cli and TUI init fallback
- 063b23a feat(onboard): improve models.dev fetch error UX
- be4688a refactor(config): omit default-valued flags from serialized ModelProv…
- e727afa feat(onboard): filter advanced-walk fields by provider family
- bd12473 test(onboard): per-channel smoke tests for telegram + mochat
- ba0bbbc docs(changelog): document onboard + provider config + CLI changes
- 6abb3ce style: remove extra blank line in src/config/mod.rs (cargo fmt)
- 50a9bab fix(main): handle Windows paths in desktop locator, daemon-from-home…
- e14b30e fix(doctor): fall back to %ComSpec% on Windows when checking shell
- 72447ac fix(config): make AutonomyConfig defaults Windows-aware
- a0f0eea docs(extension-examples): update Provider example to Option<f64> temp…
- 5532018 fix(onboard): no eager writes; complete provider flow before any persist 4112432
DengHaoke added a commit to DengHaoke/zeroclaw that referenced this pull request Apr 27, 2026
Resolve conflicts after PR zeroclaw-labs#5960 (schema-driven onboard rewrite) and
PR zeroclaw-labs#5735 (decouple gateway/tui-onboarding from agent-runtime):

- crates/zeroclaw-runtime/src/onboard/wizard.rs: deleted (whole file
  removed in master; the new onboard reads SlackConfig from the schema
  via traits::OnboardUi, so the manual field plumbing is obsolete).
- crates/zeroclaw-tui/src/onboarding.rs: take master version (rewritten
  as a pure ratatui OnboardUi implementation; no longer holds channel
  struct literals, so the explicit `strict_mention_in_thread: false`
  initializer is no longer needed).

Substantive Slack changes (schema.rs field, slack.rs struct/builder/
runtime checks/test, orchestrator/mod.rs wiring, CHANGELOG-next.md)
auto-merged cleanly. Behavior unchanged: `#[serde(default)]` keeps the
flag false on all non-configured paths, matching the old onboard
default.

Verified: cargo check --workspace --exclude zeroclaw-desktop, cargo
clippy on affected crates with -D warnings, cargo fmt --check, and
cargo test -p zeroclaw-channels --lib slack:: (96/96 pass).
perlowja added a commit to perlowja/zeroclaw that referenced this pull request Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Auto scope: src/config/** changed. core Auto scope: root src/*.rs files changed. dependencies Auto scope: dependency manifest/lock/policy changed. docs Auto scope: docs/markdown/template files changed. onboard Auto scope: src/onboard/** changed. provider Auto scope: src/providers/** changed. risk: medium Auto risk: src/** or dependency/config changes. size: XL Auto size: >1000 non-doc changed lines. tests Auto scope: tests/** changed.

Projects

None yet

5 participants