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
|
f931af793d
|
Merge branch 'feat/javapin-core-jdk-install-with-explicit-v'
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 22:46:09 +08:00 |
|
worker-javapin
|
a0aeccf303
|
fix(java): 把「这一页不存在」与「这一页读不懂」分开,解析失败不再被当成翻到尾巴(M280 r3)
r3 复审 p2-1items:r2 的吞判据按 `JavaErrorKind` 判,而 `Metadata` 这个桶里除了
404/410 与 200+空体,还装着**解析失败**(`parse_release_list`)⇒ 第二页起上游回
「看得懂的状态码 + 读不懂的内容」时,清单被静默截断且标成 `Complete`,用户看到的是
「这个 major 没有版本 X(可用:…)」,而 X 其实在上游。
## 修法:语义信号代替错误种类(复审判据里没选字面方案,选了改动面最小的等价实现)
`fetch_page` 的返回类型变成 `Result<Option<Vec<String>>, JavaError>`,`None` 只由
**新的纯函数 `page_outcome(status, body)`** 产生:404/410 或 200+空体。判据是状态码 +
体形状,**不匹配任何错误文案**。
- `page > 0` 的 `None` ⇒ 翻到尾巴:交回已取到的全部,`truncated = false`(r2 已做对,保留);
- `page == 0` 的 `None` ⇒ 这个大版本真没有 GA 清单:仍然 `Err`(与 r2 之前同口径,
只是判据从「错误种类」换成「哪一页不存在」);
- **任何页的解析失败 / 非 2xx 非 404 / 网络错误 ⇒ 一律 `Err`**(不再有「读不懂 ⇒ 没有更多」);
- 错误落在最后一页也是 `Err`,不可能再报 `Complete`。
`api/version.rs` 里新增 `page_outcome`(纯)+ `fetch_release_page`(HTTP 那一跳,判定全交给前者),
`fetch_version` 的闭包改调 `fetch_release_page`。只动本 mission scope 内的两个文件,
没有改 `net.rs`,也没有给跨 crate 匹配的 `JavaErrorKind` 加变体(cli / app-iced 的 match 不变)。
## 用例(离线;每条都有能变红的变异)
- `a_missing_page_after_a_full_page_ends_the_tail_cleanly`(判据 1):page0=20 条、page1=None
⇒ `Ok((20, false))`,逐条相等、`calls == 2`;
- `a_missing_page_is_404_410_or_an_empty_body_and_nothing_else`:404/410/200+空白 ⇒ `None`;
正常 JSON ⇒ `Some(names)`;`<html>502</html>` ⇒ `Err(Metadata)`;500/403/429/502 ⇒ `Err(Network)`;
- `a_parse_failure_on_a_later_page_is_an_error_not_the_end_of_the_list`(判据 2):夹具用**真的**
`parse_release_list("<html>502</html>").unwrap_err()` ⇒ 必须 `Err`,且报的是解析失败本身;
`Err` 分支在签名上就带不出部分结果;
- `an_error_on_the_last_allowed_page_is_never_reported_as_complete`(判据 3):错误落在
`page == MAX_PAGES-1` ⇒ 必须 `Err`,不许 `Ok((_, false))`;
- `a_missing_first_page_is_still_an_error`:第一页就不存在 ⇒ `Err`;
- `a_network_error_on_a_later_page_is_not_swallowed`:网络错误照抛。
变异(做完都还原,`git status` 干净):R3-A 把 page>0 的 None 当失败 ⇒ 判据 1 那条红;
R3-B 把 Metadata 重新并进吞掉的分支 ⇒ 判据 2/3 两条红(panic 原文就是 `(…20 条…, false)`,
即复审实测的那个 bug);R3-C 把 `page_outcome` 的解析失败并成 `None` ⇒ 底座那条红。
## 顺带两条(复审 P3)
- P3-1:报告里「钉住上游越界页就是 404」那句改成正确表述(夹具手写,钉的是「收到『这一页
不存在』要把已取到的交出去」);`--lib` 数字按 tip 重数(`aac0eef` 上是 880,本轮 +3 ⇒ 883)。
- P3-2:删掉 `catalog/tests.rs` 里冗余的否定式断言(紧邻的正向断言已蕴含它),
保留两条正向断言(`Adoptium 里可用` / `Oracle 镜像目录里可用`)。
门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-280):core --lib 883 / --test game 120 /
--test launch 91、cli --all-targets、fmt --check、clippy -D warnings 全部 EXIT=0。
|
2026-11-12 22:34:07 +08:00 |
|
worker-javapin
|
aac0eef1b8
|
fix(java): 越界页 404 不许作废已取到的清单 + 三处文档/文案(M280 r2)
r2 复审 p2-1items / fix-then-merge,只做指定的四项。
## P2-1 满页收尾会多打一页 404,把前几页一起丢掉
`collect_releases` 原来靠「本页不满 PAGE_SIZE」收尾;某条 GA 线的条数恰好是 20 的倍数时
最后一页是满页 ⇒ 会多要一页 ⇒ Adoptium 对越界页回 **HTTP 404 + 空体**(实测,不是空数组)
⇒ 那一页的 Metadata 错误把**已经取到的前几页**一起作废,整条 `--version` 路径失败。
- `page > 0` 的 Metadata(404 / 空响应)视为「翻到尾巴」:返回已取到的全部 + `truncated=false`;
- `page == 0` 的 404 仍然报错(那才是「这个 major 没有 GA 清单」);
- **网络错误**(`JavaErrorKind::Network`)在任何一页都不被吞,照样冒泡。
用例(全离线,注入夹具):
- `a_404_on_the_page_after_a_full_page_ends_the_tail_cleanly`:page0=Ok(20 条)、
page1=Err(Metadata 404) ⇒ 断言 `Ok((20 条, false))` 且 `calls == 2`(把「今天上游越界页就是 404」钉住);
- `a_404_on_the_first_page_is_still_an_error`:page0 就 404 ⇒ 必须 `Err`;
- `a_network_error_on_a_later_page_is_not_swallowed`:page1 网络错误 ⇒ 必须 `Err`。
## P3-1 `install/plan.rs` 文档与事实矛盾
改成如实描述:搬移的直接原因(400 行硬线)+ **三处语义变化**(签名接 `&InstallRequest`、
函数体改走 `chain_fetch` 与 `install` 共用同一份判据、`request.priority` 现在参与选择顺序)。
## P3-2 `api.rs` 头注释
补一句:latest 那条走本模块,「指定版本」走子模块 `version`(`feature_releases` 翻页枚举 →
`assets/release_name`),两者共用同一套响应结构。
## P3-3 Oracle 拒绝文案多一个空格
`source` 自带连接词(`Adoptium 里` / `Oracle 镜像目录里`),模板改成 `({source}可用:…)`。
真机:`(Oracle 镜像目录里可用:21.0.2 / 21.0.1 / 21)`、`(Adoptium 里可用:… 等 26 个)`。
用例补三条断言钉住这两种拼法,并断言 Oracle 那句不许出现「镜像目录 里」。
门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-280):core --lib 873 / --test game 120 /
--test launch 91、cli --all-targets、fmt --check、clippy -D warnings 全部 EXIT=0。
|
2026-11-12 22:05:19 +08:00 |
|
worker-javapin
|
1353c044c3
|
fix(java): Temurin 指定版本要翻页 + 三层「不静默回落」补上守卫(M280 r1)
复审 r1 判 p2-2items / fix-then-merge。两条都在这轮修掉。
## P2-1 Temurin 只读第一页 ⇒ 上游有的版本被报「没有」
`page_size` 上限 20 是上游硬约束,但 8/11/17 线各有 26 条 GA(实测 17/ga page=0 回
20 条、page=1 回 6 条)。原来只读第一页,`17.0.4` 这种「上游有、第一页没有」的版本
被报成「没有这个版本」,而且「等 20 个」是我们的窗口而不是上游总数。
- `releases_url(major, target, page)` 带页码;`collect_releases(fetch_page)` 是**纯**骨架
(分页由闭包注入),翻到「本页不满一页」为止,上限 `MAX_PAGES = 5`;
- `fetch_version` 用 `pick_release(&releases, wanted)` 挑(不取最新、不取第一条);
- 文案:`catalog::VersionListScope::{Complete, Truncated}` +
`available_versions_text(.., scope)`。完整时说「等 N 个」(= 上游总数),
到上限时才说「只查了前 N 条,后面可能还有」——不再把窗口当全集。
- 真机复现(复审给的命令):`17 --version 17.0.4` 现在命中 `17.0.4+8`;
`17 --version 17` 命中第二页最后一条 `17+35`;`17 --version 17.0.99` 报「等 26 个」。
## P2-2 三层保证一条守卫都没有(复审的变异 A/B/C)
现在每条都有能变红的用例:
| 守卫 | 位置 | 用例 |
| --- | --- | --- |
| Temurin 命中而非取最新 | `api/version.rs::pick_release` | `the_pinned_version_wins_over_the_newest_entry` + 两页用例 |
| 兜底闸(版本必须对上) | `catalog::version_mismatch` | `catalog/tests.rs::the_version_gate_rejects_...` |
| 装配层的「记失败继续走链」 | `install/chain.rs::resolve_one` / `chain_fetch` | `a_release_that_is_not_...`、`a_mismatch_records_a_failure_and_the_chain_moves_on`、`nothing_matching_...` |
| 钉死一家不换家 | `install()` 全链(`--vendor zulu --version`) | `a_pinned_vendor_that_cannot_pin_is_tried_alone`(断言「试过 1」+ `latest=true` + 一个字节没落盘) |
`install` 的循环与 `--dry-run` 的 `plan` 现在**共用同一份判据**(`resolve_one`):
`install` 逐家调它,`plan` 用 `chain_fetch` 驱动整条链。
## 拆分(守 400 行硬线)
- `catalog.rs` 400 → `catalog.rs` 278 + `catalog/tests.rs` 229(既有 7 条用例搬走,
唯一差异是新 API 的 `VersionListScope::Complete` 参数)
- `install.rs` → `install/plan.rs`(`plan` 段落;文档与签名原样,函数体改成走
`chain_fetch`,这正是「同一份判据」的要求)+ `install/chain.rs`(新骨架与守卫,
`install.rs` 361 行)
- `api/version.rs` 417 → `api/version.rs` 214 + `api/version/tests.rs` 207
## 门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-280)
core --lib 870 / --test game 120 / --test launch 91、cli --all-targets、fmt --check、
clippy -D warnings 全部 EXIT=0。
|
2026-11-12 22:05:19 +08:00 |
|
worker-javapin
|
9fded45d9e
|
feat(java): --version 指定 JDK 版本(Temurin/Oracle 能,MS 只认快照,Zulu 报错)+ --dry-run(M280)
用户原话:「我们内置了那么多镜像源 甚至用户不能手动选择指定版本下载。」这条之前
`InstallRequest` 根本没有版本字段,四家全是「该 major 最新 GA」。
core:
- `InstallRequest.version: Option<String>` + `with_version`;`None` 时仍走 `source::fetch`
的老路径(`latest_url` / `parse_latest` / `fetch_latest` 一个字节未动)。
- `source::fetch` 在唯一分派点按 `Option<&str>` 分两路:`latest` 与 `pinned`,后者绝不换家。
- Temurin(新 `api/version.rs`):先 `assets/feature_releases/{major}/ga` 枚举(`page_size`
上限 20),把用户给的公共版本号翻成 release 名,再打 `assets/release_name/eclipse/{name}`
取归档——不走 `latest`。实测 `/v3/assets/version/{v}` 在线上回 404,故不用它(见报告)。
- Oracle:`versions_for` 接进选择路径(`select_version`),`None` 仍是 `pick_version`。
- Microsoft:只认写死快照里那一个版本号,别的版本明确报错并说清「这家不能自选版本」。
- Zulu / Termux:上游只有 latest / 版本由系统包定 → 明确报错,不回落、不换家。
- `catalog.rs` 收策略:`matches_version`(唯一相等判据,不做前缀近似)、
`version_pin_error`(四家文案互不相同且各自说清原因 + 下一步)、`version_mismatch`
(兜底闸:装到的版本必须真是用户要的那一版)。
- `plan()` 改成接 `&InstallRequest`,`--dry-run` 与真装走同一条选择路径。
cli:
- `java install <major> [--version V] [--dry-run] [--vendor X]`;`--json` 增 `requested_version`。
- 全局 `--version` 只在子命令之前成立,否则 `java install 21 --version 21.0.13+9` 会被
当成「打印启动器版本」吞掉。
拆分(守 400 行硬线,均为纯搬移 + 逐字节核对):
- `api.rs` → `api/version.rs`、`oracle.rs` → `oracle/listing.rs`(纯的那一半)、
`cli/parse.rs` → `cli/parse/java.rs`、`commands/java.rs` → `commands/java_install.rs`。
门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-280):core --lib / --test game / --test launch、
cli --all-targets、fmt --check、clippy -D warnings 全部 EXIT=0。
|
2026-11-12 22:05:17 +08:00 |
|
AskaEth
|
c8f820caec
|
Merge branch 'feat/statelayer-pressed-alpha-and-card-surfac'
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 21:50:18 +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
|
bc2ad85c63
|
Merge branch 'feat/navpill-rail-hover-pressed-state-layer-o'
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 21:44:36 +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
|
26ebf9a944
|
Merge branch 'feat/railexit-demo-fixture-that-actually-star'
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 21:29:01 +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
|
fc48094249
|
Merge branch 'feat/javareg-registry-fallback-for-microsoft'
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 20:20:31 +08:00 |
|
AskaEth
|
23e98635dd
|
feat(core): JDK 发现补 Microsoft 注册表支(HKLM\SOFTWARE\Microsoft\JDK\*\MSI\Path)(M281)
`registry_homes()` 原来只读 `HKLM\SOFTWARE\JavaSoft\JDK`,而 MS JDK 的 MSI 把安装路径
登记在 `HKLM\SOFTWARE\Microsoft\JDK\<ver>\<flavor>\MSI` 的 `Path`(本机 21/25 两条,
JavaSoft 那个键在本机根本不存在)。装在自定义目录(不在 Program Files 下)的 MS JDK
因此漏扫——Program Files 那支兜住的只是默认位置。
- 新增 `parse_msi_paths()`:判据是「当前键尾段叫 MSI」(不数层数),多一层/空层都认;
同键里的 `JavaHome REG_DWORD`、非 MSI 键下的 `Path`、值名不是整段 `Path` 的都不认。
- 注册表读取改为可注入:`RegSource` trait + `ProcessReg`(真 `reg.exe`),
`registry_homes_from(&dyn RegSource)` 让单测喂真实 `reg query` 输出样本,不依赖本机。
- 两个键 × 64/32 两个视图各查一遍,与 JavaSoft 那支同口径;键不存在照旧静默跳过。
- `scan_java_binaries()` 拆出 `java_binaries_in(roots, homes)`:注入式验收可以屏蔽
Program Files / PATH / JAVA_HOME,只走注册表;去重改用仓库既有的 `path_key` 口径
(末尾反斜杠、`\?\` 前缀、大小写都不会重复列)。
- 注册表这支整体搬进 `discover/registry.rs`(`mod registry;` + `pub use`,
`discover::registry_homes` / `parse_java_home` 公开路径不变,`mod.rs` 一行未动)。
|
2026-11-12 20:19:23 +08:00 |
|
AskaEth
|
749d6a8fe2
|
Merge branch 'feat/motion2-sidebar-collapse-and-hover-press'
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 20:08:04 +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
|
c4706c759c
|
Merge branch 'feat/multiroot-discover-official-launcher-gam'
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 19:56:45 +08:00 |
|
AskaEth
|
72137dc658
|
fix(presenter): 回落日志两因并列 + 老 ext 档案覆盖 + 死根档案边界用例(M274 r3)
|
2026-11-12 19:55:52 +08:00 |
|
AskaEth
|
70c6aec6dd
|
fix(presenter): 写设置那道门按(根,版本)判绑定 + 反查回落留日志 + 旗标改真名(M274 r2 P1-1)
|
2026-11-12 19:55:52 +08:00 |
|
AskaEth
|
a614b49538
|
fix(presenter): 多根 r1 复审修四处(磁贴按自己根描述 / 挑 Java 按自己根要求 / 一拍一次扫描 / 只读内容级快照)(M274 r1)
|
2026-11-12 19:55:52 +08:00 |
|
AskaEth
|
dc8323531b
|
feat(presenter): 多根发现(配置根 + 官方启动器标准位置),实例按自己的根解析存档/模组/启动(M274)
|
2026-11-12 19:55:51 +08:00 |
|
AskaEth
|
dfc0f4bcb3
|
Merge branch 'feat/ghostfix-boot-must-not-draw-a-stale-prev'
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 19:44:26 +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
|
a9f691e3ff
|
Merge branch 'feat/cifix2-stabilize-mtime-probe-and-annotat'
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 18:51:27 +08:00 |
|
AskaEth
|
686dd9335a
|
test(core): mtime 探针改用显式 set_modified,update-e2e 失败升注解(M275)
tree_snapshot_sees_new_nodes_and_mtime_changes 的「同内容重写」时间维度断言在
Windows/NTFS 上不可靠:同一系统时钟 tick 内的两次写、或时间戳只在句柄关闭时按
粗粒度落盘,都会让 mtime 与写之前逐字节相同 —— main 上 test (windows-latest)
偶发红在这行 assert_ne!(check-run 130965734990)。改成显式时间戳:
File::options().write(true).open + set_modified(基线 + 5s),值由测试指定、
与 tick/落盘粒度无关;不用 sleep、不用重试。用例原本的目的(快照必须看得见
mtime 变化)保持不变,另附 4s 下界断言把「我们推的那一下」与 stat 抖动分开。
ci.yml 的 update-e2e 步骤原本是裸 cargo test,失败只留一句 exit code 1、
check-run 上零注解,匿名侧等于信息为零。改成与 test 作业同款:pwsh +
Tee-Object 存 $RUNNER_TEMP 日志、失败把 panicked at / error / test result:
FAILED / failures: 行以 ::error:: 输出(% → %25)、打印并 exit $LASTEXITCODE
(cargo 真码,不是 Tee 的码)。步骤名 update-e2e 与 --test update_e2e_test
逐字保留(check_test_source_off.py 的 ALLOWED_STEP 钉死这两点)。
行数不变(ci.yml 397 → 398,≤400):注释块等价压缩腾出的行给运行时逻辑。
|
2026-11-12 18:44:51 +08:00 |
|
AskaEth
|
a0a35596c8
|
Merge branch 'feat/adoptwire-wire-adopt-wizard-entry-on-ins'
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 14:36:05 +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
|
1c3fc2bdd9
|
Merge branch 'feat/quietspawn2-quiet-child-spawn-for-motion'
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:02:29 +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
|
f8d83d1f77
|
Merge branch 'feat/motion1-page-transition-and-dialog-motio'
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 10:52:05 +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
|
098f3b8946
|
fix(guards): 把 unsafe-scope 的说明注释压缩到 397 行(守卫文件自己也要过 400 行红线)
M256 加 EXPECTED 一行后本文件 401 行,触发 guards.yml::file-size 自己那条门禁。
只合并注释里的重复表述(词法器段 8→6 行)+ 去掉两处纯分隔空注释行;
判据逻辑、shell 片段、EXPECTED 的三条路径一字未动。
|
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
|
5a182e80de
|
Merge branch 'feat/cachedim-rust-cache-key-includes-image-d'
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 10:36:30 +08:00 |
|
AskaEth
|
a6060e7649
|
fix(cache): 发布侧 key 只取 inputs.os 且 tag run 只读不写(M270 r1 复审 P2)
r1 复审(p2-1items)指出:tag 触发的 run 只能读 main 或同名 tag 的缓存,而 main 上
没有任何 job 跑 ubuntu-22.04 / windows-11-arm 这些标签 ⇒ 发布侧四个槽位每次发布都是冷编,
写出的 target 缓存永远拿不回来,还会占 10 GB 配额把 main 的热缓存 LRU 挤掉。r0 的
「key = inputs.os + inputs.slot」既没解决死缓存,还把 Windows 借 main 热缓存的路掐了。
- 发布侧 key 改成 `${{ inputs.os }}`:linux-x64(22.04) 与 main 的 ubuntu-latest 仍然不同
key(M269 那条必须断),而 windows-x64(windows-latest) 与 main 的 ci/build 重新同 key
—— 同镜像,借它的热缓存安全(M270 前 Windows 一直走这条且产物是绿的)。
- 发布侧加 `save-if: ${{ github.ref_type != 'tag' }}`:tag run 一律不写(写出来是死缓存),
只有 main 上的 dispatch 预热会写 —— 那次 run 的作用域是 main,之后的 tag run 才读得到。
刻意不用 `cache-targets: false`:那会把 Windows 借热缓存的路一起掐掉。
- ci.yml 侧只修 Nit:`Linux-X64` → `Linux-x64`(node 的 os.arch() 是小写)。
- docs 照实重写:tag 缓存作用域、发布侧每次冷编的必然性、预热怎么做、只读不写防什么、
「同一镜像标签但底层悄悄升级」这档剩余风险与手动 bump 办法;并补回 M164 MSYS 报错的
原文(可搜索的指路)。新的 key 演算:改前主 key 相同 2 对(跨镜像 1 对)/ 跨镜像前缀
命中 2 组;r1 = 1 对(全是同镜像 windows)/ 0 组。
|
2026-11-12 10:31:23 +08:00 |
|
AskaEth
|
93dc27bed8
|
ci(cache): rust-cache 的 key 带上镜像维度,发布构建不再吃 24.04 编的 target/(M270)
M269 已端到端定性:`aws-lc-sys` 的 `bcm.o` 在 glibc≥2.38 的头下编出 `__isoc23_sscanf`,
被从 24.04 的缓存恢复到 22.04(glibc 2.35)去链接 ⇒ undefined。根因是 `Swatinem/rust-cache@v2`
的 key 只有 `-Linux-X64`:`ci.yml` 的 `build`(ubuntu-latest = 24.04)与 `release-build.yml`
的 `build`(ubuntu-22.04)同 job id、同 rustc、同一份 `Cargo.lock` ⇒ key 逐字节相同。
- 7 处 `Swatinem/rust-cache@v2` 全部加显式 `key:`(发布侧 `inputs.os-inputs.slot`,CI 侧矩阵
维度或该 job 的字面量镜像标签)。`key` 落在 key 的**前缀段**,故主 key 与兜底 `restoreKey`
两条恢复路径一起分开。
- 演算判据(按 config.ts 复刻拼接,12 站点 × 2 锁状态两两比对):改前主 key 相同 2 对、
restoreKey 跨站点前缀命中 4 组;改后均为 0。
- 机制注释写进两个 workflow(`-C metadata` 不含 glibc ⇒ 恢复后 cargo 判 fresh 的因果链),
完整推导、`cache-targets` 的取舍结论与「抓得住/抓不住」写进 packaging/docs/build-and-ci.md §3。
- 两个 workflow 仍 ≤400 行(397 / 399):加注释腾地方时按「判据一字不动、只并句」精简了
M164/M171/M184/M147 几处既有注释。
|
2026-11-12 10:18:06 +08:00 |
|