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:
AskaEth committed 2026-11-04 00:21:33 +08:00
1 parent 8db1c5ab16
commit 9253684387
11 files changed
+495 -208

No files matched your search

+6 -7
View File
@@ -285,15 +285,14 @@ impl AgentUi {
}
/// 输入框内容变化(`text_editor::Action` 由 app 层转过来)。
///
/// 这里**不**分 Enter / Shift+Enter:分流在 `conversation::composer::binding_for`(非 shift 的
/// Enter 根本不会变成 `Action`,它直接发 `Message::Submit`)。所以走到这里的 `Edit(Enter)`
/// 一定是「Shift+Enter 换行」那一支,`perform` 就够——早先这里有一段与之等价的 `if` 分支,
/// 读起来像关键拦截,实际是冗余(复审 P3-2 指出后删掉)。
pub fn edit(&mut self, action: text_editor::Action) {
use iced::widget::text_editor::{Action, Edit};
use iced::widget::text_editor::Action;
// Enter:**发送**(`Shift+Enter` 才换行)。这条语义在 `conversation::composer` 的
// `key_binding` 里已经分好流,走到这里的 `Enter` 只可能是「换行」那一支。
if let Action::Edit(Edit::Enter) = action {
self.draft.perform(Action::Edit(Edit::Enter));
return;
}
if matches!(action, Action::Click(_)) {
// 鼠标点了输入框 = 用户切回鼠标,键盘巡航结束(见 `keyboard_mode` 的理由)
self.focus = Focus::Composer;