fix(app-iced): M104 r2 —— Route::Agent ⟺ Detached 的不变式 + 复审 P3 六条
p2-1(唯一 p2):`Route::Agent` 与 `RailState::Detached` 之前没有绑定——从左侧导航第五项 (本 mission 点名的入口)进独立页时档位还是 Collapsed/Expanded,于是页头出现「Open assistant」 「Collapse/Pop out」、挂着不化的未读胶囊、Esc 也收不走页面(复审用产品 bin 复现)。 - **单一入口**:`App::sync_agent_route()`(update/agent.rs)在 `refresh_view` 开头调用, 两条对向规则:在独立页 ⇒ Detached(顺带清未读);离开独立页而档位还是 Detached ⇒ 收回右栗。 每一次路由变化都经过 refresh_view,所以导航 / ULL_START_ROUTE / detach / back 共用这一处。 - **结构性兜底**:`conversation::actions` 先按宿主分流,`Host::Page` 的动作恒为「Dock back」。 - **启动顺序**:`boot_agent()`(窗口尺寸 adopt + 拆出档直达独立页)必须在 `dispatch(RefreshAll)` 之前——那次 dispatch 会 pump→auto_select 把路由拉到实例详情,同步器随即把档位收回(实测踩到)。 - 测试:`entering_the_agent_route_detaches_the_rail`(走 `Message::Select(NavItem::Ai)` 真路径, 含未读清零 / Esc 离开 / 从页上点别的导航收回)、`the_page_navigation_entry_also_detaches`。 变异自证:把 `sync_agent_route` 改成 no-op ⇒ 3 红(含这两条)。 - 图 13/14:导航入口(折叠档带 2 条未读 / 展开档)进页后都是 Dock back、无未读胶囊、副标题单行。 P3-2 Enter/Shift+Enter 有测试了:分流抽成纯函数 `composer::binding_for`, `enter_sends_and_shift_enter_breaks_the_line` 覆盖「发送 / 换行 / 未聚焦不抢 / 其它键交回默认」; `AgentUi::edit` 里与 fallthrough 等价的冗余 Enter 分支删掉;三档测试改走 `agent.edit(...)`。 P3-4 自证式断言换字面量(320+8+852=1180 等);变异 RESIZE_GUTTER 8→0 ⇒ 6 红(含这条)。 P3-5 两条死文案真用上:8px 拖拽缝有了 tooltip(AgentResize + AgentWidthLabel「当前多宽」)。 P3-6 `on_boot` 的两块逻辑搬进 `update/agent.rs::boot_agent`:update.rs 372→383(+11,见报告逐行)。 P3-7 拖拽层补 on_press/on_exit:窗外松手不再僵住(窗内按下→清态;离窗→收在当前值)。 P3-9 顺手:只有几何/路由会变的消息才重建视图模型(每个字符、每次指针移动不再重建)。 门禁:fmt 0 / clippy(workspace,-D warnings) 0 / pages_demo 0 / 全文件 ≤400(最大 view.rs 373 之外 conversation.rs 383、update.rs 383)
This commit is contained in:
1 parent
8db1c5ab16
commit
9253684387
11 files changed
+495
-208
No files matched your search
@@ -149,7 +149,15 @@ pub fn page<'a>(ui: &'a Ui, agent: &'a AgentUi, window_width: f32) -> Element<'a
|
||||
///
|
||||
/// 为什么铺满整窗:`mouse_area::on_move` 只在**它自己的边界内**回报坐标,而拖拽时指针一定会离开
|
||||
/// 那 8px 的缝隙。铺满整窗后 `Point.x` 就是窗口坐标 ⇒ `目标宽 = 窗口宽 − x`。
|
||||
/// 松手由同一层的 `on_release` 收尾(否则会一直跟着指针走)。
|
||||
///
|
||||
/// **三条收尾路径**(复审 P3-7:`mouse_area` 在 `!cursor.is_over` 时直接 return,窗**外**松手
|
||||
/// 的 `on_release` 永远不来,于是叠层会留在最上层、右栗继续跟着指针缩放):
|
||||
///
|
||||
/// | 路径 | 触发 | 语义 |
|
||||
/// |---|---|---|
|
||||
/// | `on_release` | 窗内松手 | 正常结束 |
|
||||
/// | `on_exit` | 指针离开窗口 | 「拖到窗外」= 收在当前值(不是把指针丢掉继续跟) |
|
||||
/// | `on_press` | 拖拽中再按一次 | 兜底清态(万一上两条都没来,一次点击就能恢复) |
|
||||
pub fn drag_overlay<'a>(agent: &AgentUi) -> Option<Element<'a, Message>> {
|
||||
if !agent.is_dragging() {
|
||||
return None;
|
||||
@@ -164,6 +172,8 @@ pub fn drag_overlay<'a>(agent: &AgentUi) -> Option<Element<'a, Message>> {
|
||||
.interaction(mouse::Interaction::ResizingHorizontally)
|
||||
.on_move(|point| Message::DragTo(point.x))
|
||||
.on_release(Message::DragEnd)
|
||||
.on_press(Message::DragEnd)
|
||||
.on_exit(Message::DragEnd)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
@@ -284,33 +294,34 @@ fn unread_text<'a>(ui: &'a Ui, unread: usize, _catalog: Catalog) -> Element<'a,
|
||||
|
||||
fn panel<'a>(ui: &'a Ui, agent: &'a AgentUi, window_width: f32) -> Element<'a, Message> {
|
||||
let width = agent.rail_width(window_width);
|
||||
let catalog = Catalog::of(LANG);
|
||||
|
||||
let body = container(conversation::view(
|
||||
ui,
|
||||
spec(agent, Host::Rail),
|
||||
Catalog::of(LANG),
|
||||
))
|
||||
.width(Length::Fixed(width))
|
||||
.height(Length::Fill)
|
||||
.padding(Padding {
|
||||
top: ui.space(Space::X6),
|
||||
right: ui.space(Space::X4),
|
||||
bottom: ui.space(Space::X4),
|
||||
left: ui.space(Space::X4),
|
||||
})
|
||||
.style(move |_theme| container::Style {
|
||||
background: Some(Background::Color(ui.layer(MaterialLayer::Canvas))),
|
||||
..container::Style::default()
|
||||
});
|
||||
let body = container(conversation::view(ui, spec(agent, Host::Rail), catalog))
|
||||
.width(Length::Fixed(width))
|
||||
.height(Length::Fill)
|
||||
.padding(Padding {
|
||||
top: ui.space(Space::X6),
|
||||
right: ui.space(Space::X4),
|
||||
bottom: ui.space(Space::X4),
|
||||
left: ui.space(Space::X4),
|
||||
})
|
||||
.style(move |_theme| container::Style {
|
||||
background: Some(Background::Color(ui.layer(MaterialLayer::Canvas))),
|
||||
..container::Style::default()
|
||||
});
|
||||
|
||||
row![splitter(ui), body]
|
||||
row![splitter(ui, width, catalog), body]
|
||||
.width(Length::Shrink)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// 8px 拖拽缝:1px 分隔线居中 + 水平缩放光标 + 按下即开拖。
|
||||
fn splitter<'a>(ui: &'a Ui) -> Element<'a, Message> {
|
||||
/// 8px 拖拽缝:1px 分隔线居中 + 水平缩放光标 + 按下即开拖 + **可读标签**(tooltip)。
|
||||
///
|
||||
/// 复审 P3-5:这条 8px 的缝此前没有任何可读文字,而 `AgentResize` / `AgentWidthLabel` 两条 key
|
||||
/// 在视图里一次没用(只在测试里被断言「不空」)——那是空头支票。现在 tooltip 同时给出
|
||||
/// 「拖它做什么」(`AgentResize`)与「当前多宽」(`AgentWidthLabel`),两个 key 都真用上了。
|
||||
fn splitter<'a>(ui: &'a Ui, width: f32, catalog: Catalog) -> Element<'a, Message> {
|
||||
let line = container(
|
||||
rule::vertical::<IcedTheme>(ui.metrics().divider_thickness).style(move |_theme| {
|
||||
rule::Style {
|
||||
@@ -325,10 +336,33 @@ fn splitter<'a>(ui: &'a Ui) -> Element<'a, Message> {
|
||||
.height(Length::Fill)
|
||||
.align_x(Alignment::Center);
|
||||
|
||||
mouse_area(line)
|
||||
let grab = mouse_area(line)
|
||||
.interaction(mouse::Interaction::ResizingHorizontally)
|
||||
.on_press(Message::DragStart)
|
||||
.into()
|
||||
.on_press(Message::DragStart);
|
||||
|
||||
let tip_style = ui.style(TypeRole::BodySmall);
|
||||
let pixels = format!("{width:.0}");
|
||||
let tip = container(column![
|
||||
text(catalog.text(Key::AgentResize))
|
||||
.size(tip_style.size_px)
|
||||
.font(ui.font_of(tip_style))
|
||||
.color(ui.color(Role::OnSurface)),
|
||||
text(catalog.render(Key::AgentWidthLabel, &[("width", pixels.as_str())]))
|
||||
.size(tip_style.size_px)
|
||||
.font(ui.font_of(tip_style))
|
||||
.color(ui.color(Role::OnSurfaceVariant)),
|
||||
])
|
||||
.padding(Padding::from([ui.space(Space::X1), ui.space(Space::X2)]))
|
||||
.style(move |_theme| container::Style {
|
||||
background: Some(Background::Color(ui.layer(MaterialLayer::Overlay))),
|
||||
border: Border {
|
||||
radius: ui.radius_dp(ShapeScale::Small),
|
||||
..Border::default()
|
||||
},
|
||||
..container::Style::default()
|
||||
});
|
||||
|
||||
tooltip(grab, tip, tooltip::Position::Left).into()
|
||||
}
|
||||
|
||||
/// 会话视图的入参(**唯一**的组装点:两个宿主都走这里,不许各拼一份)。
|
||||
|
||||
Reference in new issue
Block a user