feat(web): hide desktop-only entry points on the web host

The image Search/Generate buttons, the six MCP CLI-integration
toggles, and the auto-update card all painted on web with no executor
behind them — clicking did nothing (silent dead UI, worse than a
missing feature). Gate each behind a cfg const following the existing
GIT_BUTTON_AVAILABLE pattern, with paint, hit-test, and layout heights
kept in sync; the experimental-features card moves up into the hidden
auto-update slot.
This commit is contained in:
Kayshen-X 2026-07-03 00:08:22 +08:00
parent 47ff290cf6
commit 8f1be67ab4
3 changed files with 101 additions and 55 deletions

View file

@ -147,6 +147,13 @@ fn client_config_copy_button_rect(content: Rect) -> Rect {
}
}
/// Host capability: the CLI-integration toggles write MCP endpoints
/// into external CLI config files (`~/.claude.json` etc.) via the
/// desktop MCP runtime (`mcp_integrations.rs`). The web host has no
/// consumer — hide the grid there instead of painting toggles that
/// silently do nothing (same pattern as `GIT_BUTTON_AVAILABLE`).
pub(super) const CLI_INTEGRATIONS_AVAILABLE: bool = !cfg!(target_arch = "wasm32");
pub fn hit_test(content: Rect, settings: &AgentSettings, scrolled: Point2D) -> McpHit {
if (server_button_rect(content)).contains(scrolled) {
return McpHit::ToggleServer;
@ -157,9 +164,11 @@ pub fn hit_test(content: Rect, settings: &AgentSettings, scrolled: Point2D) -> M
if !settings.mcp_server.running && (port_field_rect(content)).contains(scrolled) {
return McpHit::FocusPort;
}
for (i, cli) in McpCli::ALL.iter().enumerate() {
if (cli_cell_rect(content, settings, i)).contains(scrolled) {
return McpHit::ToggleCli(*cli);
if CLI_INTEGRATIONS_AVAILABLE {
for (i, cli) in McpCli::ALL.iter().enumerate() {
if (cli_cell_rect(content, settings, i)).contains(scrolled) {
return McpHit::ToggleCli(*cli);
}
}
}
McpHit::None
@ -187,6 +196,11 @@ pub(super) fn paint_mcp_tab(
paint_server_card(cx, theme, settings, ui, content, now_ms);
paint_client_config(cx, theme, settings, ui, content, now_ms);
// Terminal-integrations section — desktop-only (see
// `CLI_INTEGRATIONS_AVAILABLE`).
if !CLI_INTEGRATIONS_AVAILABLE {
return;
}
let mut y =
server_card_top(content) + SERVER_CARD_H + client_config_block_h(settings) + SECTION_GAP;
let section_title = TextLayout::single_run(

View file

@ -37,7 +37,18 @@ fn auto_update_card_rect(content: Rect) -> Rect {
}
}
/// Host capability: the auto-update toggle drives the desktop
/// updater (`op-host-desktop/src/update_check.rs`). The web host has
/// no updater — hide the card there instead of painting a switch that
/// silently does nothing (same pattern as `GIT_BUTTON_AVAILABLE`).
pub(super) const AUTO_UPDATE_AVAILABLE: bool = !cfg!(target_arch = "wasm32");
fn experimental_card_rect(content: Rect) -> Rect {
if !AUTO_UPDATE_AVAILABLE {
// The auto-update card is hidden — the experimental card
// moves up into its slot.
return auto_update_card_rect(content);
}
Rect {
origin: Point2D::new(
content.origin.x,
@ -60,7 +71,7 @@ fn switch_rect_for(card: Rect) -> Rect {
}
pub fn hit_test(content: Rect, scrolled: Point2D) -> SystemHit {
if switch_rect_for(auto_update_card_rect(content)).contains(scrolled) {
if AUTO_UPDATE_AVAILABLE && switch_rect_for(auto_update_card_rect(content)).contains(scrolled) {
return SystemHit::ToggleAutoUpdate;
}
if switch_rect_for(experimental_card_rect(content)).contains(scrolled) {
@ -88,15 +99,18 @@ pub(super) fn paint_system_tab(
Point2D::new(content.origin.x, content.origin.y + 20.0),
);
paint_toggle_card(
cx,
theme,
ui,
auto_update_card_rect(content),
"agents.autoUpdate",
"settings.autoUpdateDesc",
settings.auto_update_enabled,
);
// Auto-update card — desktop-only (see `AUTO_UPDATE_AVAILABLE`).
if AUTO_UPDATE_AVAILABLE {
paint_toggle_card(
cx,
theme,
ui,
auto_update_card_rect(content),
"agents.autoUpdate",
"settings.autoUpdateDesc",
settings.auto_update_enabled,
);
}
paint_toggle_card(
cx,
theme,

View file

@ -19,6 +19,13 @@ use crate::widgets::property_panel_inputs::{
use crate::widgets::PaintCx;
use crate::{Point2D, Rect, TextLayout};
/// Host capability: the Search / Generate buttons drive the image
/// search + generation sessions that only the desktop host executes
/// (`image_search_session.rs` / `image_generate_host.rs`). The web
/// host has no drain for them — hide the buttons there instead of
/// painting dead UI (same pattern as `GIT_BUTTON_AVAILABLE`).
pub(super) const IMAGE_REMOTE_ACTIONS_AVAILABLE: bool = !cfg!(target_arch = "wasm32");
/// Total height the image section consumes BEFORE the trailing
/// `SECTION_GAP` the walkers add — header + thumb row + optional
/// warning + buttons row + the legacy 34 px divider tail.
@ -28,11 +35,15 @@ pub fn image_section_height(has_warning: bool) -> f32 {
} else {
0.0
};
let buttons = if IMAGE_REMOTE_ACTIONS_AVAILABLE {
IMAGE_ROW_GAP + IMAGE_BUTTON_H
} else {
0.0
};
crate::widgets::property_panel_inputs::SECTION_HEADER_HEIGHT
+ INPUT_HEIGHT
+ warning
+ IMAGE_ROW_GAP
+ IMAGE_BUTTON_H
+ buttons
+ 34.0
}
@ -70,22 +81,24 @@ pub fn push_image_action_rects(
));
y += IMAGE_WARNING_H;
}
y += IMAGE_ROW_GAP;
let half_w = (usable_w - 4.0) / 2.0;
out.push((
PropertyPanelAction::ToggleImageSearchPopover,
Rect {
origin: Point2D::new(x0 + PAD_X, y),
size: Point2D::new(half_w, IMAGE_BUTTON_H),
},
));
out.push((
PropertyPanelAction::ToggleImageGeneratePopover,
Rect {
origin: Point2D::new(x0 + PAD_X + half_w + 4.0, y),
size: Point2D::new(half_w, IMAGE_BUTTON_H),
},
));
if IMAGE_REMOTE_ACTIONS_AVAILABLE {
y += IMAGE_ROW_GAP;
let half_w = (usable_w - 4.0) / 2.0;
out.push((
PropertyPanelAction::ToggleImageSearchPopover,
Rect {
origin: Point2D::new(x0 + PAD_X, y),
size: Point2D::new(half_w, IMAGE_BUTTON_H),
},
));
out.push((
PropertyPanelAction::ToggleImageGeneratePopover,
Rect {
origin: Point2D::new(x0 + PAD_X + half_w + 4.0, y),
size: Point2D::new(half_w, IMAGE_BUTTON_H),
},
));
}
}
#[allow(clippy::too_many_arguments)]
@ -160,30 +173,35 @@ pub fn paint_image_node_section(
y = paint_warning_row(cx, theme, warning, x, y, width);
}
// Search / Generate buttons (TS outline buttons, flex-1 h-7).
y += IMAGE_ROW_GAP;
let half_w = (usable_w - 4.0) / 2.0;
paint_outline_button(
cx,
theme,
Rect {
origin: Point2D::new(x + PAD_X, y),
size: Point2D::new(half_w, IMAGE_BUTTON_H),
},
Icon::Search,
"Search",
);
paint_outline_button(
cx,
theme,
Rect {
origin: Point2D::new(x + PAD_X + half_w + 4.0, y),
size: Point2D::new(half_w, IMAGE_BUTTON_H),
},
Icon::Sparkles,
"Generate",
);
y += IMAGE_BUTTON_H + 34.0;
// Search / Generate buttons (TS outline buttons, flex-1 h-7) —
// desktop-only, mirrored by `image_section_height` /
// `push_image_action_rects` so paint + hit-test stay in sync.
if IMAGE_REMOTE_ACTIONS_AVAILABLE {
y += IMAGE_ROW_GAP;
let half_w = (usable_w - 4.0) / 2.0;
paint_outline_button(
cx,
theme,
Rect {
origin: Point2D::new(x + PAD_X, y),
size: Point2D::new(half_w, IMAGE_BUTTON_H),
},
Icon::Search,
"Search",
);
paint_outline_button(
cx,
theme,
Rect {
origin: Point2D::new(x + PAD_X + half_w + 4.0, y),
size: Point2D::new(half_w, IMAGE_BUTTON_H),
},
Icon::Sparkles,
"Generate",
);
y += IMAGE_BUTTON_H;
}
y += 34.0;
paint_section_divider(cx, theme, x, y, width);
y + SECTION_GAP
}