feat(editor): hide account entry points for v0.8.2
This commit is contained in:
parent
5c8a4163c2
commit
4eea87db2d
|
|
@ -0,0 +1,42 @@
|
|||
use crate::widgets::agent_settings_panel::AgentSettingsPanel;
|
||||
use op_editor_core::agent_settings::AgentSettingsTab;
|
||||
use op_editor_core::EditorState;
|
||||
|
||||
#[test]
|
||||
fn account_tab_release_gate_hides_nav_and_falls_back_from_stale_state() {
|
||||
const { assert!(!crate::widgets::ACCOUNT_UI_AVAILABLE) };
|
||||
let default_state = EditorState::default();
|
||||
let default_panel = AgentSettingsPanel::for_editor(&default_state);
|
||||
let expected_agents_height = default_panel.content_total_height();
|
||||
|
||||
let mut state = EditorState::default();
|
||||
state.editor_ui.agent_settings.tab = AgentSettingsTab::Account;
|
||||
let panel = AgentSettingsPanel::for_editor(&state);
|
||||
let rect = panel.rect(1200.0, 800.0);
|
||||
|
||||
let expected_tabs = [
|
||||
AgentSettingsTab::Agents,
|
||||
AgentSettingsTab::Mcp,
|
||||
AgentSettingsTab::Images,
|
||||
AgentSettingsTab::Fonts,
|
||||
AgentSettingsTab::System,
|
||||
];
|
||||
for (index, expected) in expected_tabs.into_iter().enumerate() {
|
||||
let point = crate::Point2D::new(
|
||||
rect.origin.x + 100.0,
|
||||
rect.origin.y + 56.0 + index as f32 * 30.0 + 14.0,
|
||||
);
|
||||
assert_eq!(panel.nav_at(rect, point), Some(expected));
|
||||
}
|
||||
|
||||
let hidden_account_row = crate::Point2D::new(
|
||||
rect.origin.x + 100.0,
|
||||
rect.origin.y + 56.0 + expected_tabs.len() as f32 * 30.0 + 14.0,
|
||||
);
|
||||
assert_eq!(panel.nav_at(rect, hidden_account_row), None);
|
||||
assert_eq!(
|
||||
panel.content_total_height(),
|
||||
expected_agents_height,
|
||||
"a persisted Account tab must fall back to the first visible tab"
|
||||
);
|
||||
}
|
||||
|
|
@ -9,7 +9,8 @@ use crate::widgets::agent_settings_mcp::{self, McpHit};
|
|||
use crate::widgets::agent_settings_panel_card::paint_agent_card;
|
||||
use crate::widgets::agent_settings_panel_geometry::{
|
||||
acp_section_y, agent_card_rect_at, agent_card_rect_in, close_rect, connect_btn_rect_at,
|
||||
content_paint_clip_rect, content_rect, disconnect_btn_rect_at, nav_item_rect, tab_i18n_label,
|
||||
content_paint_clip_rect, content_rect, disconnect_btn_rect_at, full_settings_tabs,
|
||||
nav_item_rect, tab_i18n_label,
|
||||
};
|
||||
use crate::widgets::agent_settings_system::{self, SystemHit};
|
||||
use crate::widgets::editor_state_ext::theme_for;
|
||||
|
|
@ -52,7 +53,7 @@ pub enum AgentSettingsPanelMode {
|
|||
impl AgentSettingsPanelMode {
|
||||
fn visible_tabs(self) -> &'static [AgentSettingsTab] {
|
||||
match self {
|
||||
AgentSettingsPanelMode::Full => &AgentSettingsTab::ALL,
|
||||
AgentSettingsPanelMode::Full => full_settings_tabs(),
|
||||
AgentSettingsPanelMode::WebBuiltinOnly => &[
|
||||
AgentSettingsTab::Agents,
|
||||
AgentSettingsTab::Images,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::widgets::agent_settings_panel::{
|
|||
CARD_GAP, CARD_HEIGHT, CONNECT_BTN_H, CONNECT_BTN_W, NAV_ITEM_HEIGHT, NAV_ITEM_STEP, NAV_TOP,
|
||||
PAD, SECTION_GAP, SIDEBAR_WIDTH,
|
||||
};
|
||||
use crate::widgets::ACCOUNT_UI_AVAILABLE;
|
||||
use crate::widgets::{agent_settings_acp, agent_settings_builtin};
|
||||
use crate::{Point2D, Rect};
|
||||
use op_editor_core::agent_settings::{AgentSettings, AgentSettingsTab};
|
||||
|
|
@ -12,6 +13,22 @@ use op_editor_core::editor_ui_state::EditorUiState;
|
|||
|
||||
const DISCONNECT_BTN_W: f32 = 96.0;
|
||||
|
||||
const FULL_TABS_WITHOUT_ACCOUNT: [AgentSettingsTab; 5] = [
|
||||
AgentSettingsTab::Agents,
|
||||
AgentSettingsTab::Mcp,
|
||||
AgentSettingsTab::Images,
|
||||
AgentSettingsTab::Fonts,
|
||||
AgentSettingsTab::System,
|
||||
];
|
||||
|
||||
pub(super) fn full_settings_tabs() -> &'static [AgentSettingsTab] {
|
||||
if ACCOUNT_UI_AVAILABLE {
|
||||
&AgentSettingsTab::ALL
|
||||
} else {
|
||||
&FULL_TABS_WITHOUT_ACCOUNT
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn tab_i18n_label(ui: &EditorUiState, tab: AgentSettingsTab) -> &'static str {
|
||||
match tab {
|
||||
AgentSettingsTab::Agents => t_settings(ui, "settings.tab.agents"),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ use crate::{Point2D, Rect, RenderBackend};
|
|||
/// `const MIN_RAIL_WIDTH` in each host).
|
||||
pub const MIN_RAIL_WIDTH: f32 = 80.0;
|
||||
|
||||
/// Temporary release gate for the unfinished user-account experience.
|
||||
///
|
||||
/// Keep the account state, modal, menu, translations, and host dispatchers
|
||||
/// compiled and tested so a later release can restore every entry point from
|
||||
/// this one gate, then update the release-gate assertions.
|
||||
pub const ACCOUNT_UI_AVAILABLE: bool = false;
|
||||
|
||||
// Phase B primitives.
|
||||
pub(crate) mod button;
|
||||
pub mod prop_row;
|
||||
|
|
@ -174,6 +181,8 @@ pub mod brand_icons;
|
|||
// Step 4 — extra editor-chrome widgets (TS app parity).
|
||||
pub mod account_menu;
|
||||
pub mod agent_settings_account;
|
||||
#[cfg(test)]
|
||||
mod agent_settings_account_gate_tests;
|
||||
pub mod agent_settings_acp;
|
||||
mod agent_settings_acp_helpers;
|
||||
pub mod agent_settings_builtin;
|
||||
|
|
|
|||
|
|
@ -41,12 +41,10 @@ pub(super) const GIT_BUTTON_AVAILABLE: bool = !cfg!(target_arch = "wasm32");
|
|||
/// has no equivalent runtime/measurement bridge, so hide the button there
|
||||
/// rather than exposing a non-interactive preview flag.
|
||||
pub(super) const PREVIEW_BUTTON_AVAILABLE: bool = !cfg!(target_arch = "wasm32");
|
||||
/// The planned sign-in flow (OIDC Auth Code + PKCE via the system
|
||||
/// browser) is desktop-only. The web build has no equivalent, so the
|
||||
/// avatar button (and its layout slot) is compiled out there rather
|
||||
/// than painting a click that goes nowhere (same pattern as
|
||||
/// `GIT_BUTTON_AVAILABLE` / `PREVIEW_BUTTON_AVAILABLE`).
|
||||
pub const ACCOUNT_BUTTON_AVAILABLE: bool = !cfg!(target_arch = "wasm32");
|
||||
/// The account button follows the shared release gate and remains
|
||||
/// desktop-only when that experience is enabled again.
|
||||
pub const ACCOUNT_BUTTON_AVAILABLE: bool =
|
||||
super::ACCOUNT_UI_AVAILABLE && !cfg!(target_arch = "wasm32");
|
||||
/// Stacked agent-icon metrics — mirror TS `top-bar.tsx`
|
||||
/// (`w-5 h-5 rounded-md bg-foreground/10 ring-1 ring-card` chips
|
||||
/// overlapped by `-space-x-1.5`).
|
||||
|
|
|
|||
|
|
@ -335,10 +335,9 @@ fn chip_with_only_builtin_agents_reserves_no_icon_cluster() {
|
|||
);
|
||||
}
|
||||
|
||||
/// The user-avatar button sits directly left of the Globe button —
|
||||
/// between the agent chip and the locale/theme cluster.
|
||||
#[test]
|
||||
fn account_button_hit_tests_and_sits_left_of_globe() {
|
||||
fn account_button_release_gate_removes_hit_target_and_layout_slot() {
|
||||
const { assert!(!ACCOUNT_BUTTON_AVAILABLE) };
|
||||
let bar = TopBar::untitled();
|
||||
let rect = Rect {
|
||||
origin: Point2D::new(0.0, 0.0),
|
||||
|
|
@ -348,13 +347,20 @@ fn account_button_hit_tests_and_sits_left_of_globe() {
|
|||
let globe = bar.globe_rect(rect);
|
||||
assert!(
|
||||
nearly_eq(account.origin.x + account.size.x, globe.origin.x),
|
||||
"avatar button should abut the globe button's left edge"
|
||||
"dormant avatar geometry should stay ready for the next release"
|
||||
);
|
||||
let center = Point2D::new(
|
||||
account.origin.x + account.size.x / 2.0,
|
||||
account.origin.y + account.size.y / 2.0,
|
||||
assert!(
|
||||
nearly_eq(bar.chip_right_anchor_x(rect), globe.origin.x),
|
||||
"hiding the avatar must also collapse its layout slot"
|
||||
);
|
||||
assert_eq!(bar.hit_test(rect, center), Some(TopBarHit::Account));
|
||||
for x in 0..1000 {
|
||||
let hit = bar.hit_test(rect, Point2D::new(x as f32, TOP_BAR_HEIGHT / 2.0));
|
||||
assert_ne!(
|
||||
hit,
|
||||
Some(TopBarHit::Account),
|
||||
"hidden account control hit at x={x}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ impl Drop for EnvVarGuard {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn clicking_avatar_while_anonymous_opens_login_modal() {
|
||||
fn account_release_gate_blocks_login_modal_for_anonymous_user() {
|
||||
let mut host = WidgetHostNative::new();
|
||||
assert_eq!(
|
||||
host.editor_state().editor_ui.account,
|
||||
|
|
@ -75,14 +75,14 @@ fn clicking_avatar_while_anonymous_opens_login_modal() {
|
|||
account_rect.origin.y + account_rect.size.y / 2.0,
|
||||
);
|
||||
|
||||
assert!(host.apply_press(center.x, center.y, VW, VH));
|
||||
host.apply_press(center.x, center.y, VW, VH);
|
||||
|
||||
assert!(host.editor_state().editor_ui.login_modal_open);
|
||||
assert!(!host.editor_state().editor_ui.login_modal_open);
|
||||
assert!(!host.editor_state().editor_ui.account_menu_open);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clicking_avatar_while_signed_in_opens_account_menu() {
|
||||
fn account_release_gate_blocks_menu_for_signed_in_state() {
|
||||
let mut host = WidgetHostNative::new();
|
||||
host.editor_state_mut().editor_ui.account = AccountState::SignedIn {
|
||||
display_name: "Fini".into(),
|
||||
|
|
@ -95,12 +95,57 @@ fn clicking_avatar_while_signed_in_opens_account_menu() {
|
|||
account_rect.origin.y + account_rect.size.y / 2.0,
|
||||
);
|
||||
|
||||
assert!(host.apply_press(center.x, center.y, VW, VH));
|
||||
host.apply_press(center.x, center.y, VW, VH);
|
||||
|
||||
assert!(host.editor_state().editor_ui.account_menu_open);
|
||||
assert!(!host.editor_state().editor_ui.account_menu_open);
|
||||
assert!(!host.editor_state().editor_ui.login_modal_open);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_login_modal_state_does_not_dispatch_while_release_gate_is_hidden() {
|
||||
const { assert!(!op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE) };
|
||||
let mut host = WidgetHostNative::new();
|
||||
host.editor_state_mut().editor_ui.login_modal_open = true;
|
||||
|
||||
let modal = LoginModal::for_editor(host.editor_state());
|
||||
let panel = modal.rect(VW, VH);
|
||||
let sign_in_point = Point2D::new(
|
||||
panel.origin.x + panel.size.x / 2.0,
|
||||
panel.origin.y + panel.size.y - 56.0,
|
||||
);
|
||||
host.apply_press(sign_in_point.x, sign_in_point.y, VW, VH);
|
||||
|
||||
assert_eq!(
|
||||
host.editor_state().editor_ui.account,
|
||||
AccountState::Anonymous
|
||||
);
|
||||
assert!(!host.editor_state().editor_ui.login_modal_stub_hint_shown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_account_menu_state_does_not_dispatch_while_release_gate_is_hidden() {
|
||||
const { assert!(!op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE) };
|
||||
let mut host = WidgetHostNative::new();
|
||||
let signed_in = AccountState::SignedIn {
|
||||
display_name: "Fini".into(),
|
||||
handle: "fini".into(),
|
||||
};
|
||||
host.editor_state_mut().editor_ui.account = signed_in.clone();
|
||||
host.editor_state_mut().editor_ui.account_menu_open = true;
|
||||
|
||||
let top_bar = TopBar::for_editor_ui(&host.editor_state().editor_ui);
|
||||
let anchor = top_bar.account_button_rect(top_bar_rect());
|
||||
let menu = AccountMenu::for_editor_ui(&host.editor_state().editor_ui).expect("signed in");
|
||||
let menu_rect = menu.rect_at(anchor);
|
||||
let sign_out_point = Point2D::new(
|
||||
menu_rect.origin.x + 20.0,
|
||||
menu_rect.origin.y + menu_rect.size.y - 8.0,
|
||||
);
|
||||
host.apply_press(sign_out_point.x, sign_out_point.y, VW, VH);
|
||||
|
||||
assert_eq!(host.editor_state().editor_ui.account, signed_in);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn login_modal_sign_in_without_dev_flag_shows_honest_stub_hint() {
|
||||
let _lock = env_lock();
|
||||
|
|
|
|||
|
|
@ -580,7 +580,9 @@ impl WidgetHostNative {
|
|||
}
|
||||
// Sign-in modal — owns the cursor while open. Hover the close
|
||||
// `✕` + the primary sign-in button.
|
||||
if self.editor_state.editor_ui.login_modal_open {
|
||||
if op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE
|
||||
&& self.editor_state.editor_ui.login_modal_open
|
||||
{
|
||||
use op_editor_ui::widgets::login_modal::LoginModal;
|
||||
let modal = LoginModal::for_editor(&self.editor_state);
|
||||
let panel = modal.rect(self.last_viewport_w, self.last_viewport_h);
|
||||
|
|
@ -595,7 +597,9 @@ impl WidgetHostNative {
|
|||
return changed;
|
||||
}
|
||||
// Signed-in account dropdown — owns the cursor while open.
|
||||
if self.editor_state.editor_ui.account_menu_open {
|
||||
if op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE
|
||||
&& self.editor_state.editor_ui.account_menu_open
|
||||
{
|
||||
use op_editor_ui::widgets::account_menu::AccountMenu;
|
||||
use op_editor_ui::widgets::top_bar::TopBar;
|
||||
use op_editor_ui::widgets::TOP_BAR_HEIGHT;
|
||||
|
|
|
|||
|
|
@ -533,7 +533,7 @@ impl WidgetHostNative {
|
|||
}
|
||||
|
||||
// 10e. Sign-in modal — full-viewport scrim + centred card.
|
||||
if ui.login_modal_open {
|
||||
if op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE && ui.login_modal_open {
|
||||
use op_editor_ui::widgets::login_modal::LoginModal;
|
||||
frame.fill_rect(
|
||||
Rect {
|
||||
|
|
@ -558,7 +558,7 @@ impl WidgetHostNative {
|
|||
// 10f. Signed-in account dropdown — anchored under the TopBar
|
||||
// avatar button, no scrim (same tier as the file menu /
|
||||
// locale picker).
|
||||
if ui.account_menu_open {
|
||||
if op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE && ui.account_menu_open {
|
||||
use op_editor_ui::widgets::account_menu::AccountMenu;
|
||||
let top_bar_rect = Rect {
|
||||
origin: Point2D::new(0.0, 0.0),
|
||||
|
|
|
|||
|
|
@ -396,7 +396,9 @@ impl WidgetHostNative {
|
|||
self.dispatch_figma_import_press(x, y, viewport_width, viewport_height);
|
||||
return true;
|
||||
}
|
||||
if self.editor_state.editor_ui.login_modal_open {
|
||||
if op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE
|
||||
&& self.editor_state.editor_ui.login_modal_open
|
||||
{
|
||||
self.close_image_popovers_for_higher_overlay();
|
||||
self.dispatch_login_modal_press(x, y, viewport_width, viewport_height);
|
||||
return true;
|
||||
|
|
@ -405,7 +407,9 @@ impl WidgetHostNative {
|
|||
// 0a'. Account dropdown — anchored under the TopBar avatar
|
||||
// button; must hit-test before the TopBar's own block so a
|
||||
// re-click on the avatar closes rather than re-toggling.
|
||||
if self.editor_state.editor_ui.account_menu_open {
|
||||
if op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE
|
||||
&& self.editor_state.editor_ui.account_menu_open
|
||||
{
|
||||
self.close_image_popovers_for_higher_overlay();
|
||||
self.dispatch_account_menu_press(x, y, viewport_width, viewport_height);
|
||||
return true;
|
||||
|
|
@ -629,6 +633,9 @@ impl WidgetHostNative {
|
|||
return true;
|
||||
}
|
||||
TopBarHit::Account => {
|
||||
if !op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE {
|
||||
return false;
|
||||
}
|
||||
if self.editor_state.editor_ui.account.is_signed_in() {
|
||||
self.editor_state.editor_ui.account_menu_open = true;
|
||||
self.editor_state.editor_ui.account_menu_hover = None;
|
||||
|
|
|
|||
|
|
@ -443,7 +443,7 @@ impl WidgetHostNative {
|
|||
if ui.figma_import_in_progress
|
||||
|| !ui.figma_import_pages.is_empty()
|
||||
|| ui.export_dialog_open
|
||||
|| ui.login_modal_open
|
||||
|| (op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE && ui.login_modal_open)
|
||||
|| ui.agent_settings_open
|
||||
|| (ui.missing_fonts_modal_open
|
||||
&& ui
|
||||
|
|
|
|||
|
|
@ -608,7 +608,7 @@ impl WidgetHost {
|
|||
if ui.figma_import_in_progress
|
||||
|| !ui.figma_import_pages.is_empty()
|
||||
|| ui.export_dialog_open
|
||||
|| ui.login_modal_open
|
||||
|| (op_editor_ui::widgets::ACCOUNT_UI_AVAILABLE && ui.login_modal_open)
|
||||
|| ui.agent_settings_open
|
||||
|| (ui.missing_fonts_modal_open
|
||||
&& ui
|
||||
|
|
|
|||
Loading…
Reference in a new issue