Commit Graph
680 Commits
Author SHA1 Message Date
AskaEth 43f77d8865 fix(core): 收窄日志脱敏的三类误杀(品牌串 / CDN 主机名 / 长路径)
M239 实测两条形状规则会误杀三类正常文本,被抹掉的正是用户要复制的面
(`--dry-run` 的 argv、`cmd_built.argv`、诊断规则引擎的输入)。修法不是抬阈值
(40 / 3×8 / 64 一个字没动),而是给每条规则加**必要条件**:

- `-D<key>=<品牌>` 被当三段式 token:`=` 是赋值边界、退出 dotted 字符集
  (JWT 的 `=` 只可能是末尾填充),键里的点号不再把值拖进段数统计。
- CDN/镜像 URL 变成 `https:<redacted:token>`:加"必须含大写"与"首段不是 DNS
  主机名"两条必要条件(JWT 头是 JSON,base64url(`{"`) = `eyJ` 必含大写;
  base64url 不含 `/`)。扫描器先切主机名,路径那半继续判——JWT 放进 URL
  路径里照样抹。
- ≥64 无点号长串吞掉整条路径:路径形状(被 `/` 切开且每段是词形名字)整条
  保留,但**段本身**≥64 的照抹(`/…/objects/<64 位 hex>` 只抹那一段)。

真凭据的抹除能力不变:登记过明文值的替换路径没碰;正例(本仓 JWT / MSA JWT /
`-DaccessToken=` 粘着 / URL 路径里的 JWT / 标准 base64 三段式 / sha256 hex /
base64 ≥64 / `A`×64)全部仍被抹。回归语料(M239 的 68 文件 / 243 行)修前被改
1 行 → 修后 0 行。判据与扫描器拆成 redact.rs + redact/{shape,tests}.rs。
2026-11-11 10:15:48 +08:00
AskaEth 775bb75b92 Merge branch 'feat/feed-rule-engine-raw-text-while-keeping'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 09:41:54 +08:00
AskaEth edd6402fd5 fix(core): 规则结论改在脱敏前算,会话路上 fabric.loader_0_12 能命中(M241)
会话 JSONL 是写前脱敏的,而脱敏的「≥64 无点号不透明串」是有损的:
fabric.loader_0_12 唯一的证据行(Sponge mixin 类路径 66 字符)被抹成
<redacted:opaque>,规则在会话路上永不命中。

修法不动脱敏策略、也不往盘上多写一份原文:
1. SessionWriter 在内存里留一个 2000 行的原始尾窗(不落盘);
2. 收尾时把这些**原始**事实交给新的 diagnose::session::conclude 跑规则,
   产出契约形状的 diagnosis payload —— 它落盘前仍过 Redactor::scrub_json;
3. 日志页优先读这条落盘结论,老会话才回落到「在脱敏文本上重跑规则」。

为什么不是「原文与脱敏文本并存」:会话 JSONL 里存原文会踩
core/tests/launch/pipeline_cases.rs 早已钉死的「事件流不得含凭据明文」。

顺带把 EventKind 纯搬移到 launch/session/event_kind.rs 守 400 行上限。
2026-11-11 09:19:42 +08:00
AskaEth 74be510114 Merge branch 'feat/fix-narrow-window-chips-squeeze-and-menu'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 08:41:04 +08:00
AskaEth df0944894f fix(app-iced): 面板预算按内容推导 + 版本菜单不画 L2 阴影(M238)
两条都在「视口不终止内容」这一族里,修法各一半:

① 三栏档面板预算从死的 `PANEL_BUDGET = 300` 改成 `panel_budget(menu_items)`
   (内边距 24 + 首屏 76 + 间距 8 + 钉死区 88 + 菜单高 + 列表↔面板 21)。
   旧常数不含列表↔面板那 21、也不随版本菜单行数变,于是 760/900 档面板实得
   286/294px,滚动视口被挤到 54/62px——六枚 loader chip 只露出 10px(736 与 1180
   同样严重,纵向预算与窗宽无关)。修后 760 档视口 98px(chips 31px 整枚在屏)、
   900 档 106px;列表按整行让出:760 档 4→3 整行、900 档 7→6 整行;只有一条
   加载器版本时菜单只占 64px,列表仍是 4 整行。

② 版本菜单改用 `Menu::flat()`:不是「把阴影裁掉」,而是**不在滚动容器里画阴影**。
   `iced_tiny_skia` 0.14 画 quad 阴影走 `draw_pixmap(.., mask: None)`
   (engine.rs:136-143),绕过裁切掩码;`iced_wgpu` 那边是整层 scissor
   (quad.rs:96-101)⇒ 同一棵树两个后端画出来不一样。修前菜单底下 54px 一条被压暗
   (浅色 249→231,−7.2%,含 8px 缝与实例名框内上半部),修后逐像素等于画布色。
   菜单的 L2 令牌不动,真实浮层照旧带阴影。

判据(两条,抓得住的/抓不住的都写进文档注释):
- 结构:`rows/tail.rs` 断言三栏档 760/900 的滚动视口 ≥ 首屏 76px(对生产常量做算术);
- 像素:`options_shot/compact.rs` 断言 chip 描边上下两行都在、且实例名框上方那条缝
  与框内都是纯画布色。
变异自证:预算退回 300 ⇒ 结构断言红("滚动视口只有 54px < 首屏 76px");阴影无视
`flat()` ⇒ 像素断言红("y=660 x=304 是 [231,231,232] 而不是画布色")。

没验:滚动中/底部三态拍不出来(harness 无 `scroll_to` 派发通道,已另开 finding)。
2026-11-11 08:28:06 +08:00
AskaEth dbc5274965 Merge branch 'feat/wire-disabled-control-base-into-product'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 07:56:14 +08:00
AskaEth ba441351dd fix(app-iced): 复审 r2 三条收口(M237)
- P1-1 判据会让 CI 一亮红:`java_shot.rs` 原来写「没喂图时 `assert!(CI 未设置)`」,
  而 `.github/workflows/ci.yml` 的 test 作业在 ubuntu + windows 上都跑
  `cargo test --workspace --all-targets`,出图步骤只给 `options_shot` 备了 `ULL_DL_*_SHOT`
  (本判据的图不在出图清单里)⇒ `CI=true` 实测 exit 101,一合即红。改成:没喂图**一律响亮跳过**
  (`not_fed()` + CI 时多一句「本仓 CI 不出这张图,跳过是预期」),模块文档写清「要让它守门需要
  跨 scope 加出图步骤 + 注入 `ULL_M237_JAVA_SHOT`,以及那时该怎么改回硬失败」。
  自证:`CI=true` 不喂图 → exit 0(改前 101);无 CI 不喂图 → exit 0;喂图(浅/深)→ 真跑,exit 0。
- P2-1 同类错漏一处:实例列表卡画的是 `MaterialLayer::Card`(`instances.rs:263`),
  但 `list_row` 的 `ListItem` 没传底 ⇒ 行 hover/按下按 `Base::CANVAS` 合成。
  改成 `item.base(frame::BASE)`(同一层,不另立常量),并加进接线守卫。
  像素证据(`--force-state=hover`,1180×760 列表卡矩形 `(112,160)-(448,735)` 内 before/after 差):
  浅色 57923 px `(232,232,233)→(226,226,227)`,深色 57923 px `(32,38,31)→(35,40,35)`
  —— 即 hover 8% 从画布底换到卡底。
- 守卫自身升硬:`base_wiring.rs` 的计数原来按整段文本数,**把接线注释掉仍算数**(r2 实测绿)。
  改成先剔掉整行 `//` 注释再数;变异自证:注释掉 `item.base(frame::BASE)` ⇒ 红
  (`instances.rs: .base(..) 出现 0 次,期望 1 次`,退出码 101),还原后绿。
- 门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-237):fmt 0 / check --all-targets 0 /
  clippy --workspace --all-targets -D warnings 0 / test -p unlucky-app-iced --lib 907 通过 0 失败 /
  test --workspace 3344 通过 0 失败。
2026-11-11 07:56:11 +08:00
AskaEth 3dcd5fd117 feat(app-iced): 页面层把卡片底接进禁用/交互控件(M237)
M234 把 `interaction::Base` 与组件侧的 `.base(..)` 接好了,但页面层仍是默认的画布底:
卡片(`Role::SurfaceContainerLow`)里的禁用按钮 / chip / 开关把 10% / 12% / 38% 叠在画布色上。
M223 记的「卡片里禁用控件 CR 1.05、形丢失」因此只在 gallery 里修好,产品页没修。

- 导出容器底:`pages::frame::BASE = Base::of(MaterialLayer::Card.surface_role())`
  —— 取的是 `card()` **真正画出来的那一层**(不是「反正是 surface」);`logs::panel::BASE`
  是同一个常量的别名(两张卡画的是同一个材质层)。
- 接线(改前一律 `Base::CANVAS`,改后 `frame::BASE`):设置·Java(扫描 / 取消 / 设为默认 chip /
  三个输入框 / 添加 / 保存 / 预置项 chip / 下载徽标);设置·通用(语言三档、主题三档、日志行
  两个手写按钮 → 状态层叠 `BASE.role()`);实例详情(MODS 开关、SAVES 的重试与打开文件夹、
  启动参数区的两个输入框 + 后端 chip + 保存 / 恢复);日志页(诊断卡页脚 `Copy summary`)。
  详情头的启动 / 停止 / 动作行**不在卡里**,保持画布底并加注释。
- 判据:`pages/frame/tests.rs` 逐值断言「`BASE` == 卡片画出来的颜色」+ 源码结构盯 `card()`
  不许改画别的材质层;`pages/settings/tests/java_shot.rs` 是**页面级像素判据**(读 1180×760
  真截图、逐位匹配:卡区里「叠卡底」3136 px vs「叠画布」21 px,深色 3128 / 1);
  `pages/tests/base_wiring.rs` 数接线点,守像素够不到的页面(实例详情 MODS / SAVES)。
- 变异自证:撤掉「立即扫描」的 `.base(BASE)` ⇒ 像素判据红(73 px vs 3083 px,退出码 101);
  撤掉诊断卡页脚的 `.base(..)` ⇒ 接线判据红(0 vs 1,退出码 101)。均已还原。
- 门禁:fmt / check -p unlucky-app-iced --all-targets / test --workspace(3344 通过,0 失败)/
  clippy --workspace --all-targets -D warnings / 文件 ≤400(最大 389),全部本地过。
- 已知未尽:面板 α<1 的半透明卡(`Base` 只装一个 `Role`)、GPU 档、实例详情与日志页在
  1180×760 出图里没有可量测的禁用控件(夹具缺 running,已报 finding);app_bar / adopt pick /
  设置页分组列仍未导出 `BASE`;设置·下载 / 更新 / 助手、账户页、内容库、下载页仍未接线。
2026-11-11 07:56:11 +08:00
AskaEth cf8a951e29 Merge branch 'feat/fix-accounts-aba-false-red-and-probe-wal'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 06:53:54 +08:00
AskaEth 4952ee2706 fix(accounts,probe): 测试缝换稳定身份(ABA)+ 超时墙钟对齐真实预算(M236)
两条 M230 台账判定「真开着」的测试债:

1. accounts 测试缝的 key 是 Arc::as_ptr(&job.cancelled)。job 一 drop,控制块
   立刻可能分给下一个 LoginJob,于是新 job 读到旧 job 的记录(ABA),并行跑的
   「设备码入口不许置位 browser_first」因此偶发假红。改成 LoginJob 自带的
   稳定身份 JobId(cfg(test) 字段,进程内单调递增、永不复用);ENTRIES 从
   Vec<(usize,bool)> 改成带 Weak 的 Entry:record 时 retain 收走已 drop 的 job,
   同一 job 覆写成「最后一次」(消掉 mode_for 取第一条的歧义)。
   反向证据:身份退回堆地址 ⇒ the_test_seam_does_not_reuse_a_heap_address_as_identity
   红(mode_for(&younger) 读到 Some(true))。

2. probe 的 silent_server_times_out 断 `elapsed < 3s`,跟真实预算(400ms)无关。
   改成 预算 + SLACK(400ms + 1s):抓「预算没生效」(那要等假服务器睡满 5s),
   放开毫秒级精度;并新增两条判据——detail 里的时限就是本次预算(证明走的是
   预算分支)、假服务器真的收到 1 次请求(不是没连上就早退)。
   反向证据:摘掉 tokio::time::timeout ⇒ 5.08s / unreachable 红;把计时值硬编码
   3s ⇒ 3.006s > 1.4s 红。
2026-11-11 06:53:52 +08:00
AskaEth 30d4163816 refactor(accounts): 登录两条流拆到 accounts/login.rs(纯搬移,M236 前置)
accounts.rs 正好 400 行零余量,本轮要给测试缝换稳定身份,改前必须先拆。
搬移口径:start_login / start_browser_login / msa_auth / fail / finish /
store_session 的每一行(含签名与 doc)与搬移前逐行相同(130 行非空行
diff 为空,只有空行分隔差异);公开路径由 `pub use login::{...}` 保住
(host::accounts::start_login / start_browser_login 不变)。
列表/增删/LoginJob/MsaEntry/测试缝留在父文件:那是测试要用的私有件,
搬走它们就得放宽可见性,纯搬移口径会随之破掉。
2026-11-11 06:53:51 +08:00
AskaEth 32951df3d6 Merge branch 'feat/fix-disabled-control-compositing-base-at'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 06:34:31 +08:00
AskaEth 9bb3c33b8c fix(app-iced): 复审 r1 四条 P2 收口(M234)
- P2-1 判据恒绿:`gallery/probe/bases.rs` 的「反向检查」把字段档计数(`:172` 遮蔽后的
  `hits`)拿去比按钮档 stray ⇒ 恒绿。改成 `button_hits` / `field_hits` 两个计数,各比各的
  stray,失败信息的数值与单位对齐量测对象。变异自证:卡列里留一颗忘了 `.base(..)` 的按钮
  ⇒ 该断言单独红(2137 px vs 3178 px,退出码 101)。
- P2-2 FAB 的底错了:禁用态背景仍是满档 `PrimaryContainer`(`Disabled.layer() == None`),
  38% 内容却合在画布上。改成 `Base::of(PrimaryContainer)` + `on_container = false`
  (那一层 10% 在 FAB 身上不存在),两处调用收进一个 `content_color()`;新增值判据
  `disabled_fab_ink_composites_over_its_own_container`(修前 #8CA0AA / 修后 #548FBC 量级)。
- P2-3 漏改:`selection::paint::state_layer()`(hover/focus/pressed)与 `list_item` 的
  状态层叠底仍写死 `Role::Surface`,改成跟 `base`;同类两处(`assemble::transparent_state_color`
  的 Outlined/Text hover、drawer 项药丸)一并跟上。
- P2-4 文档失真:`feedback/{action,mod}.rs` 删掉「本地 α 合成 / 规则只认两档」的旧描述
  与失效链接;`list_item` 的旧签名;三处 `probe/base.rs` → `probe/bases.rs`;
  新增 `surfaces::elevated::tests::the_card_base_constant_is_the_role_the_card_paints`
  真断言(原来只在文档里声称那条测试守 `BASE`),并把这句描述改准。
- nit:`disabledbase.rs` 字符串里塌成一行的续行空格、probe 汇总行把列左缘横坐标说成
  「像素计数」的措辞。

门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-234):fmt / check --all-targets /
test -p unlucky-app-iced(896 + 6 通过,0 失败)/ clippy --workspace --all-targets -D warnings /
文件 ≤400 全绿;probe 浅色与深色都过。
2026-11-11 06:34:24 +08:00
AskaEth 35dbefb08d feat(app-iced): 禁用态合成底改成容器给的 Base(M234)
`interaction::disabled` 的四个入口一直收一个「底」,但 8 个调用点全传死
`Role::Surface`:卡片(SurfaceContainer)/ 面板(SurfaceContainerLow)里的禁用控件
把 10%(按钮族)/ 12%(面状控件族)叠在画布色上 —— M223「CR 1.05、形丢失」的根因。

- 新增 `interaction::Base`(画布 / 容器底的一等参数,`From<Role>` 兼容历史调用点);
  四个入口改成 `(ui, base, …)`,内部不再出现任何写死的 `Role::Surface`。
- 容器导出自己的 `BASE`:`surfaces::elevated::BASE`(卡)、`inputs::menu::PANEL_BASE`
  (菜单面板,项的 38% 与状态层都改叠它)、`navigation::drawer::BASE`。
- 控件收 `.base(..)`:Button / icon_on / Field / SearchBar / Slider / Checkbox / Radio /
  Switch / Chip / ListItem / Tabs;Fab 与 icon() 是画布默认值。
- `feedback/action.rs` 的本地 α 合成删掉,改走 `disabled_content_over_role` 并给真实容器底
  (Snackbar `InverseSurface` / Dialog `SurfaceContainerHigh`)。
- 判据:`interaction::disabled::tests`(卡片/面板/画布三档逐值)、
  `buttons::tests::base`(Button::base → StyleSpec → style_closure 接线)、
  `gallery` 新增 `disabledbase` 段 + `gallery::probe::bases` 精确像素断言
  (±10 容差区分不了 1/255 的差,所以自己数逐位相等的像素)。
- 变异自证:assemble 的调用点改回画布底后,上面两条都红(见 artifacts/m234/README.md)。

已知未尽:`crates/app-iced/src/pages/**` 不在 scope 内,页面侧「把控件放进卡片/面板」的
调用点尚未接 `.base(..)`(行为与本 mission 之前一致);`pages/…/java/tests/marker.rs`
有一行被迫更新(入口多了一个实参,语义不变 = 画布底)。
2026-11-11 06:34:23 +08:00
AskaEth a9d1cb4c44 Merge branch 'feat/instance-saves-read-only-list-mvp-storem'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 06:00:29 +08:00
AskaEth f217d83680 feat(saves): 实例存档 / 截图的只读列表与打开文件夹(M235 STOREMVP)
用户诉求是 PCL 差距四项里的「存档截图 / 导入」,M230 审计确认三层全缺。塔裁决 U3
把这一版钉成「只读列表 + 打开文件夹」,不做缩略图墙(缩略图要引图片解码依赖,而
AGENTS §2 只允许 palette-extract 引 image 类依赖;截图/导入产物形态也没定型)。

core
- `game::saves`(新):只读列 `<run_dir>/saves`(世界目录)与 `<run_dir>/screenshots`
  (图片文件)。只调 symlink_metadata / read_dir / metadata,不建目录、不写文件、不读
  文件内容(`icon.png` 只判存在)。目录不存在 ⇒ 空列表不是错误;存在但不可用(符号
  链接 / 不是目录 / 读不了)⇒ 具名错误。条目级异常逐条登记原因,不静默丢。
- `platform::opener`(新):`open_dir` 是**目录**打开器的唯一收敛点(explorer / open /
  xdg-open),内部复用 `child::quiet`,cfg 分支只此一处。塔明确要求按 AGENTS §6 放
  platform 而不是业务模块,并要一条可执行判据防退化
  (`the_launcher_literals_live_here_only` 扫 `crates/core/src`:除本文件外不许出现
  `"explorer"` / `"xdg-open"` 字面量)。

presenter
- `dto::saves`:SavesCommand(List / OpenFolder)、SavesListView、SavesFailureView
  (结构 + OS 原文,不拼中文)、`SavesListView` 的计数助手。
- `host::saves`:走 `job::spawn`,UI 只收快照;运行目录仍是
  `isolation::derive_game_dir` 的同一个答案(与启动 / mod 列表同源,**不写盘**)。
  「打开文件夹」打开哪个目录由 host 定:`saves/` → `screenshots/` → 运行目录。
  失败分三类(InstanceUnavailable / DirUnusable / OpenFailed),可重试。
- `state::saves`:快照整体替换、迟到结果丢弃、失败保留绑定(重试才有意义)。
- 接线:CoreEvent::Saves / AppCommand::Saves / HostTask::Saves。

app-iced
- `pages::instances::saves`:详情页 SAVES 卡片(MODS 之后、启动参数之前;**不套第二层
  滚动条**,整列那一条是唯一入口)。世界行带 UTC 时间与「有图标」,截图行带大小与时间;
  第一行固定画「这一版只读」(U3 边界让用户看得见)。读盘失败画一条红字 + 「重试」;
  打开文件夹失败**不画**重试(那个动作的重试就是再点一次按钮)。
- 接线:Message::{ReloadSaves, OpenSavesFolder}、`sync_saves`(与 `sync_mods` 同一判据)、
  Model::saves、`saves_shot` 出图夹具(ULL_SAVES_FIXTURE=1)。

i18n
- `InstancesSaves*` 20 条中英成对,catalog 与金表在**同一条锚点**后追加、逐字节一致。

证据
- core 只读判据:`game::saves::tests` 在真盘建世界/截图,调用前后各拍一次
  (相对路径, 是否目录, 字节数, mtime)指纹并逐条比对;另有用例钉「目录缺失不建目录」。
- presenter:`host::saves::tests` 真盘端到端(custom 档位派生运行目录 → 快照)。
- GUI:`--software-renderer` 1180×760 浅/深各一张 + 1180×1280 两张「整块卡片可见」
  (1180×760 下 SAVES 卡片位于折叠线附近,只能看到卡头;第二对用 ULL_WINDOW_SIZE
  把窗口拉高,口径与 M98 的矩阵一致),落 `DevDocs/artifacts/M235/`。

没做的(U3 边界内,见 handoff):缩略图墙、导入、删除/重命名、`level.dat` 的
LevelName 解析(世界名暂时就是目录名)。
2026-11-11 06:00:27 +08:00
AskaEth 3789cb7e61 Merge branch 'feat/fix-launch-dry-run-disk-side-effect-dryr'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 05:27:43 +08:00
AskaEth c917bfa18f fix(core,cli): M231 复审三条 P2 —— 恢复误删用例 / 收窄 --dry-run 文案 / 判定链去重
P2-1:`natives_cases.rs` 里 `missing_native_jar_becomes_missing_component` 是上一轮
批量截断时**误伤**掉的(我把「tree_snapshot 之后的所有内容」整段砍掉,连带这条与
dry-run 无关的用例),不是有意删。现已从 main 原样贴回,`MissingNativeJar` /
natives 组件缺失这条用户可见失败路径恢复覆盖。

P2-2:help 的「也不写盘」是过度承诺 —— `commands/launch.rs` 的 `launcher_log::set_dir`
/`record_start`/`record` 都在 `if args.dry_run` 之前,dry-run 仍会 append
`<数据根>/logs/launcher.log`(连父目录 create_dir_all)。文案收窄成「不碰版本树
(不解压 natives、不建 natives 目录);启动器自身的运行日志照记」。core 的 `DryRun`
文档同步加了「本层承诺 / 调用方日志另算」的边界。

P2-3:`extract` 与 `preview` 的「无 jar / 戳记命中 / 重解」判定链合成 `inspect()` 一份,
返回 `DirStatus`(NoJars / Ready(files) / Stale)+ 源 jar 指纹;两个入口只在这里分叉。

另:`tree_snapshot_notices_a_transient_directory` 在全量并行跑时偶发红(本轮门禁抓到 1 次,
单跑 3 次全绿)——「建了立刻删」对父目录 mtime 的影响会被 NTFS 合并。换成确定性自检
`tree_snapshot_sees_new_nodes_and_mtime_changes`(新节点 + 同内容重写的 mtime 变化),
并把这条能力边界写进 `tree_snapshot` 的「抓不住」。
2026-11-11 05:18:56 +08:00
AskaEth 1fa30c4885 fix(core): launch --dry-run 不再落盘(natives 只预览,零建目录/零写文件)
`ull launch --dry-run` 在「环境准备」段走的是真解压:即使只打印命令行,也会在
`versions/<id>/natives/` 下建目录、解 dll、写戳记,把别人的版本树改脏(父目录
mtime 也变)。失败路径(缺 jar 报 missing_components)照样留下这些副作用。

- natives 新增 `preview()`:与 `extract()` 共用条目筛选与戳记口径,但零落盘
  (无 create_dir_all / clear_natives / write_stamp),只清点「将写下几个原生库」;
  jar 打不开照样报错,预览不藏坏包。
- `NativesReport` 增 `planned` / `dir_exists`:预览报告自报家门,人读行与 JSON
  说清「未落盘」「目录尚不存在」。
- `Launcher::dry_run` 改走 `preview_environment`,`spawn` 仍走 `prepare_environment`。
- 报告类型拆到 `natives/report.rs`(mod.rs 曾破 400 行红线)。
- 回归测试(natives_dry_run_cases.rs):有 jar / 无 jar / 空 jar / 已解压过四条
  零落盘判据,用「节点类型 + 纳秒 mtime」快照逐字比较版本树,并带一条判据自检
  (瞬时建删目录必须被快照看见,防假绿)。
- help:`--dry-run` 的承诺补成「不写盘、不解压 natives、不建目录」。

证据(外部版本树 versions/1.16.5 + 真 natives jar):
修复前 dry-run 后 versions/1.16.5 mtime 1794342830.6861426 → 1794342832.4544542,
新增 natives/ + lwjgl.dll + OpenAL.dll + .ull-natives.json;修复后同一场景快照逐字
不变(mtime 1794343730.6618167),JSON natives.planned=true / dir_exists=false。
2026-11-11 05:18:54 +08:00
AskaEth 774a547aa0 Merge branch 'feat/clean-stale-comments-and-cli-loader-text'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 04:52:10 +08:00
AskaEth 38d1f3c20f docs: DOCFIX-2 收口三处旧口径注释(M232)
- cli/cli/model.rs:147 `InstallArgs::loader` 文档补 `liteloader`(第六个可装取值),
  并点明 `optifine` 是「认得出但装不了」→ `loader_unsupported`;
- commands/instances/tests.rs:103 删掉「只在认得出但装不了的那两家会红」的旧口径,
  改成真实理由(第三方前缀形状、没有 `<mc>-<kind>-<ver>` 段);:231 同句副本改成
  「optifine 装不了 / liteloader 自 M222 起能装」;
- addon/loader/liteloader.rs:44 `DEFAULT_REPO` 文档由「BMCLAPI 有 bmclapi-liteloader
  改写规则」改为「直连官方」:该规则自 M225 起 mirrors.json 里是 enabled=false
  (镜像侧实测 404),并按 M225 finding 的方向补上「库走 CENTRAL_REPO」。

纯文档,零行为变化。
2026-11-11 04:38:53 +08:00
AskaEth 654e12c100 Merge branch 'feat/m229-ink-cat-skin-rollout-app-iced-inksk'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-11 01:49:19 +08:00
AskaEth 36bac74822 docs(app-iced): probe 模块文档的复现命令补 SECTION(M229 复审 r3 P2-1)
r5 报告说「模块文档复现命令已补 `ULL_GALLERY_SECTION`」,但 diff 只碰了 `shot_path()` 与
测试体——模块文档 9-15 行一个字没改,照抄那条命令仍 exit 101(panic 报只支持 buttons/divider)。

这轮补齐:
- 模块文档复现命令改成两组(`ULL_GALLERY_SECTION=buttons` / `=divider`),产物路径用
  `${CARGO_TARGET_DIR:-target}`(本仓一树一 target,旧命令的 `./target/debug` 在 worktree 里根本
  没有 exe),并写明 `all` 不支持及其理由;
- `shot_path()` 的文档改真:它只读 `ULL_GALLERY_SHOT`,「这张图属于哪一段」由测试体开头那条
  assert 管(assert 早已搬走,且现在也认 divider)——原文还写着「此处不满足就失败」;
- 收掉 assert 文案里的九连空格接缝;
- 矩阵带过滤的注释从「`all` 默认会画全段」改成「buttons 段没有后续段,过滤是防御性的」。

自证:按文档两组命令实跑,四条命令全部 exit 0;`grep -n ULL_GALLERY_SECTION probe.rs` 命中
8/15/17/21/23(模块文档)与 144/150/357(实现)。
2026-11-11 01:49:16 +08:00
AskaEth f5a2610e4d docs(app-iced): 值字级注释改真 + probe 只认 buttons/divider + 截图主题钉死回归测试(M229 复审 r2)
1) P1-2 的注释值与量测对齐(四处):`elevated.rs` 的 `PAD`/`Texts::title`/`texts()` 与
   `pages/instances/detail.rs` 还写着「值 14/400」,而实现是 12/400;`PAD` 文档更把 12px 的
   118px 量测挂在「14px 下」。全部改成真值,并把 14px=138px 与 12px=118px 两个量测并列写清。

2) probe 门禁只解决一半:`SECTION=buttons` 绿、`SECTION=all` 仍红(sizes 段的实心块撞「按钮
   40 高」判据),断言文案却写「all 或 buttons」。现在:
   - 只支持 `buttons`(按钮矩阵,实心块判据**只量矩阵区**,后续段不再参与)与 `divider`
     (分隔线两条判据,独立出口);
   - `all` / 其它 / 未给 → 明确失败并给理由(整页比 1180×760 高,分隔线段与按钮矩阵不可能同屏;
     整页图的 chip/卡片会被当按钮量);
   - 模块文档的复现命令补上 `ULL_GALLERY_SECTION`(以前照着跑会 panic);
   - `gallery/mod.rs` 的「五段」→ 七段;`gallery/buttons.rs` 的「8 行 + icon·standard」→ 9 行 + 四式。

3) 截图通道的主题档钉死此前没有回归测试(复审 r2 P2-a)。新增
   `the_screenshot_channel_pins_the_theme_mode_instead_of_reading_the_file`:两份不同的机器配置 +
   同一组截图参数 ⇒ 同一个档位且等于参数;反证非截图通道下两份配置确实分出两档。
   变异自证:把分支改回 `bridge.load().1` → 该测试红(left: Dark, right: Light)。

门禁:fmt --check 0 / check -p unlucky-app-iced --all-targets 0 / test -p unlucky-app-iced 0
(887 passed + 6 integration)/ clippy -p unlucky-app-iced --all-targets -D warnings 0 / 400 行扫描干净。
probe 亲跑:buttons 浅+深两张 sheet OK,divider 一张 OK,`all` 按预期被拒。
2026-11-11 01:49:16 +08:00
AskaEth 8958b7f67d fix(app-iced): IconButton Outlined 真的画环 + ELEVATED CARD 值串单行(M229 复审 P1)
P1-1:`icon()` 把 `StyleSpec.stroke` 写死 `0.0`,`IconVariant::Outlined` 的描边角色只到
`roles()` 为止、没画出来 —— Outlined 与 Plain 逐像素相同。改成「有描边角色就是 1px」,
并把「角色 → 填充/内容/描边」抽成 `icon_style_spec()`(`icon()` 直接吃它),单测因此能断言
**接线**而不只是断言 `icon_stroke()` 这个纯函数(第一版正是这样漏掉调用点的)。

回归门两条:纯函数/接线(`buttons/tests/icon.rs`,变异回 0.0 时红)+ 渲染层
(`gallery/probe/rows.rs`:图标按钮左缘 4px 带里 Outlined 有环墨、Plain 必须 0 px;
变异后重出图再跑会红)。环在软渲染里是 Outline 叠画布的抗锯齿混色(实测 110/124/110,
本体 123/138/123),所以判据是「这一带有墨/没墨」而不是颜色匹配。

P1-2:ELEVATED CARD 内距 24 + 值 16px 让「2026-10-29 19:22 UTC」折行,同排卡底不齐;
旧测试还断言 20/14(不变量 118+2×padding ≤ 160 变成 166 > 160,假绿)。
按塔裁决「收内距/字号、不破卡片宽度契约」:`PAD` 24 → 20(= card_padding)、值字级 → BodySmall 12。
量测:时戳 14px 墨水宽 138px(160 外框内宽 120 放不下)、12px 为 118px(放得下,余 2px)。
测试改成四条绑一起(字级 12 / 内距 20 / 160−2×PAD ≥ 118 / 2 列实际宽度也 ≥ 118),
并反证「14px 确实放不下」防登记被当噪音删掉;变异回 14px 时红。

P2(顺带):
- 截图通道的主题档由 `--screenshot-theme` 钉死(此前读机器 launcher.json,设置页主题行会漂)
- `gallery::probe` 只认 gallery sheet(必须给 `ULL_GALLERY_SECTION`,否则整页图的 chip 会被当按钮量)、
  行表 8 → 9 行(四式图标按钮)、分隔线判据只在渲染了 divider 段时执行、逐行阈值 40 → 30 并加
  「满档色像素必须少于 38% 像素」的反向判据、probe 拆出 `probe/rows.rs` 守 400 行
- FAB extended 的重复分支合成一处,右内距提取 `EXTENDED_PAD_END`;iced 0.14 无 `Length::Min`,
  「最小宽随标签增长」退化为固定宽已登记
- 五处注释谎言改真(theme/tests、general/theme、elevated、launch_args、app/tests/theme)

`buttons/tests.rs` 加用例后 402 行 ⇒ 图标按钮那组拆到 `buttons/tests/icon.rs`。
2026-11-11 01:49:15 +08:00
AskaEth 6ed26626db docs(app-iced): 登记禁用滑块激活轨 38% 为 Pixel 级偏离 #9(M229 复审 r3)
复审核对出 artifacts README 的归因错误:`page-settings-g1-dark.png` 里禁用滑块轨是
`on-surface@38% over surface`(轨带 6030 px),不是面状控件族的 12%;上一版引用的 7 px
12% 小簇在 Image path 字段标签附近,与滑轨无关。

按塔裁决**不改实现**:规格卡只定义了面状控件族(chip 描边 / switch 轨 / 字段底 / 滑轨未激活段)的
12%,而禁用**激活轨**沿用 M3 `SliderTokens.DisabledActiveTrack` 的 38% —— 两者是两个族。
登记写在本文件参考值表下方(`DevDocs/` 被 gitignore,README 里的同一段无法入库,故以代码为准)。
2026-11-11 01:49:15 +08:00
AskaEth 4ce24d6fbd fix(app-iced): 禁用态与遮罩改从品牌板合成(M229 复审 F1)+ 选择类图注回填(F2)
F1(核心):三处 `Scheme::baseline(ui.flavor())` 自己重算了一份方案,绕开 `Ui` 里注入的品牌板,
于是 Ink 板上的禁用控件与对话框遮罩仍按 tone 派生的天蓝中性合成(复审量到滑块轨道 #58646C、
填充框 #222E36、遮罩 #060E13,全是蓝系)。修法:`Ui::scheme()` 公开已注入的那份,
`interaction/disabled.rs`(两族合成 + 内容色)、`feedback/dialog.rs`(scrim)、
`feedback/action.rs`(禁用动作文字)改为从它取。

修完 Ink 下实测:禁用容器(12%)#292F28、按钮容器(10%)#252B24、遮罩 #0A0E0A(绿系中性,
R = B);Silver 档遮罩 #A9A9AA。旧三个蓝系值在 7 张深色图里出现次数均为 0。

回归门:`disabled_colors_follow_the_brand_board_not_the_tone_baseline`(按板合成 + 与 baseline
不等 + 深色档 G ≥ B)、`scrim_is_scrimalpha_precomposited_onto_the_canvas` 增加「不许退回 baseline」。

F2:`selection/{matrix,mod,checkbox,radio,geometry}.rs` 里「20×20 / rx2 / 外圈 20」回填成实际渲染的
「18×18 / rx4 / 外圈 18」(勾形比例不变 ⇒ 10.8dp),并按塔裁决把搜索药丸容器从
`SurfaceContainerHigh` 抬到 **`SurfaceContainerHighest`**(贴规格卡,与 filled 字段同底);
`search.rs` / `probe/search.rs` / gallery `inputs` 图注同步。展开的搜索面板与菜单是浮层,
仍留 High 档(已在文档里写明不是漏改)。

`interaction/disabled.rs` 加测试后 405 行 ⇒ 单测纯搬移到 `interaction/disabled/tests.rs`。
2026-11-11 01:49:14 +08:00
AskaEth f9877a89d8 fix(app-iced): 下载页页头「内容库」选中态反转(M229 复审 F3)
`pages/content.rs::section_switcher` 把 `on_content` 写死成 `true`,而下载页也调它,
于是版本管理页的页头把「内容库」画成选中、「版本管理」画成未选中。

改成由调用方给位置:下载页传 false、内容库页自己传 true;并加一条源码层面的调用点断言
(iced 的 Element 不可内省,判据只有一行,写错必红)。

不是 M229 引入的缺陷(M109 就有),按复审要求单独提交,不与收官换肤混在一起。
2026-11-11 01:49:14 +08:00
AskaEth db541c820e feat(app-iced): Ink Cat / Silver Cat 换肤 —— 双色板默认 + 规格卡组件几何(M229)
产品出厂外观从 tone 派生的 `Scheme::baseline` 切到品牌色板:深色 = Ink(淡绿)、
浅色 = Silver(钢蓝),D38 取色链(背景图 / 壁纸)仍然优先,取色失败回落到色板。

主题三档接进设置页「通用 → 主题」:Ink / Silver / 跟随系统(落盘仍是
`appearance.theme_mode` 的 dark / light / auto,不新增配置键)。

组件几何按规格卡对齐(M228 的 `inkcat` 登记项逐条落地):
- 卡片圆角 12 → 16;实例元信息卡改用新的 ELEVATED CARD(无描边、SurfaceContainer
  底、内距 24、上标注 11/600)
- Snackbar 48/4 → 52/16(双行 72)
- Checkbox 20/rx2 → 18/rx4;Radio 20 → 18 Ø(内点仍 10)
- 按钮加 XS~XL 五档(产品仍只用 S=40),内距改走 ControlSize::pad_x
- 新增 FAB 四态(40/56/80/160×56)与图标按钮第四式 outlined
- 语言与主题两行 chips 统一成规格卡 chip(32 高 / rx8)

`theme.rs` 原本正好 400 行,先把单测纯搬移到 `theme/tests.rs` 再加色板字段;
`app/tests.rs`、`settings/general.rs` 同样按纯搬移拆分守 400 行硬线。

验证:cargo fmt --all --check、cargo check -p unlucky-app-iced --all-targets、
cargo test --workspace(除 clippy 前的一轮 flake,见报告)、cargo clippy --workspace
--all-targets -- -D warnings、全仓 400 行扫描。视觉矩阵 42 张在
DevDocs/artifacts/m229/(gitignored,不进提交)。
2026-11-11 01:49:13 +08:00
AskaEth a06b7ff4ec Merge branch 'feat/m227-narrow-window-layout-budget-narrowf'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-10 23:07:23 +08:00
AskaEth 4f5a76f238 docs(download): r2 复审返工——改名后的旧注释三处、视口高度口径如实分档、fade 单测改成对生产值断言(M227)
P2-1(三处改名后旧注释 + 一处口径,纯文档/注释,无像素变化,未重出图):
- `install.rs` 段标题「两个 Shared 分支」→「两支「面板先保预算」的分支(Shared / Narrow)」,
  两条子项分别标注 `(Shared)` / `(Narrow)`(改名后只剩一个 Shared,另一支已是 Narrow)。
- `options/view.rs` 的 `Shared` 条目删掉「(chips 档还可能有错误行)」——chips 档被 arm 1 接走
  给 `Narrow`,`Shared` 走不到错误行;`Narrow` 条目本来就写对。
- `tests/options_shot/chips.rs` 断言③ 的注释:实测这条断言在窗口 518–610 里命中的是
  y=550、宽 94px 的**实心「安装」药丸**,版本菜单容器上沿在 636–639(窗口外)——删掉
  「菜单容器的满宽底边」这个错误替代项,并把实测值写进注释。
- 滚动区高度口径:八审原文恢复为「1180×760 ≈96–104px」(署名真实性),另起一句写 M227 在
  tip 上的复量:1180×760 = 101px、1180×900 = 109px、窄档 = 120px,并注明 r2 量的 116px
  与 109px 只差「钉死区从哪一行算起」。`DevDocs/artifacts/m227/README.md` 同句一并改。

P2-2(fade 单测空转,r2 实测变异 STEPS=6 仍绿):
- 步数与 α 曲线提成 `pub(super) const VIEWPORT_FADE_STEPS` + `pub(super) fn step_alpha(i)`,
  `viewport_fade` 用它们;单测改成对**生产值**断言四条:步数 = 高度像素数(1px/条)、
  α 严格递增且除末条外 <1、相邻增量非递减(曲线要凸)、末条 = 1.0。

变异验证(自己跑,红过再还原):
- `VIEWPORT_FADE_STEPS` 12 → 6:`panicked … 步数必须等于高度像素数:12px 要 1px 一条,
  现在 6 条 ⇒ 2px/条`,MUTATION_EXIT=101;还原 12 后 GREEN_EXIT=0。
- `t.powf(1.6)` → `t.powf(1.0)`:`panicked … 第 2 条比前一条浓得更少…曲线要凸(t^1.6)`,
  MUTATION2_EXIT=101;还原后绿。

门禁(真实退出码,CARGO_TARGET_DIR=C:/Temp/ull-target/wt-227,-j 4):
fmt 0 / check -p unlucky-app-iced --all-targets 0 / test --workspace 0(67 个 ok,
app-iced lib 875 passed,四条像素守门 + fade 单测全绿)/ clippy --workspace --all-targets
-D warnings 0 / .rs 扫描 >400 行 0 个、<2 字节 0 个。
2026-11-10 23:07:20 +08:00
AskaEth 122accdd02 fix(download): 窄档滚动视口底部加 12px 淡出遮罩,去掉被硬裁的「空壳蓝条」(M227 返工)
D30 视觉审 hold 的必改项:滚动视口在 y=660 硬裁,把版本菜单的容器上沿切成 639–659 = 21px、
**零字形墨迹**的一条色块(深色下 (22,33,41) vs 画布 (9,21,28),CR 1.99),读起来像渲染错
而不是「下面还有内容可滚」。

改法(reviewer 推荐方案①,保留发现性):
- 新增 `options/fade.rs::viewport_fade` + `VIEWPORT_FADE_PX = 12`:12 条 1px、α 单调上升的
  **画布色**实色块拼成的斜坡(iced 0.14 没有渐变原语;1px 步进的差别在 1/255 量级);
- `options::section` 的窄档分支把滚动区包一层 `stack![滚动区, 底部对齐的遮罩]`,遮罩贴着
  视口内沿(≤660),压不到钉死区的实例名(≥668);
- 颜色取 `ui.layer(MaterialLayer::Canvas)`:浅色淡出到纸面、**深色淡出到墨底**(不是白雾),
  与它下方那块背景同色,所以两档都不新增颜色;
- 三栏档不铺这条遮罩:那里滚动区内容恒 76px < 视口 ≈118px,不会出现半截内容,铺了只是
  「画布盖画布」,反而会动 M222 已验的 1180 像素;
- `options/view.rs` 已 422 行、撞 400 行硬线,遮罩拆成 `options/fade.rs`(含一条几何自检单测)。

实测(720×760):
- 浅色 x=600 竖列 648 (211,229,245) → 659 (246,250,255) 单调升到画布色;深色 648 (55,72,85)
  → 659 (9,21,28),无白雾;中文与浅色逐值同串;
- 可见带 21px → 墨迹口径 18px + 底 12px 连续渐变(硬裁边消失,右侧滚动条仍在);
- 逐像素零回退:`77d675b`(遮罩前)vs 本轮 720 浅/深/中 + 700 守门,差异 bbox 只有
  (112,648)–(696,660) 这 12 行,其余像素一个都没动;1180×760 浅/深/中 与 1180×900 对
  M222 基线仍 **IDENTICAL**。

门禁:fmt ✓ / check ✓ / test --workspace(喂四张守门图)exit 0(app-iced lib 860 passed、
四条像素守门 + 400 行门全绿)/ clippy --workspace --all-targets -D warnings exit 0 /
.rs 全仓扫描 >400 行 0 个、<2 字节 0 个。

已知残留(本轮不动,按债务台账口径登记):menu 的 L2 阴影不随滚动视口裁切(压在实例名输入框上
约 6.9%,三栏档 fabric 同样存在)——根因在 `components::inputs::menu`,不在 M227 scope,
待专门 mission;finding `.tower/comms/findings/20261110-worker-m227-bug-l2.md`。
2026-11-10 23:07:20 +08:00
AskaEth a70d5f8010 fix(download): 窄档面板加专属预算,首屏露出 head+六枚 chip+说明、实例名回到 56px(M227)
720×760(chips 档)原先走 `TailShare::Shared`:面板与列表各吃 Fill 的一半,面板钉死区
(说明 + 菜单 + 实例名 ≈180px)把它吃穿 ⇒ 滚动区视口 0,head(版本 id + 安装按钮)、
六枚加载器 chip、说明一个像素都不在屏,实例名被挤成 35px 且降部被自身边框裁掉
(M222 D30 五审 `artifacts/m222/visual-verdict-r5-hold.md`)。

改法(把 M222 在 1180 档验证过的「面板先保预算、列表吃整行」落到窄档):

- `TailShare` 拆出第三支 `Narrow`(chips 档 + 面板占高度),`of()` 的三条 arm 与
  `Shared` 的机制注释按 r8 finding 重写(旧文档只写 M125 那一条条件);
- 新增 `CHIPS_STACK` 121 / `CHIPS_LIST_CHROME` 119 / `CHIPS_PANEL_BUDGET` 192 与
  `narrow_rows()`:720/760 下面板先拿预算,列表 = **132px = 3 整行**(M125 像素守门的下限);
- `options::section` 三分支:窄档钉死区**只留实例名**,head / 六枚 chip / 说明 / 版本菜单
  进滚动区(滚动区 120px ≥ 首屏 104,菜单露容器上沿 + 滚动条可见);
- `TailShare::list_height()` 把「列表吃多少」收到一处,两个装配点共用(三栏档行为不变)。

取舍:窄档列表 4 整行 → 3 整行;菜单从钉死改为可滚。理由(459px 里「钉住菜单」+「3 整行
列表」互斥)写在 `rows/tail.rs::CHIPS_PANEL_BUDGET`。

实测(tiny-skia 出图 + 自写 measure.py,DevDocs/artifacts/m227/):
720×760 分隔线 559→514、滚动区 0→120、head 539–578、chips 583–614(32px)、说明 623–634
(8.98:1 浅 / 10.83:1 深)、实例名 668–723 = 56px(inset 12/9/17);
700×760(options-fabric)实例名从「完全不在屏」回到 56px;四条像素守门全绿;
1180×760 浅/深/中 与 1180×900 **逐像素零变化**。
2026-11-10 23:07:19 +08:00
AskaEth 6c060a09e2 Merge branch 'feat/m228-ink-cat-dual-theme-design-tokens-in'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-10 22:34:08 +08:00
AskaEth 15309394ac docs(design): M228 复审 r2 两处文档口径(Ink 矩阵上限、测试指针)
P2-1 统一全矩阵口径:palette/visibility.rs 实测表 Ink 行第二列「1.54 ~ 12.13」是旧的「对
surface-container-highest 单一层」量法,改成 4×5 全矩阵(4 个状态容器 × 5 个容器层)的上限
**16.47**(primary-container 压 surface-container-lowest),下限 1.54(secondary-container 压
highest)不变——Silver 行本来就是全矩阵(1.02 ~ 1.80),现在两行同口径。表头补「4×5 全矩阵」
说明,避免下次又被按单层读。

P2-2 修正失效指针:visibility.rs 模块文档的「实测」小节把测试指向
`tests/ink_cat_boards.rs`,但容器层可视性那几条在 r1 返工时已搬到
`tests/ink_cat_visibility.rs` —— 改指后者。boards/mod.rs:42 的「26 组文字/图标对
(tests/ink_cat_boards.rs)」仍正确,未动。

纯文档两行,无数值/代码变更。fmt --all --check=0;test -p unlucky-design --all-targets=0
(267 passed = 186 lib + 81 集成;ink_cat_boards 7 / ink_cat_visibility 4 / ink_cat_geometry 3)。
2026-11-10 22:34:05 +08:00
AskaEth b8003177d7 fix(design): M228 复审 r1 三条(字段底角色对齐、四处小疵、产品默认口径)
P2-1 字段底角色不对称(塔裁决方案 a):Silver 的 #dcddde 从 surface-container-high 改判到
surface-container-highest,与 Ink 的 #262b25 同位——component_map 的 TextField 口径就是最高
容器层,单一角色接法才不会一边掉层。Silver 的 high 改取推导值 #e4e4e5(tone 90),阶梯
94.12 → 90.61 → 88.07 保持单调、步长 ≥1.74 L*。实测:Error on 新 highest = 4.686(原按单一
角色接到 high 时的 4.379 失守已消除)、OnSurfaceVariant = 6.383、OnSurface = 12.576。
测试同步:Silver 三条字段用法 pin 从 high 改 highest(与 Ink 对称),并新增
`field_backdrop_is_the_same_role_on_both_boards` 把这个不对称钉住(顺带断言 high 比 highest
更亮、highest 就是规格卡的 #dcddde)。

P2-2 四处小疵:
① inkcat/size.rs 扩展 FAB 起始内距 16 → **28**(规格卡画的数):框 x=292 → 图标盒 320(`+`
   font-size 24 居中于 x=332),28 才是图上的距离;16 是 M3 官方口径,已在 doc 里登记为偏离,
  并说明「要回 M3 的 16 得同时把图标中心挪回 316,那是要出截图的视觉改动」。测试补一条自洽
   断言:292 + 28 + 24/2 = 332。
② inkcat/metrics.rs 的 SWITCH_THUMB_INSET 注释重写(原句断成碎片且读起来像算错):写明
   轨道 y=1060..1092 / 拇指 y=1064..1088 ⇒ 上下各 4、右端 116−112=4,以及行程
   52−24−2×4 = 20 的推导。
③ Silver「状态容器 / 容器层」数字统一成实测口径:boards/mod.rs 与 palette/visibility.rs 都写
   「最低 1.02」(Ink 1.54),并点名最差的一组是 tertiary-container 压 surface-container-high
   = 1.016;visibility 表的 Silver 阶梯步长改写 −1.7 ~ −4.1。
④ inkcat/type_scale.rs 正文清单只列 11 个**文字**字号(去掉 FAB 字形 20/26/34),并注明那三个
   归 FabSize::icon_px、SpecText 没有 20 这一档。

P2-3 产品默认口径统一:palettes.rs 的「两个入口」表与 Palettes::baseline 文档、color/mod.rs
的「出厂默认色」小节、Scheme::baseline 文档统一写明「M228 起出厂外观入口是
DesignTokens::brand,本入口是 D38 取色链中性锚点」,与 lib.rs 同口径;顺带把 BASELINE_SEED /
DEFAULT_SEED / DEFAULT_NEUTRAL_CHROMA 的「产品默认」措辞改成取色链口径。

另:tests/ink_cat_boards.rs 已 400 行贴线,把 4 条容器层可视性测试拆到新文件
tests/ink_cat_visibility.rs(纯搬移 + 补 import);palettes.rs 因新增口径说明到 403 行,按
「合并相邻段落、不删信息」压回 399 行;size.rs 现在 323 行。

五步(design 口径):fmt --all --check=0;check -p unlucky-design --all-targets=0(零 warning);
test -p unlucky-design --all-targets=0(267 passed);clippy -p unlucky-design --all-targets
-- -D warnings=0;400 行扫描只剩 README.md(豁免)、无 <2 字节 .rs。
2026-11-10 22:34:05 +08:00
AskaEth 0169d4c8fb feat(design): 加 Ink/Silver 双主题色板 + 规格卡几何与字号令牌(M228)
规格卡 DevDocs/artifacts/components-ink.svg 的令牌化,只动 crates/design,零依赖不变。

- color::boards:BrandBoard(Ink 深 / Silver 浅)→ 49 角色 × 2 套固化色板。
  spec 直写值进 ANCHORS(测试逐条比 hex,规格卡改了代码不改会红);规格卡没给的
  角色按「同色相 + M3 tone 关系」推导后固化,规则写在 boards/ink.rs 的行注释里。
  Scheme::from_board / DesignTokens::brand 是两个新入口;Scheme::baseline 一字未改
  (它是 D38 取色链的中性锚点,改它等于同时改 app-iced 观感 + 取色语义)。
- palette::visibility:容器层可视性裁决,即债务台账「SecondaryContainer 对 Surface
  仅 1.06」的令牌层结论。状态容器(*-container)对 surface ≥1.20:1 是唯一数字门禁;
  容器层之间不设亮度门禁,改为「选中态 = 容器底 + 文字对比 + 形状/图标」三件套的构成
  要求(浅色板实测 1.05~1.24,任何有意义的门禁都过不了,详情见模块文档实测表)。
- inkcat:规格卡的尺寸与形状数据。ControlSize(按钮 XS~XL = 32/40/56/96/136,圆角
  一律半高)、FabSize(40/56/80/160×56,圆角 12/16/20/16)、控件方框(卡片 16、
  对话框 28、snackbar 52@16、列表行 60 药丸、chip 32@8、字段 4/28 + 内距 16/32、
  复选 18@4、单选 18、开关 52×32 + 拇指 24、进度 4/6)、SpecText(规格卡 11 个字号
  → 命中 / 落档 / 低于 11px 下限三类结论)。
  component_map 的表值一处未动:app-iced 的 pages/frame/tests.rs:42 与 shell/tests.rs:30
  按 Medium(12) pin 住卡片,改表等于连带改观感且破坏既有断言;差异在 inkcat 模块文档
  逐条登记,换肤时由 M229 一次切换 + 截图验收。
- 测试:186 lib(+13 条新单测)+ tests/ink_cat_boards.rs(10 条:完整性/锚点/双主题
  切换/26 对 WCAG AA/25 组规格卡用法/状态容器 1.2 门禁/阶梯单调)+ tests/ink_cat_geometry.rs
  (3 条:按钮 FAB 几何、控件圆角、字号校核)。
2026-11-10 22:34:05 +08:00
AskaEth 7fe38fb240 Merge branch 'feat/m224-local-mods-enable-disable-modtoggle'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-10 22:07:09 +08:00
tower-worker a0f61d8bb5 feat(mods): 本地 Mod 列表与启停(M224 MODTOGGLE)
三层一次做完:core 读写目录 → presenter DTO/host → 实例详情页 MODS 区。
r1 复审后 rebase 到 main 71144df(M223 已合入),并修掉四条文档疵。

core(crates/core/src/addon/mods/)
- suffix:`.disabled` / `.old` 的**唯一判据**(四态枚举 + 展示名 + 双向改名目标名),
  收敛原先两份同构实现——`export/scan.rs::is_disabled_path` 与 `import/plan/paths.rs::suffix_variant`
  改为委派/再导出,签名与行为不变。
- list:单根语义(入口是 derive_game_dir 派生出的运行目录)列 `<run>/mods/`,不按扩展名过滤,
  符号链接/目录/怪名字逐条登记原因;`mods/` 本身是符号链接时拒(不跟随)。
- toggle:启停 = 在 `mods/` 里改名。五条守卫(裸文件名 / 目录可用 / 普通文件 / 不覆盖 / 不做
  copy+delete 兜底)+ 幂等;`.disabled` 只插在 `.old` 之前。

presenter
- dto/mods:ModsCommand / ModListView / ModFailureView(不拼文案,只给结构 + OS 原文)。
- host/mods:后台线程列目录与启停,运行目录走与启动同一份 D20 派生、不写任何设置;
  游戏在跑时 host 侧直接挡。
- state/mods:快照整体替换、迟到结果丢弃、在飞标记;`AppState::mods`。

app-iced(实例详情页)
- MODS 区(`pages/instances/mods.rs`):名字 + 大小 + `.old` 徽标 + Switch;运行中/在飞只读。
- 进入详情页即后台列一次(不走 3 s 观察窗,避免打红 M192 的「空闲不烧帧」守卫)。
- MODS 卡排在启动参数**之前**(详情列是滚动区,排在后面 1180×1000 下永远看不见)。
- i18n:19 条中英文案(`catalog/instances.rs` + 金表)。

rebase 冲突解法(三个文件,全是"两侧同段新增",都保留):
- `i18n/catalog/instances.rs` + `i18n/catalog/golden.rs`:M223 的 Adopt* 段在前、M224 的
  InstancesMods* 段在后(= `Key::ALL` 顺序 = 金表顺序)。
- `presenter/src/state.rs`:`adopt` 与 `mods` 字段/初始化/apply_core 分支都留;`tiles()` 归
  `state/tiles.rs`(本分支搬的),`apply_agent`/`agent_rail_visible` 归 `state/agent.rs`
  (M223 搬的)——两处搬移不重叠,state.rs 里都不再出现。

r1 四条文档疵:
1. `pages/instances/mods.rs` 模块文档:挂载位置写反("启动参数卡之后"→实际之前),并补上为什么;
2. `state/mods.rs`:引用不存在的 `CoreEvent::ModsChanged/ModsFailed` → 改成 `CoreEvent::Mods`
   + 文字说明 Ok 落快照 / Err 落具名失败;
3. `app/state/mods_shot.rs`:文档说"目录串取真实派生结果"但代码留空 → 改成"故意留空";
4. `app/state/refresh.rs::note_mods_failure`:文档说"连点两次看见两次红字"与去重机制不符 →
   改成按 `(实例, 文件, 大类)` 三元组去重(防刷屏),换文件/换实例/换大类仍会再弹。

顺带(r1 已确认的 main 红):`pages/download/options/view.rs:88-89` 两条
`clippy::doc_lazy_continuation` 只缩进修掉,文字未动。
2026-11-10 21:09:37 +08:00
AskaEth 71144dfca8 Merge branch 'feat/m223-gui-adopt-import-wiring-guiadopt'
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
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
2026-11-10 20:18:14 +08:00
AskaEth 44c00b4873 fix(app-iced): 测试助手改用 matches!,消掉 deny 级 clippy(复审 r2 P1-1)
`dispatched_adopt` 内层 `match (&**command, want) { … _ => false }` 被判
`match expression looks like matches! macro`(deny 级 ⟹ CI 的 `--workspace --all-targets
-D warnings` 必红;558fbf2 原树 `cargo clippy -p unlucky-app-iced --all-targets` 实测 exit 101)。

改成 `let AppCommand::Adopt(..) = command else { return false }` + 一条 `matches!`,
判据与原来逐字等价(四种命令种类 × 对应 slug)。同一处的注释补一句「为什么不能再写 match」,
免得下一个人改回去。

教训记账:r2 自验只跑了 fmt/check/test,漏了 clippy(T23「五步少一步等于那步不存在」);
本轮五步齐跑,退出码见完成报告。
2026-11-10 19:37:45 +08:00
AskaEth fa3fab9086 fix(app-iced): 复审 r1 三条(在飞守卫补 Back/Target、预检按版本过期、shell 文档如实)
P2-1(行为):`Busy::Importing` 的早退只盖了 Close。「返回」会派 Scan、「换目标档」会派
零写盘 Preview,两条都把 `adopt_epoch` +1,于是在飞的 Confirm 交卷时 Done 被代次丢掉——
实例写进去了,界面什么都没说(正是 Close 守卫要防的形状)。两处补同款早退,且守卫写在
改本地状态**之前**:挡下来的换档不该把界面改成 B 档而盘上写的是 A 档。

P2-2(显示陈旧):`Form::fresh_plan` 原来只按目标档过期。先选 1.20.1 + 版本目录档,再改选
1.19.4,目标档没变,旧预检的 notes(说的是 `versions/1.20.1`)会继续当新版本的后果原文画。
现在连 `plan.version_id == selected` 一起判;没选中版本时也返回 None。同处那句
「版本一致性由 Self::candidate 那一路保证(选中即重扫)」与代码不符(Select 只改本地字段、
不发命令),改成如实说明过期判定只能在这里做。

P2-3(文档):`app/adopt/shell.rs` 的「纯搬移:函数体一行未改」不成立——同一提交里卡壳几何、
页脚去重、预览摘要都改过。改成逐条列出改了什么与为什么。

用例(两条都是先写红再修,红时 panic 信息就是错在哪):
- back_and_target_are_ignored_while_the_registration_is_in_flight(记账后端断言「不许派
  Scan / 不许派 Preview」);红:`登记在飞时 Back 不许派 Scan(会把 Done 的代次顶掉)`;
- fresh_plan_expires_when_the_selected_version_changes;红:`换了版本之后旧预检必须过期
  (否则画的是 1.20.1 的后果原文)`。

顺带补图:`ULL_ADOPT_STATE` 加 `pick-selected` 一档(选中行底色 + 预检启用态),
出图 `DevDocs/artifacts/m223/adopt-pick-selected-light.png`(r1 复审说这格只有代码没截图)。
2026-11-10 19:37:45 +08:00
AskaEth 6ffbd51f60 feat(app-iced): 向导的出图种子 + 卡壳几何修正(M223 视觉自检)
D30 的截图通道在本机可用(`--software-renderer` 走 tiny-skia,1180×760 落盘),所以不再把视觉
证据留空:新增 `ULL_ADOPT_STATE`(与 `ULL_JAVA_STATE` / `ULL_AI_STATE` 同一族,**只在
`--screenshot` 下生效**),可把向导摆到七种状态之一(pick / empty / preview / shared / failed /
done + 深色),于是出图不靠「赌异步时序」。

顺带修掉三处只有出图才看得见的问题:

1. **卡壳把页脚裁掉了**:内容高于 `max_height` 时 iced 直接裁,第一版 PICK 段的「实例名」与
   按钮整排不可见。现在列表段(Pick/Failed)定高 + 中间区可滚、页脚固定;短内容段
   (Preview/Done)按内容收高,不留一大片空白;
2. **两枚一模一样的「关闭」**:Failed/Done 的主按钮就是关闭,文字关闭不该再出现;
3. Preview 段少了实例名一行,且标题下的摘要重复了「共用目录 · 共用游戏目录」。

夹具只造候选/问题/运行目录这些**版面数据**,后果原文仍由 core 的 `target_notes()` 现算,
图证明版面与文案、不证明 core 判据(见 `DevDocs/artifacts/m223/README.md` 的真/假表)。

为守 400 行(`components::gate` 实测 412 行红),卡壳/标题/页脚纯搬移到 `app/adopt/shell.rs`。
2026-11-10 19:37:44 +08:00
AskaEth a36e17a5d3 fix(app-iced): 点选共享档立刻显示后果原文,登记在飞时不吃关闭(M223 自检)
两处自己复审抓到的问题:

1. 契约 §5.2 要求 Shared/VersionDir 被**点选**就展示 target_notes() 原文,而上一版要等
   用户再点一次「预检」才出现。现在点选这两档会顺手发一次**零写盘**的预检,结果落在
   Pick 段当提示读(不把用户甩到确认页:确认仍由显式的「预检」按钮触发,Form::checked)。
   改档之后旧 plan 因目标档不一致而不再显示(Form::fresh_plan),避免画上一档的后果原文。
2. 「关闭」在登记在飞时被忽略:core 那条线程已经开跑,关面板会让 Done 事件被代次丢掉,
   用户看到的就是「实例写进去了,界面什么都没说」。

用例:a_foreign_target_shows_its_notes_without_jumping_to_confirm /
choosing_a_foreign_target_previews_but_waits_for_the_user /
closing_is_ignored_while_the_registration_is_in_flight。
2026-11-10 19:37:43 +08:00
AskaEth 9ca5ee8ae7 docs(core): adopt 的两条文档小疵(r4 复审留的,M223 顺手修)
- plan.rs 第 11-12 行:一个悬空半句(「…必须落在」后面断掉)补完,并把 fspaths 的引用接上;
- run_dir.rs 第 16 行:删掉「配置写面看 <.ULL>、并额外排除游戏根」这条**已移交**
  fspaths::ensure_surfaces_clear_of_game_tree 的职责描述,明说本模块只负责数据写面这一条。

纯文档,零行为变化。
2026-11-10 19:37:43 +08:00
AskaEth 41caae3571 feat(app-iced): 实例页「导入已有版本」向导(M223)
页头「导入」按钮(以前是占位动作,点了只打日志)现在打开四段向导:

| 段 | 画什么 |
|---|---|
| Pick | 候选版本(可导入的才可点)+ 目标三档 chip + 实例名(缺省 InstanceDir) |
| Preview | 生效运行目录(回程给的)+ 隔离档 + target_notes() 原文逐条 |
| Failed | 稳定码 + problems 逐条(不折叠成一句「导入失败」) |
| Done | 实例名 + 运行目录 + 提示原文 |

- 向导是叠在页面之上的一层(遮罩 + 居中卡),不搅乱 list-detail 的版式判据;
- 表单态在 App(app/adopt.rs 的 Form),事件真相在 presenter 的 AdoptState;
- 在等回程时主按钮禁用(连点「确认」不会写两条实例);
- i18n 中英目录 + 金表补 27 条向导文案(实例域末尾追加)。
2026-11-10 19:37:42 +08:00
AskaEth 5a59cb33b2 feat(presenter): adopt 的指令/事件/任务接线 + install 那句假话变真话(M223)
- AppCommand::Adopt(Box<AdoptCommand>)、CoreEvent::Adopt(Box<AdoptEvent>)、
  HostTask::Adopt { event, epoch }:三条穷尽枚举各加一条变体(代次独立于安装与 pack);
- dispatch.rs 一条分支、handle.rs 一条分支、state.rs 的 adopt 字段与一条 apply 分支;
- host/install.rs 的「还没有接入安装管线…请先在实例页导入已有版本」改成指向真入口:
  那里以前是一句假话(实例页根本没有导入动作),现在向导落地了,它才成立。
2026-11-10 19:37:41 +08:00
AskaEth 26e4fbb91d feat(presenter): 导入已有版本的 DTO 与三条后台线程(M223)
core 的能力(M221)接进 presenter 的 IPC 面:

- dto/adopt.rs:AdoptCommand / AdoptEvent / 六种视图 / AdoptState + AdoptStage。
  全部 Serialize+Deserialize,**不 import 任何 core 类型**(契约 §5.4);
- host/adopt.rs:spawn_scan(adopt::scan)/ spawn_adopt(plan 或 adopt),结果经
  HostTask::Adopt 回流;core 的候选/计划/结果/失败逐字段搬进 DTO,problems[] 一条不少;
- CoreHost 加 adopt_epoch(每条命令 +1、关面板也 +1),连点与迟到的回程不会点亮面板;
- 登记成功后补一次 refresh_instances(新实例这一拍就出现在列表里)。

运行目录只从 plan()/adopt() 的返回值取(缺省档可能带 -2 后缀,只有 core 知道用了哪个);
target_notes() 的原文随 AdoptPlanView::notes 上屏,界面不另写一份后果话术。
2026-11-10 19:37:23 +08:00
AskaEth 9ab909d01c refactor(presenter): 两处纯搬移腾出 400 行余量(events 用例 / AI 栏事件投递)
events.rs 399 行、state.rs 400 行(AGENTS §3.1 与 §3.9 的零余量贴线文件)。M223 要往
AppCommand/CoreEvent 各加一条「导入已有版本」的变体、往 AppState 加一份事件态,都得先腾地方。

纯搬移、一行未改:
- events.rs 尾部 #[cfg(test)] mod tests(80 行)→ dto/events/tests.rs(399 → 321);
- state.rs 的 apply_agent + agent_rail_visible(28 行)→ state/agent.rs(400 → 376)。

方法路径不变(impl 写在子模块、pub use 无需新增),断言一条未动,搬移 diff 可逐行对。
2026-11-10 19:37:23 +08:00
AskaEth 13a317d35c Merge branch 'feat/m226-fix-instances-module-doc-stale-clai' 2026-11-10 19:36:47 +08:00