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 |
|
worker-mirrorwire
|
0f64acbd05
|
feat(download): 把 download.region / download.mirror_preference 接进源选择(M294)
两个键此前在引擎侧零消费者:设置页画着三档 chip,改它不影响任何一次下载。
现在它们是 RegionProfile 之上的一层显式覆盖(读时叠加,不落盘,region.json
仍是自动判定的结论):
显式 mirror_preference(official/mirror)
> 显式 region(cn/global)
> 自动探测(时区+语言+系统信号)与 24h 缓存
- core::download::preference(新):两个枚举的 from_raw(未知值/空串 ⇒ auto)
与 apply;两个键都落到 RegionMode 这一个「谁在前」开关上,不造第二套排序
- region::RegionProfile::overridden:在已有判定上叠一层模式(探测逻辑不动)
- DownloadTuning 加 region / mirror_preference 与 apply_source_selection
(组合顺序 = 优先级),shared::build 改用它;默认 Auto ⇒ 逐值等于今天
- 未进 RawTuning:CLI 的 tuning() 用完整字段字面量构造它,加字段会连累
crates/cli(不在本 mission scope),改走 with_source_selection 入口
- presenter 的 tuning_of 读这两个键(否则 GUI 侧仍是假行)
判据断具体源名序列:不设/auto/未知 ⇒ ["bmclapi","official"] /
["official","bmclapi"] 逐项不变;official/mirror/cn/global ⇒ 顺序跟着换;
另有一条从设置原文到引擎 plan() 的端到端用例。
|
2026-11-13 08:26:30 +08:00 |
|
AskaEth
|
c2fc4b7f26
|
Merge branch 'feat/m293-chunkresume-chunked-download-writes'
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-13 07:08:15 +08:00 |
|
AskaEth
|
1004af81ca
|
fix(download): 同源换表示时不再沿用上一份的校验器 + 三条 p3 收口(M293 r2)
复审 r2(p2-1):r1 的 `same_source` 只挡了「源不同」,没挡「同一 URL、换了一份表示」。
`done` 为空(Restart:`SizeChanged` / `HashChanged` / `ChunkLayout` …)时没有任何字节要接着用,
但 `journal.begin` 仍把上一份表示的 ETag 写进**新一轮**元数据;`record` 又只在缺值时写 ⇒ 本轮
3 段(新内容)配的是旧校验器 ⇒ 重试拿它发 `If-Range` ⇒ 服务器按 RFC 7233 回 200 + 完整正文 ⇒
`RangeUnsupported` ⇒ 丢整份 ⇒ 单连接从头下。实测 113664 字节(2.775×),比「干脆不续传」的
72704(1.775×)还多一整份。
修法:「谁能当校验器」收成一个 `carry`,两个维度缺一不可 ——
let carry = if done.is_empty() { None } else { same_source };
`if_range`、`etag`、`last_modified` 三处全部走它。清掉之后本轮响应里的真实校验器由
`ChunkJournal::record` 填进来;最坏也只是 `NoValidator` ⇒ 整份重下(1.775× 基线),不会更坏。
文档(`chunk_resume` 模块头的「校验器跟着源与表示走」、`record`、`carry` 处)同步改成两维口径。
判据(新用例 `chunked/resume.rs::stale_validator_from_the_previous_representation_is_not_carried`):
同一 URL、盘上是上一份表示的元数据(旧总长/旧段区间/旧 ETag `"v1"`/旧摘要)⇒ 本轮因
`HashChanged` 走 Restart、服务器此刻发 `"v2"`。断言:重试的 `If-Range` 只许是 `"v2"`、字节数
41984(1.025×,而不是 113664)、hits 5、resumed_from 30720、终态文件逐字节正确。
变异(两种,均实测红):
* 只把元数据那两行退回 `same_source`(`if_range` 仍按 `done` 门控)⇒ 复现复审的探针数字:
PROBE stale_validator if_range=["\"v1\""] hits=6 bytes=113664 resumed_from=0,
断言红在 `left: ["\"v1\""] right: ["\"v2\""]`;
* 把 `carry` 整体退回 `same_source`(连带首轮也发 `If-Range`)⇒ 红在
`left: ["\"v1\"","\"v1\"","\"v1\"","\"v1\""]`。
三条 p3:
* p3-1 `Last-Modified` 支路端到端补上了:夹具新增 `last_modified` 字段 / `with_last_modified()`
/ 发出该响应头,`validator_ok` 扩展成「ETag 或 HTTP-date 比得上」(RFC 7233 的弱校验器兜底)。
新用例 `chunked/last_modified.rs`:服务器**只给** Last-Modified,重试必须带这个日期发
`If-Range`、只补 1 段(41984 = 1.025×)。变异(`record` 不写 last_modified)⇒ 红在
「只该在重试那一次发条件请求:[]」(变成 NoValidator 干净重下)。用例同时写明它抓不住真实
服务器的 HTTP-date 比较语义。
* p3-2 `ChunkJournal::save` 的同步原子写:判断「可以接受」并给了量级 —— 一个作业 ≤17 次
(16 段 + 建账)、每次 1–3 KiB,SSD ≈ ≤30 ms / 网络盘 ≈ ≤1 s,摊在分钟到小时的作业上占比
1e-4 量级;且顺序有意义(record-then-trust),挪 `spawn_blocking` 要为几毫秒引入跨 await
加锁的顺序风险;与单连接路径同口径。退路写在文档里。
* p3-3 `integrity=None` 时末尾摘要空转的一致性边界写进 `chunk_resume` 模块头「已知代价」+
`verify_part` 文档:只剩旧段自证与 `If-Range` 两道,「同长度换内容」不可察觉;现实入口是
上游没给合法 sha1 的 `ModFile`(`addon/online/download.rs` 只在 sha1 合法时才设 integrity),
以及 `Library` 元数据 / `download.bin` 缓存等无摘要调用点;要收紧属于产品决策。
字节判据复测(三条探针形态):
healthy bytes=41984 size=40960 ratio=1.025 hits=5 resumed_from=30720
nofeature bytes=72704 size=40960 ratio=1.775 hits=8 resumed_from=0
staleval bytes=41984 size=40960 ratio=1.025 hits=5 resumed_from=30720 if_range=["\"v2\""]
即 stale-validator 那条从 2.775× 回到 1.025×,healthy 档未变差。
门禁:lib 903 / engine_mock 17 / chunked 22 / resume 12 / partfile 7 / cache 6 全绿;
clippy -D warnings 0、fmt --check 0。改动的单文件最大 391 行(chunked_test.rs),无超 400。
|
2026-11-13 06:59:58 +08:00 |
|
AskaEth
|
ffca0f4454
|
fix(download): 换源后不再沿用旧源校验器 + 三条 p3 收口(M293 r1)
复审 r1(p2-1)指出的链条:源 A 落 3/4 段失败 ⇒ 元数据 url=A + A 的 ETag;换源 B 时
`decide` 判 UrlChanged、删盘重来,但 `ChunkJournal::begin` 把 A 的校验器原样写进 url=B 的
新元数据,而 `record` 只在 `etag.is_none()` 时写入 ⇒ B 的真实校验器永远盖不上。于是拿 **A 的
ETag** 对 B 发 `If-Range`:按 RFC 7233 §3.2 服务器回 200 + 完整正文 ⇒ `fetch_chunk` 判
RangeUnsupported ⇒ 丢掉整份 ⇒ 单连接从头下 —— 债务以另一条路径复现。
修法(复审给的选项 A):校验器与**记下它的 URL** 绑定,`same_source = previous.filter(|m|
m.url == candidate.url)`,`etag` / `last_modified` / `If-Range` 三处全部走它,让「谁能当校验器」
只有一个判定点。不选 B(换源时清空)是因为 B 只是 A 在「URL 变了」这一特例上的写法,条件放在
读取处比散到写入处更难漏;不选 C(多源各记一份)是因为 `ResumeMeta` 本来就有 url,再加一个来源
字段会多一处可与 url 漂移的状态。
夹具此前不解析 `If-Range`,这一跳只能读代码推。现在 `support/` 记录每个请求的 `If-Range` 取值,
并按 RFC 7233 §3.2 处置(对不上就忽略 Range、回 200 + 完整正文),于是「真实服务器上的后果」也
进了用例。
判据(新用例 `chunked/switch.rs`,两个夹具服务器当一个官方源 + 一个镜像源):
* 源 B 收到的 `If-Range` 只许是它自己的 `"src-b"`(源 A 同源续传照旧带 `"src-a"` 作对照);
* 源 B 的字节数仍是 3 段 + 卡住的 1KiB + 补 1 段 = 41984(1.025×),换源没把同源续传打回整份重下;
* 最终文件逐字节正确;
* 第二条用例走「新源自己不发 ETag」那一支:`NoValidator` ⇒ 新源重试整份重下(`resumed_from=0`、
8 个请求、72704 字节),一个条件请求都不许发 —— 这是「无法证明就不复用」的明确取舍。
变异(去掉 URL 过滤)⇒ 两条都红,实测原文:
assertion `left == right` failed: 源 B 收到了不属于它的校验器
left: ["\"src-a\""] right: ["\"src-b\""]
新源没有任何校验器,就不许发条件请求(带 A 的值更是串源):["\"src-a\""]
同一变异下还实测到了后果:`b_bytes=113664 b_hits=6 resumed_from=0`(200 白下一份 + 单连接整份
重下 = 2.77×),即复审预测的那一跳确实存在。
三条 p3 一并收口:
* p3-1:`abort_all()` 之前用 `try_join_next` 把「已落完但还没被收走」的段记进账
(`absorb_completed`,总长判据与正常回收同一道)。这是尽力减少浪费、不是正确性前提,且
它是「谁先完成」的竞态 ⇒ 没有确定性用例能钉住,已在文档里写明边界。
* p3-2:`verify_chunks` 改为返回 `Option<u64>`(可复用字节数),`AttemptOk.resumed_from`
直接取它,删掉原来「记录另算一遍」的双份口径。
* p3-3:`chunk_resume` 模块头新增「已知代价」:重读量正比于**复用的字节**(不是整份文件)、
为什么不能用「相信摘要」来省掉、以及超时后 `spawn_blocking` 线程不可取消在 Windows 上的
句柄风险。
拆文件守 400 行硬线:`await_chunks` + `absorb_completed` 移到 `transfer_chunk/join.rs`
(逐字节搬移),`MockResponse` 移到 `support/response.rs`(逐字节搬移,只加 `with_etag` /
`without_etag` 两个构造器)。
门禁:lib 903 / engine_mock 17 / chunked 20 / resume 12 / partfile 7 / cache 6 全绿,
clippy -D warnings 0、fmt --check 0;字节判据复测未变差(resume 41984 = 1.025×,
noresume 72704 = 1.775×)。
|
2026-11-13 06:45:55 +08:00 |
|
AskaEth
|
3ade2d14a8
|
feat(download): 分块路径写续传元数据并自证旧字节(M293)
台账 P21 第 2 条 + P19(b):分块路径此前进来先 `part.discard()`、失败也 `discard()`,
188MB 的 JDK 任何一次失败都从 0 重下;M289 又给预算耗尽加了「不再重试、不再换源」的
短路,慢链路上一次晚到的失败就变成「这个文件下不来」。
写这一侧:
* `ResumeMeta` 新增 `chunks: Option<Vec<ChunkRecord>>`(段区间 + 该段 SHA-1),
由新增的 `chunk_resume::ChunkJournal` 每成功回收一段就原子落一次盘;
* `fetch_chunk` 顺带带回段摘要、`Content-Range` 总长与 ETag/Last-Modified,
所以元数据里同时有 total / 源 URL / 分块清单 / 校验所需信息;
* 分块失败不再丢中间文件,只有 `RangeUnsupported`(单连接接不住这种形状)才丢。
判这一侧:
* `chunk_resume::decide`(纯函数)要求策略允许、URL / 总长 / 摘要 / 校验器全对得上,
且每条记录精确命中本次分块规划里的一段,否则从头再来;
* `chunk_resume::verify_records` 按记录重读旧段、重算摘要:任何一段对不上就整份丢掉
(元数据本身已不可信,同批其它记录也不再被信任);
* 续传的段请求带 `If-Range`,让服务器回答「内容变了没有」;
* 整文件摘要照旧在末尾整份重算,覆盖新下 + 复用的全部字节。
单连接路径零改动:`resume::decide` 的判定一字未动,只新增
「元数据里带分块清单 ⇒ 强行判 Restart 交给分块路径」这一条,避免预分配全长文件被
单连接的 `Complete` 误判成「已下满」。
判据(都是「服务端实际吐了多少字节」,不是「最终 hash 对」):
* 3/4 处失败后续传完成:41984 字节 / 40960 文件 = 1.025×(断言 < 1.5×);
* 变异(关掉续传同样夹具):72704 = 1.775×,`< 1.5×` 必红(已实测);
* 变异(去掉旧段摘要自证):坏段被复用 ⇒ 51200 ≠ 40960 必红(已实测);
* 变异(去掉总长比较):旧大小的段被复用 ⇒ 61440 ≠ 40960 必红(已实测)。
|
2026-11-13 06:26:18 +08:00 |
|
AskaEth
|
6e1d5fa82e
|
Merge branch 'feat/m292-dlsettings-wire-download-timeouts-i'
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-13 04:29:11 +08:00 |
|
AskaEth
|
f6cdbff885
|
test(download): 补强最后一跳 + 写明「抓得住/抓不住」(M292 r1)
复审与塔的门禁在本 worktree 报了一条 `left: 120s / right: 300s`
(`changed_settings_reach_the_engine_config_and_the_budget`)。归因结论:**不是源码缺陷**,
是目标目录里跑到了不属于这棵树的 `unlucky_core` 测试二进制。本轮只做两件事:
## 1. 最后一跳补强(presenter)
`the_three_timeout_keys_travel_from_the_file_to_the_engine_config` 此前是自己拼
`tuning.apply_to(EngineConfig::default())`,即「调优 → 引擎」这一跳是**用例自己接的**,
守不住「宿主有没有把调优交给 core」。现在改走 GUI 的生产装配点
`crate::host::engine::engine(&env)`(内部 `apply_tuning` → `shared::set_tuning` → 按数据根重建),
断言读 `engine.config()`。这一步断了(宿主没调 `apply_tuning`、或 `shared` 装配点没用
`apply_to`),三条断言全红。
## 2. 用例文档写清边界(config/tests.rs)
`changed_settings_reach_the_engine_config_and_the_budget` 的文档补上返工取证:
- **抓得住**(三键逐一变异,删掉 `apply_to` 里对应赋值即红):
`response_timeout` ⇒ 278 行 `60s/180s`;`chunk_stall_timeout` ⇒ 279 行 `120s/300s`;
`min_avg_throughput` ⇒ 280 行 `32768/16384`。另抓 `from_raw` 的 KiB→字节换算与
`chunk_job_budget` 忽略下限。
- **抓不住**:①宿主有没有调用 `tuning`/`apply_to`(前半段归宿主用例);②跑的是不是这棵树的
二进制(编译期常量与代码内容都不在它视野里 —— 本轮就栽在这里)。
`config/tests.rs` 因此从 394 → 398 行(仍在 400 硬线内)。
|
2026-11-13 04:06:42 +08:00 |
|
AskaEth
|
2ecb33afae
|
feat(download): download.* 三个超时/阈值键接进 schema 与引擎(M292 2/2)
P21 第 1 条:M289 的四个超时/阈值只挂在 `EngineConfig`,`DownloadTuning::apply_to` 只搬并发与
每源尝试次数 ⇒ 慢链路用户没有任何自救手段。出厂等价于「平均吞吐 ≥32 KiB/s 才活得下来」:
188MB 在 20 KiB/s 上要 160min > 100.3min 预算,必被杀;被杀后不再重试、不再换源,所以是
「下不完」而不是「慢一点」。
## 键(schema 1.9.0,新键推 minor)
| 键 | 默认 | 范围 | 落到 |
|---|---|---|---|
| `download.throughput_floor_kib` | 32 KiB/s | 4~4096 | `EngineConfig::min_avg_throughput`(×1024 → 字节/秒)|
| `download.stall_timeout_secs` | 120s | 30~600 | `EngineConfig::chunk_stall_timeout` |
| `download.response_timeout_secs` | 60s | 10~600 | `EngineConfig::response_timeout` |
三个默认值与 M289 的硬编码**逐值相等** ⇒ 不设 = 当前行为。上界(`MAX_*` 常量)就是「把挂住变成
无界」那道兜底:越界值在写入口被 schema 拒、引擎侧再回落一次出厂值。
## 通路
`RawTuning`(宿主只按 `KEY_*` 常量取裸值)→ `DownloadTuning::from_raw`(默认/区间/回落/KiB→字节
全在 core 一处)→ `apply_to` → `EngineConfig` → `stall::job_budget(total, floor, min_throughput)`。
`job_budget` 内部 `min_throughput.max(1)` 挡除零(设置层已拒 0,这是纵深防御)。
两个宿主都读六个键:`crates/cli/src/download.rs::tuning`、`presenter::host::settings::download::
tuning_of`。CLI 在**非出厂值**时往 stderr 打一行 `DownloadTuning::summary()`(`--dry-run` 也会经过
它;实测 `ull --data-dir … java install 21 --dry-run` 的 stderr 是
「下载调优:并发=8 单源尝试=3 吞吐下限=16KiB/s 无进展窗口=300s 单次请求上界=180s gh_proxy=跟随内置默认」,
出厂/非法值时一行都不打)。
## 文档口径(各只写一处)
- `stall.rs` 模块头:「改这几个数要付的代价」(调低下限换到的是「慢」不是「无界」;两档窗口是
单次尝试的界,最坏要乘候选数×尝试次数)+「数字从哪来」改成「默认值 / 能不能改」+ 补上 P21
第 3 条要的「`permit_timeout` 是按一次尝试反推,不是一个作业」。
## 两个纯搬移(为 400 行硬线)
- `config.rs` 423 → 261:`DownloadTuning` / `RawTuning` / `secs_in_range` / `impl` 整段搬到
`config/tuning.rs`(194 行),`pub use tuning::{DownloadTuning, RawTuning};` 保住原路径;
搬移块 172 行原样 diff = 0(无归一化)。
- `stall.rs` 440 → 343:测试搬到 `stall/tests.rs`(103 行),唯一变换是整体去 4 格缩进;两个
未改动的用例函数体与 HEAD 逐字符(去空白)相同。
## 用例
core(`config/tests.rs` + `stall/tests.rs` + `keys/download.rs`):默认值与 schema/引擎三处同源、
改设置 ⇒ 引擎配置与总期限都变、0 与越界回落且不除零、doc 里的数字、`summary()` 的生效值、键顺序。
cli:生产入口 `build_engine` 拿到三个新值 + `announce` 只在非出厂值时给行 + 非法值回落。
presenter:文件 → `tuning_of` → `EngineConfig` 三段一致 + 键名常量与 schema 登记绑定。
|
2026-11-13 03:35:08 +08:00 |
|
AskaEth
|
f4b5a73594
|
refactor(setting): download 四键纯搬移到 setting/keys/download.rs(M292 1/2)
`keys.rs` 396 行、贴着全仓 400 行硬线,M292 要再加三个键 ⇒ 先纯搬移腾地方。
- 新增 `crates/core/src/setting/keys/download.rs`:并发 / 单源尝试次数 / 地区判定 /
镜像偏好四条登记项,**逐字搬来**(唯一机械变换是 `.with(` 外壳换成 `vec![` 项、
`))` 换成 `),` 定界符)。
- `keys.rs` 原地换成一行 `.with_all(download::specs())`,登记顺序不变。
逐字节证明(搬移块 28 行):
git show HEAD~1:crates/core/src/setting/keys.rs | sed -n '109,136p' \
| sed -e 's/^ \.with(/ /' -e 's/^ ))$/ ),/' > old.txt
sed -n '/^ vec!\[$/,/^ \]$/p' keys/download.rs | sed '1d;$d' > new.txt
diff -u old.txt new.txt; echo $? # 0
两条归一化规则都是**行首精确缩进锚定**:块内 `.with(` 只出现在四个项首、`))` 只出现在
四个项尾(各 4 处,与项数一一对应),所以它们不可能顺手改掉块内的真实差异。
|
2026-11-13 03:20:00 +08:00 |
|
AskaEth
|
bba98d5898
|
Merge branch 'feat/m291-quietspawn-route-presenter-junction'
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-13 00:55:30 +08:00 |
|
AskaEth
|
7d16317411
|
fix(m291): 守卫文档在 400 行内收(⑧ 并入 ⑦ 段,净增 0 行),make_dir_link 可见性收窄到 pub(super)(M291 r1)
|
2026-11-13 00:48:38 +08:00 |
|
AskaEth
|
375cc4f1fa
|
test(presenter): junction 夹具改成逐段 join(带正斜杠的路径让 mklink 报无效语法,用例一直静默跳过),守卫补记 M291 变异(M291)
|
2026-11-13 00:31:14 +08:00 |
|
AskaEth
|
27980cf5b3
|
fix(presenter): junction 夹具改走 platform::child::quiet,两处副本收成一份共享 helper(M291)
|
2026-11-13 00:28:32 +08:00 |
|
AskaEth
|
5617709792
|
Merge branch 'feat/m290-dlbound-bound-the-remaining-bare-se'
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-13 00:24:26 +08:00 |
|
AskaEth
|
bde6649aab
|
fix(download): text_bounded 不再信 Content-Length 预分配,补两道内存闸;api/version 第四处裸 send 也上界(M290 r1)
复审 r1(p2-1 / p3-1 / p3-2 / p3-5)返工,三处既有结论(send_bounded 逐字节未动、三个既有调用方
零改动、阶段只用两档)原样保留。
**p2-1 `http.rs::text_bounded` 的内存**(复审判据成立):`Content-Length` 是「对面说了算」的数字,
拿它喂 `Vec::with_capacity` 等于让远端决定我们的内存,而分配失败是 abort、进不了 `Result`。
改成三道闸:
1. 头里声明 > `MAX_IN_MEMORY_BYTES`(64 MiB)⇒ 读完头立刻有界失败,**不预分配、不读正文**;
2. 容量只按真实到达增长(`Vec::new()`);
3. 读循环逐块卡同一道上限(在累加前判,不会先撑过上限一块)。
两道闸的 `message` 分别说「声明的长度」与「内存上限」,`kind` 都是 `InvalidRequest` ⇒ 与超时
(`Timeout` + 阶段)可区分。
**p3-1 第四处裸 `send()`**:`game/java/api/version.rs::fetch_release_page` 原本
`client.get(url).send()` + `response.text()`、整文件 0 处 timeout(生产路径:`source::pinned` →
`api::fetch_version`)。改走 `http::send_bounded` + `http::text_bounded`,界是参数(生产
`http::default_deadlines()`,用例压到 200ms)。
**p3-2 `download/stall.rs` 模块头**:把「`game/java/api.rs`、`game/java/net.rs`、`update/github.rs`
是裸 `send()`——未审」这句假事实换成实测清单(谁已收、谁可以留、为什么留:`auth/http.rs` 自带
`.timeout`、`preflight.rs` 由 `check_with_deadline` 套住、`region.rs` 自带 3s),并给
`http::text_bounded` 补一行「入口/上界/阶段」。
**p3-5 doc**:删掉「与 `Response::text()` 的唯一差别是 charset 解码」这条过度披露——本仓 reqwest
没开 `charset` feature,`text()` 本来就是 `from_utf8_lossy`,解码逐字节等价;改成真实差别
(读空闲 + 内存上限)。
离线用例(全在 `#[cfg(test)]`,夹具 `game::java::net::probe` 新增 `Mode::Declared{declared,sent}`
与 `Mode::Flood(n)`,外加共用的 `bounded()` 护栏助手):
* `api/version.rs::bounded_probes`:静默 ⇒ 阶段等响应头;正文停顿 ⇒ 阶段等字节;
声明 `Content-Length: 999999999999` 且不发正文 ⇒ <2s 返回 `InvalidRequest` 且文案含
「声明的长度」;不声明长度灌 65 MiB ⇒ 读循环在 64 MiB 处拒。
* `net.rs::tests` 的六条(4 条原有 + 2 条正常路径)改用同一个 `bounded()` 助手,断言一条未放宽。
变异实跑(贴红原文见 mission 报告):
1. 退回 `Vec::with_capacity(declared)` 且删提前拒绝 ⇒ `memory allocation of 999999999999 bytes
failed` + 进程 abort(0xc0000409),测试二进制消失——不是一条红;
2. 只删提前拒绝(预分配仍是 `Vec::new()`)⇒ 干净红:`left: Timeout / right: InvalidRequest`(0.21s);
3. 删循环里的上限卡口 ⇒ 干净红:`expect_err` 拿到 `Ok("xxxx…")`(0.54s);
4. `fetch_release_page` 退回裸 `send()` + `text()` ⇒ 两条 version 用例各在护栏 5s 处 panic(5.01s)。
门禁(真实退出码):check=0 / lib=896 passed / game=120 passed / launch=91 passed /
engine_mock=17 passed / clippy -D warnings=0 / fmt=0。行数:http 389、stall 378、api 389、
net 390、version 397、github 363(全 ≤400)。
|
2026-11-13 00:22:22 +08:00 |
|
AskaEth
|
ad99303a95
|
fix(java/update): 三处模块外的一锤子 GET 也上界,失败时报出卡在哪一段(M290)
`download_bytes`(M289)修掉之后,core 里还剩三处裸 `send()`,都在「拿上游文本/JSON」的
路径上:`game/java/api.rs::fetch_latest`(Adoptium latest)、`game/java/net.rs::get_text`
(四家发行版的一锤子 GET)、`update/github.rs::get_candidate_text`(更新清单)。上游接了连接
就装死时它们永不返回,与 M289 修掉的是同族形态。
改动(全部复用 M289 的构造点,没有第二套超时包装):
* `download/http.rs` 新增 `text_bounded`(正文按 `read_step` 的空闲判定读成 `String`,与
`bytes.rs` 的读循环同口径)与 `default_deadlines()`(取 `EngineConfig::default()` 的两段界,
不另抄秒数);`send_bounded` 的签名逐字节未动,`transfer.rs`/`chunked.rs`/`bytes.rs` 三处
调用方零改动。
* 三处一律 `send_bounded`(等响应头)+ `text_bounded`(等字节):失败时 `message` 里带
`StallReport` 的阶段(`await_headers` / `await_bytes`)与已收/预期/无进展三个数。
这三条路径只用到这两档:没有分块回收、也没有引擎许可等待,所以 `await_chunk_join` /
`acquire_permit` 不适用。
* `update/github.rs` 的界取 `engine.config().deadlines()`(手里有引擎就不抄默认值)。
正常路径零改变:`fetch_latest` / `get_text` 的公开签名与错误分类一字未动(`fetch_latest` 的
404 文案与 hint 保留),`update` 的 `github_cases` 全部断言未改。
离线用例(`game::java::net::tests`,6 条;夹具 `net::probe::Hanging` 也是 M290 新增):
silent 服务端 ⇒ 引擎自己在 200ms 上界内返回(外层 5s 护栏没触发),并断言 message 含
「等响应头/await_headers」;正文停住 ⇒ 「等字节/await_bytes」;200 + 真 JSON ⇒ 正文逐字节
原样(含中文)。变异已实跑:`send_bounded` 退回裸 `send()` 三处各自在外层 5s 处挂断;
`text_bounded` 退回 `response.text()` ⇒ 等字节用例 5s 挂断;正文改成空串 ⇒ 正常路径用例红。
门禁:cargo check(core+cli, --all-targets)/ test --lib(892 passed)/ --test game(120)/
--test launch(91)/ --test engine_mock(17)/ clippy -D warnings / fmt --check,退出码全 0。
|
2026-11-13 00:22:21 +08:00 |
|
AskaEth
|
53de2a3922
|
Merge branch 'feat/m288-tokenguard-test-colors-no-longer-tr'
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-13 00:04:21 +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
|
224aa7d9ff
|
Merge branch 'feat/m287-listradius-list-row-corner-from-com'
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 23:41:34 +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
|
a07cda34cb
|
Merge branch 'feat/m289-dltimeout-bound-every-download-awai'
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 23:27:19 +08:00 |
|
AskaEth
|
d6f862a43c
|
fix(download): 补齐 download_bytes 与等许可两处无界 await,预算耗尽不再重试(M289 r1)
r1 复审 p2-1:`stall.rs` 那句「全仓只有两处无界 await」是假话。真实清单五处,
M289 之前只修了两处;这次补齐:
* `bytes.rs::fetch_bytes` 的 `send()` 改走 `http::send_bounded`(与文件下载同一个构造点);
整包 `response.bytes()` 换成 `bytes_stream` + `http::read_step` 的空闲判定,并边读边卡
`MAX_IN_MEMORY_BYTES`(`Content-Length` 可缺失/可撒谎)。生产调用点:版本清单、
版本 JSON、Modrinth 元数据。
* `engine::acquire` 的 `acquire_owned()` 加上界(`EngineConfig::permit_timeout`,默认
120min ≥ 单个作业总期限),到点报新阶段 `StallStage::AcquirePermit`(「等许可」),
不冒充「服务器不给数据」。这就是「卡死作业攥着许可把队列堵死」的连坐形态。
* `stall.rs` 模块头改成一张「哪些入口有界、谁托管给谁」的表,并写清五处 await 的真实清单。
r1 复审 p2-2:总期限用完是链路级判决,不再按满预算重试/换源:
* `DownloadError::job_budget_exhausted()` + `DownloadError::is_retryable()`(覆盖
`kind.is_retryable()`),引擎与 `download_bytes` 都改用它;
* 引擎在预算耗尽时直接结束候选循环,不再换源(同一份字节、同一条链路,换源只会再
烧满一整个预算)。188MB/20 KiB/s 的用户不再从「1.6 小时慢慢下完」变成「几小时反复重下」。
* `stall.rs` 写明「慢链路照样活」的前提 = **平均吞吐 ≥ 32 KiB/s**,以及它挡掉哪类链路;
`download.*` schema 接线留给另一个 mission(`setting/keys.rs` 不在 scope)。
顺带(复审 p3):
* `transfer.rs` 的读空闲统一走 `EngineConfig::deadlines()`(不再各读字段);
* `verify_part` 补一条代价说明:`spawn_blocking` 到点不可取消,可能让同名重建拿到
`Access is denied`(**该假设未验**,注释里已标);
* `engine.rs` 因这轮新增又贴到 410 行 ⇒ `finish` / `materialize` / `millis` 逐字节搬到
`engine/finish.rs`(`engine::millis` 路径用 `pub(crate) use` 保持可用)。
新增用例(全离线、<2s):`download_bytes` 静默服务端 / 正文中途卡住、等许可超时、
预算耗尽不重试(`max_attempts_per_source = 3` 下仍只发一轮)、预算耗尽不换源
(官方源 `hits == 0`)。
|
2026-11-12 23:24:19 +08:00 |
|
AskaEth
|
cc0def3415
|
feat(download): 给每条下载 await 上界并在超时时报出「卡在哪一段」(M289 / P19)
全仓下载路径此前只有两处无界 await(分块与单连接的 `send()`),reqwest 自己的
`timeout` 又是 `None`:服务器接了连接不回一个字节 ⇒ future 永不结束,
`ull java install` 那条 `silent()` 路径零事件零日志,用户只看到「卡住不动」。
上界选「单次请求 + 读空闲」,**不是**「整作业总预算」:
* `region.rs` 的 3s 是探测极小响应(整作业 = 一次 GET,两者同一件事);
* `font.rs` 的 ATTEMPT/TOTAL 是几百 KB 的字体(整作业几秒就完);
* 188MB 的 JDK 两者都不能照抄,所以分三段互不顶替的界,理由与取数**只写在**
`crates/core/src/download/stall.rs` 模块头一处(含「别改成 reqwest 的 timeout,
它覆盖整个响应体读取」)。
改动:
* 新增 `download::stall`:`StallStage`(连接 / 等响应头 / 等字节 / 等分块回收 / 校验)+
`StallReason`(期限用完 / 没有新字节)+ `StallReport`(阶段 + 已收 / 预期字节 +
无进展时长)+ `job_budget`(总期限按 32 KiB/s 平均吞吐反推)。
* `http::send_bounded` / `http::read_step`:两处 `send()` 与两处读循环的**唯一**构造点;
超时错误带阶段与三个数,`message` 里也抄一份(整体失败时 `suppressed` 只搬 message)。
* `transfer_chunk::await_chunks`:`JoinSet::join_next` 加总期限与无进度窗口,两者靠
`StallReason` 分开报;窗口内有新字节就续期(慢但有效不误杀)。
* `verify_part` 挪到 blocking 池并套上界:校验阶段卡住也报得出来(阶段=校验)。
* `engine.rs`:整体失败时把**最后一次**尝试的失速现场带上去,并写进用户可见的那行字。
* 配置:`response_timeout` 60s / `chunk_stall_timeout` 120s / `chunk_job_floor` 10min /
`verify_timeout` 120s(`EngineConfig`,未动设置 schema)。
测试(全离线、`127.0.0.1`、<2s):夹具新增 `silent()` / `stalling_after` / `short_body` /
`oversize_body` / `dripping`;用例覆盖静默服务端、中途卡住、短包、超长包、慢但持续
(4 倍于上界也要成功)、超时后引擎仍能下载(RAII 许可自还,不写手动归还)、以及分块
作业的「无进度 vs 总期限」两条。
另:`config/tests.rs` 的一处表达式在去缩进后行宽变化,随本 commit 由 rustfmt 合成一行
(纯搬移 commit 提交时它仍是逐字节原文)。
|
2026-11-12 23:24:19 +08:00 |
|
AskaEth
|
61c8a4eff9
|
refactor(download): 纯搬移为 M289 腾出行数余量(config 单测 / engine 只读查询面)
两个文件都是 400 行零余量的硬线,本 mission 要加超时字段与失速诊断,先纯搬移:
* `config.rs` 的 `#[cfg(test)] mod tests`(174 行)→ `config/tests.rs`,
原文件用 `#[cfg(test)] #[path = "config/tests.rs"] mod tests;` 声明;
* `engine.rs` 的七个只读 getter(`config` / `concurrency_probe` / `client` /
`region` / `mirrors` / `health` / `cache`,44 行)→ `engine/inspect.rs`。
逐字节未改:搬移块去缩进后与 HEAD 原文 `diff` 均为 exit 0。
行为零变化(同一批测试仍全绿);新文件都在 `crates/core/src/download/**` 内,
外部路径(`download::config::EngineConfig` 等)不变。
|
2026-11-12 23:24:18 +08:00 |
|
AskaEth
|
8b48c494cd
|
Merge branch 'feat/forcedwire-force-state-through-settings'
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 23:15:05 +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
|
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 |
|