Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions DESIGN-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,43 @@ dependencies were added.
expand chevron toggles the persistent collapsed state off.
- Covered the manual collapse mode resolver with targeted unit tests.

### Bundled theme set reduced to six

The bundled catalog went from 24 themes to **6**: `CastCodes Dark` (default),
`Dark`, `Light`, `Dracula`, `Gruvbox Dark`, `Solarized Dark`
([`app/src/themes/theme.rs`](app/src/themes/theme.rs)).

Retired: 10 background-image themes (Jellyfish, Koi, Leafy, Marble, Pink City,
Snowy, Dark City, Red Rock, Solar Flare, Phenomenon), 3 gradient themes
(Cyber Wave, Willow Dream, Fancy Dracula), the 2 referral-reward themes, plus
Adeberry, Solarized Light, and Gruvbox Light. The decorative themes conflict
with the Phase 1 contract (calm, dense, editor-grade, no decorative chrome),
and the referral themes could never unlock in an OSS build.

Compatibility:

- Retired `ThemeKind` variants are kept and still deserialize, so existing
settings files load. They are marked `#[schemars(skip)]` so they no longer
appear in the settings JSON schema.
- `ThemeKind::retired_replacement()` maps each retired kind to a surviving
theme of the same lightness — light themes resolve to `Light`, Fancy Dracula
to `Dracula`, the rest to `CastCodes Dark` — so a user on a light theme never
snaps onto a dark background. `WarpThemeConfig::theme()` consults it before
falling back.
- Deleting the active custom theme now resets to `ThemeKind::default()`
(CastCodes Dark) instead of the generic `Dark`.
- The upstream `DefaultAdeberryTheme` new-user override was removed; CastCodes
Dark is already the default.
- The onboarding picker now offers CastCodes Dark, Dark, Light, and Dracula.

### Radius contract cleanup

- `app/src/env_vars/env_var_collection_block.rs` — collection card was 9px,
now the named `ENV_VAR_COLLECTION_RADIUS` at the 8px contract maximum.
- `app/src/ai/document/orchestration_config_block.rs` — the toggle track used a
literal 9px on an 18px-tall pill; now `Radius::Percentage(50.)`, matching the
thumb and expressing the pill intent (pixel-identical).

## Deferred (follow-up PRs)

The remainder of Phase 1 was originally deferred here. Items 1.3–1.8 have
Expand Down Expand Up @@ -206,3 +243,82 @@ Recommended verification before merge:
./script/check_rebrand
cargo check -p warp-app --bin cast-codes --features gui
```

## Contrast fixes and a sizing scale

Follow-up to the UI/UX review. The review found the design system was sound
where it existed (theme tokens, semantic color accessors) but unenforced:
colors were re-derived as literals at some render sites, and radius, spacing
and type had no code representation at all.

### Theme-independent literals that broke on half the themes

These were not stylistic preferences — each one hard-codes a color that only
works against one background lightness, so it degrades or disappears on the
other bundled themes.

- **Settings working-directory input**
(`app/src/settings_view/features/working_directory.rs`): text was
`ColorU::black()` over a `surface_2()` background, i.e. black-on-dark and
effectively invisible in every dark theme. Now
`main_text_color(surface_2())`.
- **Tab bar icons** (`app/src/tab.rs`, three sites): the maximize indicator
and both tab-kind icons fell back to `ColorU::white()` when the tab style
carried no font color, which is white-on-light in the Light theme. Now
fall back to `active_ui_text_color()`, matching the pattern the ambient
agent icon already used a few lines away.
- **Account avatar** (`app/src/workspace/view.rs`): black initials on the
`#7c3aed` accent fill fails contrast. Now
`active_highlighted_text_color()`.
- **Shared-block overflow icon**
(`app/src/settings_view/show_blocks_view.rs`): fixed `#b3bab8` glyph, low
contrast on light backgrounds. Now `nonactive_ui_text_color()`. Its
container also moved from a 5px to the 4px contract radius.
- **Dead command-palette statics**
(`app/src/search/command_search/view.rs`): `SEARCH_ICON_COLOR` and
`INPUT_FIELD_BG_COLOR` were translucent-white constants with no remaining
callers. Removed rather than re-themed.

`active_highlighted_text_color()` now reads the `primary_foreground` token
before deriving a color from the accent. The token was defined in
`castcodes_ui_tokens` but nothing consumed it, and the accessor itself had no
callers — the avatar above is its first. Themes without a `ui` block keep the
previous derived value, covered by
`active_highlighted_text_color_falls_back_to_text_on_accent`.

Deliberately left alone after checking:

- `icon_with_status.rs` draws a black glyph on a fixed light-purple ambient
badge. The background is a brand constant, not a theme surface, so black is
the contrast-correct choice.
- `traffic_lights.rs` uses white on `WINDOWS_BRIGHT_RED` for the Windows
close-button hover, which is the platform convention.
- `login_slide.rs` copy reads as hosted-service marketing, but the login gate
is unreachable in OSS builds (`root_view.rs` requires
`ChannelState::cloud_services_available()`, and `auth_state.rs` returns
early without it). Retoning it would be churn on a dead path.

### A sizing scale to reference

`app/src/ui_components/design.rs` is new: radius, spacing, type and motion
steps from the Phase 1 contract, expressed as constants. Colors stay in the
theme accessors, since those must vary per theme while these do not.

Adopted at `ui_components::BORDER_RADIUS`, the shared status chip
(`ai/conversation_status_ui.rs`), the env-var collection surface, the tab
top radius and the avatar type size. Inherited Warp views still use bare
literals; 48 radii sit outside the 4/6/8 scale, mostly 1-3px hairlines on
badges and glyphs. Those are migrated as views are touched rather than in one
sweep, which would churn hundreds of visually unverifiable call sites.

The scale is declared in full even where the fork has not adopted a step yet,
because a partial vocabulary just sends the next author back to a bare
literal; the module carries a scoped `#![allow(dead_code)]` for that reason.
`motion` has no adopters at all — warpui still has no transition primitive —
so it documents the intended timing rather than driving it.

### Build verification

- `cargo check -p warp-app --features gui --lib` ✅ zero warnings.
- `cargo test -p warp_core ui::theme` ✅ including the two new
`active_highlighted_text_color` tests.
1 change: 1 addition & 0 deletions app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ default = [
"configurable_toolbar",
"transfer_control_tool",
"open_code_notifications",
"pluggable_notifications",
"cli_agent_rich_input",
"vertical_tabs",
"tab_configs",
Expand Down
57 changes: 54 additions & 3 deletions app/src/ai/conversation_status_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ use warp_core::ui::appearance::Appearance;
use warp_core::ui::color::coloru_with_opacity;
use warp_core::ui::theme::{Fill, WarpTheme};
use warpui::color::ColorU;
use warpui::elements::{ConstrainedBox, Container, CornerRadius, Radius};
use warpui::elements::{
ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Flex, MainAxisSize, Padding,
ParentElement, Radius, Text,
};
use warpui::Element;

use crate::ai::agent::conversation::ConversationStatus;
use crate::ai::agent_conversations_model::AgentRunDisplayStatus;
use crate::ui_components::design;
use crate::ui_components::icons::Icon;

/// Padding around the status icon
pub const STATUS_ELEMENT_PADDING: f32 = 2.;
pub const STATUS_ELEMENT_PADDING: f32 = design::space::HAIRLINE;

pub trait StatusElementStyle {
fn status_icon_and_color(&self, theme: &WarpTheme) -> (Icon, ColorU);
Expand Down Expand Up @@ -45,6 +49,53 @@ pub fn render_status_element(
)
.with_uniform_padding(STATUS_ELEMENT_PADDING)
.with_background(coloru_with_opacity(color, 10))
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.)))
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(
design::radius::COMPACT,
)))
.finish()
}

/// Render a compact status pill with the status icon and a short text label.
/// Shared by pane chrome surfaces (vertical tab detail rows, pane headers) so
/// each status reads identically everywhere it appears with a label.
pub fn render_status_element_with_label(
status: &impl StatusElementStyle,
label: impl Into<String>,
appearance: &Appearance,
) -> Box<dyn Element> {
let theme = appearance.theme();
let (icon, color) = status.status_icon_and_color(theme);

Container::new(
Flex::row()
.with_main_axis_size(MainAxisSize::Min)
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_spacing(design::space::XS)
.with_child(
ConstrainedBox::new(icon.to_warpui_icon(Fill::from(color)).finish())
.with_width(12.)
.with_height(12.)
.finish(),
)
.with_child(
Text::new_inline(
label.into(),
appearance.ui_font_family(),
design::font_size::MICRO,
)
.with_color(Fill::from(color).into())
.finish(),
)
.finish(),
)
.with_padding(
Padding::uniform(design::space::HAIRLINE)
.with_left(design::space::XS)
.with_right(design::space::XS),
)
.with_background(coloru_with_opacity(color, 10))
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(
design::radius::COMPACT,
)))
.finish()
}
2 changes: 1 addition & 1 deletion app/src/ai/document/orchestration_config_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn render_pill_toggle(is_on: bool, theme: &WarpTheme) -> Box<dyn Element> {
.finish();
ConstrainedBox::new(
Container::new(switch_inner)
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(9.)))
.with_corner_radius(CornerRadius::with_all(Radius::Percentage(50.)))
.with_background_color(track_bg)
.finish(),
)
Expand Down
3 changes: 0 additions & 3 deletions app/src/auth/login_slide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,7 @@ fn resolve_visual_path(
OnboardingIntention::Terminal => "terminal_intention",
};
let name_key = match theme_name {
"Phenomenon" => "phenomenon",
"Dark" => "dark",
"Light" => "light",
"Adeberry" => "adeberry",
_ => "dark",
};
let orientation = if use_vertical_tabs {
Expand Down
7 changes: 4 additions & 3 deletions app/src/env_vars/env_var_collection_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ use crate::{
/// The vertical padding applied to the env var collection block's content body.
/// For horizontal padding, use [`INLINE_ACTION_HORIZONTAL_PADDING`] for consistency.
const ENV_VAR_COLLECTION_BODY_VERTICAL_PADDING: f32 = 16.;

/// Large-surface corner radius, capped at the 8px design-contract maximum.
const ENV_VAR_COLLECTION_RADIUS: f32 = crate::ui_components::design::radius::SURFACE;
const ENV_VAR_COLLECTION_CANCEL_LABEL: &str = "Cancel";
const ENV_VAR_COLLECTION_ACCEPT_LABEL: &str = "Run";

Expand Down Expand Up @@ -422,9 +423,9 @@ impl View for EnvVarCollectionBlock {
})
// Rounded corners will make the header feel disconnected from its expanded details.
.with_corner_radius(if should_expand_downward {
CornerRadius::with_top(Radius::Pixels(9.))
CornerRadius::with_top(Radius::Pixels(ENV_VAR_COLLECTION_RADIUS))
} else {
CornerRadius::with_all(Radius::Pixels(9.))
CornerRadius::with_all(Radius::Pixels(ENV_VAR_COLLECTION_RADIUS))
})
.with_border(Border::all(1.).with_border_fill(border_color))
.finish();
Expand Down
18 changes: 17 additions & 1 deletion app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2954,7 +2954,6 @@ pub fn enabled_features() -> HashSet<FeatureFlag> {
| FeatureFlag::WarpManagedSecrets
| FeatureFlag::CloudConversations
| FeatureFlag::AmbientAgentsRTC
| FeatureFlag::PluggableNotifications
| FeatureFlag::AgentViewConversationListView
| FeatureFlag::CloudMode
| FeatureFlag::CloudModeFromLocalSession
Expand All @@ -2976,3 +2975,20 @@ pub fn enabled_features() -> HashSet<FeatureFlag> {

flags
}

#[cfg(test)]
mod enabled_features_tests {
use super::*;
use warp_core::channel::Channel;

#[test]
#[serial_test::serial]
fn oss_enables_local_cli_agent_notifications() {
let _channel = ChannelState::override_channel_for_test(Channel::Oss);

assert!(
enabled_features().contains(&FeatureFlag::PluggableNotifications),
"OSS builds need OSC 9/777 events for local CLI agent status"
);
}
}
10 changes: 10 additions & 0 deletions app/src/pane_group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6661,6 +6661,16 @@ impl PaneGroup {
.collect()
}

/// Number of visible terminal panes in this pane group whose agent run is
/// blocked awaiting the user's response (e.g. a permission request or a
/// question from the agent).
pub fn panes_awaiting_user_response(&self, ctx: &AppContext) -> usize {
self.terminal_views(ctx)
.into_iter()
.filter(|view| view.as_ref(ctx).is_awaiting_user_response(ctx))
.count()
}

pub fn code_views(&self, ctx: &AppContext) -> Vec<ViewHandle<CodeView>> {
self.panes_of::<CodePane>()
.map(|p| p.file_view(ctx))
Expand Down
7 changes: 0 additions & 7 deletions app/src/search/command_search/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,8 +1061,6 @@ impl CommandSearchView {
}

pub mod styles {
use lazy_static::lazy_static;
use pathfinder_color::ColorU;
use warpui::elements::{Border, DropShadow, ScrollbarWidth};

use crate::{appearance::Appearance, themes::theme::Fill};
Expand All @@ -1073,11 +1071,6 @@ pub mod styles {
pub const TOP_PADDING: f32 = CORNER_RADIUS;
pub const SCROLLBAR_WIDTH: ScrollbarWidth = ScrollbarWidth::Auto;

lazy_static! {
pub static ref SEARCH_ICON_COLOR: ColorU = ColorU::new(255, 255, 255, 204);
pub static ref INPUT_FIELD_BG_COLOR: ColorU = ColorU::new(255, 255, 255, 50);
}

/// Returns the `Fill` to be used as the background of the search results panel and details
/// panel.
pub fn panel_background_fill(appearance: &Appearance) -> Fill {
Expand Down
12 changes: 1 addition & 11 deletions app/src/settings/initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ use crate::{
auth::auth_state::AuthState,
report_if_error,
settings::input::InputBoxType,
settings::{InputSettings, PrivacySettings, ThemeSettings},
settings::{InputSettings, PrivacySettings},
terminal::session_settings::SessionSettings,
themes::theme::ThemeKind,
};

pub struct SettingsInitializer;
Expand Down Expand Up @@ -45,15 +44,6 @@ impl SettingsInitializer {
settings.disable_default_regex_trigger(ctx);
});

if FeatureFlag::DefaultAdeberryTheme.is_enabled() {
log::debug!("Setting default theme to Adeberry for new user");
ThemeSettings::handle(ctx).update(ctx, |settings, ctx| {
if !settings.theme_kind.is_value_explicitly_set() {
report_if_error!(settings.theme_kind.set_value(ThemeKind::Adeberry, ctx));
}
});
}

if cfg!(windows) {
log::debug!("Setting default font size to 16px (12pt) for a new Windows user");
FontSettings::handle(ctx).update(ctx, |settings, ctx| {
Expand Down
7 changes: 6 additions & 1 deletion app/src/settings_view/features/working_directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,12 @@ fn render_row(
.text_input(editor.clone())
.with_style(UiComponentStyles {
height: Some(TOP_MENU_BAR_HEIGHT),
font_color: Some(pathfinder_color::ColorU::black()),
font_color: Some(
appearance
.theme()
.main_text_color(appearance.theme().surface_2())
.into(),
),
font_size: Some(appearance.ui_font_size()),
padding: Some(Coords::uniform(7.)),
margin: Some(Coords::default().left(8.).right(8.)),
Expand Down
Loading
Loading