M193 的用户投诉「设置页里改了不生效」的结构性根因是 iced 侧没有设置数据通路:
`CoreEvent::SettingsChanged` 在 `crates/app-iced/src` 零命中、`pages::Model` 没有设置快照、
`download::State` 是空 struct。presenter 那套设置组早就是真值来源(`host::settings::groups`
由 `refresh_all` 与 `dispatch` 的 `SetSetting` 分支发成 `SettingsChanged`),只是没有接到 iced。
这条线现在通了:
host::settings::groups → SettingsChanged → AppState::settings_groups
→ pages::Model.settings → settings::States.settings → 下载组控件
→ AppCommand::SetSetting → 写盘 + 重新发 SettingsChanged(闭环)
主要改动
- presenter `host/settings/download.rs`:并发/重试两行改成画**引擎生效值**(`DownloadTuning`),
文件原值与生效值不一致时写明「原值 X 已按出厂 N 生效」(P3-5,两个数都现读);
gh-proxy 行补「当前生效:跟随内置默认/已关闭/覆盖为 X」。
- app-iced `pages/settings/download.rs`:`GROUP.body = Some(body)`,按 `SettingControl`
画 choice(filter chip)/ text(输入框 + 应用)/ action(按钮)/ toggle / readonly;写回走
`app/update/settings/download.rs` → `AppCommand::SetSetting`(不做乐观回显)。
- `pages::Model.settings` + `settings::States.settings`:快照进页面的唯一通道。
- i18n:新增 `SettingsDownloadsApply`(金表锚点追加,`split_keeps_every_translation` 绿)。
- 取证:`ULL_SCREENSHOT_REAL_HOST=1` 让 `--screenshot` 接真 host(截图通道默认强制 mock,
而 mock 不发 `SettingsChanged`,真值屏拍不出来)。
测试(新增 11 条)
- presenter `download_tests.rs`:P3-5 的越界回落 / 0→1 / 无差异不出提示 + P3-3 的
gh-proxy 文件→三态端到端链。
- app-iced `app/update/settings/download_tests.rs`:假后端闭环 ×4 + **真 `CoreHost` 端到端**
(点 16 ⇒ 磁盘上 `launcher.json` 真的有 16)。
- `pages/settings/tests/download_section.rs`:`body.is_none()` 绊线翻成 `is_some()` +
控件形状 / 空快照不编值。
62 lines
2.9 KiB
Rust
62 lines
2.9 KiB
Rust
//! 启动期取证:后端选项的换算 + 首帧耗时那一行日志。
|
||
//!
|
||
//! 从 `app/state.rs` 拆出(rebase 到 5adac23 之后 `state.rs` 顶到 401 行)。这两件事都属于
|
||
//! 「进程启动那一刻记了什么」,与 `App` 的状态机无关;`state.rs` 只留 `pub(super) use` 再导出,
|
||
//! 于是 `app/boot.rs`(`use super::state::STARTED`)与 `app/view.rs`(`log_first_frame`)的既有路径一字未变。
|
||
|
||
use crate::app::diag::diag;
|
||
use std::sync::atomic::{AtomicBool, Ordering};
|
||
use std::sync::OnceLock;
|
||
use std::time::Instant;
|
||
|
||
use crate::cli::Options;
|
||
use unlucky_presenter::backend::AppOptions;
|
||
|
||
/// 进程内「run() 开始」的时刻(首帧耗时基线)。
|
||
pub(crate) static STARTED: OnceLock<Instant> = OnceLock::new();
|
||
/// 首帧日志只打一次。
|
||
static FIRST_FRAME_LOGGED: AtomicBool = AtomicBool::new(false);
|
||
|
||
/// 启动参数 → presenter 的后端选项。
|
||
pub(super) fn app_options(options: &Options) -> AppOptions {
|
||
AppOptions {
|
||
// `--data-dir`:M90 起**真的消费**它(此前是「接受并忽略」的翻牌兼容参数)。
|
||
// 截图通道强制 mock,所以 CI 冒烟那条 `--data-dir <tmp>` 不会往那个目录写东西。
|
||
data_root: options.data_dir.clone(),
|
||
mock: options.screenshot.is_some() && !real_host_in_screenshot(),
|
||
}
|
||
}
|
||
|
||
/// 取证开关 `ULL_SCREENSHOT_REAL_HOST=1`:让**截图通道**接真 host(默认关)。
|
||
///
|
||
/// ## 为什么需要它(M201)
|
||
///
|
||
/// 截图通道恒 `mock: true`(D30:同一份代码出同一张图),而 `MockBackend` **不发**
|
||
/// `CoreEvent::SettingsChanged` ⇒ 设置页拿不到任何真值,只能拍演示行。可「设置页里改了就生效」
|
||
/// 这件事的正脸图**必须带真值**:M193 用户的投诉就是「图上有 8 threads,追下去是假的」。
|
||
/// 这条开关把出图的**数据源**换成真 host(`--data-dir` 指一个临时根),渲染通道与落盘一字不改。
|
||
///
|
||
/// ## 边界(别把它当产品行为)
|
||
///
|
||
/// - 默认关;开了它就不再满足 D30 的可复现(数据来自磁盘,两台机器会不同),所以**只用于取证**;
|
||
/// - 它只改 `mock` 这一位,不碰任何业务分支(`build_backend` 的其余两条路仍然照旧);
|
||
/// - 与 `ULL_START_ROUTE` / `ULL_SETTINGS_GROUP` 同一族:只影响这一次进程。
|
||
fn real_host_in_screenshot() -> bool {
|
||
std::env::var("ULL_SCREENSHOT_REAL_HOST")
|
||
.ok()
|
||
.is_some_and(|raw| raw.trim() == "1")
|
||
}
|
||
|
||
/// 首帧耗时日志(只在第一次 `view()` 打一行)。
|
||
pub(crate) fn log_first_frame() {
|
||
if FIRST_FRAME_LOGGED.swap(true, Ordering::Relaxed) {
|
||
return;
|
||
}
|
||
if let Some(started) = STARTED.get() {
|
||
diag!(
|
||
"[boot] first frame in {:.0} ms(run() 开始 → 首次 view,含后端探测与字体扫描)",
|
||
started.elapsed().as_secs_f64() * 1000.0
|
||
);
|
||
}
|
||
}
|