feat(ux): complete UX overhaul — design system, onboarding, web polish#1277
feat(ux): complete UX overhaul — design system, onboarding, web polish#1277ilblackdragon merged 25 commits intostagingfrom
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request delivers a significant user experience upgrade across the application, focusing on consistency, ease of use, and intelligent personalization. It integrates new AI capabilities for task management and routine automation, while also bolstering security and improving the overall polish of both the command-line and web interfaces. Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Pull request overview
Comprehensive UX overhaul spanning CLI and web surfaces, plus new “conversational onboarding” via workspace bootstrap/profile files. The PR also adds safety scanning for system-prompt-injected workspace documents and expands gateway status payloads for frontend display.
Changes:
- Introduces a shared CLI formatting “design system” (
cli::fmt) and refreshes boot/status/doctor/REPL output accordingly. - Adds workspace seed templates + bootstrap greeting/onboarding flow, psychographic profile scaffolding, and prompt-injection scanning for system-prompt-injected workspace files.
- Polishes web gateway UI structure (Settings tab + subtabs), adds SSE/WS event types (turn cost), and updates E2E/integration tests to match.
Reviewed changes
Copilot reviewed 65 out of 66 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/ws_gateway_integration.rs | Updates test gateway state construction to include active_config. |
| tests/support/test_rig.rs | Adds bootstrap-aware test rig behavior (optionally preserve bootstrap + ensure gateway channel name). |
| tests/support/test_channel.rs | Makes test channel name configurable (needed for bootstrap tests). |
| tests/support/gateway_workflow_harness.rs | Updates harness gateway state construction to include active_config. |
| tests/openai_compat_integration.rs | Updates test gateway state construction to include active_config. |
| tests/e2e_builtin_tool_coverage.rs | Updates expected cron expression to normalized 7-field format. |
| tests/e2e_advanced_traces.rs | Adds E2E test asserting bootstrap greeting fires on fresh workspace. |
| tests/e2e/scenarios/test_wasm_lifecycle.py | Updates navigation to match new Settings + Extensions subtab layout. |
| tests/e2e/scenarios/test_skills.py | Updates navigation to Settings > Skills and adapts removal confirmation flow. |
| tests/e2e/scenarios/test_extensions.py | Refactors tests for new Settings subtabs (Extensions/MCP/Channels) and custom confirm modal. |
| tests/e2e/helpers.py | Adds selectors for Settings subtabs/panels + confirm modal; updates tab list. |
| src/workspace/seeds/USER.md | New seed template for user context. |
| src/workspace/seeds/TOOLS.md | New seed template for environment-specific tool notes. |
| src/workspace/seeds/SOUL.md | New seed template for assistant “values/boundaries”. |
| src/workspace/seeds/README.md | New seed template documenting workspace structure. |
| src/workspace/seeds/MEMORY.md | New seed template for long-term memory file. |
| src/workspace/seeds/IDENTITY.md | New seed template for assistant identity. |
| src/workspace/seeds/HEARTBEAT.md | New seed template for heartbeat checklist. |
| src/workspace/seeds/GREETING.md | Adds static first-run greeting content persisted/broadcast on bootstrap. |
| src/workspace/seeds/BOOTSTRAP.md | Adds detailed first-run bootstrap instructions for conversational onboarding. |
| src/workspace/seeds/AGENTS.md | New seed template for session routines/operational instructions. |
| src/workspace/mod.rs | Adds system-prompt file injection scanning, bootstrap flags, profile injection logic, and profile→document sync. |
| src/workspace/document.rs | Adds canonical paths for context/profile.json and context/assistant-directives.md. |
| src/tools/builtin/routine.rs | Normalizes cron expressions to 7-field format before validation/storage. |
| src/tools/builtin/memory.rs | Routes injection rejections to NotAuthorized, marks bootstrap completed, and syncs derived docs on profile write. |
| src/setup/wizard.rs | Adds --step selective onboarding, provider autodetect from env, and refreshes completion summary UX. |
| src/setup/prompts.rs | Updates interactive prompt rendering (step indicator, banner, checkbox UI). |
| src/setup/profile_evolution.rs | New module to generate weekly profile-evolution prompts + routine prompt template. |
| src/setup/mod.rs | Exposes SetupError, exports profile_evolution, and documents conversational onboarding. |
| src/setup/README.md | Documents that personal onboarding happens via workspace bootstrap in the running assistant. |
| src/settings.rs | Adds profile_onboarding_completed persisted flag (alias supported). |
| src/main.rs | Adds top-level error formatting with hints, startup timing, ensures WASM channels dir exists, and publishes active config snapshot. |
| src/llm/nearai_chat.rs | Updates NEAR AI default model and provides fallback model list for setup wizard. |
| src/llm/mod.rs | Re-exports DEFAULT_MODEL and default_models(). |
| src/lib.rs | Exposes new profile module. |
| src/error.rs | Adds WorkspaceError::InjectionRejected. |
| src/config/llm.rs | Updates default NEAR AI model to crate::llm::DEFAULT_MODEL. |
| src/cli/status.rs | Switches status output to cli::fmt key/value lines and updated layout. |
| src/cli/mod.rs | Exposes cli::fmt and adds onboard --step flag (deprecates older single-step flags). |
| src/cli/fmt.rs | New shared terminal formatting/color token module with NO_COLOR + TTY detection. |
| src/cli/doctor.rs | Groups diagnostics and uses cli::fmt for consistent rendering. |
| src/channels/web/ws.rs | Updates test gateway state construction to include active_config. |
| src/channels/web/types.rs | Adds SSE event turn_cost and wires event typing for WS/SSE payloads. |
| src/channels/web/test_helpers.rs | Updates helper gateway state construction to include active_config. |
| src/channels/web/static/index.html | Adds Settings tab/subtabs layout, confirm modal, aria attributes, and Highlight.js assets. |
| src/channels/web/static/i18n/zh-CN.js | Adds Settings and new UI strings, updates extensions wording. |
| src/channels/web/static/i18n/en.js | Adds Settings and new UI strings, updates extensions wording. |
| src/channels/web/sse.rs | Wires SSE event type mapping for turn_cost. |
| src/channels/web/server.rs | Adds ActiveConfigSnapshot and returns active config fields in /api/gateway/status. |
| src/channels/web/mod.rs | Adds with_active_config and maps StatusUpdate::TurnCost → SSE event. |
| src/channels/wasm/wrapper.rs | Explicitly skips web-only status updates (suggestions + turn cost) for WASM channels. |
| src/channels/repl.rs | Migrates REPL output to cli::fmt, improves truncation, and refreshes /help and approval rendering. |
| src/channels/channel.rs | Adds StatusUpdate::TurnCost. |
| src/boot_screen.rs | Reworks boot screen to a 2-tier summary and links to ironclaw status for full details. |
| src/app.rs | Loads profile_onboarding_completed from settings to suppress bootstrap injection for existing users. |
| src/agent/thread_ops.rs | Emits StatusUpdate::TurnCost after a response. |
| src/agent/routine.rs | Adds cron normalization and updates cron parsing to accept 5/6/7-field expressions. |
| src/agent/dispatcher.rs | Improves thinking/status messages with tool-name-aware wording. |
| src/agent/agent_loop.rs | Adds bootstrap greeting persistence + broadcast flow driven by workspace bootstrap flag. |
| skills/routine-advisor/SKILL.md | New skill to suggest routines based on user patterns + cron examples. |
| skills/delegation/SKILL.md | New skill to guide task delegation + persistence via memory/routines. |
| CLAUDE.md | Updates repo map documentation to include new profile.rs. |
| .env.example | Updates example NEAR AI model default. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Emit per-turn cost summary | ||
| { | ||
| let usage = self.cost_guard().model_usage().await; | ||
| let (total_in, total_out, total_cost) = | ||
| usage | ||
| .values() | ||
| .fold((0u64, 0u64, rust_decimal::Decimal::ZERO), |acc, m| { | ||
| ( | ||
| acc.0 + m.input_tokens, | ||
| acc.1 + m.output_tokens, | ||
| acc.2 + m.cost, | ||
| ) | ||
| }); | ||
| let _ = self | ||
| .channels | ||
| .send_status( | ||
| &message.channel, | ||
| StatusUpdate::TurnCost { | ||
| input_tokens: total_in as u32, | ||
| output_tokens: total_out as u32, | ||
| cost_usd: format!("${:.4}", total_cost), | ||
| }, | ||
| &message.metadata, | ||
| ) | ||
| .await; |
There was a problem hiding this comment.
The u32→u64 overflow concern is fixed in this PR. The cumulative-vs-per-turn design issue is a pre-existing limitation of the CostGuard API — tracking per-turn deltas requires snapshotting before/after, which is a separate change. Filed for follow-up.
src/channels/repl.rs
Outdated
| // Top border: ┌ tool_name requires approval ─── | ||
| let top_label = format!(" {tool_name} requires approval "); | ||
| let top_fill = box_width.saturating_sub(top_label.len() + 1); | ||
| let top_border = format!( | ||
| "\u{250C}\x1b[33m{top_label}\x1b[0m{}", | ||
| "\u{250C}{}{top_label}{}{}", | ||
| fmt::warning(), | ||
| fmt::reset(), | ||
| "\u{2500}".repeat(top_fill) | ||
| ); | ||
|
|
||
| // Bottom border: └─ short_id ───── | ||
| let bot_label = format!(" {short_id} "); | ||
| let bot_fill = box_width.saturating_sub(bot_label.len() + 2); | ||
| let bot_border = format!( | ||
| "\u{2514}\u{2500}\x1b[90m{bot_label}\x1b[0m{}", | ||
| "\u{2500}".repeat(bot_fill) | ||
| ); | ||
| // Bottom border: └───── | ||
| let bot_fill = box_width.saturating_sub(1); | ||
| let bot_border = format!("\u{2514}{}", "\u{2500}".repeat(bot_fill)); |
There was a problem hiding this comment.
Fixed — the ASCII box was replaced entirely with a clean horizontal-rule card plus an inquire::Select interactive selector in commit 61d3ba0.
| /// - stdout is not a terminal (pipe, file redirect, CI) | ||
| fn colors_enabled() -> bool { | ||
| if std::env::var_os("NO_COLOR").is_some() { | ||
| return false; | ||
| } | ||
| std::io::stdout().is_terminal() |
There was a problem hiding this comment.
Valid. The suggested fix (check both stdout and stderr) is reasonable. In practice, the most common redirect scenario is piping stdout while keeping stderr on a TTY, so checking either would be an improvement. Will address in a follow-up.
There was a problem hiding this comment.
Code Review
The pull request includes several changes to improve the IronClaw agent. The default NEARAI_MODEL in .env.example was updated to Qwen/Qwen3.5-122B-A10B. A new profile.rs file was added to the src/ directory for psychographic profile types. The delegation and routine-advisor skills were added. A bootstrap greeting was added to the agent loop. The agent dispatcher was updated to provide more contextual thinking messages based on tool names. The routine advisor was updated to normalize cron expressions. The boot screen was updated to be more compact. The REPL channel was updated to use a new color scheme and to truncate long strings. The web gateway channel was updated to include per-turn cost summaries. The web gateway was updated to include a snapshot of the active configuration. The web gateway's javascript was updated to include a number of new features, including a settings tab, a confirmation modal, and a welcome card. A number of other changes were made to improve the agent's performance and stability. The code reviewer suggested that the errors from get_or_create_assistant_conversation and persist_assistant_response should be handled to prevent silent failures. The code reviewer also suggested that the current implementation of normalize_cron_expression for 5-field expressions might be subtly incorrect and that a more robust approach would be to join the fields collected from split_whitespace. The code reviewer also suggested that the logic for handling the bootstrap process can be simplified.
| let thread_id = store | ||
| .get_or_create_assistant_conversation("default", "gateway") | ||
| .await | ||
| .ok(); | ||
| if let Some(id) = thread_id { | ||
| self.persist_assistant_response(id, "gateway", "default", BOOTSTRAP_GREETING) | ||
| .await; | ||
| } | ||
| thread_id |
There was a problem hiding this comment.
The errors from get_or_create_assistant_conversation and persist_assistant_response are being ignored. This can lead to silent failures where the bootstrap greeting is not persisted to the database, which could be confusing for new users. It would be more robust to handle these potential errors, for example by logging them. This aligns with the principle of robust error handling and logging, as seen in the rule regarding tokio::task::spawn_blocking.
let thread_id = match store
.get_or_create_assistant_conversation("default", "gateway")
.await
{
Ok(id) => {
if let Err(e) = self
.persist_assistant_response(id, "gateway", "default", BOOTSTRAP_GREETING)
.await
{
tracing::warn!("Failed to persist bootstrap greeting: {}", e);
}
Some(id)
}
Err(e) => {
tracing::warn!("Failed to get or create bootstrap conversation: {}", e);
None
}
};References
- When handling errors, log them to capture debugging information and prevent silent failures. It is good practice to distinguish between different error types in the message.
There was a problem hiding this comment.
Good suggestion. The bootstrap greeting is best-effort (non-critical path), so silent failure is intentional — but logging a warning is better than silently dropping. Will add tracing::warn in a follow-up.
src/agent/routine.rs
Outdated
| pub fn normalize_cron_expression(schedule: &str) -> String { | ||
| let fields: Vec<&str> = schedule.split_whitespace().collect(); | ||
| match fields.len() { | ||
| 5 => format!("0 {} *", schedule.trim()), |
There was a problem hiding this comment.
The current implementation of normalize_cron_expression for 5-field expressions might be subtly incorrect. Using format!("0 {} *", schedule.trim()) could lead to issues if schedule.trim() itself contains extra spaces that are not handled by split_whitespace. A more robust approach would be to join the fields collected from split_whitespace.
| 5 => format!("0 {} *", schedule.trim()), | |
| 5 => format!("0 {} *", fields.join(" ")), |
There was a problem hiding this comment.
Good catch — fields.join(" ") is more robust than re-trimming. Applied suggestion.
| let bootstrap_injected = if self.is_bootstrap_completed() { | ||
| if self | ||
| .read(paths::BOOTSTRAP) | ||
| .await | ||
| .is_ok_and(|d| !d.content.is_empty()) | ||
| { | ||
| tracing::warn!( | ||
| "BOOTSTRAP.md still exists but profile_onboarding_completed is set; \ | ||
| suppressing bootstrap injection" | ||
| ); | ||
| } | ||
| false | ||
| } else if let Ok(doc) = self.read(paths::BOOTSTRAP).await | ||
| && !doc.content.is_empty() | ||
| { | ||
| parts.push(format!( | ||
| "## First-Run Bootstrap\n\n\ | ||
| A BOOTSTRAP.md file exists in the workspace. Read and follow it, \ | ||
| then delete it when done.\n\n{}", | ||
| doc.content | ||
| )); | ||
| } | ||
| parts.push(format!("## First-Run Bootstrap\n\n{}", doc.content)); | ||
| true | ||
| } else { | ||
| false | ||
| }; |
There was a problem hiding this comment.
The logic for handling the bootstrap process can be simplified. Instead of checking is_bootstrap_completed and then reading the file, you can combine these checks for better readability and to avoid nested if statements.
| let bootstrap_injected = if self.is_bootstrap_completed() { | |
| if self | |
| .read(paths::BOOTSTRAP) | |
| .await | |
| .is_ok_and(|d| !d.content.is_empty()) | |
| { | |
| tracing::warn!( | |
| "BOOTSTRAP.md still exists but profile_onboarding_completed is set; \ | |
| suppressing bootstrap injection" | |
| ); | |
| } | |
| false | |
| } else if let Ok(doc) = self.read(paths::BOOTSTRAP).await | |
| && !doc.content.is_empty() | |
| { | |
| parts.push(format!( | |
| "## First-Run Bootstrap\n\n\ | |
| A BOOTSTRAP.md file exists in the workspace. Read and follow it, \ | |
| then delete it when done.\n\n{}", | |
| doc.content | |
| )); | |
| } | |
| parts.push(format!("## First-Run Bootstrap\n\n{}", doc.content)); | |
| true | |
| } else { | |
| false | |
| }; | |
| let bootstrap_injected = if self.is_bootstrap_completed() { | |
| if self.read(paths::BOOTSTRAP).await.is_ok_and(|d| !d.content.is_empty()) { | |
| tracing::warn!( | |
| "BOOTSTRAP.md still exists but profile_onboarding_completed is set; suppressing bootstrap injection" | |
| ); | |
| } | |
| false | |
| } else if let Ok(doc) = self.read(paths::BOOTSTRAP).await && !doc.content.is_empty() { | |
| parts.push(format!("## First-Run Bootstrap\n\n{}", doc.content)); | |
| true | |
| } else { | |
| false | |
| }; |
There was a problem hiding this comment.
The suggested refactoring uses let-chains (if let Ok(doc) = ... && !doc.content.is_empty()) which requires a newer Rust edition. The current nested form is equivalent and works on stable. Will revisit when we bump MSRV.
zmanian
left a comment
There was a problem hiding this comment.
Review: massive UX overhaul + psychographic profiling + identity file safety
This is a 36-commit, 7300-line PR spanning psychographic profiling, identity file injection scanning, bootstrap/onboarding redesign, CLI design system, web UI overhaul, i18n, new skills, and new SSE events. Observations below.
Critical: CI did not run (fork PR)
Only classify/scope ran. No Clippy (any feature combo), formatting, panic-check, or regression enforcement. For a PR touching 66 files across Rust, JS, CSS, and HTML, full CI is mandatory before merge. Either push a maintainer commit to the branch or run local CI.
Concerning: scope should be split
This combines at least 4 orthogonal features:
- Psychographic profiling (profile.rs, onboarding_chat.rs, profile_evolution.rs, seeds)
- Identity file write protection (memory.rs -> workspace injection scanning)
- Web UI overhaul (CSS/JS/HTML, i18n, settings tabs)
- CLI design system (fmt.rs, boot screen, REPL, doctor/status)
Each would benefit from independent review and rollback capability. I'd strongly suggest splitting this so each feature can land and be tested independently.
Concerning: [skip-regression-check] on functional commits
Commits 35 and 36 both use [skip-regression-check] but contain substantial functional changes (SSE event handling, streaming debounce changes, boot screen restructuring, connection status logic). These aren't pure style changes.
Concerning: highlight.js CDN dependency
Commit 36 introduces syntax highlighting via a CDN. External CDN dependencies create availability risks (offline users, CDN outages) and supply chain risk (CDN compromise). Consider bundling or using a self-hosted copy.
Concerning: profile types use String instead of enums
CommunicationPreferences fields (detail_level, formality, tone, etc.) are all String. Per project conventions ("prefer strong types over strings"), these should be enums. This makes invalid states representable and means deserialization won't catch typos.
Positives:
- Identity file injection scanning evolution is good -- moving from tool layer (memory.rs) to Workspace::write/append (commit 20) ensures all write paths are protected
- LazyLock for the sanitizer (commit 33) avoids rebuilding Aho-Corasick on every write
- The non-empty profile check for bootstrap suppression is a solid edge case fix
- CSP-compliant event handlers (commit 7) is a good security improvement
- The
routine_trigger_messageCow pattern is efficient
Minor notes:
deserialize_trait_scorein profile.rs silently returns 50 on deser failure -- intentional for LLM robustness but worth a commentStatusUpdate::TurnCost-- verify the enum variant definition is included in this diff (I didn't find it in the patch)- The
env_or_overridefix for NEARAI_API_KEY (commit 5) is a standalone bug fix that should land independently
zmanian
left a comment
There was a problem hiding this comment.
Updated Review: UX overhaul + psychographic profiling + identity file safety
Updating my earlier review -- scope is fine as a single PR given the interconnected nature of the changes.
Critical: CI did not run (fork PR)
Only classify/scope ran. No Clippy (any feature combo), formatting, panic-check, or regression enforcement. For a PR touching 66 files across Rust, JS, CSS, and HTML, full CI is mandatory before merge. Either push a maintainer commit to the branch or run local CI.
Concerning: [skip-regression-check] on functional commits
Commits 35 and 36 both use [skip-regression-check] but contain substantial functional changes (SSE event handling, streaming debounce changes, boot screen restructuring, connection status logic). These aren't pure style changes.
Concerning: highlight.js CDN dependency
Commit 36 introduces syntax highlighting via a CDN. External CDN dependencies create availability risks (offline users, CDN outages) and supply chain risk (CDN compromise). Consider bundling or using a self-hosted copy.
Concerning: profile types use String instead of enums
CommunicationPreferences fields (detail_level, formality, tone, etc.) are all String. Per project conventions ("prefer strong types over strings"), these should be enums. This makes invalid states representable and means deserialization won't catch typos.
Positives:
- Identity file injection scanning evolution is good -- moving from tool layer (memory.rs) to Workspace::write/append (commit 20) ensures all write paths are protected
- LazyLock for the sanitizer (commit 33) avoids rebuilding Aho-Corasick on every write
- The non-empty profile check for bootstrap suppression is a solid edge case fix
- CSP-compliant event handlers (commit 7) is a good security improvement
- The
routine_trigger_messageCow pattern is efficient - The
env_or_overridefix for NEARAI_API_KEY (commit 5) is a nice catch
Minor notes:
deserialize_trait_scorein profile.rs silently returns 50 on deser failure -- intentional for LLM robustness but worth a commentStatusUpdate::TurnCost-- verify the enum variant definition is included in this diff (I didn't find it in the patch)
e447de7 to
2da8835
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove failed message before retry to prevent duplicate user messages - Revert connect-src to 'self' — CDN hosts only need script-src - Use red for Deny confirmation in REPL approval selector Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 36 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| result |
There was a problem hiding this comment.
main() prints a formatted top-level error via format_top_level_error(e) but then returns the same Err. When main returns Result, Rust’s termination handler will also print the error (Debug), so users may see duplicate error output. Consider handling the exit yourself after printing (e.g., print + std::process::exit(1)), or return a type that doesn’t auto-print on Err.
| } | |
| result | |
| std::process::exit(1); | |
| } | |
| Ok(()) |
There was a problem hiding this comment.
Valid — pre-existing behavior. The format_top_level_error was added for user-friendly hints (e.g. 'check your .env file'), and Rust's Debug output adds noise. Will switch to print + process::exit(1) in a follow-up.
| self.settings.llm_backend = Some(b.trim().to_string()); | ||
| } else if env_or_override("NEARAI_API_KEY").is_some() { | ||
| if let Ok(b) = std::env::var("LLM_BACKEND") { | ||
| self.settings.llm_backend = Some(b); |
There was a problem hiding this comment.
In quick setup, LLM_BACKEND is accepted as-is via std::env::var and stored even if it’s an empty string. That can lock llm_backend to Some("") and prevent the later key-based auto-detection branches from running. Filter out empty/whitespace values (trim + non-empty) before setting self.settings.llm_backend.
| self.settings.llm_backend = Some(b); | |
| let trimmed = b.trim(); | |
| if !trimmed.is_empty() { | |
| self.settings.llm_backend = Some(trimmed.to_string()); | |
| } else if std::env::var("NEARAI_API_KEY").is_ok() { | |
| self.settings.llm_backend = Some("nearai".to_string()); | |
| } else if std::env::var("ANTHROPIC_API_KEY").is_ok() | |
| || std::env::var("ANTHROPIC_OAUTH_TOKEN").is_ok() | |
| { | |
| self.settings.llm_backend = Some("anthropic".to_string()); | |
| } else if std::env::var("OPENAI_API_KEY").is_ok() { | |
| self.settings.llm_backend = Some("openai".to_string()); | |
| } else if std::env::var("OPENROUTER_API_KEY").is_ok() { | |
| self.settings.llm_backend = Some("openrouter".to_string()); | |
| } |
There was a problem hiding this comment.
Valid edge case — pre-existing code. An empty LLM_BACKEND env var would bypass auto-detection. Will add trim + non-empty filter in a follow-up.
# Conflicts: # src/cli/mod.rs # src/setup/wizard.rs
nearai#1277) * feat(ux): complete UX overhaul — design system, boot screen, onboarding, web polish Shared design system: CSS custom properties for spacing, typography, transitions, and color tokens used across web UI and boot screen. Boot screen: compact feature-tags line showing enabled subsystems (db, tools, routines, heartbeat, skills, sandbox, embeddings) at a glance. Downgrade startup info logs (libSQL, webhook, workspace seed) to debug level since the boot screen now covers this. Onboarding wizard: model picker with live API fetch, provider-aware auth flow, improved error recovery and progress display. Web UI: ARIA attributes, welcome card, streaming debounce, connection status banner, skeleton loaders, send cooldown. CLI: doctor command enhancements, status command cleanup, REPL banner consolidation, shared fmt module. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ux): Apple-level design refinements — spring physics, glass morphism, chat polish Merge staging theme support (dark/light/system toggle) and layer UX polish on top: spring-physics motion, glass morphism depth, chat experience improvements, and responsive mobile refinements. Design system: - Restore and extend design token system (spacing, typography, timing, easing) with legacy aliases for theme compatibility - Add shadow tiers, accent glow, glass morphism, spring easing tokens - Tokens defined in both dark (:root) and light ([data-theme="light"]) Micro-interactions (Phase 2): - Spring-overshoot message entry animation (slideUp) - Spring-scale button press on all interactive buttons - Tab crossfade animation, tool card smooth accordion (max-height) - Modal scale(0.95) + blur(8px) entry, toast spring slide - Sidebar width crossfade, card hover lift Visual depth (Phase 3): - Tab bar glass morphism + surface highlight + sliding indicator - Active tab accent background pill - Assistant message accent left border, user message bubble tail - Floating input area (rounded + shadow + margin) Chat polish (Phase 4): - Smooth streaming cursor (cursorPulse), message hover timestamps - Time separators (Today/Yesterday/date) - Textarea smooth auto-expand, send button glow Settings & forms (Phase 5): - iOS-style toggle switches for boolean settings - Input focus glow, save feedback spring animation - Welcome card with gradient background + proper spacing - Sticky settings group headers with glass backdrop Accessibility & mobile (Phase 6): - Animated focus ring, prefers-reduced-motion global kill-switch - Touch target audit (44px min), mobile bottom-sheet modals - Mobile bottom tab bar, toast redesign (icon + border + countdown) - Thread hover translateX, badge in_progress pulse Bug fixes: - Gateway/TEE popover z-index (tab-bar z-index: 200, popovers 500) - Connection lost banner as fixed top bar instead of flex child - Sidebar collapse keeps toggle + new thread buttons visible - Downgrade noisy startup logs (db, webhook, vector) to debug - Remove green dot pulse animation on connected status - Deduplicate confirm-modal in HTML, add tab-indicator div Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(web): mobile layout improvements — sidebar toggle, settings drill-down, tab bar polish - Fix mobile sidebar toggle: use expanded-mobile class instead of collapsed, add backdrop overlay, auto-close on thread select, outside-click dismiss - Settings: replace cramped horizontal tabs with drill-down navigation (category list → detail view → back button) - Bottom tab bar: add glass morphism, hide theme toggle, flip tab indicator to top edge - Keep thread toggle button visible in collapsed 36px sidebar strip Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(repl): interactive approval selector and transient status lines - Replace ASCII-art approval box with clean horizontal rule card - Add inquire-based interactive selector for tool approvals (↑↓ + Enter) - Selector runs directly from send_status via spawn_blocking, with stdin_locked flag to prevent readline from competing for stdin - Transient thinking/tool-started lines: each replaces the previous, all erased before final output (no clutter left in scrollback) - Esc in selector sends denial so agent never gets stuck Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: widen TurnCost token fields to u64 and remove unused variable - Change input_tokens/output_tokens from u32 to u64 in StatusUpdate::TurnCost, SseEvent::TurnCost, and the thread_ops emit site to avoid truncation on large conversations - Remove unused _routine_engine_for_loop binding in agent_loop.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: reduce startup log noise — demote info to debug Demote routine startup messages (builder, WASM tools, tunnel, WASM channels) from info to debug so the default log output stays clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): allow CDN scripts in CSP connect-src directive Add cdn.jsdelivr.net and cdnjs.cloudflare.com to connect-src so the browser can fetch marked.js and DOMPurify without CSP violations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix cargo fmt in repl.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): gate turn_cost SSE handler on current thread Prevents cost badge from attaching to the wrong message when switching threads or receiving events from background threads. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: retrigger CI * fix: add missing extension_manager to webhook EngineContext The webhook trigger path added in nearai#736 was missing the extension_manager field introduced by nearai#1453. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: ignore RUSTSEC-2026-0049 rustls-webpki CRL advisory Low impact — requires compromised CA to exploit. Tracked for upstream rustls-webpki upgrade. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(routines): use fields.join for cron normalization Use split_whitespace fields instead of re-trimming the original string to avoid preserving extra internal whitespace in cron expressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(repl): Apple-style approval card — clean vertical flow - Drop verbose tool description (the command IS the decision surface) - Unified vertical pipe layout: ◆ header → │ params → │ selector - Selector options show keyboard shortcuts inline: Approve (y) - Compact help message, answered state uses └ to close the flow - No horizontal rules, no blank-line padding — just breathing room Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(repl): replace inquire with crossterm for approval selector Drop the inquire dependency (which pulled in crossterm 0.25, duplicating the existing 0.28). The 3-option approval selector is now built directly with crossterm raw mode — same UX, zero new dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(deps): upgrade crossterm 0.28 → 0.29, eliminate duplication termimad (via crokey) uses crossterm 0.29. Upgrading our direct dependency from 0.28 to 0.29 collapses to a single crossterm version in the dependency tree. Also migrated termimad::crossterm:: references to the direct crossterm import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review comments — box_top off-by-one, smart_truncate overflow, mobile theme toggle - Fix box_top() fill calculation: was off-by-one, producing boxes 1 char too wide (fmt.rs) - Fix smart_truncate(): account for "..." in the budget so output never exceeds max_chars (repl.rs) - Move theme toggle to settings sidebar on mobile instead of display:none, so mobile users can still switch themes (style.css, index.html, app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: cargo fmt repl.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review — retry duplication, CSP connect-src, deny color - Remove failed message before retry to prevent duplicate user messages - Revert connect-src to 'self' — CDN hosts only need script-src - Use red for Deny confirmation in REPL approval selector Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Apple-level design refinements for the IronClaw web gateway and REPL — spring-physics motion, glass morphism depth, chat polish, mobile layout fixes, interactive REPL approval selector, and housekeeping cleanups.
Design System
--space-*,--text-*,--duration-*,--ease-*) with legacy aliases for theme backward compatibility--shadow-sm/md/lg), accent glow, glass morphism (--glass-bg/blur), spring easing tokens:root) and light ([data-theme="light"]) theme blocksMicro-interactions
Visual Depth
Chat Experience
Settings & Forms
Mobile Layout (≤768px)
toggleThreadSidebar()now usesexpanded-mobileclass on mobile instead ofcollapsed(desktop-only class that did nothing on mobile)display: block(wasdisplay: flexcausing horizontal layout)REPL Improvements
inquire::Selectpicker (↑↓ + Enter), matching near-cli-rs stylestdin_lockedflag prevents readline from competing with the approval selector — locked on message send, unlocked on respond/selector completionThinkingandToolStartedlines overwrite each other and are erased before final output (no clutter in scrollback)Housekeeping
TurnCosttoken fields fromu32tou64to avoid truncation on large conversations_routine_engine_for_loopvariableinfo→debug): builder, WASM tools, tunnel, WASM channelscdn.jsdelivr.netandcdnjs.cloudflare.comto CSPconnect-srcfor marked.js and DOMPurifyAccessibility & Mobile
prefers-reduced-motionglobal kill-switchBug Fixes
Test plan
cargo check— compiles cleancargo clippy --all --all-features— zero warningsprefers-reduced-motion: reducein DevTools — all animations disabled»toggle → sidebar slides open as overlay with backdropbackdrop-filterand-webkit-backdrop-filterrender🤖 Generated with Claude Code