From 6e41b6847489c94d13ff9cfe80bc315ad39fd439 Mon Sep 17 00:00:00 2001 From: Fini Date: Sat, 4 Jul 2026 03:21:13 +0800 Subject: [PATCH] chore(editor): remove the inert palette button from the chat footer The affordance has no feature behind it yet; ship it when it does. --- .../op-editor-core/src/chat_button_state.rs | 2 - .../op-editor-ui/src/widgets/ai_chat_panel.rs | 4 +- .../src/widgets/ai_chat_panel/tests.rs | 40 +++------------ .../src/widgets/ai_chat_panel/tests_paint.rs | 3 +- .../src/widgets/ai_chat_panel_footer.rs | 49 +++---------------- .../src/widgets/ai_chat_panel_hit.rs | 9 +--- 6 files changed, 17 insertions(+), 90 deletions(-) diff --git a/crates/op-editor-core/src/chat_button_state.rs b/crates/op-editor-core/src/chat_button_state.rs index 157245b09..f289e9687 100644 --- a/crates/op-editor-core/src/chat_button_state.rs +++ b/crates/op-editor-core/src/chat_button_state.rs @@ -32,8 +32,6 @@ pub enum ChatFooterButton { AgentTeam, /// Paperclip attachment button. AddAttachment, - /// Palette button — currently inert (#27 future affordance). - Palette, /// Send button in its normal idle state. Send, /// Stop button shown while a response streams. diff --git a/crates/op-editor-ui/src/widgets/ai_chat_panel.rs b/crates/op-editor-ui/src/widgets/ai_chat_panel.rs index 5d883dd3d..e55d9a50b 100644 --- a/crates/op-editor-ui/src/widgets/ai_chat_panel.rs +++ b/crates/op-editor-ui/src/widgets/ai_chat_panel.rs @@ -359,8 +359,6 @@ pub(crate) struct FooterLayout { pub(crate) agent_team: Rect, /// Paperclip attach button — bare icon, muted. pub(crate) attach: Rect, - /// Palette button — bare icon, muted, currently inert (#27 spec). - pub(crate) palette: Rect, /// Stop circle — shown only while a turn streams. pub(crate) stop: Rect, /// Send/stop circle — the primary action button. @@ -633,7 +631,7 @@ impl<'a> Widget for AIChatPlaceholder<'a> { } // Bottom toolbar (#27) — single row: - // model pill | ⚡ speed chip | 📎 attach | 🎨 palette | [gap] | ◻ stop | ↑ send + // model pill | ⚡ speed chip | 📎 attach | [gap] | ◻ stop | ↑ send let toolbar_y = input_rect.origin.y + input_area_h + attach_h; let toolbar_center_y = toolbar_y + INPUT_TOOLBAR_HEIGHT / 2.0; let footer = self.footer_layout(rect, self.input_rect(rect), toolbar_y); diff --git a/crates/op-editor-ui/src/widgets/ai_chat_panel/tests.rs b/crates/op-editor-ui/src/widgets/ai_chat_panel/tests.rs index 1c37bb8b8..deeced804 100644 --- a/crates/op-editor-ui/src/widgets/ai_chat_panel/tests.rs +++ b/crates/op-editor-ui/src/widgets/ai_chat_panel/tests.rs @@ -636,10 +636,10 @@ fn bottom_toolbar_layout_model_pill_is_leftmost() { } #[test] -fn bottom_toolbar_layout_order_is_model_speed_attach_palette_send() { - // #38: ⚡/📎/🎨 moved right; #42: stop shares the send slot. Full +fn bottom_toolbar_layout_order_is_model_speed_attach_send() { + // #38: ⚡/📎 moved right; #42: stop shares the send slot. Full // left-to-right order is: - // model (LEFT) | [gap] | speed | attach | palette | send (RIGHT) + // model (LEFT) | [gap] | speed | attach | send (RIGHT) let s = EditorState::new(); let panel = AIChatPlaceholder::from_editor(&s); let rect = Rect::xywh(0.0, 0.0, AI_CHAT_WIDTH, AI_CHAT_HEIGHT); @@ -647,7 +647,7 @@ fn bottom_toolbar_layout_order_is_model_speed_attach_palette_send() { let toolbar_top = input.origin.y + INPUT_AREA_HEIGHT; let footer = panel.footer_layout(rect, input, toolbar_top); - // Left-to-right order: model < speed < attach < palette < send + // Left-to-right order: model < speed < attach < send assert!( footer.model.origin.x < footer.speed.origin.x, "model left of speed" @@ -657,19 +657,15 @@ fn bottom_toolbar_layout_order_is_model_speed_attach_palette_send() { "speed left of attach" ); assert!( - footer.attach.origin.x < footer.palette.origin.x, - "attach left of palette" - ); - assert!( - footer.palette.origin.x < footer.send.origin.x, - "palette left of send" + footer.attach.origin.x < footer.send.origin.x, + "attach left of send" ); // #42: stop shares the send slot (toggle in place), not a separate button. assert!( (footer.stop.origin.x - footer.send.origin.x).abs() < 0.01, "stop shares the send slot" ); - // #38 specific: speed/attach/palette must all be RIGHT of the model pill. + // #38 specific: speed/attach must all be RIGHT of the model pill. let model_right = footer.model.origin.x + footer.model.size.x; assert!( footer.speed.origin.x > model_right + 4.0, @@ -710,28 +706,6 @@ fn hit_test_stop_circle_only_active_while_streaming() { ); } -#[test] -fn hit_test_palette_button_is_inert() { - // Palette is a no-op in #27; clicking it returns Inside (consumed). - let mut s = EditorState::new(); - seed_available_model(&mut s); - let panel = AIChatPlaceholder::from_editor(&s); - let rect = Rect::xywh(0.0, 0.0, AI_CHAT_WIDTH, AI_CHAT_HEIGHT); - let input = panel.input_rect(rect); - let toolbar_top = input.origin.y + INPUT_AREA_HEIGHT; - let footer = panel.footer_layout(rect, input, toolbar_top); - let palette_center = Point2D::new( - footer.palette.origin.x + footer.palette.size.x / 2.0, - footer.palette.origin.y + footer.palette.size.y / 2.0, - ); - - assert_eq!( - panel.hit_test(rect, palette_center), - Some(AIChatHit::Inside), - "palette button should be consumed (inert) in #27 layout" - ); -} - // ── Task 5.6 Parallel Agents picker tests ──────────────────────────────────── #[test] diff --git a/crates/op-editor-ui/src/widgets/ai_chat_panel/tests_paint.rs b/crates/op-editor-ui/src/widgets/ai_chat_panel/tests_paint.rs index 4697d40ec..463d86f8d 100644 --- a/crates/op-editor-ui/src/widgets/ai_chat_panel/tests_paint.rs +++ b/crates/op-editor-ui/src/widgets/ai_chat_panel/tests_paint.rs @@ -296,7 +296,7 @@ fn from_editor_picks_up_chat_button_press_targets() { #[test] fn paint_footer_neutral_hovers_use_visible_feedback() { // #27 restyle: model pill and speed/attach have hover washes; send - // is now a filled circle (bg always present); palette is bare icon. + // is now a filled circle (bg always present). // Test the subset that emit a neutral wash rect on hover. let cases = [ op_editor_core::ChatFooterButton::ModelPicker, @@ -320,7 +320,6 @@ fn paint_footer_neutral_hovers_use_visible_feedback() { op_editor_core::ChatFooterButton::SpeedChip => footer.speed, op_editor_core::ChatFooterButton::AgentTeam => footer.agent_team, op_editor_core::ChatFooterButton::AddAttachment => footer.attach, - op_editor_core::ChatFooterButton::Palette => footer.palette, op_editor_core::ChatFooterButton::Send => footer.send, op_editor_core::ChatFooterButton::Stop => unreachable!(), }; diff --git a/crates/op-editor-ui/src/widgets/ai_chat_panel_footer.rs b/crates/op-editor-ui/src/widgets/ai_chat_panel_footer.rs index 49e763e04..71056bc9a 100644 --- a/crates/op-editor-ui/src/widgets/ai_chat_panel_footer.rs +++ b/crates/op-editor-ui/src/widgets/ai_chat_panel_footer.rs @@ -1,7 +1,7 @@ //! Footer toolbar geometry and paint for the AI chat panel. //! //! Computes the bottom single-row toolbar rects: -//! model pill (LEFT) | [gap] | ⚡ parallel-agents chip | attach | palette | send (RIGHT) +//! model pill (LEFT) | [gap] | ⚡ parallel-agents chip | attach | send (RIGHT) //! //! As of #38 the ⚡/📎/🎨 cluster moved from the LEFT (between model and gap) to //! the RIGHT. As of #42 the cluster sits snug against the single send/stop @@ -46,7 +46,7 @@ const PARALLEL_AGENTS_ROW_H: f32 = PARALLEL_AGENTS_ROW_H_PUB; /// Width of the Parallel Agents picker overlay. const PARALLEL_AGENTS_PICKER_W: f32 = 130.0; -/// Width of the bare-icon buttons (attach, palette) in the toolbar. +/// Width of the bare-icon buttons (attach) in the toolbar. pub(crate) const FOOTER_ICON_W: f32 = 24.0; /// Diameter of the circular send/stop buttons. @@ -73,7 +73,7 @@ impl<'a> AIChatPlaceholder<'a> { // Agent-team chip — zero-width logical rect for schema compat; contains() = false. let agent_team = Rect::xywh(model_x + FOOTER_MODEL_PILL_W, cy - 11.0, 0.0, 22.0); - // Right cluster (#38/#42 layout) — ⚡ chip | 📎 attach | 🎨 palette | send, + // Right cluster (#38/#42 layout) — ⚡ chip | 📎 attach | send, // laid out right-to-left from right_edge (stop shares the send slot). let right_edge = rect.origin.x + rect.size.x - PAD; @@ -90,17 +90,9 @@ impl<'a> AIChatPlaceholder<'a> { // against the single circle with no reserved gap between 🎨 and send (#42). let stop = send; - // Palette icon — immediately left of the send/stop circle. - let palette_x = send.origin.x - FOOTER_GAP - FOOTER_ICON_W; - let palette = Rect::xywh( - palette_x, - cy - FOOTER_ICON_W / 2.0, - FOOTER_ICON_W, - FOOTER_ICON_W, - ); - - // Attach icon — immediately left of palette. - let attach_x = palette_x - FOOTER_GAP - FOOTER_ICON_W; + // Attach icon — immediately left of the send/stop circle. (The + // palette slot is removed until the feature behind it exists.) + let attach_x = send.origin.x - FOOTER_GAP - FOOTER_ICON_W; let attach = Rect::xywh( attach_x, cy - FOOTER_ICON_W / 2.0, @@ -118,7 +110,6 @@ impl<'a> AIChatPlaceholder<'a> { speed, agent_team, attach, - palette, stop, send, } @@ -276,7 +267,7 @@ pub(crate) fn paint_parallel_agents_picker( /// Paint the bottom-toolbar row of the AI chat panel (#27 / #32 layout). /// -/// Draws: model pill | [gap] | ⚡ parallel-agents chip | 📎 attach | 🎨 palette | ↑ send (◻ stop while streaming) +/// Draws: model pill | [gap] | ⚡ parallel-agents chip | 📎 attach | ↑ send (◻ stop while streaming) /// /// The ⚡ chip shows "{N}x" in gold (N = `agent_team_size`) and opens the /// Parallel Agents picker on click. @@ -444,32 +435,6 @@ pub(crate) fn paint_bottom_toolbar( 1.4, ); - // --- Palette button — bare icon, inert (#27 future affordance) --- - let palette_rect = footer.palette; - if widget.footer_hover == Some(ChatFooterButton::Palette) - || widget.footer_pressed == Some(ChatFooterButton::Palette) - { - cx.backend.fill_round_rect( - palette_rect, - 6.0, - chat_neutral_feedback_color( - &widget.theme, - widget.footer_pressed == Some(ChatFooterButton::Palette), - ), - ); - } - draw_icon( - cx.backend, - Icon::Palette, - Point2D::new( - palette_rect.origin.x + attach_icon_offset, - palette_rect.origin.y + attach_icon_offset, - ), - 12.0, - widget.theme.muted_foreground, - 1.4, - ); - // --- Stop circle — shown only while a turn streams --- if streaming { let stop_rect = footer.stop; diff --git a/crates/op-editor-ui/src/widgets/ai_chat_panel_hit.rs b/crates/op-editor-ui/src/widgets/ai_chat_panel_hit.rs index 5a12298ca..64798335e 100644 --- a/crates/op-editor-ui/src/widgets/ai_chat_panel_hit.rs +++ b/crates/op-editor-ui/src/widgets/ai_chat_panel_hit.rs @@ -188,7 +188,7 @@ impl<'a> AIChatPlaceholder<'a> { return Some(AIChatHit::FocusInput); } // Bottom toolbar strip (#27 layout): - // model pill | ⚡ speed chip | 📎 attach | 🎨 palette | [gap] | ◻ stop | ↑ send + // model pill | ⚡ speed chip | 📎 attach | [gap] | ◻ stop | ↑ send if point.y >= toolbar_top { let footer = self.footer_layout(rect, input_rect, toolbar_top); let streaming = self.is_streaming(); @@ -220,10 +220,6 @@ impl<'a> AIChatPlaceholder<'a> { AIChatHit::AddAttachment }); } - // Palette is inert in #27 — consume the click so canvas is not affected. - if (footer.palette).contains(point) { - return Some(AIChatHit::Inside); - } // Stop circle — only a live target while streaming. if streaming && (footer.stop).contains(point) { return Some(AIChatHit::Stop); @@ -451,9 +447,6 @@ impl<'a> AIChatPlaceholder<'a> { if !streaming && (footer.attach).contains(point) { return Some(op_editor_core::ChatFooterButton::AddAttachment); } - if (footer.palette).contains(point) { - return Some(op_editor_core::ChatFooterButton::Palette); - } if streaming && (footer.stop).contains(point) { return Some(op_editor_core::ChatFooterButton::Stop); }