app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 61bc8b5b1befa2829c799ba76b81c96e2a1d9eaa
parent 718d88f95583b5a5198722af164c467dec6c3e94
Author: triesap <tyson@radroots.org>
Date:   Tue, 21 Apr 2026 03:52:41 +0000

ui: harden shared focus and control semantics

- add a labeled shared icon-button spec for icon-only desktop actions
- restore visible focus borders for shared text inputs across shipped forms
- collapse shared checkbox rows to one tab stop and localize the account more action
- extend source-guard coverage for the new settings account control label

Diffstat:
Mcrates/launchers/desktop/src/source_guards.rs | 1+
Mcrates/launchers/desktop/src/window.rs | 34++++++++++++++++------------------
Mcrates/shared/i18n/src/keys.rs | 1+
Mcrates/shared/ui/src/lib.rs | 8++++----
Mcrates/shared/ui/src/primitives.rs | 41++++++++++++++++++++++++++++++++++-------
Mi18n/locales/en/messages.json | 1+
6 files changed, 57 insertions(+), 29 deletions(-)

diff --git a/crates/launchers/desktop/src/source_guards.rs b/crates/launchers/desktop/src/source_guards.rs @@ -500,6 +500,7 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[ "AppTextKey::SettingsAccountActivationInactive", "AppTextKey::SettingsAccountAddAction", "AppTextKey::SettingsAccountLogOutAction", + "AppTextKey::SettingsAccountMoreActions", "AppTextKey::SettingsAccountOpenWorkspaceAction", "AppTextKey::SettingsNavFarm", "AppTextKey::SettingsFarmPanelBody", diff --git a/crates/launchers/desktop/src/window.rs b/crates/launchers/desktop/src/window.rs @@ -26,8 +26,7 @@ use radroots_app_models::{ ProductsListRow, ProductsSort, RecoveryKind, RecoveryState, ReminderDeadlineProjection, ReminderDeliveryState, ReminderId, ReminderLogEntryProjection, ReminderLogProjection, ReminderSurface, ReminderUrgency, RepeatDemandEligibility, RepeatDemandHandoffProjection, - ShellSection, - TodayAgendaProjection, TodaySetupTaskKind, + ShellSection, TodayAgendaProjection, TodaySetupTaskKind, }; use radroots_app_remote_signer::{ RadrootsAppRemoteSignerApprovedSession, RadrootsAppRemoteSignerPendingPollOutcome, @@ -44,7 +43,7 @@ use radroots_app_sync::{ SyncConflictResolutionStatus, SyncConflictSeverity, }; use radroots_app_ui::{ - APP_UI_THEME, AppCheckboxFieldSpec, AppFormFieldSpec, + APP_UI_THEME, AppCheckboxFieldSpec, AppFormFieldSpec, AppIconButtonSpec, AppSegmentButtonIconSpec as IconSegmentButtonSpec, LabelValueRow, app_button_card, app_button_choice as choice_button, app_button_compact as action_button_compact, app_button_icon as action_icon_button, app_button_list_row as list_row_button, @@ -1293,7 +1292,10 @@ impl HomeView { replace_existing: bool, cx: &mut Context<Self>, ) { - match self.runtime.repeat_personal_order(order_id, replace_existing) { + match self + .runtime + .repeat_personal_order(order_id, replace_existing) + { Ok(true) => cx.notify(), Ok(false) => {} Err(runtime_error) => { @@ -5812,8 +5814,11 @@ impl SettingsWindowView { cx, )) .child(action_icon_button( - "account-more", - IconName::ChevronDown, + AppIconButtonSpec::new( + "account-more", + app_shared_text(AppTextKey::SettingsAccountMoreActions), + IconName::ChevronDown, + ), |_, _, _| {}, cx, )), @@ -7818,9 +7823,8 @@ fn buyer_order_detail_card( replace_confirmation: Option<&BuyerCartReplaceConfirmationProjection>, cx: &mut Context<HomeView>, ) -> AnyElement { - let repeat_confirmation = - replace_confirmation.filter(|confirmation| confirmation.incoming_farm_display_name - == detail.farm_display_name); + let repeat_confirmation = replace_confirmation + .filter(|confirmation| confirmation.incoming_farm_display_name == detail.farm_display_name); home_card( app_shared_text(AppTextKey::PersonalOrdersDetailTitle), @@ -7883,9 +7887,7 @@ fn buyer_order_detail_card( .child(home_body_text(format!( "{} {} {}.", replace_confirmation.current_farm_display_name, - app_shared_text( - AppTextKey::PersonalDetailReplaceCartBody, - ), + app_shared_text(AppTextKey::PersonalDetailReplaceCartBody,), replace_confirmation.incoming_farm_display_name, ))) .child( @@ -7945,9 +7947,7 @@ fn buyer_order_detail_card( .into_any_element() } -fn buyer_repeat_demand_action_label( - repeat_demand: &RepeatDemandHandoffProjection, -) -> SharedString { +fn buyer_repeat_demand_action_label(repeat_demand: &RepeatDemandHandoffProjection) -> SharedString { match repeat_demand.eligibility { RepeatDemandEligibility::Eligible => { app_shared_text(AppTextKey::PersonalOrdersRepeatDemandActionEligible) @@ -7961,9 +7961,7 @@ fn buyer_repeat_demand_action_label( } } -fn buyer_repeat_demand_note( - repeat_demand: &RepeatDemandHandoffProjection, -) -> Option<SharedString> { +fn buyer_repeat_demand_note(repeat_demand: &RepeatDemandHandoffProjection) -> Option<SharedString> { match repeat_demand.eligibility { RepeatDemandEligibility::Eligible => None, RepeatDemandEligibility::Partial if repeat_demand.unavailable_item_count == 1 => Some( diff --git a/crates/shared/i18n/src/keys.rs b/crates/shared/i18n/src/keys.rs @@ -318,6 +318,7 @@ define_app_text_keys! { SettingsAccountActivationActive => "settings.account.activation.active", SettingsAccountAddAction => "settings.account.action.add_account", SettingsAccountLogOutAction => "settings.account.action.log_out", + SettingsAccountMoreActions => "settings.account.action.more_actions", SettingsAccountOpenWorkspaceAction => "settings.account.action.open_workspace", SettingsViewAccount => "settings.view.account", SettingsViewSettings => "settings.view.settings", diff --git a/crates/shared/ui/src/lib.rs b/crates/shared/ui/src/lib.rs @@ -5,10 +5,10 @@ mod text; mod theme; pub use primitives::{ - AppCheckboxFieldSpec, AppFormFieldSpec, AppSegmentButtonIconSpec, LabelValueRow, - app_button_card, app_button_choice, app_button_compact, app_button_icon, app_button_list_row, - app_button_primary, app_button_primary_disabled, app_button_secondary, app_button_text, - app_checkbox_field, app_cluster, app_detail_row, app_divider, app_form_field, + AppCheckboxFieldSpec, AppFormFieldSpec, AppIconButtonSpec, AppSegmentButtonIconSpec, + LabelValueRow, app_button_card, app_button_choice, app_button_compact, app_button_icon, + app_button_list_row, app_button_primary, app_button_primary_disabled, app_button_secondary, + app_button_text, app_checkbox_field, app_cluster, app_detail_row, app_divider, app_form_field, app_form_input_text, app_form_section, app_heading_section, app_heading_view, app_input_text, app_scroll_panel, app_segment_button_icon, app_split_shell, app_stack_h, app_stack_v, app_status_indicator, app_surface_card, app_surface_card_section, app_surface_panel, diff --git a/crates/shared/ui/src/primitives.rs b/crates/shared/ui/src/primitives.rs @@ -34,6 +34,22 @@ impl AppSegmentButtonIconSpec { } } +pub struct AppIconButtonSpec { + pub id: &'static str, + pub label: SharedString, + pub icon: IconName, +} + +impl AppIconButtonSpec { + pub fn new(id: &'static str, label: impl Into<SharedString>, icon: IconName) -> Self { + Self { + id, + label: label.into(), + icon, + } + } +} + pub struct AppCheckboxFieldSpec { pub id: &'static str, pub label: SharedString, @@ -402,7 +418,7 @@ fn app_checkbox( ); } - button + button.tab_stop(false) } pub fn app_checkbox_field( @@ -545,7 +561,7 @@ pub fn app_input_text(input: &Entity<InputState>, disabled: bool) -> Input { Input::new(input) .with_size(Size::Medium) .disabled(disabled) - .focus_bordered(false) + .focus_bordered(true) .bg(rgb(background)) .text_color(rgb(foreground)) .border_color(rgb(tokens.border)) @@ -647,18 +663,18 @@ fn app_button_label( } pub fn app_button_icon( - id: impl Into<ElementId>, - icon: IconName, + spec: AppIconButtonSpec, on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, cx: &App, ) -> impl IntoElement { let sizing = APP_UI_THEME.components.app_button.sizing; let colors = app_button_colors(AppButtonVariant::Secondary); - app_button_base(id, AppButtonVariant::Secondary, on_click, cx) + app_button_base(spec.id, AppButtonVariant::Secondary, on_click, cx) .with_size(Size::Size(px(sizing.square_width_px))) + .tooltip(spec.label) .icon( - Icon::new(icon) + Icon::new(spec.icon) .with_size(Size::Size(px(sizing.icon_size_px))) .text_color(rgb(colors.foreground)), ) @@ -877,7 +893,9 @@ fn app_button_disabled_colors(variant: AppButtonVariant) -> crate::AppButtonColo mod tests { use gpui_component::IconName; - use super::{AppCheckboxFieldSpec, AppFormFieldSpec, AppSegmentButtonIconSpec}; + use super::{ + AppCheckboxFieldSpec, AppFormFieldSpec, AppIconButtonSpec, AppSegmentButtonIconSpec, + }; #[test] fn icon_segment_spec_preserves_id_and_label() { @@ -909,4 +927,13 @@ mod tests { Some("Saved locally") ); } + + #[test] + fn icon_button_spec_preserves_id_label_and_icon() { + let spec = AppIconButtonSpec::new("more", "More actions", IconName::ChevronDown); + + assert_eq!(spec.id, "more"); + assert_eq!(spec.label.as_ref(), "More actions"); + assert!(matches!(spec.icon, IconName::ChevronDown)); + } } diff --git a/i18n/locales/en/messages.json b/i18n/locales/en/messages.json @@ -297,6 +297,7 @@ "settings.account.activation.active": "Activated", "settings.account.action.add_account": "Add Account...", "settings.account.action.log_out": "Log Out", + "settings.account.action.more_actions": "More Actions", "settings.account.action.open_workspace": "Open Workspace...", "settings.view.account": "account", "settings.view.settings": "settings",