AskaEth
|
7e1178fcb1
|
docs(motion): M305 r2 —— α 口径与代码对齐(唯一 → 唯一一处动画中间帧;登记两处静态 α)
只改 //! 文档行,零逻辑改动(git diff -U0 里没有一行非 //! 的增删):
- motion/reveal.rs:「全仓唯一一处把 α 交给渲染器」→「唯一一处**动画中间帧**的 α」,
并指向 fade.rs 里登记的两处静态 α
- motion/fade.rs:表里补两行静态例外 —— options/fade.rs::viewport_fade(常驻渐变,
12 条 1px 画布色块,α 从 (1/12)^1.6 到 1.0,不受 D27 约束的原因写清)与
ui.layer() 的 panel_alpha(CPU 强制 1.0;GPU 开背景图时才是真 α,属另一条线)
|
2026-11-13 22:30:17 +08:00 |
|
AskaEth
|
d24ef21bee
|
fix(motion): M305 r1 —— P1 空轨道重复脉冲 + 三条 P2(进场被删跳零 / 纯重排不落终态 / 遮罩取色与口径)
- P1-1:observe_progress 在「没有轨道且目标没变」时直接 return false。少了这一条,
ratio 恒为 1.0 之后每条 stage 事件都会插一条 from==to 的空轨道 ⇒ 200ms 后再闪一次
150ms 脉冲(违反「一次收束不循环」+ 空烧帧)。用例 a_repeated_reading_after_the_pulse…
- P2-1:visibility_of 改成按**当前采样**取(原来取 track.from ⇒ 进场途中被删的行 0→0,
高度当拍归零、下面整列上跳)。用例 deleting_a_row_mid_enter_does_not_jump_to_zero
- P2-2:遮罩底色 Card → Canvas(版本行脚下就是画布;深色档会露出亮一档)
- P2-3:纯重排(同 id 换序)当场把 order 对齐 current。没有轨道 ⇒ advance 早退 ⇒
expire 不跑 ⇒ order 会永远停在旧序。用例 a_pure_reorder_lands_on_the_new_order_at_once
- P2-4:fade.rs 收成唯一口径(预合成 = 恒不透明;子树淡出是唯一例外,且必须自成一层),
reveal.rs 指向它并登记 interaction α 守卫扫不到这一处的盲区
- 出图补深色档 list-exit-dark.png,probe 复跑 PASS(墨色 110→148 / 105→154)
|
2026-11-13 22:12:43 +08:00 |
|
AskaEth
|
ae33674293
|
feat(motion): M305 —— 列表增删(错位淡入/退场后才移除)+ 进度插值(到点一次收束)
- 新轨道两条:components/motion/pump/{rows,progress}.rs(纯算术,连拍 advance(dt) 可判)
· rows:两条清单(画出来的顺序 / 真值)+ 逐行可见度;退场行钉在原位、走完才移除
· progress:真实读数一跳,画出来的值沿 short4 追上去;到 100% 一次脉冲(0→1→0,不循环)
- 接线:pages/download/model.rs::from_snapshot(scope 内唯一 update 侧入口)
· 行清单 observe → 差分;退场行的数据进 leaving 池(否则退场行画不出来)
· 进度 observe(key = 版本 id)
- 视图:rows/list.rs 换成 keyed::Column(按 id 做连续匹配)+ 逐行 revealed(裁切框收放)
+ 同色遮罩淡出;install.rs 画插值后的值 + 收束
- MotionScale::NONE:不插轨道、不留幽灵,值严格等于最终值、零采样
- 顺手:rows/tests.rs 重新挂上 mod(此前一直没编译),i18n 扫描补两个新文件
判据:motion::tests::{rows,progress}、rows/motion_tests(Tree::diff 后未变行状态盒不变)、
出图 DevDocs/artifacts/m305(probe.py 量行距与墨色)
|
2026-11-13 21:42:18 +08:00 |
|
AskaEth
|
2fb61d8006
|
fix(launch): M304 r1 —— 根不见了必须拒绝 + 运行时态落进快照 + 真 host 端到端同拍判据
三条 r1 复审 P2,全部按代码修(不是只改注释)。
**P2-1(唯一实质问题)**:`ext-` 实例「所属根消失 / 改名」被静默放行 ⇒ 可能起错副本。
`live.rs::discovered` 反查落空时原来一律 `Ok(())`;而 `roots::all` 对非配置根是
`if !extra.is_dir() { continue; }`,于是官方根被删 / 改名 / 盘没挂上时反查必然落空 ——
放行之后 `build_request` → `game_for` 会退回配置根,配置根里恰好有同名 `version_id`
就把**另一份**拉起来(gameDir / 存档都在另一边),相对改前(同步扫盘 ⇒ 扫不出来 ⇒ 拒绝)
是回归。改法:把 M273 的「只哈希版本目录名」搬进 `discovered.rs`
(`legacy_external_instance_id`,与 `external_instance_id` 共用同一个多段 FNV),反查落空时
**老档案放行、其余一律拒绝**并给具名原因(+ `stale` 触发重扫)。
另加 `game::tests::instance_id_hashes_are_the_released_ones`:两个哈希公式的字面量钉死
——这次把哈希抽成公共函数,公式一漂就会误杀所有存量 `ext-` 档案。
**P2-2**:`send()` 只认 `InstancesChanged`,而 app 侧 `state.rs:274` 把
`InstanceRuntimeChanged` **就地**写进自己的列表 ⇒ `runtime` 这一位必定分叉,而
`accept::running_pid` 的兜底读的正是它。改:`send()` 同时把运行时态落到 `published`
对应条目,并加用例 `runtime_events_land_in_the_published_snapshot`。
**P2-3**:补真 `CoreHost` + 真 app 的端到端判据(`app/update/instances/scan_phase.rs`):
临时数据根 + 真 host,`advance` 到首份快照落地后点启动,**`advance` 之前**详情页必须已
`can_stop`。变异(把 Busy 挪进启动线程、只有回程才发)实测必红。
判据(新增 3 条 + 1 条钉公式):`revalidate_tests`(根不见了 ⇒ 拒绝)、
`target_tests`(运行时态落快照)、`scan_phase`(真 host 同一拍)、`game::tests`(哈希公式)。
用例夹具按 400 行硬线拆到 `launch/fixtures.rs`。
门禁:`cargo test -p unlucky-presenter -p unlucky-app-iced --all-targets` 全绿
(426 + 1076)· `clippy --workspace --all-targets -- -D warnings` 0 ·
`fmt --all --check` 0 · 行数全 ≤400(最大 366;未碰的 `app/update/instances.rs` 399)。
|
2026-11-13 20:46:00 +08:00 |
|
AskaEth
|
23c7beee8d
|
test(instances): M302 r2 —— 四条 P2:线程判据真能红 + 两处相位守卫 + 「只报一次」改成可数
- P2-1:`spawn_scan` 的闭包提成 `scan_body`,单测可装探针 + 闸门(只拦同一数据根的扫描)。
新用例 `a_refresh_returns_while_the_scan_body_is_still_parked` 断言四件事:扫描体跑在
**另一条线程**、返回时**一趟都没跑完**、闸门**还没放行**、返回耗时 < 闸门上限的一半。
把 `spawn("instances", job)` 换成直接 `job()` ⇒ 该用例红(原文见报告);旧用例
`a_refresh_returns_before_the_scan_is_done` 的 docstring 改成诚实口径(它证明不了不在调用方线程,
inline 时它照样绿——正是复审 r2 点的那条)。
- P2-2(真缺陷):`check_scan_stall` 补上与 `refresh_instances` 同一个守卫——已就绪
(`adopted().is_some()`)之后不再报 `Stalled`,否则一轮慢刷新会把有数据的一屏换成
「扫描实例比平时久 + 重新扫描」。新用例 `a_slow_refresh_after_ready_never_reports_stalled`。
- P2-3:`a_stalled_scan_reports_once_and_a_late_snapshot_still_lands` 原来把 `drain_events` 调了
两次(第二次必空 ⇒ 断言空转)。改成数实际发出的条数:第一拍恰好 1 条,之后三拍必须 0 条。
- P2-4:新用例 `a_refresh_after_ready_emits_no_scanning_phase`(真 host:已就绪后再刷新一条相位
事件都不许发)+ app 级 `a_refresh_after_ready_keeps_the_list_and_the_tree`(相位仍 `Ready`、
真控件树的文字/按钮节点数与刷新前逐项相等、列表没被清空)。
四条变异(inline / 去 P2-2 守卫 / 去「只报一次」锁 / 去 Scanning 守卫)都实测红,原文进报告。
|
2026-11-13 19:36:33 +08:00 |
|
AskaEth
|
f03411d58b
|
fix(instances): M302 r1 P2 —— 冷启动首帧三态(占位 / 真空态 / 有实例)+ N1~N4
复审 P2-1:扫盘搬后台之后,`App::dispatch(RefreshAll)` 返回时快照还没到(`pump` 只抽队列、
不 `tick`),**首帧必然**在快照之前。改前那一帧画的是「计数 0 + 还没有实例 + 实心导入 CTA」
——一句假话,而且 CTA 能点。改后三态分开:
- 真源放 presenter:`dto::InstancePhase { Scanning, Stalled, Ready }`,`AppState::instances_phase`
在 `CoreEvent::InstancesChanged` 落地时置 `Ready`;**不从 `instances.is_empty()` 推**(那就是 bug 形态)。
- `Scanning` = 首个快照未到:详情列画「正在扫描实例…」占位(新 key `InstancesScanning`,golden 两语),
列头**不写计数**(藏起来,不写 0 也不写「—」),**没有**空态 CTA。
- `Stalled` = 有界兜底:`host::game::SCAN_STALL = 15 s`(= 仓库内最坏实测 1.35 s 的约 11 倍,
理由在常量文档里)。超时只**报**一次、不掐线程;迟到的结果照样落地(代次没变)。
那一屏给一枚「重新扫描」→ `InstanceMessage::RetryScan` → 新命令 `AppCommand::RefreshInstances`
(只重扫实例目录,不重拉版本清单)。那一支在 `selected_instance_id()` 守卫**之前**——冷启动没有选中项。
- `Ready` = 收到过快照(空列表也算):这时才轮到 M273 的两种空态(没选中 / 一个都没有)。
- 已就绪时**不发** `Scanning`:否则每次刷新列表都退回占位(把卡顿换成闪屏)。
判据(都能红,变异原文见报告):
- `pages/instances/tests/cold_boot.rs`:真控件树(未就绪 7 枚按钮 = 有实例那一屏,零实例 = 8 枚,
多的那枚就是 CTA;每行 2 条文字节点的差分数行数)+ 源码结构(`Scanning`/`Stalled` 两支不碰
`detail_empty` 与空态文案、超时那支接 `RetryScan`、三个 key 真的被读)。
- `app/update/instances/scan_phase.rs`:首帧 = `Scanning`、空快照 = `Ready`、`Stalled` 经重试回到
`Scanning`、重试只派 `RefreshInstances`。夹具 `ULL_INSTANCE_PHASE` 只给出图(与 `ULL_SAVES_FIXTURE` 同一条纪律)。
顺手收 N1~N4(全是措辞 / 观测口径,不动行为):
- N1 两处 docstring 不再声称「`admit` 写成 `>=` 会红」(实测等价),改成「去掉或恒真才红」;
- N2 收尾用例那条 Idle 断言的失败消息改准(它不区分新旧快照,区分靠条数/代次断言);
- N3 boot 双刷新必然丢 1 份 ⇒ 丢弃日志分成「此前没有快照落地(启动期,非交错)」与「已有落地(真交错)」,
`Scan::dropped` 的文档登记这条已知噪音;
- N4 app 用例 docstring 收窄成「一拍无事件不清表、不钉时钟」(它绕过 `App::dispatch`,抓不住那 5 行)。
拆文件守 400 行:`detail/scan.rs`(分派 + 占位)、`refresh_stall_tests.rs`(空根 / 超时)、
`app/update/instances/scan_phase.rs`(app 级三态用例);`i18n` 的 `PAGE_SOURCES` 同步加上新页面文件。
|
2026-11-13 19:00:30 +08:00 |
|
AskaEth
|
a0413a5f94
|
feat(host): M302 实例列表刷新搬出 UI 线程(扫盘进后台 + 代次丢弃过期结果)
`refresh_instances()` 以前把「扫盘」与「盖回运行时态」写在一起,而 `dispatch(RefreshAll)`
那一支是唯一没走 `job::spawn` 的(邻座 ListVersions / ListLoaderVersions 都走了)⇒ 同步跑在
UI 线程:官方根 200 版本约 236 ms、叠加 10 份外部档案 1.35 s(仓库内旧实测 M297)。
两半分家:
- `game::instances(&env)` 是纯 env 函数 ⇒ 搬进 `job::spawn("instances", …)`,回程是新载荷
`HostTask::Instances { epoch, instances, elapsed_ms }`;
- 「按 `launches/pending/failed` 覆写 Running/Busy/Failed」要读 host 自己的表 ⇒ 留在 host 线程,
挪到结果回来那一刻(`handle::apply_instances`),结论与之前逐字一致。
顺序:每次刷新领一个只增不减的代次(`game::Scan`),只采纳当代次那份。并发两次刷新 / 扫描途中
删实例 / 收尾触发第三次这三种交错下,先发后到的扫描一律丢(不发事件:清空再重填只是把卡顿换成
白屏)。不做线程取消 —— 扫描是纯读,没有值得取消的东西,丢结果比掐线程安全。
可观测:每次采纳打一行 `[instances] N 个实例 · 扫盘 X ms · epoch E · 丢旧代次 D 份`,
丢弃也各打一行;账本 `game::Scan` 可直接断言(实例数 / 耗时 / 丢包数)。
判据(都真构造:真临时根 + 真 profiles.json + 真线程,只有交付顺序是摆的):
- `a_refresh_returns_before_the_scan_is_done`(改回 inline 即红)
- `two_refreshes_in_flight_publish_only_the_newest`
- `an_older_scan_cannot_resurrect_what_a_newer_one_saw_deleted`
- `a_launch_end_refresh_beats_the_two_that_were_in_flight`
- app 侧 `a_refresh_in_flight_keeps_the_list_and_does_not_pin_the_clock`(不清空 + M298 的
view_dirty 不永久置真)
- `launch_state_tests` 的 5 处 `refresh_instances()` 后补一行 `pump_instances()`(断言一字未动)
|
2026-11-13 17:58:59 +08:00 |
|
AskaEth
|
cb580a257e
|
feat(layout): M301 页面宽度跟随活轨宽(折叠 −64 / 展开 −88 / 动效插值)
用户口径「折叠腾出空间」只完成一半:M300 落了折叠态(ui.nav.collapsed + 一条并列宽度轨)
与折叠档令牌 nav_collapsed_width = 64,但 `pages/layout.rs` 的 `rail_width()` 仍是
`const fn` 恒返回 88 ⇒ 折叠后页面照旧按 88 扣宽,内容区右侧留一条 24px 空白带。
## 轨宽从常量改成**当帧活值**(不存第二份状态)
- 新增 `pages/layout/rail.rs`:thread-local `Option<f32>` 采样槽,`rail_width()` 读它,
未采样回落展开档令牌 88(不 panic、不 unwrap;NaN/inf/负数不写入)。
- 写入口**只有一个**:`app/view/rail.rs` 的 `rail_account()` —— M300 自己点名的「折叠档与
过渡进度的唯一构造点」,一次 `App::view()` 调用恰好一次,且排在页面树之前
(`app/view.rs` 的 `let account = self.rail_account();`),所以页面扣的宽度与外壳画的轨
是同一帧同一个数。折叠的**决定**仍在 presenter `prefs.nav_collapsed`,进度仍在 motion 轨。
- `rail_width()` 签名不变 ⇒ `pages/mod.rs`、`pages/agent/**`、20 个页面/测试文件的调用面
**零改动**(`git diff --stat` 里那两处都没有出现)。
- 动效期间自动插值:采样值就是 `RailAccount::nav_width` 的 64⇄88 插值结果。
- 门槛跟着动(公式里本来就是 `+ rail`):720/816 → 折叠 696/792。
## 测试
- `pages/tests/rail_width.rs`(新):折叠=窗口−64、展开=窗口−88、差正好 24、块右缘贴死窗口;
无外壳通道回落 88;采样不粘滞;门槛跟活值;**唯一写入口**(扫 `src/**` 源码);
**先写后读**(扫 `app/view.rs` 语句顺序)。期望值全从令牌取,不再写 88.0 / 64.0。
- `app/view/rail.rs` 新增接线用例:折叠→采样→展开→再采样必须回到 88(不粘滞)、
动效中间帧页面可用宽跟着插值(首尾逐位落在端值)。
- 原有钉 88 的三条改为「跟随模型」并逐条给了新算式(标题左缘 1180−88−48、块宽、
主列 pane);`pages/tests.rs` 400 行门禁前先拆出 `tests/demo_model.rs`。
## 判据
- app-iced:1064 passed(M300 基线 1053 + 11 条新用例);no_hardcoded_tokens 6 passed。
- `clippy --workspace --all-targets -- -D warnings` / `fmt --all -- --check` 均 0。
- 变异:`rail_width()` 改回常量 88 ⇒ 2 条几何判据 + 门槛判据红(1068 vs 1044);
删掉外壳那一行写入 ⇒ 2 条接线用例红(折叠仍按 88 扣宽)。
- 像素(DevDocs/artifacts/m301/,本地不进仓):日志页折叠态内容右缘 1107 = 展开态 1107
(24px 全进内容),变异版折叠态 1083(那条 24px 空白带);`instances` 页因详情列被 424
封顶、列表列固定 336,改前改后**逐像素相同**,所以举证必须用单列页。
|
2026-11-13 17:06:12 +08:00 |
|
AskaEth
|
fa53d72f73
|
feat(nav): M300 导航轨折叠按钮 + 折叠宽度令牌 + 宽度过渡与落盘
用户原话「默认左侧固定,允许折叠」:M297 盘查确认 iced 从来没接过折叠按钮,
presenter 的 toggle_nav/nav_collapsed 是 Slint 遗留、唯一调用方在已退役的 crates/app。
## 令牌(先定数值再动手)
- nav_collapsed_width = 56 + 2 × Space::X1 = **64**:胶囊(M283 定稿 56×32,本轮不动)
两侧各留 4px 才不被裁切;旧值 48 的死因写进注释(48 − 2×4 = 40 < 56)。
- 断言:折叠宽 ≥ 胶囊宽 + 2×X1、≥ touch_target 48、在 4px 栅格上。
## 折叠按钮与折叠态
- 按钮放**轨底、头像位之上**:轨顶一个像素都不动(M283/M299 的展开态对照图仍可比),
轨底本来就是「固定贴底」的槽,两态下位置不变。几何与导航项同一套(56×32 药丸 + 52 命中高)。
- 折叠态:隐藏文字标签与账户名,每项/头像/按钮都补 M3 气泡 tooltip(折叠态不许是一列无名符号);
纵节奏不变(项高仍 52),所以切换时图标不上下跳。五项图标两两不同由用例钉住。
## 动效(复用底盘、零新常数)
- 帧泵加**第二条并列宽度轨**(nav ⇄ rail),时长/曲线复用同一张 rail_spec();
借用右栗那条会让左轨折叠改掉右栗的目标(暗耦合),tower 已否掉那个方案。
- 起跑在 update 侧;启动按落盘档对表(幂等,仅在没有轨道在跑时);折叠**不置 view_dirty**
—— 宽度在 view 里逐帧采样,帧由轨道自己续,跑完 needs_frames() 回 false。
## 落盘(任务要求「重启读回仍折叠」)
- core schema 登记 ui.nav.collapsed(纯增量 bool,不碰 schemaVersion);presenter 桥加
load/persist 一对(走唯一写入口 write_with,现读→改→落盘);启动 construct 读回。
- 截图通道不读用户配置(D30),折叠态出图走 ULL_NAV_COLLAPSED=1。
## 判据
- app-iced: 1053 passed(新增 3 条接线用例 + 7 条 shell/几何/树结构用例 + 6 条帧泵用例);
presenter: 408 passed(新增落盘往返 1 条);fmt / clippy --workspace --all-targets 均 0。
- 变异实验:删 observe_nav ⇒ 宽度停在 88 红;删启动对表 ⇒ 首次展开硬跳红。
- 像素(DevDocs/artifacts/m300/):折叠态内容左缘 112→88(24px 真回收)、药丸两态都 56 宽
且折叠档右缘 59 < 64(不裁切)、图标行是展开态子集(纵节奏不变)。
## 已知偏离(已登记,tower 批了紧随 mission)
crates/app-iced/src/pages/layout.rs 的 rail_width() 仍是 const fn(恒 88),所以折叠态内容块
宽度不变、整块左移 24px ⇒ 右侧多一条 24px 空白带(像素图里保留、未裁)。
|
2026-11-13 16:15:16 +08:00 |
|
AskaEth
|
fa36ccbb66
|
fix(gate): M299 r1 —— tabs 判据改测闭包本身 + 反例判据抗重排
P2-1:tabs 的样式闭包抽成 tab_style(..)(返回闭包本身,照 chip_style / choice_style
的写法),单测喂的就是交给 button 的那个闭包。上一版只测闭包里的助手 tab_fill(..),
复审把 fill 挪到闭包外、闭包写回 |_theme, _status| 时用例仍全绿——本 mission 在 tabs 上
唯一的代码修复当时没有判据。现在同一变异红(hover 必须叠状态层)。
P2-2:反例判据改成「去空白 + 去花括号」后比对 .or_else(||Some(ui.color( 与
.unwrap_or_else(||ui.color(,换行 / 缩进 / 闭包加括号都躲不过;扫描跳过 .../tests/ 与
tests.rs;新增 KNOWN_COPIES 豁免表(slider handle,scope 外,已报 finding)与
every_known_copy_is_still_there(豁免不许挂空条目)。自证:名单外第 12 个站点用
换行+花括号写法 ⇒ 红(两种形态各一次),删掉后三条全绿。
行数:tab_style 拆到 components/navigation/tabs/style.rs(tabs.rs 398 → 370)。
像素:new 与 new2 两套 24 张逐张 md5 相同 ⇒ 行为中性。
|
2026-11-13 15:24:44 +08:00 |
|
AskaEth
|
8144714f30
|
docs(ui): M299 补三处与行为对齐的文档(chip 选中支、tabs 状态层、分组导航的半径实测)
只改注释,零行为变化:cargo test -p unlucky-app-iced --all-targets 全绿(1040 + 6)。
|
2026-11-13 14:56:20 +08:00 |
|
AskaEth
|
0d917e88da
|
feat(settings): M299 选中项收口到 selected_fill + 分组导航改真按钮 + tabs 用 status
- pages/settings/frame.rs:分组导航从 mouse_area 换成 button(四态可辨),
几何不变(ShapeScale::Full 胶囊),颜色走唯一的 selected_fill;forced 从
settings::view 递下来,于是 --force-state 能拍到这一处
- language / theme / appearance choice 三处「选中档位」补状态层
- components/selection/paint/chip.rs:选中分支用上 state 参数
- shell/nav.rs / shell/nav/account.rs:两处私有拷贝收口到 selected_fill(几何没动)
- components/navigation/tabs.rs:状态层在样式闭包里跟 button::Status 求值
- 新增门禁 selection/tests/single_entry.rs:11 个站点必须调 selected_fill +
产品代码里不许再出现 or_else(|| Some(ui.color( 这个私有拷贝形态
- 行数:frame.rs / nav.rs / appearance::section 的单测按「纯搬移」拆出去
|
2026-11-13 14:41:57 +08:00 |
|
AskaEth
|
3b4f8bb997
|
fix(notify): M298 r1 —— toast 退场腿接上触发点,四条 nit 一并收
复审 P2-1:进场腿自己续帧所以能跑,但进场结束 view_dirty 回 false,
3.6s 后 remaining_ms 掉进 150ms 退场窗口时没有任何代码重新置脏 ⇒ 那一段
收缩从不发生,expire 一抹胶囊整块消失(复审探针:120 拍后 progress 仍 1.0)。
- update/clock.rs:expire 之后用无副作用的 ToastTracks::needs_frame 置脏,
落到既有的 if view_dirty { refresh_view() } 上采样;refresh_view 仍是门控,
没有变成每帧无条件调用(静止态判据不变)。
- state/toast.rs:进出两条腿改成**同一个时钟**(寿命 = 首次看见时的
remaining_ms,已活 = 寿命 - remaining),去掉 Instant 与节拍的二时钟错位;
新增 needs_frame;新增两条真节拍用例(退场腿必被抽到 / 到期后不再续帧)。
- N1:装配失败与 spawn 失败也用 publish_before_start 的返回值填 session。
- N2:m298 用例注释改准(它抓不住删守卫,那条由 a_report_from_another_session 守)。
- N3:before_start 文档不再把两种来源都说成「进程没起来」。
- N4:refresh_view 末尾的死赋值 view_dirty = false 删掉。
红灯原文(摘掉那 3 行触发点,其余为最终代码):
退场窗口内必须真的开始收(remaining=Some(40)ms, progress=1)
|
2026-11-13 13:50:41 +08:00 |
|
AskaEth
|
aa25583806
|
test(launch): M298 用例搬到 dialog/tests/m298.rs(守 400 行单文件门禁)
|
2026-11-13 13:21:24 +08:00 |
|
AskaEth
|
97768b7e3f
|
fix(launch): M298 收尾失败不再静默、Toast 接上渲染点
三条提示通道各漏一半,用户才会「启动失败但找不到崩溃日志」:
1) 收尾失败静默不弹:finish() 报错那条出口把 LaunchFinished.session 发成真实
会话,而它登记的现场走 before_start 写死空串 ⇒ dialog.rs 的并发守卫
(!finished.session.is_empty() && report.session != finished.session)成立后
return。改成**会话归属单一来源**:before_start 新增可选 SessionAttribution,
presenter 的 publish_before_start 把它写进报告后**返回同一个值**给 DTO;
两处不可能再各写一遍。守卫原样保留(A 场的现场不许解释 B 场,有用例钉住)。
2) Toast 只 push 不渲染:新增 app/view/toast.rs 覆盖层(MD3E 胶囊 =
ShapeScale::Full,底部居中,InverseSurface 底),接在页面树之上、模态之下;
过期仍走 clock.rs 的 expire;进/出过渡复用模态那一对腿(motion::modal_spec /
modal_scale / scaled),时间轴在 update 侧由 state/toast.rs 采样,关掉动效时
恒为终值(出图可复现)。两个拷贝路径补上 view_dirty(推了不置脏要等下一次事件)。
3) 三个贴线文件先拆再改(纯搬移 + pub use 保路径,机器核对:搬出的 14 个函数体
逐行相同):crash_report.rs 394→318(+context.rs / slot.rs)、
host/launch.rs 399→233(+build.rs)、app/dialog.rs 390→178(+model.rs)。
证据:DevDocs/artifacts/m298/toast-capsule.png(--screenshot + ULL_TOAST_FIXTURE,
图上可见「游戏崩溃 · 12.0 秒」胶囊)。
|
2026-11-13 13:18:11 +08:00 |
|
AskaEth
|
638e2a3efe
|
feat(app-iced): 选中项叠状态层收口到一处,导入向导的版本行不再「选中就没反馈」(M296)
用户 2026-11-13:已选中的控件在悬停/按下时没有任何反馈。塔裁决「要加」——
MD3 的行为是选中容器(SecondaryContainer)上**再叠**状态层,两者不互斥。
枚举(全仓 grep SecondaryContainer / selected + fill,表见报告)后事实是 3/4 已做:
菜单项(inputs/menu.rs)、列表行(navigation/list_item.rs)、导航项药丸
(navigation/drawer/item.rs)各自写了一遍同一个 6 行形状;真正「死」的是导入向导的
版本选择行(app/adopt/pick.rs:底色写死在容器样式里,按钮样式恒 None)。
三件事:
1. 抽 interaction::selected_fill(ui, state, content, container)——第三次法则的落点,
与未选中态的 layer_color 分两条(未选中静息没有底,选中项静息有底);
2. 三处拷贝收敛到它(menu / list_item 从 overlay_color 升到带时间轴的 layer_color,
无取样控件时逐位相同;drawer 只换入口);
3. pick.rs 的候选行底色搬进 button 的样式闭包 → 选中行按 hover 8% / pressed 10% 叠层;
未选中行仍恒 None。
判据:rest 图 16/16 md5 与改动前全等(含 adopt-pick-selected);列表选中行
rest/hover/pressed 三色互异(浅 212,228,246 / 196,212,230 / 192,208,225);变异把
selected_fill 改回「不叠」→ 7 条单测红 + 该行三张图逐像素相同(1/3)。
全量 cargo test -p unlucky-app-iced --lib:1017 passed / 0 failed。
|
2026-11-13 12:58:50 +08:00 |
|
worker-mirrorwire
|
69e3e198bf
|
fix(download): M294 r1 —— JDK 下载页与 CLI 也接上源选择,文档同步
复审 p1:发布版 GUI 的「下载 JDK」页是第二处 DownloadEngine::new(要 with_progress
拿走引擎),此前只读了 gh_proxy,region / mirror_preference 到不了那里;报告却把 GUI
写成「已接」。复审 p2:CLI 四条命令同样不认这两个键 —— 同一份 launcher.json 在 GUI 与
CLI 上表现不同,等于把「设置项说假话」挪到 CLI 侧。
- app-iced downloads/engine.rs:新增 SourceSelection(只读键、不翻译)+
engine_with(.., &SourceSelection),在 gh-proxy 三态之前叠
DownloadTuning::with_source_selection(..).apply_source_selection(..)
- Request 加 source_selection 字段,构造点(app/update/settings/java/downloads.rs)
用 SourceSelection::of_config(&config) 显式传值(不从缓存根反推配置路径:
反推失败会静默按 auto 收场,正是本 mission 要消灭的形状)
- cli download.rs 的 tuning():链 .with_source_selection(..),四条命令共用这一处装配
- core 的 summary():把「地区 / 镜像偏好」两档印进 CLI 那行生效值(CLI 既然读了,
少印两项就是新的半真话)
- shared.rs 注释说清「共享引擎」与本文件的第二处装配点;app-iced 模块头补对照表
- 新判据:JDK 下载页与 CLI 各一条断候选源顺序的用例,均配变异验证(换回裸
EngineConfig::default() / 裸 tuning 即红);补一条 region=cn + gh_proxy=off 的
叠加次序守卫(复审 P3-2)
- docs/download-settings.md:§1 表格两行、§5 拆「已接 / 未接」、新增 §3.1.1 覆盖面
表、§6 补 M14-M16 三条变异、§7 补「镜像可达性没验」
连带(不加就编不过,已报备 tower):downloads/tests.rs 的 Request 字面量 +1 行、
downloads/tests/gh_proxy.rs 两处 engine_with 调用点补第四参。
|
2026-11-13 09:00:00 +08:00 |
|
AskaEth
|
9780fca1c7
|
fix(app-iced): blend 用例端点改用真实令牌色(M288)
`blend_hits_both_endpoints_bit_for_bit` 原先用 `Color::from_rgb(0.1, 0.2, 0.3)` /
`(0.7, 0.8, 0.9)` 当端点,撞上 `tests/no_hardcoded_tokens.rs` 的色值守门(它扫 `src/**`,
测试模块也算在里面),使 base 上「全量测试绿」永不可达。
修法在**被判的那一侧**:端点取状态层真正会插值的一对角色——`Role::Surface`(容器)与
`Role::OnSurface`(内容)——经产品同一条令牌路径(`BrandBoard::for_flavor(..).scheme()`
→ `to_iced`)取值,浅/深两套板各跑一遍;判据值全部现算(每通道取两端 min/max)。
三条判据一条不少:端点逐位相等、中点**严格**落在两端之间(比原先的闭区间更强)、α 全程 1.0。
守卫文件一行未改(仍 400 行),扫描范围没有放宽。
|
2026-11-12 23:55:24 +08:00 |
|
AskaEth
|
5174845cda
|
fix(app-iced): 列表行圆角改从组件表取(M287)
`list_item.rs` 硬写 `ui.radius_dp(ShapeScale::None)`,旁边还写着「4.x:ListItem 的形状
是 `CornerNone`(直角),没有圆角档」——而令牌表 `component_map::table` 的 `ListItem`
是 `Small(8)`。产品侧根本没读表,注释在替错误站台。
改为 `row_border(ui)` → `ui.radius_dp(Component::ListItem.spec().shape)`,数值只留在
令牌表。变异实测(只改表的那一行 `Small→Large`):行圆角 8 → 16,240×60 选中行的顶行
缩进 6 → 13;改回后 11 张图逐字节复原(`DevDocs/artifacts/listradius/`,不入库)。
同 commit 改掉那条直角注释、模块文档补 4.9 行,并加两条单测:渲染路径的
`Border.radius` = 表值折算;源码里不许再出现硬编码的 `ShapeScale::*` 档。
外观影响的边界(写进取证报告):行只在 选中/悬停/按下 时有容器色,未选中静息与禁用
两态 `fill` 为 `None`,那两态逐字节不变是**正确**的,不是没生效。
|
2026-11-12 23:38:56 +08:00 |
|
AskaEth
|
5ce6d8567b
|
docs(app-iced): 残余清单改成「残留快照,不是契约」(M285 r3 p2-1)
r3 复审的判据改成「不假」而不是「穷尽」,按它给的结构重写这一段:
- 删掉「**所有** components::selection 的 chip/switch 站点」(这个词两头都不准:字面上吞掉了
已接的 general/language.rs + general/theme.rs 那 6 枚,又把另一个页面 pages/content 的 chip
算了进来);
- 第 3 条改成「还剩没搭线的那几处(已知:…;这一串会漂,接线时请更新)」,并补
java/view.rs 运行时的「设为默认」chip(action_chip 在非默认行返回可点的 Chip::filter,
runtime_row 没有 forced 形参);同时明写 language/theme 那 6 枚已接、别算进残余;
- 新增第 5 条:ai/view.rs 的 base_url / model / api_key 三只 Field(此前四处都不落);
- 末尾加「这份清单是残留快照,不是契约:接上一处就划掉一处,判据只有『有没有拿到 forced』」。
顺带订正 r3 的两条 p3(同一段、纯注释):
- 主题 chip 枚数 2 → 3(general/theme.rs 的 THEME_MODES: [ThemeMode; 3]);
- 第 4 条去掉「下载按钮」:java/download_view.rs 里一个 Button 都没有(那两处是 chip,已在第 3 条)。
第 1 条(Downloads 组 + e283c585… 三态 md5)与「已经消费的」「没搭线≠组件库缺能力」两句一字未动。
纯 //! 注释,零代码行。harness.rs 347 → 356 行。
|
2026-11-12 23:12:21 +08:00 |
|
AskaEth
|
a75ee465a7
|
docs(app-iced): harness 文档的残余清单改写(三条与代码不符的句子,M285 r2 p2-1)
r2 复审逐句拿代码核过,三处不符:
(a) 「裸 iced::button 已经消费」—— 8 颗预设色块本身就是裸 button(...) 且不消费(accent.rs:153/
163-166,ACCENT_PRESETS: [&str; 8])⇒ 改成「这几类控件(choice / chip / action_button)已消费」。
(b) 残余清单漏了三类 ⇒ 补:Downloads 组整组(body 形参就是 _forced,switch/chip/两颗 Button::new
都不看 forced;实测 --screen=download 三态 md5 全等 e283c585…)、所有 components::selection 的
chip / switch 站点(update 自动检查 chip、ai provider/reveal chip 与开关、download 两处、java
prefs_view 与 download_view)、java 的字段/预设/下载按钮(fields.rs / prefs_view.rs /
download_view.rs)。同时修掉与本 mission 自己的 artifact README §1「下载页不消费 forced」的
互相打脸。
(c) 「要接得先给组件开口」对滑块/文本框是假的 ⇒ Slider::state / Field::state / Chip::state /
Switch::state 全都存在且被消费(gallery 矩阵用的强制视觉态),没接是**没搭线**;真正缺 forced
入口的只有本地助手 accent::swatch。并加一句反向锚:别把「裸 button」当「已消费」的同义词。
纯文档,零接线改动。harness.rs 330 → 347 行。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
3255579bba
|
docs(app-iced): 把「裸 button 不消费 forced」这句假话改掉 + 用法表补 --force-state(M285 r1 p2-1)
复审原话:harness.rs:43-45 那段「设置页外观区的滑块 / 文本框 / 裸 iced::button 都不消费
forced」**正是 M282/M284 放弃像素验收的原始理由**——M285 之后后半句已经是假的,留着它下一
个 agent 会再推一遍同一个死胡同。
- pages/harness.rs:参数清单补 --force-state=rest|hover|pressed;那段改成现状(裸 button 已
经消费:外观 11 处 choice + 语言/主题 chip + 日志 action_button 走 settings::resolve_state;
components::buttons::Button 三组走 forced_button;仍不消费的只剩滑块 / 文本框 / 色块),
并明确「别把它改回去」。
- examples/pages_demo.rs:用法表补 --force-state / --appearance / --passthrough(此前
--force-state=bogus 先报约束、却打出一张没有该参数的用法表)。
实测:`--force-state=bogus` → exit 2,用法表里含 [--force-state=rest|hover|pressed]。
顺带 r1 p3-1(机制说法订正):forced_button 的文档写清真实优先级 —— Button::forced() 是
`self.state.or(没有消息 ⇒ Disabled)`,**state 在前**,所以 disabled 也能被 .state() 盖掉;
并加一条单测钉住实际结果(forced_button(Button::disabled(..), Some(Hovered)) ⇒ state:
Some(Hovered))。今天 7 个站点都写在启用分支里所以安全,但**别**把它套到 if..else 外面。
行数:settings/mod.rs 396 → 397(先算过余量,未破 400);harness.rs 321 → 330;
pages_demo.rs 37 → 39。判据 1 复验:重出 5 张 rest 图,md5 与基线逐张相同。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
e15bd741cd
|
fix(app-iced): 守住 400 行 + 接线计数点跟着语言行搬(M285 门禁红)
- settings/mod.rs 401 → 396 行(压自己新加的文档,不删任何语义):
components::gate::every_source_file_stays_under_the_line_limit 红过。
- pages/tests/base_wiring.rs:语言行搬进 general/language.rs 后,`BASE.role()` 的
计数点也要跟着搬(general.rs 0 次 → language.rs 1 次),否则
every_card_internal_control_site_still_passes_a_base 红。计数与语义都没变。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
20607a0584
|
test(app-iced): 三处测试调用点补 forced=None(M285 形参同步)
section_columns / settings::view / appearance::section 各加了 forced 形参,
测试里的调用点必须同步(同行情改,不增行;frame.rs 用一次局部绑定避免折行)。
frame.rs 393 → 394 行。产品逻辑零改动。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
7b80b0e314
|
feat(app-iced): --force-state 穿过设置页与外观区(M285 FORCEDWIRE)
pages::Model.forced 此前只到得了外壳 + 实例页(唯一消费者 pages/instances.rs:159),
设置页「外观」区的滑块/文本框/裸 iced::button 全都不消费它 ⇒ M284 那四处状态层
(语言档 / 主题档 / 日志 action_button / 外观 choice)只有单测证据,做不出像素判据。
改动:
- settings/mod.rs: resolve_state(forced, status) 与 forced_button(button, forced)
成为全仓唯一两个「forced 怎么解释」的点;view → form → section_columns 一路
新增 forced 形参(默认 None ⇒ 今天的行为逐字节不变)。
- Group.body(registry.rs)加第 4 个形参 Option<Interaction>,六组统一签名。
- 四类裸 iced::button 控件把 forced 递进样式闭包并经 resolve_state 落地:
语言档 chip(general/language.rs)、主题档 chip(general/theme.rs)、
日志 action_button(logs_row.rs)、外观 choice(appearance/section/mod.rs,
含 accent/window/bg_image/colour_source 共 10 处调用)。
- components::buttons::Button 的三组(update / ai / java)走 forced_button 的 .state()。
- harness: request.rs 解析 --force-state=rest|hover|pressed(未知值报错,词与产品
cli.rs 同一套),slug 加 -hover/-pressed;app.rs 把它写进 model.forced,
截图通道默认 Rest 的纪律保持(显式值优先)。
- pages/mod.rs: 唯一调用点把 model.forced 传给 settings::view(397 → 399 行)。
单测:resolve_state 的 Some 覆盖 / None 退回;forced_button 的 Some 钉态 / None 原样。
|
2026-11-12 23:12:20 +08:00 |
|
AskaEth
|
767727efb7
|
refactor(app-iced): 语言行从 general.rs 纯搬移到 general/language.rs(M285 前置)
general.rs 390 行、撞 400 行硬线,而 M285 要给语言行三枚 chip 接 forced。
切法与既有的 general/theme.rs 同构(一个行控件一个文件):正文 view + chip +
chip_style。语句逐字节搬移(去缩进去可见性后 diff 退出码 0),只把
fn language_row 改名成 pub(super) fn view(与 theme::view 同一入口命名)。
general.rs 390 → 245 行;language.rs 170 行。
|
2026-11-12 23:12:19 +08:00 |
|
AskaEth
|
589162de89
|
refactor(app-iced): 设置页分组注册表纯搬移到 settings/registry.rs(M285 前置)
settings/mod.rs 撞 400 行硬线,而 M285 要给 Group.body 加 forced 形参。
按语义切分(不按行数):mod.rs 留「整页 → 表单列 → 分组正文」流水线,
registry.rs 留「分组是什么」—— Body / Group / GROUPS / GROUP_LABELS /
SELECTED / GENERAL_GROUP / UPDATE_GROUP / AI_GROUP / JAVA_GROUP。
行为零变化:搬移块去缩进去可见性后与 mod.rs:190-275 逐字节相同
(diff -u 退出码 0,见 mission 报告)。可见性只有两类改动:
- Group / Body 用 pub(crate),因为 mod.rs 要 pub(super) use(E0365:再导出
不能比原声明更可见);
- 其余常量 pub(super),mod.rs 私有 use,子模块的 super::GROUPS 写法照旧可用。
|
2026-11-12 23:12:19 +08:00 |
|
AskaEth
|
747521fe54
|
fix(app-iced): pressed 状态层改走 Interaction 表 + 外观 choice 叠卡底(M284)
四处手写按钮(语言 chip / 主题 chip / 日志行两个动作 / 外观互斥按钮)把
`matches!(status, Hovered | Pressed)` 合成分支换成 `Interaction::from_status(status)`
→ `interaction::layer_color`:pressed 从被压成 hover 的 8% 回到令牌表的 10%。
`appearance/section/mod.rs` 的 `choice` 合成底从写死的 `Role::Surface` 换成
`frame::BASE.role()`(= `MaterialLayer::Card` = `SurfaceContainerLow`),与同屏
`general.rs` / `general/theme.rs` 同源——M234/M237 修过两轮的那个错,这是漏网的第三处。
四处样式闭包抽成自由函数(`choice_style` / `chip_style` / `action_style`),单测直接喂
`Hovered` 与 `Pressed`:断言两色不等 + 各自逐值等于 `state_over` 的令牌求值(改 α 也会红)。
语言行那枚 chip 的断言住在 `general/theme.rs`(`general.rs` 已 390 行,贴 400 硬线)。
未验:hover/pressed 的图像判据。`--force-state` 到不了设置页/外观区(那里不消费
`Model.forced`,`pages/harness.rs` 自己写着),接线另立任务;本轮只出 rest 图并比 md5。
|
2026-11-12 21:48:03 +08:00 |
|
AskaEth
|
813ec88d89
|
fix(app-iced): 导航轨 hover/pressed 状态层落回药丸(M283)
`rail_item` 里同一个元素并存两种形状:选中画 56×32 `radius(Full)` 的药丸,
而 hover/pressed 走 `button(stack).style(..)`,底色铺在整个按钮上
(88×52,`Border::default()` ⇒ r=0)。用户原话就是这一处:「侧边栏按钮
hover/click 是父容器整个方形区域颜色加深,而不是 MD3E 应有的胶囊 UI」。
改法照 `components/navigation/drawer/item.rs`(全仓唯一正确的导航实现):
- 状态层改由药丸自己的 `container.style` 求值,经 `interaction::layer_color`;
选中/未选中两条分支不合并(选中静息兜底 `SecondaryContainer`,未选中兜底 `None`);
- `base` 取 `MaterialLayer::Canvas.surface_role()`(导航轨画的就是 Canvas),不写字面量角色;
- `button.style` 的 `background` 恒为 `None` —— 按钮不再有任何形状上的主张;
- `interaction::tracked` 只在 `forced.is_none()` 时套(强制态是取证,不许有过渡中间帧);
- 轨底账户位同型:状态层落在头像那个 Ø40 圆上,不是 88×52 的按钮;
- 药丸几何收敛到 `state_layer_box()` 一个定义点(`rail_item` 与单测都读它)。
判据:
- `shell::tests::the_state_layer_is_drawn_on_the_pill_not_across_the_rail`(几何)
- `shell::tests::the_rail_never_paints_its_state_layer_on_the_button`(结构:扫源码,
`button(stack)..on_press` 闭包里不许出现 `Background::Color`、`tracked` 必须有
`forced.is_none()` 守卫)。两条都做过反向验证:把写法改回去,两条同时红。
取证(`DevDocs/artifacts/navpill/`):`--force-state=rest` 与改前**逐字节相同**
(md5 1ebb611b3edc156925ca802d3f4fa76d);hover/pressed 的高亮区从 88×52 矩形
变成 56×32 药丸(四角为空),账户位从 88×52 变成 Ø40 圆。
|
2026-11-12 21:42:30 +08:00 |
|
AskaEth
|
ca086a40ee
|
feat(app-iced): 右栗折叠腿的取证夹具 ULL_AGENT_RAIL=exit-collapsed(M279)
`AgentUi::new()` 出厂就是折叠档,而 `collapse()` 只在档位是 Expanded 时才改档并
`observe_rail(false)`(`pages/agent/state.rs:245-255`)⇒ 原来的 `ULL_AGENT_RAIL=collapsed`
是**空操作**:配 `ULL_MOTION_AT` 拍「折叠中途」时,拍到的只是静息折叠 + 页面入场位移
(M257 r2 复审实测,md5 与静息帧相同)。而折叠腿恰好是「页面模型比物理区更宽」的那一侧
(`content_width()` 吃目标档、`rail_width()` 吃动画进度),最可能出现右侧裁切/重叠的方向
反而没有任何像素证据。
新取值 `exit-collapsed`:先 `expand()`,把展开这一腿 `snap_rail(true)` 落成静息,再在
**同一拍** `collapse()` ⇒ `observe_rail(false)` 真的从进度 1.0 起跑一条 1 → 0 的折叠轨道。
snap 不能省:钉住相位时两条轨道共用同一个起点(`Pump::start_now()` 恒为 0),不 snap 的
话收起那一腿会从展开的半路反向,三张相位图就不是同一条腿,位移表也就没法与展开腿对照。
原有 `collapsed` / `expanded` / `detached`(含「认不出的取值 ⇒ 一动不动」)语义逐字未改,
由新用例 `the_old_rail_values_keep_their_exact_meaning` 守卫。档位落法抽成纯函数
`seed_rail`(用例直接喂名字,不摆环境变量);启动日志多一个 `seed=` 字段——静息落档与
「真的起了折叠轨道」两者的 `rail` 都是 `collapsed`,只有 `seed` 能分辨。
用例:`exit_collapsed_really_starts_the_collapse_leg_from_full_open`
(轨道在跑、第一帧严格 320、进度严格递减、末端严格 0、帧数 10~20)。
|
2026-11-12 20:59:54 +08:00 |
|
AskaEth
|
c869a7b346
|
fix(motion2): 修掉 drawer 药丸守卫的同义反复(M257 r1 复审 P2-1)
`rail_selected_label_is_secondary` 里那条 `pill_color(..) == pill_color(..)` 比的是同一个
表达式,恒真、抓不到任何东西,而注释还写着「rail 与 drawer 两条线的药丸一致」。
药丸从 M257 起不吃 layout(`pill_color` 的签名里没有 `NavLayout`),所以「两条线一致」由
签名保证;本用例改判真正要守的那一件事:静息选中药丸来自 `SecondaryContainer`。
变异验证:把 `pill_color` 的兜底角色换成 `Primary` ⇒ 该用例在 tests.rs:54 红。
|
2026-11-12 20:05:58 +08:00 |
|
AskaEth
|
b61f342431
|
feat(motion2): 右栗折叠过渡 + 状态层 hover/pressed 过渡
A2 侧边栏(右栗 AI 栏)折叠/展开:
- 帧泵第三条轨道(pump/rail):展开 medium1 250ms + Emphasized、收起 short4 200ms +
EmphasizedAccelerate;拆出档走 snap(不做过渡);MotionScale::NONE 下不插轨道
- 宽度在 metrics::rail_width 逐帧插值;面板内容按目标宽排版 + 按动画宽裁切
(不重排 / 不溢出),宽度回到 48 时换成图标条(图标贴右端,位置不跳)
- pages/agent/mod.rs 的展开档面板纯搬移到 panel.rs(371 → 289 行)
A8 状态层过渡:
- interaction/layer:令牌化的进度时钟(short1 50ms),NONE 当帧落终值
- interaction/hover:取样控件把起跑搬到 Widget::update(早于订阅表求值)
- 按钮五式 + IconButton + 导航项药丸统一走 interaction::layer_color
(没有取样控件时逐位退回旧口径,出图基线不变)
- components/motion/mod.rs 的 needs_frames / advance 各加一句委托,
帧订阅仍然只有一处判断点(app/subscription.rs 未改)
|
2026-11-12 20:05:58 +08:00 |
|
AskaEth
|
dc8323531b
|
feat(presenter): 多根发现(配置根 + 官方启动器标准位置),实例按自己的根解析存档/模组/启动(M274)
|
2026-11-12 19:55:51 +08:00 |
|
AskaEth
|
fb30598604
|
fix(app-iced): 首屏(Kind::Boot)不再把旧页画成 250ms 幽灵
`pages::render` 把 `previous_view` 整棵画在当前页**下面**(`stack![opaque(ghost),
current]`),只在空隙里露出来。boot 那份快照是 `construct.rs` 的零实例初始模型,
被 `refresh.rs` 在第一次 refresh 时换进去 ⇒ 每次启动都闪一张「这里还没有实例…」+ 空态
图标磁贴(退场腿 250ms,`Kind::Boot` 用的就是它)。
修法:`App::sync_motion` 丢掉旧页快照的判据从「没有轨道了」扩成「没有轨道了 **或**
这一档本来就没有旧页」,后者走 `Kind::has_old_page()`——M256 写好的设计口径,此前全仓
无人调用(死代码)。丢在 `update` 的尾巴、`view` 之前 ⇒ 一帧都画不到;不动
`components/motion/**`(M257 的 scope),也不动 `app/state/refresh.rs`(不在本 scope)。
用例:`motion_sync/tests.rs` 新增三条(首屏每拍都不许留旧页 / Peer 换页仍留着旧页退场 /
NONE 档既不起轨道也不留旧页),删掉守卫即红。`motion_sync.rs` 已 398 行,按 400 行硬线
把测试模块**纯搬移**到 `motion_sync/tests.rs`(233 行逐字节相同,仅去掉模块缩进)。
出图:`DevDocs/artifacts/ghostfix/`(页面区 y<720 残余 = 0px;去掉守卫的同一棵树在同样
三相位残余 6897/5291/4762px)。
|
2026-11-12 19:35:19 +08:00 |
|
AskaEth
|
c5009442c5
|
fix(presenter): 自动发现实例按盘上痕迹选运行目录 + 空态用例补上分支/传参(M273 r1)
r1 复审的两条 P2。
## P2-1:外部实例不再一律 isolated
`versions/<id>/` 里出现 `mods` / `saves` / `config` / `options.txt` 任一 ⇒ `isolated`
(gameDir = `versions/<id>`);否则 ⇒ `shared`(gameDir = 游戏根 `.minecraft`)。
判据只 `exists()`,只读,不改盘;写盘仍然只在用户第一次改设置时(`ensure_profile`)。
- 判据收在 `host/game/discovered.rs::base_mode()`,签名 `(env, id, version_id)`:
**有档案的实例返回全局 `instances.mode`**(D20 原口径,一字不变),自动发现的按痕迹判。
- 派生运行目录的五处(启动 / mods / saves / addon install / addon pack)从传
`global_mode` 改成传 `base_mode` ⇒ 「读到的存档/模组在哪」与磁贴上的 ISOLATION 同一份判据。
- `isolation_of(env, id, version_id)` 多收一个版本 id:实例文件优先,缺省 `base_mode`。
于是懒创建档案(只写 launch 段、isolation 仍为空)之后,磁贴不会从「共用目录」翻回「独立目录」。
- 磁贴/详情:自动发现的实例多一句「运行目录由盘上已有的文件决定」(i18n 中英);
按启动时再弹一条说清**这一次**用哪一边(`pages::instances::discovered_run_toast`,
同文案在屏不重复叠加)。
- `dto::DISCOVERED_ID_PREFIX` / `is_discovered_instance` 成为「自动发现」的公开判据
(不给 `InstanceSummary` 加字段:那要改 24 处结构体字面量,跨 agent / cli)。
## P2-2:空态用例的覆盖面
r1 复审实测:把 `!block.rows.is_empty()` 改成 `block.rows.is_empty()`(0 实例时反而没按钮),
当时三条用例全绿。现在:
- `detail_empty` 拆成 `empty_not_selected` / `empty_no_instances` 两个函数,分支各自可断言;
- `the_cta_lives_only_in_the_no_instances_branch`:CTA 只在「一个都没有」那一支、
「没选中」那一支必须是 `Vec::new()`,且分派必须写成 `if has_instances {`(取反即红);
- `the_page_asks_the_empty_state_with_the_negated_row_count`:两条调用点(宽窗 + 窄窗)
都必须传 `!block.rows.is_empty()`,条数也钉住(多一条即红)。
- 两个变异已自证:传参取反 ⇒ 后一条红;CTA 挪支 ⇒ 前一条红(红原文见交付报告)。
## P3
- `java::target_major()` 改读并集快照:只有自动发现实例的用户原来会拿到默认 JDK17 而不是
该版本要求的 JDK(`profiles.json` 是空的)。有档案时顺序与旧写法一致。
- `instances()` 全量扫仍在 UI 线程——本轮不动,刷新时机与代价写进报告(塔另开一条)。
## 结构
`host/game.rs` 394 → 286 行:自动发现那一簇(痕迹判据 / 扫出来的版本 / 合成视图)搬到
`host/game/discovered.rs`(138 行),只为守 400 行硬线;`game::base_mode` 仍是同一条路径。
## 门禁
app-iced lib 973 passed / presenter all-targets 386 passed / i18n 36 passed /
fmt 0 / clippy -D warnings 0。
|
2026-11-12 14:21:30 +08:00 |
|
AskaEth
|
5479f25068
|
feat(presenter): 实例清单改并集(档案 ∪ versions/*)+ 空态接导入向导(M273)
用户实测:HMCL 能看见 `.minecraft/versions/樱落怡然客户端`,ULL 的实例页是空的
(日志 `instances=0`)。根因不是路径找错,而是 `host::game::instances()` 只列
`profiles.json` 里的档案,第三方启动器装好的版本在界面上根本不存在。
## 列表改并集(presenter/src/host/game.rs)
- `versions/*` 里**能解析出清单**(`<id>/<id>.json` 的 id 与目录名一致、继承链走到底、
符号链接不越界)且没有被任何档案绑定的版本,就地合成一条实例视图:
显示名 = 目录名、`version_id` = 目录名。扫描复用 `core::game::adopt::scan`,
没有另写一套目录遍历。
- 只读:合成视图不建目录、不写 profiles.json、不碰 versions/ 一个字节。
- 档案优先:某版本已被档案绑定时只按档案出一条(名字/隔离档/启动参数都保留),不重复列。
- 坏目录(无清单 / JSON 坏 / id 不符 / 缺父 / 越界)不当实例;`versions/` 读不出来也只是
退回「只有档案」的旧行为,不让实例页报错。
- 外部实例的视图 id 用 `ext-<fnv64>`:`is_valid_instance_id` 只收 `[A-Za-z0-9_-]`,
而版本目录名可以是中文(现场就是),直接沿用版本 id 会让**启动**在读实例设置时就失败。
## 懒创建档案(只有第一次改它才写盘)
`set_isolation()` 与 `apply_launch_args()` 的「找不到实例」改成 `game::ensure_profile()`:
外部实例第一次改隔离档 / 启动参数时才写 profiles.json(幂等,id 与视图 id 相同)。
## 空态可见入口 + 文案(app-iced / i18n)
- 空态分两种:列表里有东西但没选中(原「未选择实例」)与**一个实例都没有**
(新:「还没有实例」+ 实心主行动「导入已有版本」)。用户现场看不到任何入口正是前一种文案
在误导。
- `adopt_action()` 成为「导入已有版本」意图的唯一产地,页头按钮与空态主行动共用它。
## 门禁
`cargo test -p unlucky-app-iced --lib` 971 passed / `cargo test -p unlucky-presenter
--all-targets` 384 passed / `cargo test -p unlucky-i18n` 36 passed /
`cargo fmt --all --check` 0 / `cargo clippy -p unlucky-presenter -p unlucky-app-iced
--all-targets -- -D warnings` 0。
|
2026-11-12 13:13:53 +08:00 |
|
AskaEth
|
751ae80f28
|
chore(release): 版本号 0.2.5(动效第一批 + 发布链路三处根因修复 + 平台测试收口)
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
GPU evidence / GPU 证据 — 真实产物判据 (push) Canceled after 0s
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Canceled after 0s
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Canceled after 0s
Dev Guards / GPU 证据门禁(同 main 口径;无 GPU 只跑自测) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-12 12:06:46 +08:00 |
|
AskaEth
|
634955d323
|
fix(app-iced): 探针与字体测试的子进程改走 platform::child::quiet(M272)
`cargo test --workspace` 在合并 tip f8d83d1 上被 child_quiet_guard 抓住两处绕开
`platform::child::quiet` 的子进程构造:
① `platform/motion.rs` 的 OS「减少动效」探针(M256 新增,Linux gsettings / macOS
defaults)是产品代码真回归:`run()` 里裸 `Command::new(program)`。改走 `quiet(program)`。
`quiet` 返回的就是普通 `Command`,`.spawn()` 的 `Child` 仍归 `run` 自己,所以
**300ms 超时 + 超时 kill + wait 一个字没丢**;`OnceLock` 缓存、单调用点、不在帧路径
这三条性质也原样保留(这次只换了构造器)。
② `fonts/tests.rs` 里重起自身 test binary 的那处同样改走 `quiet`;`.output()` 的管道
语义不变(已实测子进程断言仍然「1 passed」)。没有往 ALLOWED_ROLES 加白。
|
2026-11-12 11:28:56 +08:00 |
|
AskaEth
|
add8617494
|
fix(app-iced): 修 about_center 的平移项(模态从「斜滑」回到「绕中心缩放」)+ 补几何断言
复审 r3 的 P1:about_center 写成 T(-c)·S·T(c),按 iced_core 的「先右后左」算出来是
p → s·p − (1−s)c,不动点在 (−cx,−cy)(窗口外的镜像点)⇒ 卡片开框时从左上斜滑约
(1−s)·c,而不是绕中心缩放。改成 T(c)·S·T(-c)(= s·p + (1−s)c,不动点就是 c),注释同步。
根因是 zoom.rs 里 0 条 #[test](矩阵从没被断言过,967 条全绿抓不住)。新增
tests::about_center_keeps_the_center_fixed:中心不动(<1e-3)、尺寸 = 倍率、缩小时左上角
收向中心;三组矩形 × 三档倍率。把 translate 换回去即红。
顺带补齐「抓得住/抓不住」:components/motion/** 与 motion_sync.rs、platform/motion.rs 里
31 条用例现在全部两侧都有(脚本核对 missing=0)。
|
2026-11-12 10:50:04 +08:00 |
|
AskaEth
|
b6127b4735
|
fix(app-iced): 补回被变异回滚误删的两处修复(钉住档位 + 退场起点)
上一提交把一个中间态(BYPASS_PROBE + serialize 未删、Modal::Closing 无 from)提交了:
变异实验后我用 git checkout -- 复原,而那些修复当时还没入库,于是被一起抹掉,
直接用例把这件事抓了出来(3 条恒定红)。现在按正确内容重做:
- delete motion::serialize()(空操作)
- FORCED_SCALE: Cell<Option<MotionScale>>,configure 里钉住优先
- Modal::Closing { start_ms, from },退场从当前进度连续缩到 0
(教训记在报告:变异只在已提交的树上做,别在未提交的树里 checkout。)
|
2026-11-12 10:50:03 +08:00 |
|
AskaEth
|
787cb16189
|
fix(app-iced): 删掉空操作串行门 + 钉住档位贯穿产品启动路径 + 退场起点接当前进度
复审 r2 的三条:
1) 删掉 motion::serialize() 那把串行门:PUMP/LAST_FRAME 都是 thread_local,libtest 每用例
一线程 ⇒ 它改变不了任何结果(ULL_MOTION=off 下握门照样红)。留一个不起作用的同步原语
比没有更坏。subscription.rs 与 motion_sync.rs 的 idle_app 恢复原签名。
2) 真实根因是「喂给 configure 的值」:idle_app 先钉 STANDARD,随后 on_boot →
configure_motion(app/update.rs:276)→ boot_env::motion_scale(..) 再定一次档,把钉住覆盖掉。
改成 BYPASS_PROBE(bool) → FORCED_SCALE(Cell<Option<MotionScale>>):configure 里钉住优先,
于是三条定档路径(configure_for_test / on_boot / boot_env 的 env·后端 tier)都穿不过去。
两条用例钉住它(纯合成 + 真 Message::Boot),删掉「钉住优先」两处都红。
3) P2-2:Modal::Closing 记住起手进度 from,progress = from*(1-eased) —— 进场途中被关掉时
从当前进度接着缩,不再先跳到 1.0(反向硬跳)。新用例
closing_during_the_entrance_does_not_jump_to_fully_open,把 from 改回 1.0 即红。
|
2026-11-12 10:50:03 +08:00 |
|
AskaEth
|
c21c6df51b
|
fix(app-iced): 动效全局态按「测试隔离」修 —— 串行门 + 探针旁路(复审 P2-5 返工)
tip 8b6f090 上 7 条用例在 -j 2 下红,根因两层:
1) P2-5 引入的 configure_for_test 只挡调用点,而 App::update(Boot) 会走**产品** configure
再按真探针定档 —— 本机(RDP 会话)SPI_GETCLIENTAREAANIMATION 报「动画关」⇒ 档位被压成
NONE ⇒ 位移恒 0 ⇒ 门槛 B/C 的守卫全红(日志里 [motion] scale=0.00 就是它)。
2) 动效全局态(档位 + 进程级时间轴)在并行用例间共享,谁先写谁赢。
修法(判据一条没动):
- components :: motion :: serialize():**跨模块共用的唯一一把**串行门,subscription.rs 与
update/motion_sync.rs 的用例都取它(idle_app 把 guard 一起返回,活到用例结束)。
- configure_for_test 置 thread-local BYPASS_PROBE,configure 读它 ⇒ 产品启动路径也不会再按
探针改档;reset() 清旁路,供「故意走真探针」那条用例使用。
- NONE 仍要求严格 0、STANDARD 仍要求 12~20 帧与端点归位。
|
2026-11-12 10:50:03 +08:00 |
|
AskaEth
|
1edf645c2f
|
fix(app-iced): 复审 p2 五条(方向取轨序 / 退场重开 / 遮罩口径 / 恒真断言 / 探针隔离)
- P2-1 方向改由**导航轨序**决定:新增 nav_rank(route)(NavItem::ALL 下标),
Pump::observe 收 (screen, rank, detail),forward 比较 (rank, key) 而非只比屏键 ——
修掉 Logs↔Settings 的左右镜像;Download↔Content 同 rank,仍判同级水平(已登记偏离)。
- P2-2 退场途中开新框:observe_modal 先当场收尾(Hidden + 待关)再起 Opening;
sync_motion 收尾时若队列里还有框就不把 dialog_model 置空。
- P2-3 只改文字:遮罩起手是**整块画布色的不透明矩形**(把页面内容遮住),不是透明看不见。
- P2-4 删掉恒真断言(单个 Option 的 <=1),写明「不累积」是字段形状 + mem::replace 给的;
tests/timeline.rs 与 tests/modal.rs 的每条用例都补了「抓不住」。
- P2-5 新增 motion::configure_for_test(绕过 OS 探针),用例一律走它;
另留一条故意走真探针的用例证明 Reduced ⇒ NONE 的合成仍在产品入口上。
- 顺带:pump 的模态状态机拆到 pump/modal.rs(守 400 行硬线)。
|
2026-11-12 10:50:03 +08:00 |
|
AskaEth
|
4a77f1b421
|
fix(app-iced): 退场窗口内 Esc 也被吃掉(补键盘入口的闸门)
模态退场期间逻辑状态已关(state.dialogs 空),但 Esc 走的是 Message::Agent(Key) 那条
独立路径,不经过 on_dialog 的 modal_closing 闸门 ⇒ 那 150ms 里 Esc 会落到 AI 栏去收起面板。
escape() 顶部加同一道闸门(返回 true = 被吃掉),并给退场用例补一条断言。
|
2026-11-12 10:50:02 +08:00 |
|
AskaEth
|
b1c09c4c91
|
feat(app-iced): 页面切换按导航深度定向 + 模态缩放淡入(M256 MOTION1)
- transition.rs:唯一一张动效表。同级换页走水平(±12 / 旧页 ∓8)、进深层走纵向
(新页 +16 上浮 / 旧页 -8),进腿 medium2/medium1(300/250),退腿 short4→medium1(更短),
进用 Standard、退用 StandardAccelerate。幅度全部取自 Space 令牌。
- pump.rs:Slot 三互斥槽 → Kind(Boot/Peer{forward}/Push/Pop)+ 两腿轨道;新增模态状态机
(Opening/Open/Closing,展开后不占帧,退场走完交回一次 take_modal_closed)。
- components/motion/zoom.rs:绕中心缩放的自绘控件(renderer.with_transformation,
tiny-skia 与 wgpu 都实现了,文字也真的跟着缩放);scale==1.0 时不套变换 ⇒ 出图基线不变。
- pages/mod.rs:page_with_settings_transition(ui, current, previous, …) 叠
stack![opaque(旧页@退场位移), 新页@进场位移];旧函数只剩一句委托(blast radius 不变)。
- App.previous_view:换页时用 std::mem::replace **移动**旧模型(冻结快照),过渡结束当拍
置 None(不随换页次数增长)。
- 模态:逻辑状态当拍关(state.dialogs 立刻空),画面多留 150ms;退场期间弹窗消息被闸门吞掉。
- platform/motion.rs:系统「减少动效」三态探针(Windows=SystemParametersInfoW,
Linux/macOS=子进程+300ms 超时),OnceLock 缓存;Unknown **不降级**(探针失败不静默关动效)。
- MotionScale::HALF 语义核清并写进 design 文档:是**时长减半**(更快),不是半速。
|
2026-11-12 10:50:02 +08:00 |
|
AskaEth
|
d4959fd3d3
|
test(m262): 三条平台/时序依赖的测试改成确定性判据
① prefs.rs:custom 落点不再写死 D:/JDKs(Linux 上它是相对路径,会被拼到数据根后,
同一条断言在两个平台上验的是两件事)。改由临时目录给绝对路径;另加一条相对路径
必须按数据根解析的断言,把上一版抓不住的那一半补上。
② fonts/tests.rs:install::status() 读的是进程级静态槽,同 binary 的 page_messages
用例切 zh_cn 会真的发起下载并把它写成 Downloading ⇒ 前后快照比较与谁并行谁赢。
改成用 current_exe() 在**只有本用例的进程**里量,并断言前后逐值相等(任何写槽都算越界)。
③ fixture_cases.rs:目录 mtime 不再赌「写文件恰好落在另一个 tick」,改为显式钉 mtime
(t0/t1 整秒,对任何 filetime 粒度无余数)并逐 ns 比对。
三条都补了「抓得住什么 / 抓不住什么」。
|
2026-11-11 22:41:06 +08:00 |
|
AskaEth
|
4b184df1ba
|
chore: 版本号 0.2.4(里程碑快照;本批含存档截图/强调色/回车提交/GPU 门禁/签名就绪度)
|
2026-11-11 21:26:15 +08:00 |
|
AskaEth
|
9ce54a8119
|
test(app-iced): accent 回车守卫改判代码行并整取实参(M252 r2 P2-1)
上一轮(e2a9692)声称三处守卫都改了,实际只有 field.rs 与 rows.rs 进了提交:
`section/accent.rs` 的改写留在工作树里,被我自己重放 C4 变异时的 `git checkout --`
还原掉了 —— 提交面因此看不到它,那条守卫仍是 `body.contains(..)`,注释掉接线后它照样绿
(只有白名单顺手红)。先修过程、再修判据:
- `the_accent_field_wires_enter_to_commit` 改成 `code_line(body, ".on_submit(")` 取**代码行**,
再用 `call_arguments` **整取**实参并断言等于 `["accent_commit"]` —— 顺带堵上
「`accent_commit_none` 这类同前缀产地静默通过」。
- `code_lines()` 的边界文档换成实测口径(`app-iced/src` 9 处 `r#"`、8 处内容含引号;
全仓 `crates/**.rs` 291 处),并写清两个方向:内嵌引号 ⇒ 尾部注释漏剥 ⇒ **假绿**;
JSON 原始字符串同行有 `//` ⇒ 多剥 ⇒ **假红**。两种都由 `code/tests.rs` 钉住。
过程纪律(写进报告):**变异重放只在已提交的树上做**,否则 `git checkout --` 会把未提交的
修复一起扔掉,而「声称改了、diff 没动」正是这次被复审抓到的形状。
|
2026-11-11 19:55:21 +08:00 |
|
AskaEth
|
a17f6fb29f
|
test(app-iced): 接线守卫改判「代码行」,注释掉不再算接线(M252 r1 P1-1)
复审实测:三处接线守卫都是纯文本 `contains`,把真接线注释掉(行注释,或 `/*` 独占一行的
块注释)之后 943 条用例一条不红;`field.rs` 那处连 rustc 的 unused-variable 兜底也能用
`let _submit` 一起绕掉 —— 功能整条静默失效时,测试与 lint 两条线同时不响。
- `pages/layout/source/code.rs`(新):`code_lines()` 剥 `//` 与跨行/可嵌套 `/* … */`,
字符串与字符字面量整段保留(`"http://x"` 里的 `//` 不剥);`code_line()` / `has_code_line()`
给「某行代码的行首就是那个调用」这条谓词。边界写在模块文档:原始字符串里出现 `"` 是唯一
可能朝松方向错的形态(本仓 src 现无此写法),其余偏差都是假红。
- 三处守卫改用它:`text_field_forwards_the_submit_callback`(行首是 `on_submit_maybe(`
且实参不是 `None`、消息来自 `submit_message`)、`the_rename_field_wires_enter`(回车与
「改名」按钮的行都含 `commit_rename()`,且都不含第二种页面意图 —— 一并关掉 r1 P2-1
「Debug 自证撑不住判据」)、`the_accent_field_wires_enter_to_commit`(含产地那条代码行)。
- `submit_is_wired_only_at_the_two_call_sites` 的 `scan()` 换成同一谓词并跳过测试文件
(守卫自己的 fixture 里也有 `.on_submit(` 样本)。
- T29 顺手收敛 scope 内的同族旧断言:`pages/instances/tests.rs` 的行距/散文接线判据与
`pages/instances/tests/i18n.rs` 的五条取词判据改走 `code_text()`(一行改动、方向不变)。
|
2026-11-11 19:55:21 +08:00 |
|
AskaEth
|
92a3c1bbc8
|
refactor(app-iced): 「禁用不提交」的规则收进 Field::submit_message
原来 text_field 里内联着 `if state.is_disabled() { None } else { field.submit_message() }`,
那条分支在 Element 上量不到(没有内省接口)⇒ 等于没验。现在把状态作为参数交给
Field::submit_message(state),规则本身可单测:submit_is_opt_in 多了 Disabled 那一档断言
(接了回调 + 禁用 ⇒ None),text_field 相应少 3 行。
|
2026-11-11 19:55:21 +08:00 |
|