Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
- **装机反馈三条已闭环(报告 255)**:① ⌥L 漏 `¬` → 组合键处理前移,已修;② 「我」拆字键位错 → 运行时资源按 `positionMismatch` 截断并降级,已修;③ `trnt` 不自动上屏 = 同码还有「特性/选择性」,预期行为不改。**修正需重新出候选包装 air**。
- **B3 学习区已实现(报告 253)**:⌥L 学习区两行式(完整码+简码 / 字根格+识别码),候选窗零改动;词组按输入码对应到字;`LearningHints` 读 `B0-runtime.tsv`,缺失退回 D14。离屏实拍 `docs/design/learning-real-*.png`。**装机后的性能/内存实测仍待候选包**。
- **B3 草图(已成历史)**(`docs/design/l3-hint.md` + `learning-area-mock.png`):按用户裁定改为**候选窗零改动**,学习信息只放 ⌥L 学习区,学习区自己两行封顶(单字:字+码+简码 / 字根+识别码依据;词组:前 3 字 + 「逐字 ▸」)。`partial` 未知字根显示 `?`、标注「拆解待核验」;数据源优先 B0-runtime,未覆盖的字走 D14 兜底。**实现未开始,等评审三个细节**。
- **B5 已收口(ADR-314 / 报告 258)**:冷首键 ~28 ms(学习区关着也一样,用户不可感知)→ 不优化;A1/A2/A3/A5 全部关闭并写触发条件;内存按 ADR-312 双阈值(默认 30 / 学习区 34)。
- **B4 练习已实现(ADR-313 / 报告 257)**:只看 `full` 出题,题型 A(看字打码)+ C(识别码判断);进度写 `~/Library/Application Support/Lampyris/practice.tsv`(有界 3,000 行 / 256 KiB、原子写、删除即重置);快捷键 `hotkey.open_practice` 默认 **⌥K**,入口在设置页快捷键列表;**尚未装包实测**。

- **B2 数据地基已出产物(报告 252 / ADR-311)**:`data/generated/B0-runtime.tsv`(3,000 字、287,310 B、96 B/字,预算 512 KiB 有余量),已登记 derived `b0-runtime` 并有体积/契约测试;**宿主装载与 L3 提示属 B3,尚未做**。
- **corpus 第二阶段未开工(ADR-309/310)**:需求见 `docs/结构库第二阶段需求.md`;缺口是许可干净的字根字形身份库(N1,249 条占位的根因)与真实笔顺(N2)。B0 现 558 B/字,扩 3,000 字约 1.6 MiB,超 512 KiB 预算 → 必须先定运行包字段子集。
- **D3 只能作构建期 oracle(ADR-310)**:许可「未声明」,不得进发布产物、不得复用其字体。
Expand Down
2 changes: 2 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
- [x] **1.2.0 B2 数据地基(报告 252,ADR-311)**:`data/generated/B0-runtime.tsv`(3,000 字预渲染 TSV,287,310 B = 96 B/字,预算 512 KiB),登记为 derived `b0-runtime`;新增 `test-b0-runtime.py`(体积/四列/无 PUA/消费契约)。宿主装载与 L3 提示留给 B3。
- [x] **1.2.0 B3 学习区改造(报告 253)**:⌥L 学习区改成两行(完整码+简码 / 字根格+识别码两行),候选窗零改动;词组按实际输入码对应到字(KWWL → 中K 华W 人W 国L);数据走 `B0-runtime.tsv`,缺失退回 D14。离屏实拍见 `docs/design/learning-real-*.png`。
- [x] **1.2.0 装机反馈修正(报告 255)**:⌥L 在无候选窗时不再漏出 `¬`(组合键前移到客户端判定之前);拆字键位按 `positionMismatch` 截断降级(「我」→ `丿T 扌R ?N`);`trnt` 不自动上屏确认是「同码不唯一」的预期行为(我/特性/选择性)。
- [x] **1.2.0 B4 练习(ADR-313,报告 257)**:`Practice.swift`(只出 full 题、判分、识别码表、进度 TSV 有界原子写)+ `PracticeWindow.swift`(独立窗口,题型 A 看字打码 / C 识别码判断);新增配置 `hotkey.open_practice`(默认 ⌥K)。
- [x] **1.2.0 B5 质量收口(ADR-314,报告 258)**:A4 冷首键实测 ~28 ms(与学习区无关,用户不可感知);A1/A2/A3/A5 全部关闭并写触发条件;内存口径按 ADR-312 双阈值。
- [ ] **1.2.0 候选**(dev 已含二级简码 598 条,报告 238;Cargo 1.2.0 / build 39):**执行顺序见 [`docs/迭代计划-1.2.0.md`](docs/迭代计划-1.2.0.md)**:B1 工程收口 → B2 数据地基 → B3 L3 提示 → B4 L4 练习 → B5 质量项(有证据才做)→ B6 数据扩张;只换一次包,tag 需单独授权
- [ ] **pro 本机实键入确认**(SSH 驱动不了 GUI;用户打几个字确认候选与切源正常)

Expand Down
3 changes: 2 additions & 1 deletion crates/ime-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ mod tests {

#[test]
fn schema_is_single_source_and_matches_v1() {
assert_eq!(schema().len(), 22);
assert_eq!(schema().len(), 23);
let keys: Vec<&str> = schema().iter().map(|s| s.key).collect();
assert_eq!(
keys,
Expand All @@ -888,6 +888,7 @@ mod tests {
"hotkey.open_settings",
"hotkey.open_update",
"hotkey.open_about",
"hotkey.open_practice",
"input.punctuation",
"input.full_width",
"input.chinese_variant",
Expand Down
9 changes: 9 additions & 0 deletions crates/ime-config/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ pub static SCHEMA: &[OptionSpec] = &[
doc_zh: "组合键如 alt+a(默认,A = About) | none = 关闭;与菜单项的快捷键提示同源",
scope: Scope::Hotkey,
},
OptionSpec {
key: "hotkey.open_practice",
value_type: ValueType::String,
enum_labels_zh: &[],
default: "\"alt+k\"",
title_zh: "练习",
doc_zh: "组合键如 alt+k(默认,K = 练习) | none = 关闭;打开离线练习窗口",
scope: Scope::Hotkey,
},
OptionSpec {
key: "input.punctuation",
value_type: ValueType::Enum {
Expand Down
2 changes: 1 addition & 1 deletion crates/ime-ffi/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ mod tests {
#[test]
fn schema_layout_matches_v1() {
let layout = build_schema();
assert_eq!(layout.view.len, 22);
assert_eq!(layout.view.len, 23);
let first = layout._options[0];
// SAFETY:指针由 build_schema 的 CString/Vec 持有,测试期间有效。
unsafe {
Expand Down
36 changes: 36 additions & 0 deletions docs/reviews/256-learning-area-memory-ab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 256 — 学习区内存 A/B:开启多约 3 MiB,懒加载没吃掉差额

- 日期:2026-11-06 | 同一构建 `exec 2dc69977`(learning hints 按需解析) | 探针:`memory-gate.sh ROUNDS=3`

## 1. 数据(私有已提交,稳态 = six-words / idle-8s)

| 配置 | 轮 1 | 轮 2 | 轮 3 | 判定(门禁 30 MiB) |
|---|---|---|---|---|
| **学习区关**(默认) | 29.8 / 29.7 | 30.0 / 29.9 | 28.7 / 28.6 | **PASS** |
| **学习区开** | 33.3 / 33.3 | 31.9 / 31.9 | 31.7 / 31.7 | **FAIL** |

footprint 同步差约 2–3 MiB(关 31.7–33.2,开 34.8–36.4)。

## 2. 已排除的原因

- **不是数据解析**:把 3,000 条改成按需解析(只留 120 KB 字节 + 索引,命中时解析单行、缓存 64 条)后,
差额没有变化 → 不是 `LearningHints` 的常驻。
- **不是视图对象数量**:学习区约 15 个子视图,即使全部常驻也只有几十 KB 量级。

## 3. 最可能的原因

**字形/文本渲染缓存**:学习区会额外绘制大量不同汉字与字根(`禾 口 亻 ⺈ 小 丿 扌 ?` 等),
这些字形在候选窗里本来不会出现;CoreText 会为每个新字形缓存位图/轮廓,量级正好是 MiB 级。
这类开销随"显示过的不同字形数"增长,属**功能本身的成本**,不是泄漏。

## 4. 两个可选处置

| 方案 | 做法 | 影响 |
|---|---|---|
| **A. 分开立阈值(推荐)** | 默认(学习区关)仍守 **≤30 MiB**;学习区开启时放宽到 **≤34 MiB**,并在文档/发布说明写明"+3 MiB,默认关闭不付这个钱" | 无代码改动,口径诚实 |
| B. 继续压 | 每次只显示更少字形(例如字根最多 3 个、去掉识别码字母),或改成"按住某键才显示" | 牺牲可读性/交互,收益有限 |

## 5. 结论

学习区默认关闭时内存**不回归**(28.6–30.0 达标);开启时的 +3 MiB 是"多画了很多字"的成本。
建议按方案 A 立阈值并写进 ADR,不再为它改交互。
45 changes: 45 additions & 0 deletions docs/reviews/257-b4-practice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 257 — B4 练习:只出 full 题,A+C 两种题型,进度本地有界

- 日期:2026-11-06 | 决策:ADR-313(已实现) | 快捷键:`hotkey.open_practice`(默认 **⌥K**)

## 1. 做了什么

| 文件 | 内容 |
|---|---|
| `platforms/macos/Sources/Practice.swift` | 题库(只取 `quality=="full"`)、判分(码 / 识别码表)、进度(`practice.tsv`) |
| `platforms/macos/Sources/PracticeWindow.swift` | 独立非模态窗口:题干、输入框或按钮、判分反馈、进度摘要、下一题/显示答案/打开进度文件 |
| `crates/ime-config/src/schema.rs` | 新增 `hotkey.open_practice`(默认 `alt+k`,可选 `none`) |
| `InputController` | 热键路径打开练习窗口(与设置/关于同一套处理,不依赖菜单) |

## 2. 题型(用户选定 A + C)

- **A 看字打码**:给一个字,输入完整五笔码;大小写与空白不敏感。
- **C 识别码判断**:给字 + 拆解提示,选「末笔」(横竖撇捺折)与「结构」(左右/上下/杂合),按反查表判对错。

> B(看码选字)按建议暂不做:它练的是速度,A/C 练的是不会的东西。

## 3. 数据与边界

- 题库来自 `B0-runtime.tsv`(ADR-311 v2),**只用 `full`**(3,000 字里 2,010 条);
`partial`/`unknown` 不出题——不拿不确定的东西考人。
- 识别码表:横 GFD / 竖 HJK / 撇 TRE / 捺 YUI / 折 NBV(与 B0 生成规则同源)。
- 进度:`~/Library/Application Support/Lampyris/practice.tsv`,`字 \t 题型 \t 次数 \t 正确 \t 最近时间`;
**数据不是配置**(不写 config.toml / UserDefaults / 第二个配置文件);上限 3,000 行 / 256 KiB,超限按次数少淘汰;
写入先写临时文件再 `replaceItemAt` 原子替换,失败保留旧文件。

## 4. 验证

- `cargo test -p ime-config` / `-p ime-ffi`:Schema 数量与键列表更新为 23 项,全绿。
- 离线回归 **62 文件 / 328 例 PASS**(`wubi-learning.swift` 里新增练习断言:只出 full 题、判分大小写不敏感、
识别码表两例、进度 3,200 次记录后有界且可重载)。
- `make verify` PASS(232 产物零网络符号)。
- 设置页断言更新:快捷键行含 `hotkey.open_practice`,行数 23。

## 5. 还没做

- **装包实测**:新窗口的真机交互(键盘焦点、关闭释放、导出)尚未在装机包上走查。
- 题型 B 与错题复现策略(ADR-313 已列为可选)。

## 6. 关联

ADR-311/312/313、报告 252/256、`docs/设计`、`docs/配置架构.md`。
52 changes: 52 additions & 0 deletions docs/reviews/258-b5-quality-closeout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 258 — B5 质量项收口:A4 有数据、A1–A5 明确关闭并写触发条件

- 日期:2026-11-06 | 口径:ADR-304/307/312 | 环境:air,学习区**关**(默认)

## 1. A4 冷首键:有数据,但不是学习区的锅

| 版本 / 配置 | 冷首键 | 稳态 p95 |
|---|---|---|
| 1.1.0(历史,报告 229) | p50 15.88 / p95 25.77 | 6.71 |
| 1.2.0 学习区**开** | p50 27–30 / p95 33–40 | 6.12–8.32 |
| **1.2.0 学习区关(本次,10 轮)** | **p50 28.55 / p95 42.85 / max 42.85** | **7.09** |

- 学习区关闭时冷首键仍是 ~28 ms → **与学习区无关**(此前怀疑排除);
- 稳态 p95 全部远低于 16 ms 门禁;
- 用户实测"**没有第一下顿的感觉**";
- 结论:**不优化**,按 ADR-307 继续"单独披露、不进 PASS/FAIL"。
**触发条件**:用户可感知的首次输入延迟,或冷首键 p95 > 50 ms。

## 2. A1–A5 的明确关闭

| 项 | 结论 | 触发条件(重开条件) |
|---|---|---|
| **A1** 排序观察期 P2(误选不学习 / 时间衰减 / MRU) | **关闭**。1.2.0 评测 ALL 首选 86.4%(1.1.0 为 83.8%),无"学了更乱"证据;ADR-304 原本就要求"有证据才做" | 用户报告"学过的词不生效"或"误选被学进去",且有样本集对比 |
| **A2** 位置记忆 /「固定顺序」开关 | **关闭**。属新增配置项,无证据 | 出现"老手被学习打乱顺序"的重复反馈 |
| **A3** 三级简码入表 | **关闭**(报告 239:按键仅 −0.22、582 处撞码、+53 KB) | 拿到官方三级简码表,且用户反馈"识别码难记" |
| **A4** 冷首键优化 | **关闭**(见 §1) | 冷首键 p95 > 50 ms 或用户可感知 |
| **A5** 拼音后端质量评估 → 是否自研 FSA | **关闭**,移出 1.2.0 范围 | 1.3.0 起独立评估(需质量数据 + ADR) |

## 3. 同时确认的两件事

1. **内存口径**:ADR-312 双阈值——默认(学习区关)≤30 MiB,开启 ≤34 MiB。
`memory-gate` 实测:关 28.6–30.0 / 开 31.7–33.3(报告 256)。
> `perf-session` 的单点 footprint(本次 41.0)按"仅参考"处理;权威值以 `memory-gate` 的 3 轮 4 时点为准。
2. **1.2.0 出厂默认**:`candidate.show_wubi_learning=false`(候选窗保持简洁、内存不受学习区影响)。

## 4. 结论

B5 收口后,1.2.0 的**输入体验面**判定为合格:

| 维度 | 证据 |
|---|---|
| 手感(2 码/简码) | 用户确认跟手;评测 jianma2 首选 100% |
| 长编码/翻页 | p95 5.6–7.2 ms |
| 首次输入 | ~28 ms,不可感知(用户确认) |
| 内存 | 默认 28.6–30.0 MiB(门禁 30) |
| 网络 | 全程 0 连接(perf/soak) |
| 学习区 | 两行式、默认关、按码对应到字 |

## 5. 下一步

一次性装包验收:把 B4(练习窗口 ⌥K)+ B5 收口一起打成候选包,装 air 走查:
练习窗口交互、学习区默认关闭、⌥L/⌥K 热键、内存与延迟。
55 changes: 55 additions & 0 deletions docs/决策记录.md
Original file line number Diff line number Diff line change
Expand Up @@ -2944,3 +2944,58 @@
规则一致性用"同一字在 `b0_query.py` 与运行时 TSV 的文案相同"抽样校验。
- **状态**:已采纳(2026-11-06,1.2.0 B2 批)。
- **关联**:ADR-189/308/309、报告 240–251、`docs/结构库第二阶段需求.md`、`docs/迭代计划-1.2.0.md`。

### ADR-312:内存门槛按"学习区开/关"分别设定

- **决策**:常驻内存门禁维持 **≤30 MiB(私有已提交)** 作为**默认配置**(`candidate.show_wubi_learning=false`)的硬线;
学习区开启时放宽到 **≤34 MiB**,并在 `memory-gate.sh` 与文档里写明两个口径。
- **理由**:实测(报告 256,同一构建 A/B)——关闭 28.6–30.0 MiB,开启 31.7–33.3 MiB,稳定差约 3 MiB。
已排除数据解析(改为按需解析后差额不变)与视图对象(15 个子视图只有几十 KB);
差异来自**多画了大量不同汉字/字根**带来的字形缓存,属功能成本而非泄漏。学习区默认关闭,默认用户不付这份钱。
- **边界**:不改默认关闭、不新增配置项;若将来把这个差额压下去(例如只画 3 个字根),再收紧门槛。
- **验证**:`memory-gate.sh` 在两种配置下各跑 3 轮(报告 256 表格)。
- **状态**:已采纳(2026-11-06)。
- **关联**:ADR-263/292/311、报告 254/255/256、`tools/macos-probe/memory-gate.sh`。

### ADR-313:L4 练习(学·练·用 的"练")草案

- **状态**:**已实现**(2026-11-06,用户批准"按建议进行",题型取 A+C,并新增 `hotkey.open_practice` 默认 `alt+k`)。
- **目标**:把 B0 结构事实库变成**离线可用的练习**——练五笔拆字与识别码,而不是做题库游戏。
- **范围(只做三件事)**:
1. **题库**:只从 `full` 条目出题(可判分的前提);`partial`/`unknown` 一律不出题,避免"拿不确定的东西考人"。
2. **判分**:本地即时判对错,错了给 B0 的解释(字根 + 识别码依据),不联网、不上报。
3. **进度**:本地记录每题的对错与次数,可导出 TSV;**不做**排行、不做社交、不做云同步。
- **题型(三选一或全做,评审时定)**:
A. **看字打码**:给字,输入完整码(练拆字顺序);
B. **看码选字**:给码,从候选中选正确字(练逆向记忆);
C. **识别码判断**:给不足四码的字,选末笔 × 结构(练识别码,这是三级简码暂缓后最主要的痛点)。
- **数据**:`data/generated/B0-runtime.tsv`(ADR-311 v2,3,000 字,`full` 占 2,010)。**不新增数据源**;
出题范围与顺序由构建期数据决定,运行期不重新推导。
- **进度存储**:`~/Library/Application Support/Lampyris/practice.tsv`(TSV v1,`字 \t 题型 \t 次数 \t 正确 \t 最近时间`)。
- 属**数据**而非配置:不进 `config.toml`、不写 UserDefaults、不写第二个配置文件;
- 有界:最多 3,000 行 / 256 KiB,超限按"次数少的先淘汰",写入前先写临时文件再原子替换;
- 删除即重置;导出只是复制该文件。
- **界面**:**独立窗口**(不碰候选窗),非模态、可随时关闭;关闭后不常驻、不占焦点;
键盘焦点只在打题输入框内,切走即暂停。入口放「关于/设置」同级菜单项「练习…」。
- **隐私**:零网络、零遥测;进度不外发;不做"错题上报"。
- **性能**:练习窗口生命周期独立于输入法热路径;打开/关闭不重启宿主、不影响稳态 p95;
内存按需加载 B0(复用 `LearningHints`),关闭窗口后释放。
- **验收**:
1. 只出 `full` 题(自动化断言:取样 200 题全部 `quality == full`);
2. 判分与 `B0-runtime.tsv` 的码/识别码一致(抽样 100 题逐条对照);
3. 进度文件有界、可导出、删除即重置;写入失败不破坏旧文件;
4. 断网可用;`make verify` + 离线回归全绿;
5. 打开练习窗口前后,稳态 p95 与常驻内存无劣化(沿用 ADR-307/312 口径)。
- **非目标**:排行榜、社交、云同步、AI 生成题目、语音、打字统计上报、把练习成绩用于候选排序。
- **风险**:① 题型设计不当会变成"背题库"——用随机抽样与错题复现控制;② 进度文件是新增落盘文件——已按"数据非配置"归类并设上限。
- **关联**:ADR-189/309/311/312、报告 240–256、`docs/结构库第二阶段需求.md`、`docs/迭代计划-1.2.0.md`(B4)。

### ADR-314:1.2.0 质量项收口(A1–A5 关闭与触发条件)

- **决策**:ADR-304 的 P2/P3(误选不学习、时间衰减、MRU、位置记忆、固定顺序开关)、三级简码、冷首键优化、拼音后端自研
在 1.2.0 **全部关闭**,各自写明重开触发条件;冷首键维持"单独披露、不进门禁"(ADR-307)。
- **理由**:1.2.0 的评测(ALL 首选 83.8%→86.4%,jianma2 100%/100%)没有暴露"学了更乱"或"顺序被打乱"的证据;
三级简码收益仅 −0.22 键、582 处撞码;冷首键 ~28 ms 且用户确认不可感知,根因与学习区无关(关闭状态同样 ~28 ms)。
- **触发条件**:见报告 258 §2(用户反馈/数据门槛,逐项列出)。
- **状态**:已采纳(2026-11-06)。
- **关联**:ADR-304/307/312、报告 239/254/256/258。
2 changes: 2 additions & 0 deletions docs/配置架构.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ crates/

Schema 增加 input.punctuation = "chinese" | "english"(默认 chinese)和 input.full_width = "half" | "full"(默认半角);默认值仍只由 Schema 定义,配置注释自动生成。中英快捷键(Shift)是会话内临时切换,不写配置;**全/半角快捷键自 ADR-305 起与标点/字形一致,写配置并持久化**(旧版布尔值 `false/true` 读取时迁移为 `half/full`,不改写用户文件)。hotkey.toggle_mode 由宿主消费,正式模式切换经 ime_config_set 保存。

2026-11-06(ADR-313):新增 `hotkey.open_practice`(默认 `alt+k`),打开离线练习窗口;作用域 `Hotkey`,`none`/空即关闭,与其它快捷键同一套冲突检测;不新增配置文件、不改持久化路径。

2026-10-18(ADR-207):新增 `hotkey.open_settings`(默认 `ctrl+,`)与 `hotkey.open_about`(默认 `ctrl+shift+a`),由 Schema 提供默认值,设置页与其它快捷键统一管理;宿主与预览把同一配置显示为菜单项右侧快捷键,按键路径在控制器中处理,不依赖菜单跟踪。`none`/空 = 关闭。

## 快捷键语义(ADR-093)
Expand Down
Loading
Loading