Skip to content

feat(reborn): frictionless local-dev onboarding (auto-provision serve, REPL model wizard, onboard launcher) - #6285

Closed
loopstring wants to merge 1 commit into
mainfrom
feat/reborn-local-dev-onboarding
Closed

loopstring wants to merge 1 commit into
mainfrom
feat/reborn-local-dev-onboarding

Conversation

@loopstring

Copy link
Copy Markdown
Contributor

Summary

Makes first-run onboarding work end-to-end on a local-dev laptop with no manual env setup, while keeping hosted/production fail-closed. Motivated by a from-scratch run of cargo build … && ironclaw-reborn onboard hitting a chain of dead ends (missing WebUI token, missing user id, opaque corepack error, pre-set model, hang on missing API key).

Changes

  • serve (local-dev): auto-generate + persist the WebUI bearer token and default operator user id when unset; print a ?token= sign-in URL and auto-open the browser (--no-browser to opt out). Production/hosted still require operator-supplied IRONCLAW_REBORN_WEBUI_TOKEN + _USER_ID — gated by RebornProfile::allows_dev_credential_autoprovision.
  • repl: first-run model wizard — no LLM configured → prompt for provider/model; provider set but its API-key env missing → prompt for the key (this session) instead of hanging on the first message. Skipped for --no-setup / non-interactive stdin.
  • onboard: after scaffolding, prompt to launch REPL or Web UI and re-exec into it. Web UI option is gated on webui-v2-beta so a CLI-only build never offers a serve it lacks.
  • config init template: [llm.default] left commented so a fresh onboard leads to model setup (matches the sparse first-run seed) instead of pre-activating a provider that suppressed the prompt.
  • Default features: webui-v2-beta is now default for ironclaw_reborn_cli, so a default build ships both REPL and Web UI.
  • webui_v2/build.rs: clear, actionable error when corepack/pnpm is missing instead of an opaque Os NotFound.
  • Welcome subtitle: more prominent (accent color, balanced wrap).

Verification

  • serve local-dev with zero env → binds 127.0.0.1, bearer=200 / no-auth=401 (auth still enforced), lands on /welcome when no model set.
  • Production profile with no env → still errors requiring the token/user id.
  • fmt + clippy clean on changed crates; unit tests: profile predicate, serve credential resolver (auto-provision / fail-closed / env-wins), repl provider-choice parse, onboard launch-choice parse.

Follow-ups (not in this PR)

  • REPL streaming + spinner + markdown rendering (currently blocks and prints raw markdown).

@railway-app

railway-app Bot commented Jul 19, 2026

Copy link
Copy Markdown

This PR was not deployed automatically as @loopstring does not have access to the Railway project.

In order to get automatic PR deploys, please add @loopstring to your workspace on Railway.

@ironloopai

ironloopai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: ed3b151e0597870ea83ffc66e5916d3fc2dd83dd
Result: One or more review results were superseded by a newer PR head.
Next: Run @ironloopai review on the latest PR head.
Updated: 2026-07-19T20:38:45.633Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-19T20:23:30.867Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: cac6623. Previous verdict: Review declined.
Recent activity
Time Reviewer State Detail
2026-07-19T20:12:27.043Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head ac1349b.
2026-07-19T20:12:27.043Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-19T20:12:27.211Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-19T20:12:30.085Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (head_ref) at ac1349b.
2026-07-19T20:13:20.127Z ironloop/common-reviewer (reviewer) Result captured Skipped; 0 blocking findings.
2026-07-19T20:13:20.127Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
2026-07-19T20:23:30.867Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (cac6623).
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent>
Run metadata

Admission: webhook accepted the request and IronLoop persisted reviewer state before this projection.

@github-actions github-actions Bot added size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: regular 2-5 merged PRs labels Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ed65443b-13d4-442d-a73f-f660da4382f7

📥 Commits

Reviewing files that changed from the base of the PR and between 917c21a and ed3b151.

📒 Files selected for processing (8)
  • crates/ironclaw_reborn_cli/Cargo.toml
  • crates/ironclaw_reborn_cli/src/commands/config/init.rs
  • crates/ironclaw_reborn_cli/src/commands/onboard.rs
  • crates/ironclaw_reborn_cli/src/commands/repl.rs
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
  • crates/ironclaw_reborn_config/src/profile.rs
  • crates/ironclaw_webui_v2/build.rs
  • crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • WebChat v2 (beta) is enabled by default.
    • Onboarding can skip the post-scaffolding launch step with --no-launch.
    • REPL first-run setup can be skipped with --no-setup.
    • serve can auto-provision and persist local-dev Web UI sign-in credentials, open the browser automatically, and provide --no-browser to disable it.
  • Bug Fixes
    • config init generates a clearer, commented LLM configuration stub.
    • Improved, tailored errors when Web UI build tooling commands are missing.
    • Hosted/production now fails closed with explicit missing credential messages.
  • Style
    • Refined Web UI onboarding subtitle layout/typography.
  • Tests
    • Added coverage for launch/provider choice parsing and credential/provisioning behavior.

Walkthrough

The CLI enables the WebUI beta by default, adds interactive onboarding and model setup controls, provisions local-development WebUI credentials, and improves WebUI build errors and onboarding presentation.

Changes

CLI and WebUI flow

Layer / File(s) Summary
Profile contract and default configuration
crates/ironclaw_reborn_config/src/profile.rs, crates/ironclaw_reborn_cli/Cargo.toml, crates/ironclaw_reborn_cli/src/commands/config/init.rs
Profiles restrict credential autoprovisioning to local-development modes; WebUI beta is enabled by default; generated LLM settings are commented out with catalog guidance.
Onboarding and REPL setup
crates/ironclaw_reborn_cli/src/commands/onboard.rs, crates/ironclaw_reborn_cli/src/commands/repl.rs
Onboarding supports --no-launch and REPL/WebUI handoff; REPL supports --no-setup and interactive provider/API-key configuration.
Serve credential provisioning and sign-in
crates/ironclaw_reborn_cli/src/commands/serve.rs
Local-development credentials are persisted and reused, non-local profiles require explicit environment values, and generated sign-in URLs can open automatically unless --no-browser is set.
WebUI build and onboarding presentation
crates/ironclaw_webui_v2/build.rs, crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx
Missing frontend build commands produce tailored errors, and the onboarding subtitle styling is updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant OnboardCommand
  participant ReplCommand
  participant ServeCommand
  participant RebornProfile
  participant RebornHome
  participant Browser
  Operator->>OnboardCommand: choose launch surface
  OnboardCommand->>ReplCommand: re-exec repl
  ReplCommand->>Operator: configure provider and API key
  OnboardCommand->>ServeCommand: re-exec serve
  ServeCommand->>RebornProfile: check profile credential policy
  ServeCommand->>RebornHome: load or persist local-dev token
  ServeCommand->>Operator: print tokenized sign-in URL
  ServeCommand->>Browser: optionally open URL
Loading

Possibly related PRs

Suggested reviewers: serrrfirat, benkurrek, henrypark133

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses Conventional Commits and accurately summarizes the onboarding and WebUI/REPL feature work.
Description check ✅ Passed The description covers summary, changes, verification, and follow-ups, but it omits several template sections like a linked issue.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/reborn-local-dev-onboarding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

⏭️ IronLoop Review Declined: reviewer

Review at a glance

Disposition Head
⏭️ Review declined ac1349b2a752

Head: ac1349b2a752ab53672dc4d38116c02cbe33c5ca
Reason: The comparison spans 1,177 files with 31,633 additions and 93,809 deletions, including 359 full renames plus hundreds of unrelated modifications/deletions. A reliable complete review is not feasible within scope.
Next: Split the onboarding implementation from the WebUI migration and unrelated repository-wide changes, or provide a narrowed comparison against the immediate parent of the restructuring commit; then request review of each focused diff.

Run details

Status: Current
Trustworthy review produced: no

Summary

Skipped: the supplied base-to-head comparison is a mega diff dominated by broad repository restructuring, not a reviewable focused onboarding change.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces first-run onboarding improvements, including interactive prompts to launch the REPL or Web UI, automatic model setup, and auto-provisioning of local-dev WebUI credentials with automatic browser launching. Feedback on these changes highlights a security race condition when writing the dev token to disk without atomic permissions, a usability issue where manual setup instructions are skipped if the launch prompt is declined, and a bug where using port 0 results in an invalid auto-open URL.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +128 to +139
fn write_dev_webui_token(path: &std::path::Path, token: &str) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, token)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
}
Ok(())
}

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.

security-medium medium

The current implementation of write_dev_webui_token writes the token to disk first with default permissions (which may be world- or group-readable depending on the system's umask) and only restricts them to 0o600 afterwards. This creates a brief race condition where another local user could read the sensitive session-signing token.

On Unix platforms, you can use std::os::unix::fs::OpenOptionsExt to set the file permissions to 0o600 atomically at creation time.

fn write_dev_webui_token(path: &std::path::Path, token: &str) -> std::io::Result<()> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    #[cfg(unix)]
    {
        use std::io::Write;
        use std::os::unix::fs::OpenOptionsExt;
        let mut file = std::fs::OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .mode(0o600)
            .open(path)?;
        file.write_all(token.as_bytes())?;
    }
    #[cfg(not(unix))]
    {
        std::fs::write(path, token)?;
    }
    Ok(())
}

Comment on lines +95 to +102
if !self.no_launch && io::stdin().is_terminal() {
if let Some(surface) = prompt_launch_surface()? {
return launch_surface(surface);
}
} else {
println!();
println!("next: run `ironclaw-reborn repl` or `ironclaw-reborn serve` to finish setup");
}

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.

medium

When onboarding is run interactively on a TTY and the user chooses to skip the launch prompt (or enters an invalid choice), the command exits silently without printing the next steps. The instructions on how to manually start the REPL or Web UI are only printed in the else block when the prompt is skipped entirely (e.g., via --no-launch or non-interactive stdin).

Removing the else constraint ensures that the manual setup instructions are always printed if the automatic launch is skipped or not executed.

        if !self.no_launch && io::stdin().is_terminal() {
            if let Some(surface) = prompt_launch_surface()? {
                return launch_surface(surface);
            }
        }
        println!();
        println!("next: run `ironclaw-reborn repl` or `ironclaw-reborn serve` to finish setup");

Comment on lines +426 to +437
if let Some(dev_token) = dev_token_for_url.as_deref() {
// The SPA auto-signs-in from `?token=`, so a local-dev operator can
// click straight through without pasting the generated dev token.
let sign_in_url = format!("http://{listen_addr}/v2/?token={dev_token}");
eprintln!(
"ironclaw serve: open this URL to sign in automatically (dev token embedded):\n \
{sign_in_url}"
);
if !self.no_browser {
spawn_browser_open(sign_in_url);
}
}

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.

medium

There is an issue with the browser auto-open implementation when port 0 is used. If the operator starts the server with --port 0 (which is explicitly supported to let the OS pick a free port), listen_addr will contain port 0. The printed URL and the browser will attempt to open http://127.0.0.1:0/..., which is invalid.

While a fixed-duration sleep is a pragmatic tradeoff for waiting on resource initialization in non-critical paths, we should still leverage the bound_addr_tx oneshot channel in RebornWebuiServeOptions to resolve the actual bound address and print/open the correct URL with the dynamically allocated port.

References
  1. A fixed-duration sleep (e.g., tokio::time::sleep) can be a pragmatic tradeoff for waiting on resource initialization in non-critical paths, especially if a fallback mechanism (like logging) exists and a more robust solution is out of scope.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_reborn_cli/Cargo.toml`:
- Line 22: Update the default feature list in the CLI crate’s Cargo
configuration to remove webui-v2-beta, leaving root-llm-provider enabled.
Preserve webui-v2-beta as an opt-in feature so default builds do not include the
web UI stack.

In `@crates/ironclaw_reborn_cli/src/commands/serve.rs`:
- Around line 426-437: Map unspecified listen hosts to loopback when composing
the sign-in URL in the serve command, reusing the existing mapping from
webui_oauth_callback_origin. Use the mapped host for both the printed URL and
spawn_browser_open while preserving the embedded dev token and port.

In `@crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx`:
- Around line 244-246: Update the subtitle paragraph’s text color utility in the
onboarding page to use Tailwind v4’s `text-(--v2-accent)` shorthand instead of
the bracketed `text-[var(--v2-accent)]` form, leaving the surrounding markup and
styling unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 912d5b68-4003-4bd5-97d3-c7bb73cec54c

📥 Commits

Reviewing files that changed from the base of the PR and between c486eac and ac1349b.

📒 Files selected for processing (8)
  • crates/ironclaw_reborn_cli/Cargo.toml
  • crates/ironclaw_reborn_cli/src/commands/config/init.rs
  • crates/ironclaw_reborn_cli/src/commands/onboard.rs
  • crates/ironclaw_reborn_cli/src/commands/repl.rs
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
  • crates/ironclaw_reborn_config/src/profile.rs
  • crates/ironclaw_webui_v2/build.rs
  • crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx


[features]
default = ["root-llm-provider"]
default = ["root-llm-provider", "webui-v2-beta"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== crates/ironclaw_reborn_cli/Cargo.toml ==\n'
nl -ba crates/ironclaw_reborn_cli/Cargo.toml | sed -n '1,120p'

printf '\n== search for webui-v2-beta usage ==\n'
rg -n --hidden --glob '!target' --glob '!**/node_modules/**' 'webui-v2-beta|root-llm-provider|serve' crates/ironclaw_reborn_cli .claude AGENTS.md CLAUDE.md 2>/dev/null || true

Repository: nearai/ironclaw

Length of output: 238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== list likely repo policy files ==\n'
fd -a 'CLAUDE.md|AGENTS.md|\.claude' . 2>/dev/null || true

printf '\n== read top-level policy files if present ==\n'
for f in CLAUDE.md AGENTS.md .claude/rules .claude/rules.md .claude/CLAUDE.md; do
  if [ -f "$f" ]; then
    printf '\n--- %s ---\n' "$f"
    nl -ba "$f" | sed -n '1,240p'
  fi
done

Repository: nearai/ironclaw

Length of output: 7040


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Cargo.toml around feature defaults =='
sed -n '1,80p' crates/ironclaw_reborn_cli/Cargo.toml | nl -ba

echo
echo '== all references in CLI crate =='
rg -n 'webui-v2-beta|root-llm-provider|serve' crates/ironclaw_reborn_cli

Repository: nearai/ironclaw

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== crates/ironclaw_reborn_cli/Cargo.toml =='
sed -n '1,140p' crates/ironclaw_reborn_cli/Cargo.toml | awk '{printf "%4d  %s\n", NR, $0}'

echo
echo '== root policy files =='
for f in CLAUDE.md AGENTS.md crates/ironclaw_reborn_cli/AGENTS.md crates/ironclaw_webui_v2/CLAUDE.md crates/ironclaw_webui_v2/AGENTS.md; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,220p' "$f" | awk '{printf "%4d  %s\n", NR, $0}'
    echo
  fi
done

Repository: nearai/ironclaw

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== feature references in CLI crate =='
grep -RIn --exclude-dir=target --exclude-dir=node_modules -E 'webui-v2-beta|root-llm-provider|serve' crates/ironclaw_reborn_cli || true

echo
echo '== likely gating in workspace =='
grep -RIn --exclude-dir=target --exclude-dir=node_modules -E 'webui-v2-beta|webui.*beta|WebChat v2|HTTP gateway|axum|SSE|WS' crates | head -n 200 || true

Repository: nearai/ironclaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== crate-specific policy =='
if [ -f crates/ironclaw_reborn_cli/AGENTS.md ]; then
  sed -n '1,220p' crates/ironclaw_reborn_cli/AGENTS.md | awk '{printf "%4d  %s\n", NR, $0}'
fi

echo
echo '== workspace policy mentions of webui-v2-beta =='
grep -RIn --exclude-dir=target --exclude-dir=node_modules -E 'webui-v2-beta must be off by default|must be off by default|explicitly gate the WebChat v2 HTTP gateway' . || true

Repository: nearai/ironclaw

Length of output: 4596


Remove webui-v2-beta from default
crates/ironclaw_reborn_cli/AGENTS.md:35-58 says this feature is off by default and keeps serve out of default builds; crates/ironclaw_reborn_cli/Cargo.toml:22 flips it on, pulling the axum/auth/HTTP/SSE/WS stack into every default binary. Drop it from default unless that crate contract changes in the same PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_reborn_cli/Cargo.toml` at line 22, Update the default feature
list in the CLI crate’s Cargo configuration to remove webui-v2-beta, leaving
root-llm-provider enabled. Preserve webui-v2-beta as an opt-in feature so
default builds do not include the web UI stack.

Source: Coding guidelines

Comment on lines +426 to +437
if let Some(dev_token) = dev_token_for_url.as_deref() {
// The SPA auto-signs-in from `?token=`, so a local-dev operator can
// click straight through without pasting the generated dev token.
let sign_in_url = format!("http://{listen_addr}/v2/?token={dev_token}");
eprintln!(
"ironclaw serve: open this URL to sign in automatically (dev token embedded):\n \
{sign_in_url}"
);
if !self.no_browser {
spawn_browser_open(sign_in_url);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Sign-in URL embeds the raw bind address. When [webui].listen_host is 0.0.0.0/::, the printed and browser-opened URL is http://0.0.0.0:PORT/..., which most browsers (macOS/Windows) refuse to connect to. Reuse the same unspecified→loopback mapping already applied in webui_oauth_callback_origin when composing sign_in_url.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_reborn_cli/src/commands/serve.rs` around lines 426 - 437, Map
unspecified listen hosts to loopback when composing the sign-in URL in the serve
command, reusing the existing mapping from webui_oauth_callback_origin. Use the
mapped host for both the printed URL and spawn_browser_open while preserving the
embedded dev token and port.

Comment on lines +244 to +246
<p className="mx-auto mt-3 max-w-md text-balance text-lg font-medium text-[var(--v2-accent)]">
{t("onboarding.subtitle")}
</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n '\[var\(--' crates/ironclaw_webui_v2/frontend/src

Repository: nearai/ironclaw

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== tailwind deps ==\n'
rg -n '"tailwindcss"|`@tailwindcss`|tailwind' crates/ironclaw_webui_v2/frontend/package.json crates/ironclaw_webui_v2/frontend/pnpm-lock.yaml crates/ironclaw_webui_v2/frontend/yarn.lock crates/ironclaw_webui_v2/frontend/package-lock.json 2>/dev/null || true

printf '\n== tailwind config / docs pointers ==\n'
git ls-files 'crates/ironclaw_webui_v2/frontend/**' | rg 'tailwind|postcss|README|md$|css$'

printf '\n== onboarding snippet context ==\n'
sed -n '236,250p' crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx

Repository: nearai/ironclaw

Length of output: 5867


🌐 Web query:

Tailwind CSS v4 arbitrary values CSS variables parentheses brackets documentation

💡 Result:

In Tailwind CSS v4, the syntax for using CSS variables as arbitrary values has been updated to use parentheses instead of the square brackets [] used in v3 [1][2]. This change was made to resolve ambiguities in modern CSS [1]. Key changes and usage in v4: - Syntax Update: You can now use a CSS variable directly by wrapping it in parentheses, such as bg-(--brand-color) instead of the old bg-[var(--brand-color)] [1][3]. This syntax is a shorthand that automatically adds the var function for you [3]. - Resolving Ambiguities: If Tailwind cannot determine the utility type automatically, you can provide a "hint" using a CSS data type before the variable inside the parentheses [3]. For example: -

(forces a font-size utility) [3] -
(forces a color utility) [3] - Custom Utilities: When defining your own utilities in v4 using the @utility directive, you can support arbitrary CSS variables by using the --value([*]) syntax, which acts as a wildcard for variables [3][4]. - Arbitrary Properties: For completely arbitrary CSS properties that are not standard Tailwind utilities, square bracket notation remains the standard for defining properties and their values, such as [mask-type:luminance] or [--scroll-offset:56px] [3]. Note that in v4, commas in arbitrary values are no longer automatically replaced with spaces; you must use underscores to represent spaces (e.g., grid-cols-[max-content_auto]) [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== frontend README tailwind notes ==\n'
sed -n '1,220p' crates/ironclaw_webui_v2/frontend/README.md

printf '\n== app.css tailwind entrypoint ==\n'
sed -n '1,220p' crates/ironclaw_webui_v2/frontend/src/styles/app.css

Repository: nearai/ironclaw

Length of output: 9541


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n 'text-\(--|bg-\(--|border-\(--|accent-\(--|ring-\(--|fill-\(--|stroke-\(--|from-\(--|to-\(--|via-\(--' crates/ironclaw_webui_v2/frontend/src crates/ironclaw_webui_v2/frontend/public || true

Repository: nearai/ironclaw

Length of output: 153


🌐 Web query:

Tailwind v4 bg-[var(--brand-color)] still valid or deprecated arbitrary value CSS variable brackets

💡 Result:

In Tailwind CSS v4, the previous syntax for using CSS variables in arbitrary values has changed to resolve ambiguity [1][2]. While bg-[var(--brand-color)] remains technically valid in that it will be parsed, the recommended, modern syntax for referencing a CSS variable as an arbitrary value is bg-(--brand-color) [1][3]. Here is a breakdown of the current state of this syntax: 1. Recommended Syntax: Use bg-(--brand-color) [1][3]. This is treated as syntactic sugar that automatically injects the var() function for you [4][2]. 2. Previous Syntax: The older bg-[var(--brand-color)] is still supported by the parser [2], but you should migrate to the parenthetical syntax for better consistency and to avoid potential issues with newer CSS properties that allow dashed-idents (where var() is explicitly required) [2]. 3. Why it changed: In earlier versions, bg-[--brand-color] was often used as a shorthand, but it became ambiguous because some modern CSS properties accept raw dashed-idents (like --brand-color) without a var() wrapper [2]. To resolve this, Tailwind v4 introduced the (...) syntax to clearly distinguish when you are referencing a CSS variable [2]. If you are using Tailwind v4, you can safely update your classes to the ( ) notation [1][5]. If you need to perform calculations or combine variables, you can still use the [ ] notation with the full var() syntax if necessary (e.g., bg-[calc(var(--brand-color)/2)]) [6][7].

Citations:


Prefer the Tailwind v4 shorthand here. text-(--v2-accent) is the current form; text-[var(--v2-accent)] still parses, so this is a cleanup rather than a syntax fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx`
around lines 244 - 246, Update the subtitle paragraph’s text color utility in
the onboarding page to use Tailwind v4’s `text-(--v2-accent)` shorthand instead
of the bracketed `text-[var(--v2-accent)]` form, leaving the surrounding markup
and styling unchanged.

@loopstring
loopstring force-pushed the feat/reborn-local-dev-onboarding branch 2 times, most recently from cac6623 to da8a89c Compare July 19, 2026 20:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_reborn_cli/src/commands/serve.rs`:
- Around line 262-302: Update resolve_webui_credentials to return a
WebuiCredentialSource alongside the token and user ID, setting it when the
resolver auto-provisions credentials and otherwise marking operator-supplied
credentials. In the serve caller, destructure the source and derive
token_autoprovisioned with a source match instead of recomputing
token_env.is_none() && profile.allows_dev_credential_autoprovision(). Preserve
the existing sign-in URL and notice behavior based on the returned source.
- Around line 130-141: Update write_dev_webui_token to create the token file
with restrictive permissions before writing, using platform-appropriate
file-open options and 0o600 on Unix. Replace the
std::fs::write-then-set_permissions sequence with an initially secured file
creation/write flow, while preserving parent-directory creation and the
function’s existing Result behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 082b0253-9eca-4eaa-8108-d0323c215ee0

📥 Commits

Reviewing files that changed from the base of the PR and between cac6623 and da8a89c.

📒 Files selected for processing (8)
  • crates/ironclaw_reborn_cli/Cargo.toml
  • crates/ironclaw_reborn_cli/src/commands/config/init.rs
  • crates/ironclaw_reborn_cli/src/commands/onboard.rs
  • crates/ironclaw_reborn_cli/src/commands/repl.rs
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
  • crates/ironclaw_reborn_config/src/profile.rs
  • crates/ironclaw_webui_v2/build.rs
  • crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx

Comment on lines +130 to +141
fn write_dev_webui_token(path: &std::path::Path, token: &str) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, token)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
}
Ok(())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Dev token briefly written with default (world-readable) permissions before chmod.

std::fs::write creates the file with the process umask (commonly 0o644) and only afterward is it restricted to 0o600. Since this file doubles as the session-signing key, another local user on a shared box/CI runner could read it during that window. Open the file with mode 0o600 up front instead of writing-then-chmod.

🔒 Proposed fix — open with restrictive mode atomically
 fn write_dev_webui_token(path: &std::path::Path, token: &str) -> std::io::Result<()> {
     if let Some(parent) = path.parent() {
         std::fs::create_dir_all(parent)?;
     }
-    std::fs::write(path, token)?;
-    #[cfg(unix)]
-    {
-        use std::os::unix::fs::PermissionsExt;
-        std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
-    }
+    #[cfg(unix)]
+    {
+        use std::io::Write;
+        use std::os::unix::fs::OpenOptionsExt;
+        std::fs::OpenOptions::new()
+            .write(true)
+            .create(true)
+            .truncate(true)
+            .mode(0o600)
+            .open(path)?
+            .write_all(token.as_bytes())?;
+    }
+    #[cfg(not(unix))]
+    std::fs::write(path, token)?;
     Ok(())
 }
📝 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
fn write_dev_webui_token(path: &std::path::Path, token: &str) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, token)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
}
Ok(())
}
fn write_dev_webui_token(path: &std::path::Path, token: &str) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
#[cfg(unix)]
{
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(path)?
.write_all(token.as_bytes())?;
}
#[cfg(not(unix))]
std::fs::write(path, token)?;
Ok(())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_reborn_cli/src/commands/serve.rs` around lines 130 - 141,
Update write_dev_webui_token to create the token file with restrictive
permissions before writing, using platform-appropriate file-open options and
0o600 on Unix. Replace the std::fs::write-then-set_permissions sequence with an
initially secured file creation/write flow, while preserving parent-directory
creation and the function’s existing Result behavior.

Comment on lines +262 to +302
// Resolve the boot profile so local-dev laptops can boot the WebUI with
// zero env-var setup, while hosted/production profiles still fail closed
// and require an operator-supplied token + user id.
let profile = crate::runtime::effective_profile(boot_config, config_file.as_ref())?;
let token_env = env::var(env_token_var).ok();
let user_id_env = env::var(env_user_id_var).ok();
let token_autoprovisioned =
token_env.is_none() && profile.allows_dev_credential_autoprovision();
// Default the auto-provisioned WebUI user to the config's runtime owner
// so it matches `[identity].default_owner` (avoids the owner/WebUI-user
// mismatch that hides threads from the turn runner).
let dev_user_id_default = config_file
.as_ref()
.and_then(|file| file.identity.as_ref())
.and_then(|identity| identity.default_owner.as_deref())
.unwrap_or(DEFAULT_DEV_WEBUI_USER_ID);
let (token_value, user_id_raw) = resolve_webui_credentials(
profile,
token_env,
user_id_env,
boot_config.home().path(),
env_token_var,
env_user_id_var,
&boot_config.home().config_file_path(),
dev_user_id_default,
)?;
// Kept for the sign-in URL printed after the listen address resolves —
// `token_value` itself is moved into the authenticator below.
let dev_token_for_url = token_autoprovisioned.then(|| token_value.clone());
if token_autoprovisioned {
eprintln!(
"ironclaw serve: no {env_token_var} set — using an auto-generated local dev \
bearer token persisted under the Reborn home ({}). Set {env_token_var} to \
override. This is enabled only for local-dev profiles.",
boot_config
.home()
.path()
.join(DEV_WEBUI_TOKEN_FILE)
.display(),
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

token_autoprovisioned re-derives the exact branch condition already computed inside resolve_webui_credentials.

Line 269's token_env.is_none() && profile.allows_dev_credential_autoprovision() duplicates the match arm at line 80. If the resolver's branching ever changes (e.g. a future profile gets a different autoprovision rule), this caller-side copy silently drifts out of sync with the actual credential source and the sign-in-URL/notice logic would misfire. Have the resolver return the source alongside the credentials instead of making the caller re-derive it.

♻️ Proposed refactor — return credential source from the resolver
enum WebuiCredentialSource {
    OperatorSupplied,
    AutoProvisioned,
}

fn resolve_webui_credentials(...) -> anyhow::Result<(String, String, WebuiCredentialSource)> {
    let mut source = WebuiCredentialSource::OperatorSupplied;
    let token = match token_env {
        Some(value) => value,
        None if profile.allows_dev_credential_autoprovision() => {
            source = WebuiCredentialSource::AutoProvisioned;
            load_or_create_dev_webui_token(home_path)?
        }
        None => return Err(/* ... */),
    };
    // ...
    Ok((token, user_id_raw, source))
}

Caller then does let token_autoprovisioned = matches!(source, WebuiCredentialSource::AutoProvisioned); instead of recomputing the predicate.

As per coding guidelines, "Keep functions focused and extract helpers when logic is reused."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_reborn_cli/src/commands/serve.rs` around lines 262 - 302,
Update resolve_webui_credentials to return a WebuiCredentialSource alongside the
token and user ID, setting it when the resolver auto-provisions credentials and
otherwise marking operator-supplied credentials. In the serve caller,
destructure the source and derive token_autoprovisioned with a source match
instead of recomputing token_env.is_none() &&
profile.allows_dev_credential_autoprovision(). Preserve the existing sign-in URL
and notice behavior based on the returned source.

Source: Coding guidelines

@loopstring
loopstring force-pushed the feat/reborn-local-dev-onboarding branch from da8a89c to 917c21a Compare July 19, 2026 20:37
…e, REPL model wizard, onboard launcher

Make first-run onboarding work end-to-end for a local-dev laptop without
manual env setup, while keeping hosted/production fail-closed.

- serve (local-dev): auto-generate + persist the WebUI bearer token and
  default operator user id when unset, print a `?token=` sign-in URL, and
  auto-open the browser (--no-browser to opt out). Production/hosted still
  require operator-supplied token + user id (RebornProfile::
  allows_dev_credential_autoprovision gates this).
- repl: first-run model wizard — when no LLM is configured, prompt for a
  provider/model; when a provider is set but its API key env is missing,
  prompt for the key (set for the session) instead of hanging on the first
  message. Skipped for --no-setup and non-interactive stdin.
- onboard: after scaffolding, prompt to launch REPL or Web UI and re-exec
  into it. Web UI option is gated on the webui-v2-beta feature so a
  CLI-only build never offers a `serve` subcommand it lacks.
- config init template: leave [llm.default] commented so a fresh onboard
  leads to model setup (matches the sparse first-run seed) instead of
  pre-activating a provider that suppresses the setup prompt.
- webui-v2-beta is now a default feature of ironclaw_reborn_cli so a
  default build ships both REPL and Web UI.
- webui_v2 build.rs: clear, actionable error when corepack/pnpm is missing
  instead of an opaque `Os NotFound`.
- onboarding welcome subtitle: more prominent (accent color, balanced).

Tests: profile predicate; serve credential resolver (auto-provision vs
fail-closed vs env-wins); repl provider-choice parse; onboard launch-choice
parse.
@loopstring
loopstring force-pushed the feat/reborn-local-dev-onboarding branch from 917c21a to ed3b151 Compare July 19, 2026 20:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx (1)

244-246: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer the Tailwind v4 shorthand here. text-(--v2-accent) is the current form; text-[var(--v2-accent)] still parses, so this is a cleanup rather than a syntax fix.

♻️ Proposed refactor
-          <p className="mx-auto mt-3 max-w-md text-balance text-lg font-medium text-[var(--v2-accent)]">
+          <p className="mx-auto mt-3 max-w-md text-balance text-lg font-medium text-(--v2-accent)">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx`
around lines 244 - 246, Update the subtitle paragraph’s Tailwind text color
utility to use the v4 shorthand `text-(--v2-accent)` instead of the bracketed
`text-[var(--v2-accent)]`, leaving the surrounding classes and translation
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_reborn_cli/src/commands/repl.rs`:
- Around line 146-160: Update provider_requires_api_key at the
admin.list(Some(id), true).ok() call to add an inline “silent-ok” comment
explicitly stating that unreadable provider metadata intentionally defaults to
false and avoids prompting for an API key.

---

Duplicate comments:
In `@crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx`:
- Around line 244-246: Update the subtitle paragraph’s Tailwind text color
utility to use the v4 shorthand `text-(--v2-accent)` instead of the bracketed
`text-[var(--v2-accent)]`, leaving the surrounding classes and translation
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4d50aaae-c2d0-46d0-857d-d47cfb76c37a

📥 Commits

Reviewing files that changed from the base of the PR and between da8a89c and 917c21a.

📒 Files selected for processing (8)
  • crates/ironclaw_reborn_cli/Cargo.toml
  • crates/ironclaw_reborn_cli/src/commands/config/init.rs
  • crates/ironclaw_reborn_cli/src/commands/onboard.rs
  • crates/ironclaw_reborn_cli/src/commands/repl.rs
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
  • crates/ironclaw_reborn_config/src/profile.rs
  • crates/ironclaw_webui_v2/build.rs
  • crates/ironclaw_webui_v2/frontend/src/pages/onboarding/onboarding-page.tsx

Comment on lines +146 to +160
fn provider_requires_api_key(
admin: &ironclaw_reborn_composition::RebornProviderAdmin,
provider_id: Option<&str>,
) -> bool {
let Some(id) = provider_id else {
return false;
};
admin
.list(Some(id), true)
.ok()
.and_then(|list| list.providers.into_iter().next())
.and_then(|provider| provider.metadata)
.map(|metadata| metadata.api_key_required)
.unwrap_or(false)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

.ok() swallows the provider-metadata read without a silent-ok marker. admin.list(...).ok() drops the error on a settings read and continues with false. The default-false behavior is defensible here (an unreadable provider just shouldn't nag), but the guideline requires the fallback be named explicitly.

As per coding guidelines: "justified fallbacks must include an inline // silent-ok: <reason> comment naming the operation."

♻️ Add the marker
     let Some(id) = provider_id else {
         return false;
     };
+    // silent-ok: provider-metadata lookup — an unreadable/unknown provider just
+    // means "don't nag for a key", not a lost authoritative read.
     admin
         .list(Some(id), true)
         .ok()
📝 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
fn provider_requires_api_key(
admin: &ironclaw_reborn_composition::RebornProviderAdmin,
provider_id: Option<&str>,
) -> bool {
let Some(id) = provider_id else {
return false;
};
admin
.list(Some(id), true)
.ok()
.and_then(|list| list.providers.into_iter().next())
.and_then(|provider| provider.metadata)
.map(|metadata| metadata.api_key_required)
.unwrap_or(false)
}
fn provider_requires_api_key(
admin: &ironclaw_reborn_composition::RebornProviderAdmin,
provider_id: Option<&str>,
) -> bool {
let Some(id) = provider_id else {
return false;
};
// silent-ok: provider-metadata lookup — an unreadable/unknown provider just
// means "don't nag for a key", not a lost authoritative read.
admin
.list(Some(id), true)
.ok()
.and_then(|list| list.providers.into_iter().next())
.and_then(|provider| provider.metadata)
.map(|metadata| metadata.api_key_required)
.unwrap_or(false)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_reborn_cli/src/commands/repl.rs` around lines 146 - 160,
Update provider_requires_api_key at the admin.list(Some(id), true).ok() call to
add an inline “silent-ok” comment explicitly stating that unreadable provider
metadata intentionally defaults to false and avoids prompting for an API key.

Source: Coding guidelines

@loopstring

Copy link
Copy Markdown
Contributor Author

Superseded: main independently landed the interactive credential wizard, WebUI token provisioning + serve fallback, and login link. The genuinely net-new bits (onboard launcher, serve auto-open, clear corepack error) are in #6297; REPL spinner+markdown is #6289.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: regular 2-5 merged PRs risk: low Changes to docs, tests, or low-risk modules size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants