用户实测「MC 启动失败找不到崩溃日志」的直接修法:失败时弹一个框,把退出码、 一句人话原因、证据形态、四条真实路径与三个按钮摆在同一个地方。 - app-iced:最小一层模态(遮罩 + 卡片 + 主/次按钮 + Esc/点遮罩关闭), 消费 presenter 的 DialogQueue(此前全仓零引用,所有弹窗都画不出来); 队列管弹窗,CrashCard 管这次失败的事实,按 DialogId 绑定。 - 弹窗区分两种失败:游戏自己写了 crash-reports(贴 Description + 栈片段)/ 游戏没来得及写(明说「这次没有生成崩溃报告」并贴出捕获的最后 30 行)/ 什么都没抓到(给最可能原因分类)。绝不对第二种说「找不到日志」。 - 导出诊断包:core 侧新 crash_bundle 写 zip(清单 + 启动器日志 + 会话 JSONL + crash-reports/* + hs_err + latest.log + 版本 JSON),落盘即脱敏(家目录 / token / 会话 id),单文件 4 MiB、总量 48 MiB 上限并在清单记截断。 - 启动器自身日志落盘(core::launch::launcher_log → <.ULL>/logs/launcher.log, 登记目录前先缓冲),补上「双击启动时诊断行直接蒸发」的缺口。 - i18n:25 条中英文案进 catalog/logs.rs + 金表。 - 取证:ULL_DIALOG_FIXTURE=crash-report|no-logs|export 在临时目录造真文件出图, wgpu 与 tiny-skia 各一套,见 DevDocs/artifacts/m187/。 未复现 M190 的「owned 文字在 wgpu 下丢字」:同一串中文两条文字路径、两种后端逐字相同 (ab-wgpu.png / ab-tinyskia.png),因此本轮未改任何渲染路径。
104 lines
4.9 KiB
Rust
104 lines
4.9 KiB
Rust
//! 应用消息:单一大枚举。
|
||
//!
|
||
//! 纪律:本枚举是**唯一**的消息类型,`view` 产出的所有回调都必须落到这里;组件库与页面层
|
||
//! 各自用自己的 `Message`(组件契约 §2 的泛型纪律),由 `view` 做一次 `.map` 接上。
|
||
//!
|
||
//! ## 三类输入(M90)
|
||
//!
|
||
//! | 来源 | 变体 | 落点 |
|
||
//! |---|---|---|
|
||
//! | 窗口/框架 | `Boot` / `WindowReady` / `Window` / `Frame` / `Captured` / 三个 caption | [`super::update`] 直接处理 |
|
||
//! | **时间** | `Frame`(忙碌,33 ms 闸门)+ `Heartbeat`(空闲,1 Hz) | presenter 的 TICK 节拍:`backend.tick` + `drain_events` + `apply` |
|
||
//! | **页面** | `Page(pages::Message)` | 导航 → `AppState::navigate`;启动/停止 → `AppCommand` |
|
||
|
||
use iced::{window, window::Direction};
|
||
|
||
use unlucky_presenter::router::Route;
|
||
|
||
use crate::pages;
|
||
use crate::shell::NavItem;
|
||
|
||
use super::components;
|
||
|
||
/// 消息(单一大枚举)。
|
||
#[derive(Debug, Clone)]
|
||
pub enum Message {
|
||
/// 启动即发一次(`iced::application` 的 boot 闭包)。
|
||
Boot,
|
||
/// `window::latest()` 的结果。
|
||
WindowReady(Option<window::Id>),
|
||
/// 系统主题探测结果(只可能在**交互模式**出现,截图模式必有主题)。
|
||
SystemTheme(iced::theme::Mode),
|
||
/// 一帧画完。**只在需要 33 ms 节拍时订阅**:截图通道 / 有 Busy-Running 实例 / 有未过期
|
||
/// toast / 派发后的 3 s 观察窗(见 `subscription::needs_frames`)。
|
||
Frame,
|
||
/// **空闲态**的 1 Hz 心跳:`Task::perform(sleep(1s))` 的自续链,用来替代空闲时的帧循环
|
||
/// (M90 r3 / P2-2)。它保证 `theme::config_poll_due` 仍按「30 × 33 ms ≈ 1 秒」的
|
||
/// presenter 语义跑,外部 CLI/AI 改配置后主题 1 秒内生效。
|
||
Heartbeat,
|
||
/// 窗口事件(M90:`Resized` 决定布局档位,600 / 840 两条断点靠它生效)。
|
||
Window(window::Event),
|
||
/// 窗口句柄(M93:`window::run` 探测出的 raw handle;`None` = 拿不到,整窗 α 无法下发)。
|
||
WindowHandle(Option<isize>),
|
||
/// 页面消息(导航 / 启动 / 停止 / 占位动作)。
|
||
Page(pages::Message),
|
||
/// **AI 助手栏 / 独立页**的消息(M104:三档状态、输入框、拖拽、键盘)。
|
||
///
|
||
/// 为什么不走 `pages::Message`:助手栏是**外壳级视图**(它挂在任何页面右边),不是某一页的
|
||
/// 消息;塞进页面枚举还要动 `app/update/page_messages.rs` 的穷尽 match(那个文件正被未合的
|
||
/// M101 改),绕开它就少一处 rebase 面。落点是 `app/update/agent.rs`。
|
||
Agent(crate::shell::agent::Message),
|
||
/// 外壳导航轨点选(`src/shell/nav.rs` 的回调仍然发这一条;收到就翻成同一个路由跳转)。
|
||
///
|
||
/// M90 起产品界面用 `pages` 里的真 `NavigationRail`,这条变体是给**外壳回退路径**
|
||
/// (`Route::Agent`)留的——`shell` 不在本 mission 的 scope 里,不能删它的消息。
|
||
Select(NavItem),
|
||
/// 按下标题栏 → 拖窗。
|
||
DragWindow,
|
||
/// 边缘热区:开始缩放(方向由坐标决定)。
|
||
ResizeStart(Direction),
|
||
/// 最小化。
|
||
Minimize,
|
||
/// 最大化/还原。
|
||
ToggleMaximize,
|
||
/// 关窗退出。
|
||
Close,
|
||
/// 截图回来了。
|
||
Captured(window::Screenshot),
|
||
/// gallery(组件库 harness)里的演示交互。
|
||
Demo(components::gallery::DemoMessage),
|
||
/// 模态对话框(M187):主/次按钮、遮罩、展开、导出、打开文件夹、复制路径。
|
||
Dialog(super::dialog::Message),
|
||
}
|
||
|
||
/// 外壳导航项 → 路由(导航项只在一级页之间有对应;AI 项对应 [`Route::Agent`])。
|
||
///
|
||
/// 这是 `NavItem → Route` 的**唯一**转换点:外壳(`src/shell/nav.rs`)与页面导航轨
|
||
/// (`pages::NAV`)最终都落到同一个 `Route`,所以两条入口不会各走一套页面语义。
|
||
pub(super) fn route_of(nav: NavItem) -> Route {
|
||
match nav {
|
||
NavItem::Instances => Route::Instances,
|
||
NavItem::Download => Route::Download,
|
||
NavItem::Logs => Route::Logs,
|
||
NavItem::Settings => Route::Settings,
|
||
NavItem::Ai => Route::Agent,
|
||
// 轨底头像位(M114):账户是一级页,但**不在主序列**里
|
||
NavItem::Account => Route::Accounts,
|
||
}
|
||
}
|
||
|
||
/// 路由 → 外壳导航项([`route_of`] 的逆;外壳 rail 与 caption 键都按它高亮)。
|
||
///
|
||
/// `Route::Detail(_)` 高亮「实例」(与 `Route::nav()` 同源)。
|
||
pub(super) fn nav_item_of(route: &Route) -> NavItem {
|
||
match route {
|
||
Route::Instances | Route::Detail(_) => NavItem::Instances,
|
||
// M109:内容库与版本管理共用「下载」那一个导航项(B1 裁定)
|
||
Route::Download | Route::Content => NavItem::Download,
|
||
Route::Logs => NavItem::Logs,
|
||
Route::Settings => NavItem::Settings,
|
||
Route::Agent => NavItem::Ai,
|
||
Route::Accounts => NavItem::Account,
|
||
}
|
||
}
|