chore(editor): remove the inert palette button from the chat footer

The affordance has no feature behind it yet; ship it when it does.
This commit is contained in:
Fini 2026-07-04 03:21:13 +08:00
parent a963502d1a
commit 6e41b68474
6 changed files with 17 additions and 90 deletions

View file

@ -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.

View file

@ -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);

View file

@ -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]

View file

@ -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!(),
};

View file

@ -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;

View file

@ -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);
}