AskaEth
00f132d8ce
Merge branch 'feat/cli-unify-launcher-binary-subcommands-wi'
2026-11-02 08:08:20 +08:00
AskaEth
fb3d3f46c9
docs(readme): 未知子命令的提示改成 unlucky-launcher --help(M82 r4 复审 P2)
...
两版 README 的「未知子命令是错误,不是开窗」一段写的是提示 `ull --help`,与真实输出相反:
GUI 二进制跑出来是 `unlucky-launcher --help`(只有 `ull` 自己跑才是 `ull --help`),
而且同文 87/77 行已经写着「自称的名字跟着你敲的那个二进制走」——正文自相矛盾。
只改这两处;全文其余 `ull` 都是指 `ull` 那个产物本身(对照表、§ 说明、`ull --help` 的对比句),
按语境保留。
2026-11-02 08:08:18 +08:00
AskaEth
8ca1e1e965
fix(cli): 程序名占位符在构造时渲染,两条出口(人类可读 / --json)不再分叉(M82 r3 复审 P2)
...
r2 只在人类可读通道渲染 `%prog%`,JSON 两条出口(错误信封的 message/hint、Report 的
warnings)直接序列化未渲染文本,于是 `--json lst` 吐出 `"hint":"%prog% --help 查看命令列表"`
——连 `ull` 自己的 JSON 都被这轮改动弄坏了。
改成**构造时渲染**,一个入口管住所有出口:
- `CliError::usage/user/internal/with_hint` 统一过 `render()`(错误只有这一处成形);
- `Report::new` 渲染 human 行,`Outcome::with_warnings` 渲染 warnings;
- `print_report` / `print_error` 不再各自渲染(渲染点只有一个,不会再有第三条出口漏掉)。
回归测试三条:`json_error_envelope_never_leaks_the_placeholder`、
`json_warnings_never_leak_the_placeholder`、`human_lines_render_the_placeholder_too`
(后者同时断言「账户名恰好叫 ull」这种用户数据不被改)。
2026-11-02 08:08:16 +08:00
AskaEth
2f8bbd1746
fix(cli): 程序名只替换模板占位符,不再改写用户数据(M82 r2 复审 P2)
...
r1 把 `rename` 套在**插值后的整条文本**上,于是用户数据被改坏:账户名恰好是 `ull` 会变成
`unlucky-launcher`,路径 `D:/tmp/ull/.ULL/...` 会变成 `D:/tmp/unlucky-launcher/.ULL/...`,
回显的子命令 `ull` 也会被改名。
改成模板占位符:文案里写 `%prog%`,**只在渲染时**(print_report / print_error / 帮助)替换成
argv[0] 的 basename。占位符只存在于静态模板里,插值进来的账户名 / 路径 / 子命令碰不到它。
用 `%prog%` 而不是 `{prog}`:这些模板要活在 `format!` 里,`{prog}` 会被 format! 当成参数名
编译失败。
同时补上 r1 漏的反方向:**stdout 的正常输出**(Report.human 与 warnings)现在也做替换,
以前只有 stderr 的错误做了,两边不一致。
回归测试:`user_data_that_says_ull_is_never_rewritten` 断言「账户名 `ull`」「路径 `\ull\`」
「未知命令 `ull`」在 fill 之后逐字不变。
占位符替换覆盖 21 个文件的 63 处文案;测试夹具里形如 `ull-mcp-diagnose-<pid>` 的临时目录名
已回滚为字面量(那不是文案)。
2026-11-02 08:08:13 +08:00
AskaEth
c9e6236a87
refactor(cli): 判据收敛到 unlucky_cli::cli,banner 按被调用的二进制名自称(M82 复审)
...
去重:`looks_like_cli(args) -> bool` 落在 `unlucky_cli::cli`,两个 GUI 的 main.rs 只调用它,
判据从两份变一份。cli crate 里**不**出现 GUI 的 flag 表——语义仍是「首个 token 是裸词就接管;
首个 token 是 flag 时只有解析出真命令才接管,解析失败不接管」。原来两个 main.rs 里的 5+5 条
单测合并成 cli crate 的一份(用例集按复审要求原样搬,含 --screenshot/--renderer/--diff/
--mock/--force-state/--scale-factor、--help/-h/--version/-V、10 个已知子命令、--json 前置、
未知子命令),两个 main.rs 的旧测试已删除。
banner:新增 cli::program 模块,从 argv[0] 的 basename 取程序名(剥目录、剥 .exe、空值回落
ull),把帮助/版本/错误提示里独立出现的 `ull` 换成它——`unlucky-launcher --version` 不再自称
`ull`,也不会把人指去敲 `ull --help`。`run()` 签名未改;`ull` 自己跑时输出逐字不变(替换是
恒等),打包脚本的 `ull --version` 冒烟不受影响。
2026-11-02 08:08:12 +08:00
AskaEth
fecff989e7
docs: 修掉「把没量的东西标成实测」的四处引用(M82 复审 P2)
...
- 两个 GUI main.rs:把「不 dlopen 任何图形库」改成「不依赖任何图形库(缺失时不影响)」,
并把「实测见 §7」换成「§7.4 是依赖取证;耗时是结构性判断,没重量(§10-8)」
- packaging/README §1:§7.6(不存在)→ §7.4
- packaging/README §4:实测引用 §7.1 → §7.4(ELF 取证在 §7.4)
- packaging/README §7.4 标题补 ELF DT_NEEDED,并记下复审在 bookworm-slim 里的真跑结果
- packaging/release-notes:ull 示例前补 chmod +x(Release 资产不带可执行位)
2026-11-02 08:08:11 +08:00
AskaEth
0d4628f85e
docs(packaging): 如实记下 M82 的体积/首帧没有重量
...
本机当时三个 worker 同时在编,全量 release profile(codegen-units=1 + fat LTO)要
重跑 400+ crate,这两个数留给空闲窗口补。写清结构性判断(分发在 MainWindow::new()
与后端探测之前,GUI 路径只多一次 argv 解析)与它的性质:不是实测。
2026-11-02 08:08:10 +08:00
AskaEth
1c7285cea9
feat(cli): GUI 二进制内建 CLI 子命令分发,ull 纳入发布资产(M82)
...
一个二进制两种用法:两个 GUI 的 main.rs 在进 UI 之前分发子命令,命中就把控制权交给
unlucky_cli::run —— 不建窗口、不探后端、不扫字体、不 dlopen 图形库。
分发判据只有一条,子命令表不重复:crates/cli 的解析器是唯一真相源。
- 首个 token 是裸词 → 子命令位置,交 CLI(未知子命令由 CLI 报错 + 提示 --help,退出 1,
不静默开窗)
- 首个 token 是 flag → 只有 CLI 解析出真命令才算 CLI 调用;--data-dir X 解析成 Welcome、
--screenshot / --renderer / --diff 等 GUI 参数解析失败,一律仍走 GUI(CI 脚本零改动)
顺带:unlucky-launcher --version 以前会直接开窗,现在走 CLI 分发报版本。
packaging:ull 作为独立裸资产随每个槽位发布(make_artifacts.py + release.yml 的 assets
数组与 SHA256SUMS glob),矩阵完整性检查改成 GUI/CLI 各查一次。
app-iced 的 x11 / wayland / linux-theme-detection 收进 target 专属的 cfg(target_os) 段。
门禁实跑:cargo fmt --all --check 通过;cargo clippy --workspace --all-targets -D warnings
通过(exit 0);cargo test --workspace 通过(51 个测试二进制 / 1369 个用例,含本次新增的
5 + 5 条分发判据用例)。
2026-11-02 08:08:09 +08:00
AskaEth
ef3212021a
Merge branch 'feat/p4-iced-components-inputs-textfield-sear'
2026-11-02 07:45:42 +08:00
AskaEth
05fefc8a7b
fix(app-iced): M77 r1 —— 菜单项 trailing 槽位靠右(视觉 r5 P1)
...
`item_row` 的 Row 早先是 `leading → label → trailing`,中间没有撑开的空间,于是
trailing(快捷键 / 子菜单箭头 / 勾)**紧贴 label 右边**,一列菜单里读不出一条竖线,
也与 M3「trailing 落在行右内边距上」不符。
改法(只动 `menu.rs` 的行装配):
- label 与 trailing 之间插一个 `space::horizontal()`(宽度 Fill),**只在有 trailing 时插**——
没有 trailing 的项仍然左边起排,不被居中/右对齐;
- 把 Row 包进 `container(..).width(Fill).height(Fill).align_y(Center)`:Fill 空间要有宽度可撑
(Q8 的教训:撑开靠容器,不靠子元素),顺带把内容垂直居中(`button` 只把内容放左上角);
- trailing 的右缘现在落在 item 右内边距上,与 leading 左内边距对称(12 = 4 内缩 + 8 button pad);
- 分组分隔线 / 禁用项的行为未动(同一函数的其它分支没改)。
渲染层断言(`probe/menu.rs`):带 trailing 的项,其 trailing 墨迹右缘 = `menu_right − 17`
(= 4 指示器内缩 + 12 内边距 + `Check` 字形在 24 viewBox 里约 5 的边界)±1。
变异(删掉那行 `space::horizontal()`)后实测 `menu_right=559、墨迹右缘=438 ⇒ 偏 121`,断言红;
修复后偏 **17**。重出 `menu-{light,dark}.png`,勾已贴行右缘成一条竖线。
2026-11-02 07:45:39 +08:00
AskaEth
21f2734981
test(app-iced): M77 渲染层 probe —— 逐行几何断言(5 条,能被变异打红)
...
M68 的教训:纯函数返回对、图可能是错的。`inputs/probe.rs` 对着**真窗截图**量像素,
没给 `ULL_INPUTS_DIR` 就大声跳过(与 `gallery::probe` 同一口径)。
量什么(每条对应一次真实可能画错的形态):
- 文本框高**精确 56**(320 宽格)、Filled 指示线 静息 1dp / 聚焦 2dp 的墨量比;
- Outlined 描边 1dp / 2dp(描边墨量随亚像素相位在 1.0~1.67 浮动,所以用**比值**判);
- 错误态:框内标签与框下辅助文本都是 `Error` 墨迹;
- 搜索条高 56、前置图标墨迹偏移 20(= pad 12 + offset 4 + viewBox ≈4)、
docked 面板 宽 360 / 头 56 / 1px `Outline` 分隔 / 结果区 240;
- Slider 竖条 4×44、0% 贴左、50% 分界在正中、100% 无未激活段(thumb 不越界);
- 菜单容器 161(= 8 + 48×3 + 1 + 8)、项高 48、选中指示器内缩 4、分组线 1dp。
像素工具因 `gallery::scan` 是私有模块而自带一份最小读图器(约 60 行),已报 finding。
2026-11-02 07:45:38 +08:00
AskaEth
ddd234b3c8
feat(app-iced): M77 输入家族 —— TextField / Search / Slider / Menu(含 gallery 登记)
...
D39 五家族并行的「输入类」这一条线。四个组件全部对消息类型泛型(`M: Clone`,不碰
`app::Message`),状态一律走 `components::interaction`,精确尺寸走 `components::layout`,
零尺寸字面量(取不到的集中登记 G16~G20 在 `inputs/geometry.rs`)。
- TextField(12b §9):Filled / Outlined;56 高(无标签 40)、4 圆角、内边距 16/16、
前置图标可视边 12(文本从 48 起)、指示线/描边 静息 1dp → 聚焦与错误 2dp、
错误态线/标签/辅助文本全 Error、禁用 12%/38%(Filled 容器官方 4%,令牌只有 12% → D10)。
标签常浮起(无上浮动画,D12);A1 无 Pressed token,press 按 hover(D11)。
- Search(15 §20):SearchBar(收起)+ SearchView(展开 docked)分开实现。bar 消费
`ComponentMetrics::SEARCH_BAR`,图标可视边 = pad 12 + icon_offset 4 = 16(只做加法,
不写 16/20),聚焦 1px Secondary;view 头部 56 + 1px **Outline** 分隔 + 结果区 240。
- Slider(12b §11):自绘 `advanced::Widget`——轨道 16 full、竖条 4×44(focus/pressed 收窄到 2)、
激活 Primary / 未激活 SecondaryContainer、命中 48、离散停靠点 4(未激活轨在点两侧留 6 空隙)。
位移区间取 handle/2..W−handle/2,所以 0%/100% 竖条不越界。
- Menu / DropdownMenuItem(12b §8):SurfaceContainer + L2 阴影(Elevation 令牌)、项 48、
水平 12、上下 8、选中 SecondaryContainer(指示器内缩 4、圆角 8)、分组分隔线复用
`components::divider`。圆角取令牌表 4(12b 说本项目 12 → D18)。
- gallery/inputs.rs:四组件状态矩阵登记(五态 × 两式 + 空/有值/错误、0%/50%/100%、菜单展开态),
按 `part` 拆成四段(整段比 760 高的窗口还高)。
单文件 ≤400 行:四个组件的单测与 Slider 的纯几何分别拆到 `<name>/tests.rs` 与 `slider/track.rs`。
2026-11-02 07:45:36 +08:00
AskaEth
78667953f5
Merge branch 'feat/p4-iced-components-selection-checkbox-ra'
2026-11-02 07:14:48 +08:00
AskaEth
9519ce8e1b
test(selection): 状态层的两种 α 都要查(复审 P2-4 补,第二次变异曾漏网)
...
原来只查 hover(8%)那一色,于是「把 pressed 列的状态层画成方块」照样绿——实测变异
mut-one-layer-box 当时是 passed。现在 hover(8%) 与 pressed/focus(10%) 两种颜色各查一遍,
每一块的四个角都不许有墨。
变异证据(六个变异全部转红,见报告):
- mut-one-box-22 → 18.3:Primary 块不得超过 21,实测 22×22 @ (541,222)
- mut-one-layer-box → 18.5:Pressed 状态层必须是圆(四角不该有墨)@ (532,212)
- mut-chip-34 → 13.1:filter chip 应恰好四块,实测 3
2026-11-02 07:03:13 +08:00
AskaEth
4155429f45
test(selection): 像素判据从「存在性」改成「逐处都对」(复审 P2-4)
...
- 20(±1) 的 Primary 块:**每一块**都不许超过 21(22 = 描边画到框外),且总数恰好 12
- 状态层:**每一块** Ø40 都要四角无墨(原来只看两块角点),数量 ≥5
- switch 轨道 **恰好一块** 52×32、chip **恰好一块** 32 高(原来 any(...) 就算过)
- 新增 P2-1 / P2-3 的像素断言:禁用 chip 4 块完整宽度 + filter·on 仍填充;20×20 禁用块不许是 12%
- 断言与像素扫描搬到 selection/probe.rs + selection/pixel.rs:gallery/selection.rs 只剩「摆矩阵」
(原文件 458 行,超 400 红线)
2026-11-02 06:55:34 +08:00
AskaEth
0c9d4049b3
fix(selection): Checkbox / Radio 命中区恒 48×48(复审 P2-2)
...
- 无标签时行宽只有状态层的 40dp(A1 的 minimumInteractiveComponentSize 是 48)
- ControlGeometry::hit_padding() = (48−40)/2 = 4,view 侧左右各留这段透明内边距
- 单测钉住 40 + 2×4 == 48(checkbox 与 radio 各一遍)
2026-11-02 06:55:31 +08:00
AskaEth
c5765c57e7
fix(selection): Chip 禁用改成修饰器,只清消息、不删图标与选中态(复审 P2-1)
...
- 旧 API Chip::disabled(label, variant) 强制 leading/trailing/selected = None/false:实测
assist 78→54、filter·on 74→50(连填充和勾都没了),禁用列里 filter·on 与 filter 逐像素同形
- 新 API Chip::assist(label, msg).leading(k).disabled():只清 on_press,视觉信息全保留
(research/12b §13 的禁用态是「容器/描边 12%、文字与图标 38%」——只降不删)
- 矩阵里禁用格改用修饰器,图标与选中态一并保留
2026-11-02 06:55:28 +08:00
AskaEth
00dbb5a5d5
fix(selection): 选中禁用的 Checkbox 容器改用 OnSurface@38%,并补逐格断言(复审 P2-3)
...
A1:SelectedDisabledContainerColor=OnSurface + SelectedDisabledContainerOpacity=0.38(不是普通禁用容器的
12%)。断言同时钉住三件事:fill == 38% 叠色、fill != 12% 容器色、未选中禁用描边与它是同一个 38%。
(同一提交还带着 P2-2 的命中区单测:两处断言都在 tests.rs,无法按 hunk 切分。)
2026-11-02 06:55:25 +08:00
AskaEth
17394b73ff
refactor(app-iced): paint.rs 按组件拆成 paint/ 目录(复审 P2-5)
...
- paint.rs 原本正好 400 行、零余量:再加一个态就得动它(和 app.rs 同一个问题)
- 拆成 mod.rs(Frame / 状态层 / 禁用色 / 标签色)+ check.rs / radio.rs / switch.rs / chip.rs
- 对外 API 一字不改(selection::paint::* 路径全部保持)
- 顺带把「选中且禁用的 Checkbox 容器」从 12% 改成 A1 的 38%(复审 P2-3):这一行就在被移动的
check.rs 里,单独切 hunk 只会让历史不完整;断言在下一条提交
2026-11-02 06:52:10 +08:00
AskaEth
b69a2cdd7f
feat(app-iced): M76 选择类家族 —— Checkbox / Radio / Switch / Chip(15 §18/§19 + 12b §10/§13)
...
- selection/:geometry(令牌→像素,缺档登记 G16)/ paint(Interaction×选中×错误→角色)/
shell(48 透明外壳 + 帧内状态通道 + 勾形 SVG)/ checkbox·radio·switch·chip / matrix(12×5 状态矩阵)/ tests
- gallery/selection.rs:小节 + **渲染层像素断言**(20×20 选中与未选中同尺寸 · Radio 内点 10 ·
Ø40 状态层是圆 · 勾形 14.8 · 短横 · 轨道 52×32 · Chip 32 · 禁用 38%),按颜色+墨迹块定位、不假设行号
- gallery/mod.rs:新增 ULL_GALLERY_SECTION 段落过滤(tower 批准,M77 零成本复用)+ 四个 helper 放宽到 pub(crate)
A1 证据(本机 AAR javap):未选中 hover/focus/pressed 描边是 OnSurface(不是 OnSurfaceVariant)、
Radio 选中态 hover 仍 Primary、Checkbox SelectedDisabledIconColor=Surface、Switch hover 滑块 PrimaryContainer。
2026-11-02 06:11:21 +08:00
AskaEth
35345f3399
Merge branch 'feat/p4-iced-component-base-buttons-gallery-h'
2026-11-02 04:51:10 +08:00
AskaEth
4d7b198822
fix(app-iced): M75 r6 —— probe 删 19 行重复块 + 补第 8 行(icon·tonal) + 三处注释按 visual-r4 P2-2 更正
...
① 19 行重复块(第 4 次报):probe.rs 218-236 与 198-216 逐字重复(on_primary/left_ink 双层循环/
icon_pad/assert 各两遍,靠 shadowing 编过),已删;粘在一起的注释头随之消失。
滑动窗口脚本:改前 'DUP at 199 and 219' → 改后 'no 19-line duplicate'。
② 逐行护栏补到 8 行:(7, OnSurface, true, "icon·tonal");汇总打印 '8 行通过'。
变异(该行墨迹涂回满档色)→ probe 红。
③ 三处过期注释按 review 的 §P2-2 逐条改:
- probe 模块文档 药丸顶行宽 ≈9 → 左内缩 ≈14;
- 圆角推导改成像素中心闭式 inset(k)=r−√(r²−(r−k−0.5)²),r=20,k=1 给 12.40;
- gallery/mod.rs 列宽注释 132 → 160(cell_width 的实际值)。
2026-11-02 04:51:07 +08:00
AskaEth
4954ba3463
chore(app-iced): M75 r5 —— 删掉被 count_in 取代的 count_pixels(clippy dead_code)
2026-11-02 04:51:06 +08:00
AskaEth
b563c6ac44
docs(app-iced): M75 r5 —— 三处过期注释按 A1 更正(会被四条家族线当规格抄)
...
- buttons/mod.rs 表行 1.8:禁用内容角色「按变体」(Variant::disabled_content),容器 α 注明 A1 是 10%
- buttons/mod.rs 表行 2.7:IconButton 禁用内容 = OnSurface@38%(A1),落点改到 disabled_content_over_role
- interaction α 表 / disabled_colors 文档:容器 0.12 与 A1 的 0.10f 差异点名 D8;内容默认为 OnSurfaceVariant
- overlay_color 的 content 说明从 OnSurface 改成 OnSurfaceVariant(与 A1 一致)
2026-11-02 04:51:04 +08:00
AskaEth
eed1c9d3f7
fix(app-iced): M75 r5 —— 禁用内容角色按变体(A1)+ 删 probe 重复块 + 逐行护栏
...
1) A1 逐变体(AAR javap):Filled/Elevated/Outlined/Text = OnSurfaceVariant@0.38 ;
FilledTonal = OnSurface@0.38 (FilledTonalButtonTokens.DisabledLabelTextColor);
Standard/Filled IconButton = OnSurface@0.38 (*IconButtonTokens.DisabledColor)。
新增 Variant::disabled_content() / IconVariant::disabled_content(),
interaction::disabled_content_over_role(ui, role, on_container) 成唯一入口。
2) probe.rs 删掉被两个 reviewer 点名的重复块(disabled_colors 调用两次、
disabled_container 绑定两次),并补 grep 证据。
3) 护栏改成**逐行**:矩阵 7 个有内容的变体行各自断言 disabled 列的 38% 墨迹 > 40 px
(浅/深同一套阈值,单行回退就会红),取代原来按浅色标定的全局 500 阈值。
6) 删掉 interaction/mod.rs 里挂在 mod tests 头上的悬挂文档。
2026-11-02 04:51:03 +08:00
AskaEth
b31a758cda
test(app-iced): M75 r4b —— 无容器禁用墨迹的护栏收窄到 disabled 列 + 阈值 500(变异实测会红)
...
整图计数会把别处抗锯齿像素算进来(正常 800/1966 px,涂回满档色后仍剩 348 px),
护栏实际上拦不住 code-r2 说的「text/standard 那两行」。改为只在矩阵最右 1/3 列内计数,
阈值 500(浅 800 / 深 1966 正常,变异后 348)——变异实测:红。
2026-11-02 04:51:02 +08:00
AskaEth
a878fc6a58
refactor(app-iced): M75 r4 —— interaction 拆成 mod.rs(287) + tracker.rs(159),恢复被压掉的文档
...
interaction.rs 连压三笔才回到 400 行(靠删文档腾空间),而它会被四条家族线各扩一次。
- interaction/mod.rs:五态枚举 + overlay_color / disabled_colors / disabled_content_over(态→颜色)
- interaction/tracker.rs:事件→态的状态机 + 它的两条单测;Tracker 用法示例(ignore 代码块)恢复
- 对外路径不变:components::interaction::* 照旧(mod.rs 里再导出 Tracker)
- 两份文件都写了「加东西前先看行数余额」
顺带修两处门禁:注释里去掉 hex 色值(守门连注释一起扫)、再导出显式 allow。
2026-11-02 04:51:01 +08:00
AskaEth
ab5f5802a8
test(app-iced): M75 r3f —— 去掉一条与上一行重复的断言,interaction.rs 400 行
2026-11-02 04:50:59 +08:00
AskaEth
95a6f56060
docs(app-iced): M75 r3d —— interaction.rs 再收 2 行,确保 ≤400
2026-11-02 04:50:58 +08:00
AskaEth
a3b3c9eba7
docs(app-iced): M75 r3c —— interaction.rs 文档收口(Tracker 用法/α 表压行,回到 400 行内)
2026-11-02 04:50:57 +08:00
AskaEth
86f7a31d9a
fix(app-iced): M75 r3b —— IconButton 高度断言按抗锯齿实测改为 39..=40 + interaction.rs 收回到 400 行内
...
exact-color 的连通块高度会排除底部抗锯齿行,实测 40×39;宽度仍精确 40。
区间收到 39..=40 仍能抓住 ±2px 回归(42→41、38→37 都落区间外)。
2026-11-02 04:50:55 +08:00
AskaEth
5fd1f3ca26
fix(app-iced): M75 r3 —— 禁用内容角色按 A1 改 OnSurfaceVariant + 两项语汇 + probe 双色护栏
...
A1 定案(本机 gradle 缓存 material3.aar + javap):
FilledButtonTokens.<clinit>: DisabledContainerColor=OnSurface, DisabledContainerOpacity=0.1f
DisabledIconColor/DisabledLabelTextColor=OnSurfaceVariant, 两者 Opacity=0.38f
OutlinedButtonTokens/TextButtonTokens: LabelTextColor=OnSurfaceVariant(12b §1.9 原本就对)
据此:
- interaction::disabled_content_over / disabled_colors 的内容角色从 OnSurface 改成
OnSurfaceVariant(α 仍取令牌 DISABLED_CONTENT_ALPHA=0.38);设计层 color::state::disabled_content()
用的是 OnSurface,与 A1 不符 —— 令牌线修好前这里按 A1 直接求值,已登记。
- content_color 的「底」按有无容器分开:有容器叠 12% 容器、无容器叠 surface(视觉 r2 P2-1)。
- probe 加第二条护栏:无容器三式的 38% 墨迹单独计数,避免只护住有容器那四式(code r2 p2-1)。
- gallery 样例图标改描边(Download + ChevronRight),并加单测钉「同控件不混用 Fill/Stroke」(P2-3)。
- 滚动条:透明轨 + OutlineVariant thumb(P2-4;宽度 10px 是 iced 内部常量,只能改颜色)。
- probe 文档修好被 //! 打断的命令、shot_path 注释归位、去掉空转的重复断言(P2-5)。
2026-11-02 04:50:54 +08:00
AskaEth
d075feb77d
test(app-iced): M75 复审 P1-B —— 渲层断言收紧到 2px 级(变异实测会红)
...
- 高度断言改 assert_eq!(true_height, 40):变异成 42 实测变红(证据 target/m75/evidence.sh)
- 补「带图标左内边距 16」断言:墨迹 ≈21(16→21 / 24→29 / 8→13 三档互不重叠)
- 分隔线:行数 2..=4 + 单条墨量覆盖率 0.8..=1.3(1.5dp 会到 ~1.5),
只统计「连续非底色 > 900」的长横线行,避免把文字墨量算进去
- 两条判据都踩过误报:白色与浅色页面底色在容差内(padding 量出 3)、
圆角透出的底色被当成满档 OnPrimary;已改成「内带 + nearer 判定」并注明原因
2026-11-02 04:50:52 +08:00
AskaEth
e905fa3922
fix(app-iced): M75 复审 P1-A/P1-B/P2 —— 内容色跟态走 + 断言收紧 + 矩阵排版
...
P1-A 禁用内容色没下发(视觉 reviewer:text/standard 的 enabled 与 disabled 逐像素相同):
assemble 里内容色恒取 ui.color(content) 再被 text().color() 钉死,Style.text_color 的 38%
永远下发不出去。新增 assemble::content_color(ui, forced, content),文字按钮与 IconButton
共用;probe 补两条像素断言(禁用 38% 墨迹必须存在、禁用容器内不得有满档 OnPrimary)。
P1-B 渲染层断言抓不住 2px 级回归(reviewer 变异验证:高度 42 仍绿):
高度改成精确 == 40;补「带图标左内边距 16(墨迹 ≈21)」断言;分隔线从「行数」改成
「墨量覆盖率 ≈1.0」(1.5dp 会到 ≈1.5)。
P2-1 probe 给了图却读不到时硬失败(只有完全没给 ULL_GALLERY_SHOT 才跳过)。
P2-2 Geometry::stroke 真正接到 Border.width;IconGeometry::icon 改由 side−2×padding 推导。
P2-3 矩阵 cell 宽 192→160(总宽 1040<1180,disabled 列不再被裁);禁用格保留前后图标。
P2-4 补注:snap 仅 wgpu 实现,CPU 验收图不受它影响。
2026-11-02 04:50:51 +08:00
AskaEth
8072f1cc7e
test(app-iced): M75 —— probe 分隔线判据改为「非底色长横线」+ 修 forced 改成 Option 后的两处测试
...
线在 fractional y 上被抗锯齿摊到两行,按精确 OutlineVariant 色匹配抓不到(实测 0 行);
改为「非 Surface 的长横线」计数,断言 2..=6 行(两条线 × 各 1~2 行)。
2026-11-02 04:50:49 +08:00
AskaEth
5f85891e7e
fix(app-iced): M75 —— 截图取证必须能强制「静息」态(光标污染修掉)
...
StyleSpec.forced 从 Interaction 改成 Option<Interaction>:Some(Enabled) 与 None 语义不同——
前者强制画静息,后者才跟随 button::Status。修前光标恰好停在某个格子上时,gallery 的
enabled 列会被画成 hover(表现为 text 行出现不该有的灰底容器,看着像 Text 按钮自带容器)。
2026-11-02 04:50:47 +08:00
AskaEth
5861881073
refactor(app-iced): M75 —— probe 拆出 scan 工具 + clippy 全绿 + 三个字面/生命周期修正
...
- gallery/{probe,scan}.rs 拆分:像素扫描(连通块/外接框/顶行宽度)与判据分离,
后续四条家族线可以自带 probe 复用扫描逻辑
- probe 的实心块判据跳过字形割出的小碎片(area < 800),阈值写明原因
- app.rs / gallery::mod 的 clippy 修正(needless_lifetime / field_reassign_with_default / collapsible_if)
- cargo test -p unlucky-app-iced 与 cargo clippy --all-targets -D warnings 已绿(见报告)
2026-11-02 04:50:46 +08:00
AskaEth
4774e7b788
WIP(app-iced): M75 组件基座 checkpoint —— 组件骨架/按钮五式/分隔线/gallery harness
...
已落地:
- components/{mod,interaction,layout,divider,gate}.rs + buttons/{mod,geometry,assemble,tests}.rs
- gallery/{mod,buttons,probe,scan}.rs + 四个家族桩(selection/inputs/feedback/navigation)
- cli.rs: --screen=shell|gallery + capture_flavor(截图默认 Light,删掉等系统主题的闸门)
- app.rs: gallery 路由 + DemoMessage 接线(400 行)
待办(下一个 commit):编译/测试全绿、浅深矩阵出图、1:1 并排与数值表。
2026-11-02 04:50:44 +08:00
AskaEth
22ddc74784
Merge branch 'feat/p4-iced-page-frame-layout-system-breakpo'
2026-11-02 04:44:53 +08:00
AskaEth
831bd00620
docs(app-iced): SIM 常量口径精确化(字符最多行 402 / 全段最宽行 411)
...
视觉 r3 的不计入裁决的精度提醒:`SIM_SAFE_MAX_INK = 402` 其实是**字符最多**那一行的 ink,
全段**墨迹最宽**的行是 411px(渲染 410),所以「余量 22px」实际是 13px。
- 拆成两个常量并分别断言:`SIM_SAFE_MAX_CHARS_INK = 402`(67 字符那行)、
`SIM_SAFE_WIDEST_INK = 411`(墨迹最宽那行,新加断言 `cap >= 411`,并在失败信息里打印余量);
- 文档写明「两者不是同一行:402 那行字符多但字形窄」。
证据:`cargo test -p unlucky-app-iced --no-default-features`
→ `test result: ok. 86 passed; 0 failed` / `test result: ok. 6 passed; 0 failed`。
2026-11-02 04:10:05 +08:00
AskaEth
589f51f4fb
docs(app-iced): 订正测试注释(1180 上限是 424,不是 435)
...
r3 复审:`layout/tests.rs` 的注释说「现在必须是 435」,同一函数的断言却是 424。
证据:`grep -n 435 crates/app-iced/src/pages/layout/tests.rs`
- 第 229 行(旧):`// 1180 —— M68 就是在这条上翻的车(712px),现在必须是 435` → 改成 **424**;
- 剩下两处 435 都是**平均口径上界**的正确引用,不是残留:
第 176 行注释「不是『平均步进 × 72』的算术值 435」、
第 180 行 `assert_eq!(measure_average_bound_px(), 435.0)`。
2026-11-02 03:52:28 +08:00
AskaEth
87d5dd8f67
docs(app-iced): 订正 meta_grid 注释(1180 与 900 都是 2 列)
...
r3 复审:`meta_grid` 的文档还写着「1180 → 3 列,900 → 2 列」,但本轮起两者都是 2 列。
证据:`grep -n "3 列" crates/app-iced/src/pages/instances.rs`
- 旧:`/// 元信息网格:列数由 [...] 决定(1180 → 3 列,900 → 2 列)。`
- 新:写明「1180 与 900 都是 2 列;3 列需要 3×160 + 2×12 = 504px > 行宽上限 424,数学上不可行;
硬塞回 3 列会让 NeoForge 20.4.237(118px)折行、同排卡底不齐」。
- 现在文件里唯一出现「3 列」的地方就是这条「不可行」的说明(第 275/276 行)。
2026-11-02 03:52:24 +08:00
AskaEth
0de8e63ad6
fix(app-iced): 必修1 跟进 —— 旧断言 435→424 + clippy 常量断言
...
必修1 改完 cap 之后跑门禁抓到的两处自身的漏改:
1. `settings_pane_keeps_the_drawer_width_and_a_readable_form_column` 里还留着
`assert_eq!(measure_cap_px(), 435.0)`(P2-1 轮加的锚点),cap 收到 424 后它必红。
已改 424 —— 这正是「锚点断言」该有的行为:常量一改就红,而不是悄悄跟着漂。
2. clippy `assertions_on_constants`:`assert!(SIM_SAFE_MAX_CHARS <= MAX_MEASURE_CHARS)`
两边都是 const,改成 `const { assert!(..) }`。
门禁(本机私有 target,CPU 档):86 lib passed + 6 集成 passed;clippy -D warnings 干净。
2026-11-02 02:42:58 +08:00
AskaEth
c5c543ba43
docs(app-iced): 必修3 504→435 一轮留下的三处文档漂移
...
视觉 r2 逐条点出来的三处「偏离登记与对外口径没跟着改」:
1. `pages/mod.rs` 偏离 #1:详情列右缘写 976 / 分歧 180px —— 行宽收到 424 后实测是
页头右缘 **1156**、详情列右缘 **896**(472 + 424),分歧 **260px**;
2. `pages/mod.rs` 偏离 #3:单列页占比写 48%(435/1044)—— 现在是 **40.6%(424/1044)**;
3. `frame/pieces.rs` 的 `scroll_style` 函数头仍写「滑块 `Outline`、尺寸取 iced 默认」——
实现早已是 `OutlineVariant` + 6px 轨 + 2px 内缩,文档跟上。
三处都在 D30 要求「逐条写清」的偏离登记/口径里,属于对外承诺,不是内部注释。
2026-11-02 02:04:32 +08:00
AskaEth
ddf6937b2b
fix(app-iced): 必修1 「72 字符」改成逐行口径,上限 435 → 424
...
代码 r2 的复核:`435 / 6.0526 = 71.9` 是**整段平均步进**换来的数,不是每行都 ≤72;
它实测交付图里就有 **73 字符**行(`guess. Nothing on this page is wired to real data yet: the layout is, the`,
ink 428px)。上一轮是系数错,这一轮是**把平均当成了逐行上界**。
选 **(a)**:不变式改成逐行可验证,cap 跟着压到能满足它的地方。依据是 FreeType 量宽 + 贪心词折行的模拟表
(同一段 361 字符正文扫列宽):
| 列宽 | 最长一行 | 该行 ink | 判定 |
|---|---|---|---|
| 435(= 平均口径上界) | 73 字符 | 428px | ✗ |
| 428 | 73 字符 | 428px | ✗ |
| 426 | 71 字符 | 426px | ✓ 只剩 1 字符余量 |
| **424**(4px 栅格) | **67 字符** | **402px** | ✓ 取这个 |
- `measure_cap_px()` = `min(逐行安全宽 424, 平均口径上界 435)` = **424**;平均口径另立
`measure_average_bound_px()`(435)并在文档里标明「只是上界,不是取值」。
- 单测 `line_length_is_capped_at_72_characters` 改成**两侧夹逼**:`402 ≤ cap < 428` ——
放大到 428 会重新出现 73 字符行(红),压到 402 以下白丢宽度(也红);另断言
`cap < measure_average_bound_px()`,防止有人把平均口径当上限。
- §3 新增「逐行口径」小节:模拟表 + **作用域**(承诺只覆盖 14px 正文;12px 辅助文案是 1~2 行短句,
不做逐行承诺;CJK 全角 1em 另行定)。
- 四页文档与 `pages/mod.rs` 的 ≤435 同步改 424。
2026-11-02 02:04:23 +08:00
AskaEth
a650f8184b
fix(app-iced): 必修2 元信息卡最小宽改由内容推(136 → 160),1180 下网格 2 列
...
视觉 r2 抓到的回归:上一轮为塞进三列把 `META_CARD_MIN_WIDTH` 收到 136,只算外框、忘了
`card_padding = 20`,卡内只剩 96px —— demo 里 `NeoForge 20.4.237`(实测 118px)与
`NeoForge 21.1.72`(110px)都会按词折成两行,同排另两张卡仍一行 → **卡底不齐**。
- 最小宽改由**卡内最长内容**反推:118(Segoe UI 14px 实测)+ 2×20(令牌 `card_padding`)= 158
→ 取 4px 栅格 **160**(值上等于最初的 160,但这次是内容推出来的,不是凑上限凑出来的)。
- 于是元信息网格在 1180 宽落到 **2 列**(2×160 + 12 = 332;3 列要 504 > 行宽上限),
卡内可排 **166px** ≥ 118,所有值单行放下;6 格 = 3 行 2 列。
- 新增单测 `meta_card_is_wide_enough_for_the_longest_value`(此前 39 条 pages 测试没有一条覆盖它):
① 夹具 `NeoForge 20.4.237` 必须仍在 DEMO 里(数据变了就红,提醒重新量);
② `META_CARD_MIN_WIDTH − 2×card_padding ≥ 118`;③ 实际两列布局下每卡内宽也 ≥ 118。
- `meta_grid_drops_columns_as_the_detail_narrows` 跟着改成「上限处 2 列」(1180 与 900 同为 2 列,
差别落在详情列宽 435 vs 404)。
2026-11-02 02:04:04 +08:00
AskaEth
dc67be529d
docs(app-iced): P2-2 r2 712 是 ink 不是列宽 + 补偏离登记
...
代码 reviewer 把 P2-2 说得更准,我上一轮只改了数字没改算式,这里按它的复核改到底:
- **712 是那一行的墨迹宽度,不是列宽**。早期文档写「1180 − 88 − 2×24 = 712」,这个减法得 **1044**;
M68 的卡片是 x=112..1156(1044px),卡片内文本区 **1004px**,那一行 ink 才是 712px(x=133..845)。
- 交叉验算对上了:137 字符 × 5.19px(12px × 实测 0.4323em)= **711px** ≈ ink 712。
所以事故是「1004px 的文本区里,一行排了 137 字符」,不是「列宽 712」。
- 订正记录改成两条(① 712 是 ink ② 字符数按实测),并写明早期口径的错法。
顺带把视觉 reviewer 的「请登记」落地:`pages/mod.rs` 新增**偏离登记表** 6 条
(页头右缘与详情右缘 180px 分歧、空态标题比页标题大、单列页只用 48% 内容宽、空态字形 40、
返回入口无箭头、实例空详情态未出图),每条写了「为什么」与「什么时候收口」。
2026-11-02 01:05:27 +08:00
AskaEth
c495fccd6c
fix(app-iced): 视觉 P2-2/3 滚动条变细变淡 + 图标语汇统一成描边
...
视觉复审两条:
- **P2-2 滚动条偏重**:原来直接用 iced 默认 10px 轨 + `Role::Outline`(那份灰绿在浅色下压过列表内容)。
改 **6px 轨 + 2px 外边距 + `Role::OutlineVariant` 滑块**(比 `Outline` 弱一档的容器描边角色),
圆角由 full 按 6px 折算成 3。功能不变(仍是列表列独立滚动、仍只有这一条 scrollable)。
- **P2-3 图标线宽语汇不一致**:设置页「Java」分组用的是令牌里的 `Play`——它是**填充**字形,
同屏其它图标都是描边。改成描边的 `IconKind::Folder`(Java 分组讲的是运行时安装位置,语义也对),
并在 `pieces.rs` 写明规矩:页面骨架**只用描边图标**,将来若要「主操作允许实心图标」,
由 `components/` 统一定义并只留一处。守门见上一条 commit 的 `pages_use_only_stroked_icons`。
2026-11-02 00:39:58 +08:00
AskaEth
a0ccf79b2b
test(app-iced): P2-3 尺寸硬编码守门扩到 pages/(并补图标语汇守门)
...
代码 reviewer 指出:`no_hardcoded_tokens` 的**尺寸**守门只扫 `src/shell`,`pages/` 是新增目录、
**根本没被扫到**,页面里写 `.width(24)` 没人会发现(颜色守门扫全 `src/**`,所以颜色那条是覆盖的)。
- `shell_takes_every_size_from_tokens` → `layout_sources_take_every_size_from_tokens`:
扫描面改成 `src/shell` + `src/pages`,并把「至少 2 个文件」的哨兵断言提到 **≥10 个**(防扫描逻辑坏掉)。
- 实测结果:**扩了之后 `pages/` 是干净的(0 处)**——所有尺寸都走 `layout::*` 常量 / `LayoutMetrics` /
`Space` / `TypeStyle`,没有需要修的违规。
- 顺带补 `pages_use_only_stroked_icons`:`pages/` 里不许出现令牌层的填充字形(`Play`/`Stop`/`More`),
这是视觉 P2-3「图标语汇不一致」的机器化守门(修法见下一条 commit)。
- 文件顶到 403 行 → 压到 399(仓库硬限 400)。
2026-11-02 00:39:48 +08:00
AskaEth
367100848a
docs(app-iced): P2-2 M68 事故口径订正(实测 137 字符,不再引用估算值)
...
代码 reviewer 指出:712px ≈ 「118 字符」的那个数本身不是实测,是拿 0.5em 估的步进换算的,
和 P2-1 是同一个根因(高估了步进)。按实测重写事故描述:
- M68 那张卡里的正文是 `BodySmall` **12px**,步进 = 14px 实测值 6.0526 × 12/14 = **5.19px/字符**,
所以 712px **实测 137 字符**(不是 118);
- 补一条**订正记录**说明早期数错在哪:既高估了步进、也对错了字号,两条叠加把事故说轻了 16%;
- `pages/mod.rs` 的历史判词同步改成 712px + 137 字符;
- 全 `pages/` 已无「118 字符」这个数(`grep` 为空)。
2026-11-02 00:13:42 +08:00
AskaEth
fae1b61d9e
fix(app-iced): P2-1 「72 字符」改成实测口径(上限 504 → 435)
...
两位 reviewer 都量到:`CHAR_ADVANCE_EM = 0.5` 是估的,14 × 0.5 × 72 = 504px 实际能放 83 字符,
「72 字符」只活在名字里。改成实测:
- 口径:`BodyMedium` 14px / Segoe UI Regular / 无 letter-spacing(iced 0.14 不支持字距),
样本 = 实例页详情那段正文(361 字符,大小写/标点/空格/`→` 混排),FreeType getlength
实测 **2185.00px / 361 = 6.0526 px/字符 = 0.43233em**(旧 0.5 高估 15.7%);
交叉验证:同段首行 78 字符渲染墨迹 484px,FreeType 对同一串 479px,差 1%(抗锯齿外扩)。
- `CHAR_ADVANCE_EM` 0.5 → **0.43233**;`measure_cap_px()` 加 **floor** → **435**
(435.79 四舍五入成 436 就会让最后一两个字符挤不进 72 字符的承诺);下限 315 → 272。
- 版面连带重算:`META_CARD_MIN_WIDTH` 160 → **136**(3×136 + 2×12 = 432 ≤ 435,否则 1180 宽下
元信息网格会从 3 列掉到 2 列)。**816 / 720 两个两列阈值不受影响**(它们由 LIST/DETAIL/FORM 宽度算,
与行宽上限无关),单测里的绝对值断言原样成立。
- 单测:`line_length_is_capped_at_72_characters` 里新增 tower 要求的断言 —— 用**实测步进**
(样本字符数 + 实测宽度,独立于常量)反算「上限列宽能放多少字符」≤ 72(实际 71.87),
并加一条回归护栏:同一实测步进下旧口径 504px = 83.3 字符,改回 0.5em 立刻红。
- 文档:`layout.rs` §3 补「怎么重新量」(字体、样本、工具、交叉验证、CJK 不适用)与全部数字;
`mod.rs` / 四页文档里的 ≤504 同步改 435。
2026-11-02 00:13:20 +08:00
AskaEth
2c442585b1
feat(app-iced): M84 页面骨架与布局系统(断点 / 行宽上限 / 三种版面)
...
视觉复审在 M68 的判词是不能把「全宽单卡」当页面模板(1180 宽下正文单行 712px ≈ 118 字符)。
本 mission 把它落成一套**布局系统**,四个页面只是它的用例:
- `pages/layout.rs`:官方 600 / 840 两个档位 → 三种版面(单列 / supporting-pane / list-detail),
阈值由列宽和算出(设置 240+24+320 → 720;实例 336+24+320 → 816 ≤ 840),行宽硬上限
14 × 0.5em × 72 = **504px**,全部是纯函数,`pages/layout/tests.rs` 用绝对值和边界表钉死。
- `pages/frame.rs`:页头(56 / 两行 84)+ 内容列(≤504)+ 两列骨架 + 空态位;
`pages/frame/pieces.rs` 是占位控件(卡片 / 分隔线 / 文本按钮 / 图标),令牌驱动、无完整状态机,
待 M75 的 components 家族合入后替换。
- 四页:实例(list-detail,列表列 336 独立滚动,窄窗退化成整页切换)、
设置(supporting-pane,240 分组导航 + ≤504 表单列)、下载 / 日志(单列,两行页头)。
- `pages/harness.rs` + `examples/pages_demo.rs`:自带 `--screen/--width/--screenshot-theme`,
不碰 `cli.rs` / `app.rs`(M75 在改)。
偏离与说明:
- 布局用「页边距 16/24」而非 16/24/32,官方 expanded 也是 24(少一条偏离登记);
- 实例空详情态一屏会有 5 档字号(22/16/24/12 + 14 的操作按钮),未截图的状态,已知并登记;
- `lib.rs` 只加一行 `pub mod pages;`。
2026-11-01 23:27:28 +08:00
AskaEth
352dd7937e
Merge branch 'feat/p4-core-platform-wallpaper-source-detect'
2026-11-01 21:18:02 +08:00
AskaEth
69d0025d09
fix(core): M73 r1 —— Windows 壁纸来源改为「当前主题优先」,通配主题包降为末档
...
复审 p2-1(medium):Win10+ 上 `%APPDATA%\Microsoft\Windows\Themes\` 通常只有
`TranscodedWallpaper`,一个 `.theme` 都没有;当前生效的主题记在
`HKCU\Software\Microsoft\Windows\CurrentVersion\Themes` 的 `CurrentTheme` 值里,
指向 `%LOCALAPPDATA%\Microsoft\Windows\Themes\Custom.theme`。
- 新增第二步 `windows-current-theme`:`reg.exe query ... /v CurrentTheme` →
归一化(含 `%LOCALAPPDATA%` 展开)→ 读该 `.theme` 的 `Wallpaper=`;
- `%APPDATA%\...\Themes\*.theme` 通配扫描从「第二步」降为第四步兜底,
evidence 如实标注「用户另存的主题包,可能未启用」;
- 新顺序:TranscodedWallpaper → CurrentTheme → 注册表 WallPaper → 另存主题包;
- `HostRoots` 增加 `local_app_data`(`%LOCALAPPDATA%`,退 `~/AppData/Local`)。
复审 p2-2(low):`transcoded_wallpaper_hit_comes_first` 补一条字面量路径断言,
防止夹具与生产代码用同一构造函数一起拼错。
复审 p2-3(low):`HostIo::run` 现在检查退出码(`!output.status.success()` 也返回
`None`),与四处「非零退出 → 失败」的文案对齐;`io.rs` 补一条夹具用例
(有 stdout 但退出码非零)证明旧行为会被挡掉。
行长:`windows.rs` 371 → 拆出纯解析到 `wallpaper/windows/parse.rs`(100 行),
全部 7 个文件 ≤400 行。真机复跑 `detect()`:第 1 条 transcoded、第 2 条
`windows-current-theme` → `C:\Windows\web\wallpaper\Windows\img0.jpg`(与注册表同源,
去重后只剩一条),`%APPDATA%` 下无主题包的 note 照旧。
2026-11-01 21:17:55 +08:00
AskaEth
d3e9a62b21
feat(core): M73 桌面壁纸路径探测(D38 第②级输入)
...
新增 platform::wallpaper:只做「桌面壁纸可能是哪个文件」的路径探测,
不解码图片、不取色(莫奈取色与三级降级留给 crates/design 的后续 mission)。
- 唯一入口 detect() / 注入式 detect_with(io, env, platform):返回有序候选列表
+ 各步失败原因,全部找不到时是空列表而不是错误;
- Windows 三步:TranscodedWallpaper(无扩展名,靠魔数认)→ *.theme 的
Wallpaper= → reg.exe query HKCU\Control Panel\Desktop /v WallPaper;
- Linux 四步:gsettings picture-uri(-dark) → KDE Image= → hyprpaper/sway
配置 → XFCE xfce4-desktop.xml,每步失败不影响下一步;
- macOS 两步:com.apple.wallpaper/Store/Index.plist 字符串扫描(不引 plist
依赖,兼容 bplist00)→ defaults read com.apple.desktop Background;
代码与文档明确标注「未在真机验证」;
- 零 unsafe / 零 FFI(core 是 #![forbid(unsafe_code)]),文件读取与子进程
全部经 ProbeIo 注入,环境变量表经 EnvSource 注入;三平台采集逻辑不带 cfg,
CI 的 Linux 上也能跑 Windows / macOS 夹具用例;
- 不新增第三方依赖;6 个文件均 ≤400 行,32 条单测覆盖三平台的
正常命中 / 多候选 / 全部失败。
2026-11-01 21:17:52 +08:00
AskaEth
614bd253c8
Merge branch 'feat/docs-remove-affiliation-disclaimer-foote'
2026-11-01 20:26:14 +08:00
AskaEth
9e30d2cf15
docs(readme): 删掉文末「与 Mojang / Microsoft 无关联」免责脚注
...
中英两份都删,不补替代句;文件以感谢段落 + 单个换行结束。
2026-11-01 20:24:14 +08:00
AskaEth
6cfac9c823
Merge branch 'feat/docs-tone-pass-remove-defensive-wording'
2026-11-01 20:02:10 +08:00
AskaEth
4069204379
docs(readme): 复审收尾两处——ull 行补回分发状态,平台独立性改正面陈述
...
- bin 表 `ull` 行:State 列补回「未随 v0.1.0 资产分发」/ "not in the v0.1.0 assets",
只看表格的人也读得到分发状态
- 平台基线末条:去掉「而非独立产物链」这半句对没人提的质疑的反驳,
改为「每个平台跑各自的测试矩阵」/ "Each platform runs its own test matrix"
2026-11-01 20:00:41 +08:00
AskaEth
0cb1454067
docs(readme): 致谢改为平实感谢,去掉「欠人情」的债务叙事
...
原段落把作者写成亏欠方(「这个项目欠它们一份很大的人情」/ "exists very much in
their debt"),并把 HMCL / PCL2 称作 "old-timers"。两者都是 GPL-3.0 开源项目,
阅读与借鉴本就在许可之内,不需要愧疚来偿付。
- 债务词全部移除:欠 / 人情 / debt / owe / old-timers / enormous amount
- 保留感谢本身,改成「它们做得久 + 本项目从中受益」两句话
- 中文补一次断行以保持 ≤100 显示列
2026-11-01 19:55:55 +08:00
AskaEth
aca280fd30
docs(readme): 删掉「合规声明」整节,账户信息归位到特性表
...
这是项目的 README,不是审核材料:把单独开列的合规节去掉,信息放回它该在的位置。
- 删除整节 `## Compliance statement` / `## 合规声明`(含 5 条 bullet)
- 「Microsoft 账户登录 + OAuth device code → XBL → XSTS → Minecraft Services + 令牌只存本机」
并入特性表新增的「账户 / Accounts」一条(功能说明,不是专项保证)
- 「不分发游戏文件」「联机需账户与权益」两条删除(常识范围 / 与前一句重复)
- 「与 Mojang、Microsoft 无关联」降为文末一行普通脚注
- 平台基线:标准工具链那条去掉三重「无」,改为平铺陈述
- 平台基线:删掉与「构建 → 渲染开关」重复的渲染兜底条目(第三处),
文件内不再出现「完整界面」/ "the full UI"
- bin 表:`ull` 状态列去掉与 Track A bullet 重复的「未随资产分发」,保留「同源同版本构建」
- UI 一条:「驱动真实引擎」→「由 core 引擎驱动」/ "drive the real engine" → "run on the core engine"
2026-11-01 19:54:50 +08:00
AskaEth
5a44870de5
docs(readme): 语气体检——去掉「正版登录」及同类防御腔与自我声明
...
- zh 模块表 auth 行:「Microsoft 正版登录」→「Microsoft 账户登录」,与英文 "Microsoft sign-in" 对齐
- zh/en:CLI 行去掉「完整可用」/ "is fully usable"(要读者相信的形容词)
- zh/en:CPU 软渲染兜底不再承诺「完整界面」/ "the full UI",改为陈述渲染结果
- zh/en:轨道 B 首句去掉「没有任何…任何下载都拿不到它」/ "Nothing…no download contains it" 的双重否认,
并与紧邻的「只能自己从源码编」合并为一句
- zh:轨道 A 小标题去掉「真能」的强调语气
2026-11-01 19:51:46 +08:00
AskaEth
a426cb5dcb
Merge branch 'feat/docs-refresh-readme-for-iced-rewrite-and'
2026-11-01 19:49:27 +08:00
AskaEth
9d9ab3e7e0
docs(readme): 合规段第一条去掉防御腔 —— 平静陈述「用 Microsoft 账户登录」
...
- 英文:Microsoft sign-in only → Sign-in with your Microsoft account;删掉 There is no other
account provider(过度否认本身是信号)
- 中文:只走 Microsoft 正版登录 → 用 Microsoft 账户登录;删掉「没有别家账户通道」,
并避开「正版」这个词(中文圈里该词天然与「破解」对照,带防御姿态)
- 事实不变:device code → XBL → XSTS → api.minecraftservices.com,用玩家自己的账户与权益
- 第三条「联机游玩需要 Microsoft 账户与有效权益」保留独立一条(事实陈述,非否认)
2026-11-01 19:49:08 +08:00
AskaEth
d0d24d42e7
docs(readme): 按用户令删除全部离线档案描述;门禁口径改为 main 推送 + PR
...
- 合规条里「Offline profiles cannot play online / 本地离线档案仅限本地与隔离网络测试」整条删除,
只保留正面陈述「联机游玩需要 Microsoft 账户与有效的游戏权益」
- 模块地图 auth 行去掉「local offline profiles / 本地离线档案」,只留 Microsoft 正版登录全链路
- 全文 grep offline|离线|隔离网络 零命中;不做「不支持离线」的反向承诺,也不暗示别的账户通道
- 顺手修 nit:CI 段「Every push」→「Every push to main and every pull request」
(ci.yml on.push 限 main 分支,非 main 且无 PR 时门禁不跑)
2026-11-01 19:47:40 +08:00
AskaEth
a4bd4d2b38
docs(readme): r1 复审修复 —— 去掉 ull 随包分发的假话、macOS 探针降级为 advisory、离线档案正面陈述、合规段提到第一屏
...
- P1: ull 与 v0.1.0 同源同版本但发布资产只有 GUI 单文件(make_artifacts.py 只搬 launcher、
release.yml assets 只 glob unlucky-launcher-*)→ 4 处改为「自行 cargo build --release --bin ull」
- P2-1: macOS 运行期探针 if workflow_dispatch + continue-on-error,属 advisory → 与 Linux 冒烟拆句
- P2-2: 离线档案去掉「不能连校验账户的服务器」这种可反读的限定,标题改「联机游玩必须使用 Microsoft 账户」
- P2-3: 合规声明挪到引言之后、特性 bullet 之前(第一屏可见),文字不变
- nit: 三份合规文本每个 Release 都挂(Windows 因 exe 装不下才并列);Slint 版无显式探测,改为两套栈
各自回退到 CPU 的准确表述
2026-11-01 19:44:07 +08:00
AskaEth
2a0493c3eb
docs(readme): 两份 README 改写为 iced/M3 Expressive 现状(双 bin 并存 + 两轨 Status)
...
- UI 描述:已发布 v0.1.0 = Slint + Metro;下一代 = iced 0.14 + Material 3 Expressive,dev 上施工中
- 双 bin 并存逐条讲清:unlucky-launcher(Slint,v0.1.0 已发布)/ unlucky-launcher-iced(施工中)
/ ull(CLI),并说明翻牌前不改名的原因(同 target 目录不能同名 bin)
- 模块地图补 crates/design(真 HCT 令牌、零 UI 依赖)与 crates/app-iced;crates/app 保留到等价验收
- Status 重写为两轨:Track A 已发布(含 v0.1.0 资产范围与 CLI 命令集,取自 release.yml 与 cli/help.rs)
/ Track B 施工中(已完成=令牌+骨架;未完成=组件库/页面/i18n,页面全是占位)
- 删除全部过期统计数字(983+ tests 等),改为行为性门禁表述;无测试数、无产物体积
- 合规段落只留 Microsoft 正版登录 / 凭据只存本机且 AI 不可访 / 离线不能联机 / 不分发不镜像不代理
/ 与 Mojang-Microsoft 无关联;无第三方账户服务字样
- 许可:Slint RF 2.0 归属保留并补 iced(MIT)、MCU(Apache-2.0)、其余主要依赖
- 致谢 HMCL / PCL2 改为口语化语气,不加粗、不撇清
- 开头保留 GitHub 官方主页行(英文自指、中文醒目指引 + Releases 链接),全文无 gitea 地址
- 平台基线补 macOS 未签名需清 Gatekeeper 隔离标记
2026-11-01 18:53:26 +08:00
AskaEth
cf52751124
Merge branch 'feat/p4-design-cool-sky-blue-default-seed-can'
2026-11-01 18:13:28 +08:00
AskaEth
40864d93d0
fix(design): M70 r4 —— 偏离登记补齐「neutral-variant 8→14」+ 文档口径与 pin 可读性
...
复审 P2-1:neutral-variant 的那一半偏离漏登记,且文档把 nv 说成官方相对式。
- `color/mod.rs`:标题改「两处有意偏离(同一处偏离的两半)」;偏离登记把
neutral 6→12 与 neutral-variant 8→14 分列,写明官方 `getNeutralVariantPalette`
是**绝对值 8**(不是 neutral+2)、影响角色含 outline/outline-variant/surface-variant,
以及「nv 钉回 8 会让表面(12)比中性变体(8)更有色」的倒挂理由
- `palettes.rs`:`NEUTRAL_VARIANT_OFFSET` 文档改述为「官方绝对值 8,本实现相对式=有意偏离」;
`DEFAULT_NEUTRAL_CHROMA` 的矛盾区间改正为「数值安全区间 6..=13(14 才撞 secondary),产品取 12」;
`from_seed_with_neutral_chroma` 加调用方约束(越界静默退化:<0/NaN → 彩度 0;≥14 → nv 撞 secondary);
新增 `product_default_neutral_chroma_is_a_registered_deviation`(钉 neutral 12 / nv 14,官方锚点仍 6/8)
- nit1:`sky_seed_candidates_meet_wcag_aa` 的「三档」→「四档(208 项)」
- nit2:`product_default_is_the_adopted_candidate` 改成比 hex 文案,失败时可直读「期望 #3C8DBC、实测 #87CEEB(③)」
- nit4:`lib.rs` 的 `DesignTokens::baseline` 文档改准(产品默认 ④ 天蓝,官方口径走 `Scheme::official_baseline`)
无物理行为变更:算法、tone 表、gamut、所有色值一个没动。
2026-11-01 18:09:14 +08:00
AskaEth
e37b452345
feat(design): M70 r2 —— 出厂默认定为 ④ 天蓝 #3C8DBC(用户 2026-11-01 拍板)
...
- `palettes::DEFAULT_SEED`:`#87CEEB`(③ 浅冷蓝)→ `#3C8DBC`(④ 天蓝,主色 `#236488`);
`DEFAULT_NEUTRAL_CHROMA` 保持 12
- `tests/skyseed_candidates.rs`:`product_default_is_the_recommended_candidate` →
`product_default_is_the_adopted_candidate`,绑定 `CANDIDATES[3]`(④);四档 pin 不变
- 文档:`color/mod.rs` / `palettes.rs` 的「临时默认 ③ / 待用户确认」全部换成
「出厂默认 ④(用户 2026-11-01 拍板)」,③ 标注为备选(值仍 pin 住,可一行换回)
- 文档新增定位(D38):这两个常量只定义「**莫奈关闭时的出厂预选色卡**」(配色来源第三级),
不再代表唯一配色来源;背景图/壁纸取色与内置色卡库都不在本 crate、尚未实现
2026-11-01 18:09:14 +08:00
AskaEth
9519a89472
feat(design): M70 r1 —— 补第 4 档「天蓝」候选 #3C8DBC(四档对比)
...
③ 的 primary `#0C6780` 偏青(teal),不是「天蓝」。新增 ④ 天蓝:种子 `#3C8DBC`
(CAM16 hue 239.5°,夹 ③ 与 ①② 之间)+ 中性彩度 12 → primary `#236488`。
- `tests/skyseed_candidates.rs`:候选表 3 → 4 档,④ 的 9 角色 × light/dark 全部进 pin;
`the_three_tiers_are_actually_distinguishable` 改为 `the_candidates_are_actually_distinguishable`,
增加「③ 与 ④ 中性彩度相同、主色/容器必须靠色相区分」的断言
- `tests/contrast_aa.rs`:候选 WCAG 门禁 3 → 4 档(26 × 4 × 2 = 208 项)
- 默认值**不动**:仍为 ③ `#87CEEB`(临时默认,待用户看完四档再拍);
`product_default_is_the_recommended_candidate` 保持原样,专门在默认与推荐不一致时报警
- 文档:`color/mod.rs` 与 `palettes.rs` 的候选清单补上 ④ 与「改一行即可切 ④」的说明
2026-11-01 18:09:13 +08:00
AskaEth
79404fa9d1
feat(design): M70 默认色候选 —— 浅冷天蓝落成两行常量 + 三档候选色卡
...
默认色从官方紫 `#6750A4` 换成「浅冷天蓝」,并把「换默认只改哪一行」做成常量:
- `palettes::DEFAULT_SEED` = `#87CEEB`(CSS skyblue)+ `palettes::DEFAULT_NEUTRAL_CHROMA` = 12
- `Palettes::baseline()` / `Scheme::baseline()` = 产品默认(消费方拿到的就是它)
- 官方口径锚点独立保留:`Palettes::official_baseline()` / `Scheme::official_baseline()`
(种子 `#6750A4` + 中性 6),官方对齐测试改指向它,98 项仍每通道误差 0
- 新增 `Palettes::from_seed_with_neutral_chroma(seed, neutral_chroma)`:中性彩度从写死
常数变成入参(neutral-variant 按官方 +2 跟随),算法、tone 表、gamut 映射一行未动
有意偏离登记(已写进 color 模块文档):中性彩度 6 → 12 是对官方 Tonal Spot 的偏离,
理由=产品要冷调表面;primary 的 tone 被官方钉在 40,种子再浅也变不成浅蓝,
「浅」只能落在中性色上。
三档候选(同一条 HCT 管道,只差种子与中性彩度)逐个 pin 在 tests/skyseed_candidates.rs:
① 保守蓝 `#2196F3`/6、② 冷调蓝 `#2196F3`/12、③ 浅冷蓝 `#87CEEB`/12(临时默认,待用户确认)。
WCAG AA 扩到候选档:26 组 × 3 档 × 2 模式 = 156 项全绿,最紧 4.99:1。
2026-11-01 18:09:12 +08:00
AskaEth
c20c742b60
Merge branch 'feat/p4-iced-app-skeleton-renderer-fonts-shel'
2026-11-01 17:58:46 +08:00
AskaEth
a1adf024ba
fix(app-iced): M68 r1 —— M69 迁移 + 守门补洞 + 药丸 56×32 + caption 图标 24 + 东侧热区回 1180
...
代码复审 p2-2items / 视觉复审 p2-2items 的修复轮(配色重拍等 M70 定案)。
M69 迁移(2 行)
- nav 轨宽:nav_rail_width(metrics) 函数体改用 metrics.nav_rail_width
- theme::STROKE_PX 改为 pub use unlucky_design::stroke::HAIRLINE as STROKE_PX
P2-1 零硬编码守门补洞(reviewer 造样 5 个漏网样本全部补上,并进自检正向断言)
- COLOR 调用表补 from_rgb8 / rgb8 / rgba(`from_rgb(` 子串匹配不到 `from_rgb8(` 是根因)
- 新增 `Color { r: .., g: .. }` 结构体字面量检查
- 尺寸规则遇到 Length::Fixed( / Padding::from( / Size::new( 往里剥一层再判数字
- 新增渲染层回归守门 pill_never_overrides_its_size_with_fill(禁 nav.rs 出现 center_*( ):
视觉复审 P2-1 的病根就是 center_*(Length::Fill) 把容器尺寸覆盖成 Fill
视觉 P2-1 药丸 88×32 → 56×32(实测 x=16..71)
- 去掉 center_x/center_y(Fill);居中改由父 column 的 align_x + 显式 width(轨宽) 完成
(只去掉 center_* 还不够:column 会 shrink 到 56,药丸贴左缘 x=0..55,是同一个坑的第二层)
视觉 P2-2 / 用户点名 caption 图标 46 → 24(按钮容器保持 46×48)
- button 内包 container(...).width(24).height(24).center(Length::Fill):
只设 width/height 会让容器缩到 24 并贴 button 内容左上角;center(Fill) 才既撑满又居中
视觉 P3-1 东侧/南侧缩放热区改成叠层
- 原来是 row 的兄弟列,吃掉 4px 布局宽(右留白 28、关闭键离窗角 4px);
现在用 stack! + 对齐容器叠在工作区上,布局宽回到 1180(卡片右缘 x=1155 = 右留白 24)
P3 清单
1. --data-dir 接受并忽略(翻牌前 CI 兼容;现役 smoke 命令行可原样跑,exit 0)+ 单测
2. --diff 内建 ±N px 平移扫描(--diff-scan),CI 不再依赖 numpy
3. 截图模式等 SystemTheme 到达才出图(否则暗色系统上会拍到浅色)
4. (Auto, 未编入 wgpu) 返回 tiny-skia,不再把 wgpu 写进候选列表
5. --renderer=gpu 也跳过适配器枚举(决策与探测无关,省 ~0.6~1.2s)
6. icons::handle 加 debug_assert_eq!(IconKind::ALL[kind as usize], kind)
7. 轨顶留白改 container padding-top,实测 24(原来 spacer + column spacing = 28)
顺带:shot.rs 拆成 shot/mod.rs + shot/diff.rs(≤400 行门禁)
门禁:fmt OK / clippy -D warnings 干净 / 46+5 测试全过 / cargo check --workspace --all-targets OK
体积:双后端 6,769,664 B(6.46 MiB)、仅 CPU 3,955,712 B(3.77 MiB)
截图:r1 代码验证图(r1-verify / hover / pressed / compare-nav-rail)已出;4 张交付主图待 M70 定案后重拍
2026-11-01 17:46:55 +08:00
AskaEth
14da7b3dec
refactor(app-iced): M68 —— 把 nav 轨宽收敛成一个 M69 迁移点(三处调用 → 一行改动)
...
M69(141e9b1)把 LayoutMetrics::nav_expanded_width 改名 nav_rail_width、删掉
nav_collapsed_width、新增 nav_drawer_width=240。本分支基线 0aac307 还没有 M69 的
crates/design(那个目录也不在本 mission 的改动范围),现在直接改名会编不过。
所以把 shell/nav.rs 的三处 metrics.nav_expanded_width 收敛到 nav_rail_width(metrics),
并在函数文档里写清 M69 前后对照与「合入后只改函数体一行」;theme::STROKE_PX 同样
注明可换成 unlucky_design::stroke::HAIRLINE。行为/视觉零变化(值都是 88 / 1.0)。
2026-11-01 17:11:25 +08:00
AskaEth
df4ff373b7
feat(app-iced): M68 —— iced 0.14 骨架(后端探测/字体基座/自绘标题栏/导航轨/深浅主题/截图通道)
...
新增 crates/app-iced(package `unlucky-app-iced`,bin `unlucky-launcher-iced`),与 Slint 版并存,
为 D26「UI 从头写」打地基。本 mission 只做骨架:能开窗、颜色/字体对、层级在,不做业务页面。
- 后端(D27):启动探测 wgpu 适配器(只看 device_type,避开 WARP 误判)→ 写 ICED_BACKEND;
auto/gpu/cpu 三条路径都保留 tiny-skia 兜底,绝不硬失败;`--renderer=cpu` 跳过探测(省 ~0.85s)
- 字体(D28 前半):令牌三平台字体栈 → 用 iced 全局 FontSystem 查装机情况 → 族名与 400/500/700
字重落点(缺档按 Nearest/Synthetic 记录)打一行启动日志
- 令牌适配层 theme::Ui:唯一把 unlucky-design 的值翻成 iced Color/Font/Border/Radius/Theme 的地方;
49 个 M3 角色留在自己的 Scheme 里,iced 的 6 色 Palette 只给 devtools
- 骨架:1180×760 无边框窗口、48 高自绘标题栏(drag + 东/南 drag_resize + 最小化/最大化/关闭)、
88 宽导航轨(56×32 SecondaryContainer 药丸,由令牌推导)、页头 56 + 一张 OutlinedCard
- 主题:系统主题探测 + --theme,切主题只重建 Ui(Theme::name 变化触发重算),不动 UI 状态
- 截图通道:`--screenshot <png>` + `--screenshot-theme`(真开窗第 3 帧出图),以及
`--diff <a> <b>` 自带逐像素对比(CI 不依赖 Python/ImageMagick)
- 图标:令牌 SVG path → iced svg 控件(颜色走 alpha 蒙版,深浅共用一份位图缓存)
- 门禁:tests/no_hardcoded_tokens.rs 扫源码挡色值/尺寸字面量 + 挡 slint 依赖(自检也算过)
实测(Windows Server 2025 / 96 DPI / RX 6600 XT):浅/深 × GPU/CPU 四张真开窗截图;GPU vs CPU
差异 0.72% 像素、全部落在字形/图标抗锯齿与 1px 圆角描边、最佳对齐 (0,0)、平坦区颜色逐字节相同;
release 体积双后端 6.44 MiB / 仅 CPU 3.75 MiB;首帧 GPU 1.18~1.31s、CPU 33~37ms。
未做/未验证:macOS 与 Wayland 全链路、西/北边缩放热区、letter spacing(iced 0.14 无此属性)、
中文 CDN 字体、玻璃;翻牌步骤写在 DevDocs/SubAgentsTalk/app-iced-骨架与翻牌.md(不入库)。
2026-11-01 17:11:24 +08:00
AskaEth
3fcdc3d3e1
Merge branch 'feat/p4-design-token-extensions-component-met'
2026-11-01 17:10:04 +08:00
AskaEth
16bdad1b98
fix(design): M69 r1 —— SEARCH_BAR 内边距语义收口 + ComponentRadius 取值口上闸
...
P2-1(语义缝隙):`SEARCH_BAR` 的 pad_start / pad_end 原为 16,与
icon_offset = 4 按字面相加得 20,比官方图标可视边 16 多 4px(越过 rubric
±2px 容差);而被加数的 12 不在 CM 里,消费方只能自己写 12.0 —— 正是 G10
要消灭的字面量。改为 12(文本框内容内边距,Space::X3)+ 4,两侧同式:
图标可视边 = pad + icon_offset = 16。字段文档写明求和公式;测试钉死
pad + offset == 16 且 pad != 16(防有人把 16 写回 pad 再叠加)。
顺带给 SEARCH_BAR.gap 补 [估] 出处(官方无图标↔文字 token)。
P2-2(绕过路径):ComponentRadius::dp() 收成 pub(crate)。它不看尺寸、
无条件返回 2.0,公开等于把 D32 的尺寸闸交给自觉。对外唯一取值口是
radius_px(size) -> Option<f32>;ALL 只暴露变体名、不暴露数值。
tokext_pins 原用 dp(),改为紧邻的 radius_px(20.0) == Some(2.0),覆盖不丢。
顺手:修掉本轮新引入的 rustdoc warning(component_map 文档链到私有模块
metrics,改指 ComponentMetrics);Divider 行标注 motion/duration 为占位,
并在 ComponentSpec 的 text / motion 字段文档写明「无文字 / 无动效的组件
这里是占位值,消费方不得据此排版 / 排动效」。
门禁:test 165 全绿、fmt --check、clippy -D warnings、check --workspace
--all-targets、doc(warning 2 → 1,只剩既有的 typography→emphasized 一条)。
2026-11-01 17:05:19 +08:00
AskaEth
141e9b12c7
feat(design): M69 令牌补齐 —— 圆角分层 / 组件微度量 / 骨架值与一像素线
...
G13(D32 分层):新增 shape::ComponentRadius(白名单只有 Micro=2)、
MICRO_CONTROL_MAX_SIZE=24 与纯函数 radius_layer_for();微半径走
radius_px(size) -> Option<f32>,尺寸 >24dp 一律 None,靠签名而不是自觉拦。
ShapeScale 与 rank() 一个字未动,2 不进刻度。
G11:LayoutMetrics 加 search_bar_height=56 / nav_rail_width=88 /
nav_drawer_width=240 / divider_thickness=1;删除语义相反的
nav_expanded_width=88 / nav_collapsed_width=48(D33:展开 drawer 240 /
折叠 rail 88;值 88 的后继是新名字 nav_rail_width,48 无后继)。
G10:新增 component_map::ComponentMetrics(12 字段 + 4 个命名常量 +
indicator_width),入口 Component::metrics();框内几何与 LayoutMetrics 的
骨架值分工写进模块文档,骨架级尺寸在 CM 里留 0 不许重复。
G9:Component 末尾追加 Checkbox / Radio / Search / NavigationDrawer /
Divider(20 -> 25),同步 table.rs 槽位、ALL 与 name();ComponentSpec 新增
micro_radius(Some 时 shape 必须是占位 None,由构造器 + 测试钉死)。
G12:新增 spacing::stroke::HAIRLINE = 1.0(crate 根再导出为
unlucky_design::stroke),divider_thickness 直接引用它。
其余:lib.rs 的「OKLCh 近似」文案改回真实口径(真 HCT / ColorSpec2021);
component_map 单测拆到 tests.rs 以免 mod.rs 超 400 行;测试 145 -> 165。
2026-11-01 16:53:30 +08:00
AskaEth
b89cc91b78
Merge branch 'feat/p4-design-exact-hct-color-scheme-hctcolo'
2026-11-01 16:40:38 +08:00
AskaEth
a4d86e9b19
docs(design): M65 r1 收口 —— 修正覆盖数量口径(290 项 / 8 个种子)
...
复审 P2-1:两处「验证覆盖数量」还停留在扩种子之前的旧值,只改文档文本。
- NOTICE:「242 项」→「合计 290 项」,并写出分项口径
(baseline 49 角色 × 2 模式 = 98;强调色 8 种子 × 12 角色 × 2 模式 = 192)。
这是 Apache-2.0 归属声明,覆盖范围写错比普通注释更该修。
- crates/design/src/color/mod.rs:51:「6 个种子」→「8 个种子 × 12 个强调族角色 × 2 模式 = 192 项」。
顺带核对了 242 的来历:242 = 98 + 6×12×2 = 98 + 144,是扩到 8 个种子之前的旧值,过期而非算错。
配色相关的 HCT 彩度措辞(#E8DEF8 是 16.096 而非 11.9)此前只出现在我的完成报告里,代码/文档/测试
均未写入 11.9(已 grep 确认),故无第三处改动。
不动代码、不动测试、不动阈值。
2026-11-01 16:40:36 +08:00
AskaEth
29f65b8e6f
feat(design): M65 真 HCT 色彩 —— CAM16 + ColorSpec2021 + 官方逐角色对齐
...
把 M61 的「OKLCh 近似 CAM16 + gamut 硬裁」换成官方的 HCT 算法,与
material-color-utilities(Apache-2.0)逐角色对上:baseline 49 角色 ×
light/dark、8 个种子 × 12 个强调族角色 × 2 模式,**实测每通道误差 0**
(判据写入测试:≤1)。
实现(逐函数对照官方 Java 重写为 Rust,f64 内部精度):
- color/cam16.rs + viewing_conditions.rs:CAM16 色相/彩度(D65、200 lux、
背景 L* 50),默认观察条件的中间量与 Java 实测逐位对齐
- color/hct_solver.rs + critical_planes.rs:官方 gamut 映射(Newton 迭代 +
色调平面上的色相二分,255 项关键平面表原样搬运),只压彩度不硬裁
- color/hct.rs:HCT 坐标(hue/chroma/tone),公开给工具与测试
- color/space.rs:官方 ColorUtils 的 sRGB↔线性↔XYZ↔L* 换算(矩阵、阈值、
取整口径一致)
- color/palettes.rs:Tonal Spot 官方彩度常数 36/16/24/6/8,error 25°/84
- color/scheme.rs:tone 表换 ColorSpec2021(on-*container 10→30、
background 99/10→98/6)
两个旧缺陷关门:
- primary-container 与 secondary-container 不再撞色(彩度 36 vs 16,
tone 90 装得下,无需「共享基准压彩度」补丁);reference_chroma /
tone_scaled 随之删除
- 强调色色相不再被 gamut 硬裁(只压彩度,实测偏移 <1°)
对齐证据:tests/official_baseline.rs(98 项 + 12 个 HCT 探针)、
tests/official_accent.rs(192 项)。期望值是把官方 Java 实现本机编译运行
的输出抄本,并用 Flutter 的 ColorScheme.fromSeed 用例交叉验证
(#00FF00 → #406836、#000000 → #8C4A60、#FFFFFF → #006874 一致)。
诚实口径:只做 Tonal Spot 变体 + contrastLevel 0;对比度三档、其他变体、
壁纸多色取色、非默认观察条件都在模块文档的 gap 表里登记。
注:token_pins 的 tone 值变化来自官方算法(ColorSpec2021),不是为过测试而改。
NOTICE 增「Google Material Color Utilities」归属(Apache-2.0 要求,
含逐文件映射与对齐方法)。
2026-11-01 16:40:36 +08:00
AskaEth
958ced2d7e
Merge branch 'feat/p3-guards-file-size-cover-py-sh-guardsiz'
2026-11-01 16:22:54 +08:00
AskaEth
95e8a5ca42
fix(ci): M67 文件行数守门补上 *.py / *.sh —— 脚本不再漏网
...
file-size job 原先只扫 *.rs/*.md/*.slint/*.yml/*.yaml/*.toml/*.json,
packaging/ 下的构建辅助脚本(make_artifacts.py、check_screenshot.py、
linux-smoke-docker.sh)完全没有任何行数保护。
- 在 git ls-files 的 pathspec 末尾加 '*.py' '*.sh',语义不变(>400 行即硬失败)
- 保留 LICENSE*/CHANGELOG* 排除;Cargo.lock 等机器生成文件刻意不入列
- 阈值、失败方式(::error + exit 1)均未改动
2026-11-01 16:22:51 +08:00
AskaEth
0aac307d92
Merge branch 'feat/p3-ci-headless-render-smoke-xvfb-xplatci'
2026-11-01 16:15:49 +08:00
AskaEth
7e08e0ccc9
fix(docs): M62 r1 —— 修正「缺字体→空图+exit 0」的错误论据,拆出运行时冒烟细节文档
...
review r1(p2-1items/fix-then-merge)只要求改文档与注释,门禁逻辑一行未动。
- P2-1(唯一阻塞项):原稿称「字体库缺失 → 合法 PNG + 退出码 0」,这是错的。review 实测两条失效都是
**硬失败**:完全没有字体 → fontique panic(退出码 101,不产生 PNG);非软渲染(默认 femtovg)→
`take_snapshot is not supported by this FemtoVG backend`(退出码 1)。改正口径:`--screenshot` 的 PNG
是进程自己编码写出的,只要走到 `take_snapshot` 就是「合法 PNG + 退出码 0」,像素断言是**纵深防御**,
守的是「进程活着、PNG 合法、但场景几乎没画」这一档;其余情形本机暂无样本,属防御性设计、不宣称已复现。
落点:packaging/ci-runtime-smoke.md §1 与 packaging/check_screenshot.py 模块 docstring。
- P3-1:ci.yml 的 runtime-smoke job 头补一行 —— 本 job 绑死 Slint `Window::take_snapshot`,迁 iced 后必须
提供等价离屏截图通道,否则静默退化成「能起进程」。
- P3-2:阈值余量写明量级(342/64=5.3×、19.45/3.0=6.5×、0.00761/0.001=7.6×,改「5~8 倍」);并写明
闸门下限 ≈「至少画出一条标题栏」(review 实测切片 174/35.10/0.02623 与右下 400×300 切片仍 pass),
即只证明「场景被真实渲染过」,不证明元素齐全、更不证明视觉正确。
- P3-3:README 398/400 已贴红线 —— 借 review 给的逃生口**提前拆**:细节搬进 packaging/ci-runtime-smoke.md
(78 行),README §11 收缩为 12 行索引(374 行),并写明「再增长继续往细节文档写,两文件都 ≤400」。
- P3-4:本地复跑示例改 Windows 安全路径 `D:/tmp/ull-smoke`;脚本对 repo_root/out_dir 统一 `cygpath -m`
归一并回显真实路径(Git Bash 下 `/tmp` 会静默落进 Docker VM,宿主什么都收不到 —— review 实测踩到)。
- P3-5:两个 `run:` 里的 `set -o pipefail` 保留但加注释说明「当前无管道,为将来加管道先有失败传播」;
`cargo clean -p unlucky-app || true` 加内联注释(N7 兜底,CI 里没有对应行,别误抄进 ci.yml)。
验证:`--selftest` 三组仍全部 fail(判红能力未被改坏);真实截图仍 pass(1180x760 / 342 / 19.45 / 0.00761);
0 字节与非 PNG 路径 exit 1;`yaml.safe_load` 通过(6 job,Linux 槽位无 continue-on-error,artifact 步
`if: always()`);`cargo fmt --all --check` 退出 0;cygpath 修复后重跑 Docker 冒烟,宿主机 D:/tmp/ull-m62-rev2
拿到 PNG,md5 `156c382a…` 与前两次 run 及 review 的 artifact 完全一致。
2026-11-01 16:15:47 +08:00
AskaEth
ea5f66cf92
feat(ci): M62 —— Linux runtime-smoke(xvfb 真渲染一帧)+ 像素非空白门禁
...
「编得过」不等于「跑得起来」:CI 里 Linux/macOS 此前只 build + test,从未创建过真实图形窗口。
本 commit 补第一份 Linux 真实图形环境运行证据,并把它做成不可伪造的门禁。
- ci.yml 新增 runtime-smoke(ubuntu-latest):装 xvfb + 软渲染/字体依赖,用 xvfb-run 起 GUI 进程,
走既有 `--software-renderer --screenshot` 通道渲出一帧 PNG,再断言 ①退出码 0 ②PNG 宽高 > 0
③非空白(唯一色数 / 亮度σ / 强边缘占比三项过阈值),并把截图作为 artifact `runtime-smoke-screenshot` 上传。
既有 fmt/clippy/test/build 四个 job 与触发器语义零改动(纯追加)。
- ci.yml 新增 runtime-smoke-macos 探针:只在 workflow_dispatch 触发且 continue-on-error。官方 macOS
runner 处于 LaunchDaemon 上下文、无 WindowServer 会话,大概率建不了窗口;本机无 macOS 且 worker 禁
push,这条路径尚无实测证据,故不拿它卡 PR 门禁(首跑结论决定升常驻或按 D26 归入 iced 重写)。
- packaging/check_screenshot.py:零第三方依赖的 PNG 解码(zlib + 反过滤 0~4)与三项指标判定,
`--selftest` 打印阈值依据(人为纯色帧 1/0.0/0.0 必红,真实帧 342/19.45/0.00761 全过线)。
- packaging/linux-smoke-docker.sh:在 Docker 里复刻同一 job(TUNA apt/rustup 源),便于无 Linux 桌面的本地复跑。
- packaging/README.md §11:覆盖平台、阈值依据数值、本地复跑、已知跳过项、迁 iced 后需重新验证。
本机实测(Debian 12 容器 + xvfb + 软渲染):退出码 0,PNG 1180x760,唯一色 342 / σ 19.45 / 强边缘 0.00761。
CI(Ubuntu 24.04 runner)首跑仍未验证——本 job 证明「能出图」,不证明视觉正确。
2026-11-01 16:15:47 +08:00
AskaEth
a681ccb526
Merge branch 'feat/p4-design-system-tokens-typography-icons'
2026-11-01 16:06:40 +08:00
AskaEth
83e19e58f0
fix(design): M61 r1 收口 —— 官方 emphasized/弹簧/高程 + 冻结测试
...
reviewer r1 判 p2-2items(fix-then-merge),本轮按条目收口:
P2-1 冻结测试补全(变异实测证明原来可静默漂移):
- tests/type_scale_pins.rs:baseline 15 档 (size,line_height,weight,tracking,family)
与 Expressive 强调 15 档逐值对拍,期望值独立于实现重写
- tests/token_pins.rs:49 条 tone(light+dark 全表)、7 条 easing 曲线参数、
16 档时长、9 组弹簧、28 个 IconKind 下标、形状阶梯
- 自证:TitleMedium 16→17 与 EmphasizedDecelerate 换曲线两次变异均 FAILED
P2-2 emphasized 口径事实错误(用户点名):
- 取回 A 级官方值(androidx TypeScaleTokens v0_103 的 *Emphasized* 四元组):
label-*/title-medium/title-small 强调是 700(此前给 600)、body-large 0.5→0.15、
title-medium 0.2→0.15、body-medium 0.2→0.25、display-large -0.2→0
- TypeRole::emphasized() 改为官方表(typography/emphasized.rs);
原「只加一档字重」规则改名 emphasized_simplified() 并标注非官方
- 顺带按官方订正 baseline tracking(display-large -0.2、body-medium 0.2、
title-medium 0.2)与 TitleLarge 的 Brand 族归属
- crate 文档新增「baseline MD3 vs M3 Expressive 差异清单」+ 未支持能力 gap
弹簧改为两套 token 结构(用户追加令):
- motion::spring(scheme × category × speed) 二维表 12 格:
standard spatial ζ=0.9 (k 1400/700/300)、expressive spatial ζ=0.6~0.8 (k 800/380/200)、
effects 两套同值 ζ=1.0 (k 3800/1600/800),均取 A 级 MotionTokens 原值
- Spring 改存阻尼比(与 Compose 令牌同口径),9 个具名常量
中文字重覆盖(用户追加令):
- FontFace 带 WeightCoverage(可变区间/静态表),Platform::resolve_weight 给
Native/Nearest/Synthetic 三档结论;雅黑无 500、苹方无 700 两处 gap 显式登记
component_map 高程按官方回调(P2 并入 finding):
- FilledCard L2→L0、OutlinedCard L1→L0、Menu L4→L2、Dialog L5→L3(Menu 圆角同时
按 MenuTokens 改 4dp);新增官方值 pin 测试 + R4「全屏都在飘」守门断言
P3 四条:
- 同族容器撞色:彩度按共享基准等比压缩(Palettes.reference_chroma),
primary/secondary-container 不再同值,且强调色覆盖时表面逐像素不变
- femtovg 引用改函数名(行号已漂);删掉 contrast_aa 的死代码后门
- 骨架数值跟 M63:nav 56→44、titlebar 40→48,control 40/touch 48 换算依据入文档
- 图标折算口径订正为「居中描边,视觉外扩 <1px」
2026-11-01 15:58:19 +08:00
AskaEth
65ca0ed29a
feat(design): 加 unlucky-design 令牌 crate(M3 色彩/排版/形状/动效/图标)
...
D26 把视觉基线锁到 Material 3 Expressive,本 crate 是「UI 整体性」的地基:
令牌唯一真源,后续 iced 页面只消费不新增。零第三方依赖、零 UI 框架依赖
(cargo tree -p unlucky-design --edges normal 只有它自己一行)。
- 色彩:49 个 M3 语义角色 × light/dark,种子色派生 6 条 tone 调色板;
tone 轴严格用 CIELAB L*(偏差 ≤1),色相/彩度用 OKLCh 近似 CAM16 并在
模块文档里写明口径与已知偏差(不宣称是标准 HCT 动态取色)
- 对比度:26 组文字对 × 6 组种子 × 2 模式全部 ≥4.5:1;3 条例外显式登记
(Outline 非文字 3:1、OutlineVariant 装饰、SurfaceVariant 是容器色)
- 排版:M3 type scale 15 档 + 三平台字体族回退栈(只写族名,不内嵌受限字体)
- 形状/高程/动效:Expressive 形状刻度 + shape morph 数据、elevation 级别与
surface container 映射、时长/缓动/弹簧(纯函数可采样)
- 图标:24 个从 Slint 版迁移的 SVG path + 4 个新增,IconKind 冻结只可末尾追加;
写明 Slint 的 stroke-width 是绝对像素、SVG 是 viewBox 用户单位的语义差异,
换算封装成 Icon::stroke_width
- 组件档位表:20 个组件的形状/高程/字体/动效一次取全
TODO(M63):elevation shadow 的 blur/offset/alpha 与四组弹簧参数是占位值,
接口先定、数值待玻璃视觉规格表(tower 已通告边界)。
2026-11-01 15:34:04 +08:00
AskaEth
fef0922073
Merge branch 'feat/p3-release-linux-arm64-slot-linuxarm'
2026-11-01 14:21:12 +08:00
AskaEth
4c1a6c54e5
fix(release): M51 r1 —— linux-arm64 改用 ubuntu-22.04-arm(与 x64 同 glibc 2.35 地板)
...
P2-1:24.04-arm 会给出 2.39 地板,把 Debian 12 / Raspberry Pi OS Bookworm
(2.36)挡在门外,且「arm64 只有 24.04 档」这句前提是错的(同段注释自己就承认
ubuntu-22.04-arm 有效)。改 os 为 ubuntu-22.04-arm,注释改成「与 x64 同地板
(2.35),覆盖 Debian 12 / Ubuntu 22.04 arm64 / RPi OS Bookworm」;
README §3/§4/§7.5/§9 与 release-notes 的 Downloads/Support matrix 全量订正
2.39 → 2.35+,删掉「arm64 地板高于 x64」的对比措辞。槽位名、产物名、六槽位
齐全性检查与打包脚本均未变。
P2-2:rustup target add 步骤改 env TARGET + `rustup target add "$TARGET"`,
并补 shell: bash(此前是全文件唯一没显式 shell 的 run,在 Windows 槽位会落进
pwsh);Linux 图形栈依赖那步也补了 shell: bash,现在所有 run 都显式 bash、
run 块内零 `${{ }}` 直插。
顺带 nit:host_slot() 报错改回原始 arch token(`riscv64` 不再显示成 None),
README §7.5 宣传的那个用例现在诊断可读。
验证:yaml 结构断言(六槽位 / linux-arm64=ubuntu-22.04-arm / 齐全性循环 6 /
run 块 ${{ }} 零泄漏 / run 步骤全带 shell)、全部 run 块 bash -n、grep 2.39
零残留、host_slot 7 组合注入 + freebsd/riscv64 报错含原名、image_arch 真 exe、
windows-arm64 stub 全流程、架构守卫拦截、linux-arm64 归档两次 sha256 一致
(f949d0f5… 与上一轮相同)、windows-x64 原生回归(冒烟 + sha256sum -c)。
2026-11-01 14:16:29 +08:00
AskaEth
15caa1b61f
feat(release): M51 —— 发布矩阵补 Linux arm64 与 Windows arm64 两槽位(六槽位)
...
- release.yml:加 linux-arm64(原生 ubuntu-24.04-arm runner)与 windows-arm64
(windows-latest 上交叉编译到 aarch64-pc-windows-msvc);产物根改由
matrix.build_dir 参数化,dumpbin 自检与打包都吃矩阵路径而非硬编码 target/release;
交叉槽位多两步(rustup target add + ARM64 库体检),release job 的槽位齐全性
检查从四扩到六。
- make_artifacts.py:--slot / --build-dir 参数化 + 六槽位映射;打包前回读可执行文件头
(PE 机器码 / ELF e_machine / Mach-O cputype)校验架构,跨槽位构建自动跳过
ull --version 冒烟并打印原因(不在 x64 上假装跑得动 arm64 二进制)。
- packaging/README.md + release-notes.md:两张支持矩阵同步加 Windows 11 ARM64 与
Linux arm64 行;arm64 glibc 地板如实写 2.39(高于 x64 的 2.35),并说明与 Termux
实验级的区别(这两个是正式发布槽位)。
本地验证:宿主判定注入(七个组合 + 非法宿主 exit 1)、文件头架构识别(真 x64 exe +
合成 PE/ELF/Mach-O 头)、windows-arm64 stub 全流程、架构守卫真拦、linux-arm64 归档
两次 sha256 一致、windows-x64 原生回归(冒烟 + sha256sum -c)。arm64 真机构建由 CI 补。
2026-11-01 13:48:37 +08:00
AskaEth
0bfe22aa6f
Merge branch 'feat/p3-release-windows-dumpbin-locator-fix-d'
Release / build (macos-x64) (push) Canceled after 0s
Release / build (macos-arm64) (push) Canceled after 0s
Release / build (linux-x64) (push) Canceled after 0s
Release / build (windows-x64) (push) Canceled after 0s
Release / GitHub Release (push) Canceled after 0s
2026-11-01 13:06:12 +08:00
AskaEth
a6013c7d22
fix(ci/release): M50 —— Windows 槽位 dumpbin 定位链改三级(vswhere → find → PATH)
...
v0.1.0 首跑(run 41273841717)windows-x64 在「Windows 单文件自检」挂
`找不到 dumpbin,无法校验导入表(静态 CRT 门禁不降级)`。两个独立根因:
- `command -v dumpbin` 在 windows-latest 上不成立:VS 环境未预置进 PATH。
- 兜底 glob 深度错了一级:写的是 `<VS 根>/<年>/VC/Tools/...`,真实布局是
`<VS 根>/<年>/<版次>/VC/Tools/...`(2022/Enterprise、2022/BuildTools),
所以即便 PATH 失守、glob 也不可能命中。
- 且只扫了 64 位 `Program Files` 根,Build Tools 落在 `Program Files (x86)` 根。
改成三级定位链,全不命中才 fail(门禁语义一字未动:工具缺失仍 exit 1、
导入表出现 VCRUNTIME/MSVCP/MSVCR/ucrtbase 仍 exit 1,文案不变):
1. vswhere —— VS 官方定位器,安装器自身路径固定,与 VS 版本/版次/布局无关:
`.../Installer/vswhere.exe -latest -products '*' -find <pattern>`。
模式先试反斜杠(官方文档形式)、再试正斜杠,输出统一折成正斜杠后再执行。
2. find 兜底 —— 两个 VS 根(64 位 + x86)按路径模式扫,`-maxdepth 11`
留一级余量,不再依赖固定层数的 glob。
3. PATH —— 最后才试,PATH 最不可控。
另加一行 `dumpbin: <路径>(定位自 <层级>)`,下次真机挂掉能直接看出命中哪一级。
同文件其余步骤不依赖 VS 环境(已逐条核对),无需收敛。
验证(本机无 VS):
- 抽出 run 块 `bash -n` 通过;13 条 stub 用例全绿:无工具→fail 文案不变且不写
dependents.txt、vswhere 反斜杠/正斜杠两个模式、两处 VS 根 find 兜底、PATH 兜底、
动态 CRT 仍 fail-closed、vswhere 优先于 PATH、vswhere 输出的反斜杠路径归一化后可执行。
- 未改写的原始 run 块在本机实跑:干净 exit 1 + 原文案,不误命中、不崩。
- yaml 解析 + 23 条结构断言全过(矩阵四槽位、触发条件、门禁 grep/文案/exit 原样);
文件 276 行 ≤ 400。
- git-bash 行为实证:`.exe` 的 `-x` 为真、非可执行文件为假、`find` 接受含空格的
`C:/Program Files` 根。
真机验证由 merge 后 retag 重跑流水线完成。
2026-11-01 12:58:40 +08:00
AskaEth
37ccaaf23f
Merge branch 'feat/p3-fix-game-java-discover-cfg-gating-jav'
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Release / build (macos-x64) (push) Canceled after 0s
Release / build (macos-arm64) (push) Canceled after 0s
Release / build (linux-x64) (push) Canceled after 0s
Release / build (windows-x64) (push) Canceled after 0s
Release / GitHub Release (push) Canceled after 0s
2026-11-01 11:00:32 +08:00
AskaEth
dc4fac8391
fix(core/game): M49 —— Java 去重键平台无关归一化(Linux CI 三红)
...
`game::java::discover::path_key` 只在 `cfg!(windows)` 分支折分隔符,POSIX 上
把 `C:\a\b\java.exe` 原样留成带反斜杠的键,与 `C:/a/b/java.exe` 不同键 →
`dedupe` 不去重。三条用例(Windows 语义无 cfg 门)因此在
`test (ubuntu-latest)` 挂:`path_key_normalises_separators`、
`dedupe_keeps_the_higher_priority_source`、
`java_discover::dedupe_collapses_one_install_found_twice`。
- `path_key`:`\?\` 前缀剥离与分隔符折成 `/` 改为**与平台无关**(同一个
`bin/java[.exe]` 用哪种拼法都是同一个文件,键必须相同);大小写折叠仍只有
Windows 做——POSIX 上 `Java` 与 `java` 是两个目录,折了会把两份真实安装误并。
已知取舍写进文档:POSIX 上文件名里的字面反斜杠会一并被当分隔符。
- 三条用例:不靠 `#[cfg(windows)]` 跳过。`path_key` 单测改成平台无关断言 +
大小写折叠的双侧断言(Windows 折 / POSIX 不折,两侧都钉住);集成用例的
「同一路径第二种写法」按平台给(Windows 用大小写+分隔符,POSIX 用分隔符)。
- 回归钉子:新增 `one_install_normalises_to_one_key_whatever_the_spelling`
(普通写法 / 反斜杠写法 / `\?\` 前缀写法 / 混合写法 四种拼法同键)与
`path_key_folds_case_only_where_the_filesystem_does`。
验证:Linux(rust:1-bookworm 容器,ull-linux-target 热缓存)
`cargo test -p unlucky-core --lib --test game` → 224 + 120 passed / 0 failed
(基线是 lib 220/2 failed、game 119/1 failed);
`cargo test --workspace --all-targets --no-fail-fast` → 34 个测试块
1137 passed / 0 failed(该次跑在 M48 launch 修复叠加的临时副本上,因为 M48
尚未合入 main,见报告)。Windows 本机 `cargo fmt --check`、
`cargo clippy -p unlucky-core --all-targets -D warnings`、
`cargo test -p unlucky-core` 全绿(17 个测试块 0 failed)。
2026-11-01 11:00:25 +08:00
AskaEth
38832df095
Merge branch 'feat/p3-fix-posix-tree-kill-ci-red-posixkill'
2026-11-01 10:48:15 +08:00
AskaEth
6ce825db8d
fix(launch): M48 —— POSIX 组杀假成功导致 stop() 空等超时
...
CI test (ubuntu-latest) 自 M43 起红的根因不是「组杀打到自身进程组」,
而是 procps-ng 的 kill 把裸 `-<pgid>` 当成又一个信号选项吞掉:退出码 0、
什么都没发。旧实现据此提前 return,单 pid 兜底永远不执行,于是
process_smoke::stop_kills_process_tree 与
pipeline_cases::pipeline_stop_records_killed_exit 只能干等
graceful+5s 预算后报「强制结束后进程仍在运行」。
- process.rs:Unix 分支用 CommandExt::process_group(0)(std 1.64+ 安全
API,无需 pre_exec/unsafe)让子进程自成一个进程组,与 Windows 的
CREATE_NEW_PROCESS_GROUP 对称;这也让「树」回收真正覆盖后代。
- tree_kill.rs:组杀改用 `kill -TERM -- -<pgid>`(`--` 不可省),并且
先过 group_kill_target 三重验证——目标必须是自己的进程组组长、必须
读得到我们自己的组、目标组不能就是我们所在的组;单 pid 兜底改为
「永远执行」,kill 缺失(NotFound)返回明确错误而不是静默失败。
pid==0 守卫从 kill_tree 提到 terminate/force_kill 共用入口。
- 测试钉子(Linux 门住):spawn 后子进程 pgid == 自己的 pid 且 !=
测试进程 pgid;组杀只接受验证过的独立组;组杀覆盖孙进程且不碰我们
自己的组;同组子进程靠单 pid 兜底仍能被干掉;pid 0 一律拒绝。
验证:rust:1-bookworm 容器(ull-linux-target 热缓存)cargo fmt --check、
cargo clippy --workspace --all-targets -D warnings、cargo test -p
unlucky-core --test launch(82 passed,两条原红灯转绿)、cargo test
--workspace --all-targets --no-fail-fast 不再被 SIGTERM 打断;Windows
本机 fmt/clippy/cargo test -p unlucky-core 全绿。
2026-11-01 10:32:18 +08:00
AskaEth
eaa1414847
Merge branch 'feat/p3-packaging-chore-six-p3-findings-packc'
2026-11-01 09:40:59 +08:00