改代码前先看这一页:架构上不能越的线、代码怎么组织、版本号与提交信息怎么写、改了行为要同步哪些文档。 设计的来龙去脉在 design/architecture.md,各 crate 的实现要点在 notes/crate-notes.md。
长版与理由见 design/architecture.md。
- Core 与平台层严格解耦。
qingjian-core及其兄弟 crate 必须平台无关:词库、拼音解析、候选生成、排序、学习、翻译、文本变换全部属于 Core。 平台层(IMK / TSF / IBus-Fcitx)只做两件事:把系统输入事件翻译成 Core 的输入,把 Core 返回的帧画到候选窗口。 平台层里不允许出现排序逻辑、词库访问、翻译调用或文本变换。 判断标准:把 IMK 换成 TSF,不应该需要改 Core 的任何一行。 - 一个候选词只显示一种辅助语言。 用户配置 Primary Language + 单个 Learning Language。不要设计成
translations: Vec<Translation>或HashMap<Lang, String>这类多语言并列的数据结构,那会在 API 层面把「一次只学一种语言」这条产品原则给破坏掉。翻译是候选词的 annotation(可选、单条)。 - 输入优先于学习。 任何为学习功能增加的延迟、弹窗、UI 干扰都是设计错误。翻译查询不能阻塞候选生成,Core 必须能在翻译尚未就绪时先返回候选。
- 输入方案是配置项,不是模式。 双拼、注音这类键盘方案放
[general]里当设置,中 / 英切换始终是布尔;新方案不能改变别的方案的既定按键行为(user/getting-started/keys.md)。 - 显示面自绘、控件面原生。 候选窗、拼音行、状态条这类显示面由渲染器出位图各平台贴图(主题靠它);偏好设置、菜单、安装器用各平台原生控件。见 design/rendering.md。
- 一个类型一个文件。 一个 struct / enum / trait 及其 impl 单独一个文件;模块文件只做
mod声明、re-export 与自由函数,不把一个 crate 平铺在lib.rs里。 - 子模块用目录。 有子模块的模块用
foo/mod.rs,不用foo.rs+foo/并列。 - 同词干的兄弟文件收进目录,绝不用文件名前缀分组。
key_event.rs+key_outcome.rs→key/mod.rs+key/{event,outcome}.rs,哪怕没有key.rs这个共同父文件;query/+querying.rs这种也不行。判断:两个及以上文件名共享一段前缀且同属一个概念,就收进以那段前缀命名的目录。 - 一个职责连带它专用的类型收进一个目录。
engine/commit/mod.rs放上屏部分,chain.rs/last.rs放只有它用的类型。 - 大类型的
impl按职责拆成子模块。 每个文件一个impl Foo { … }(host/settings.rs这样),结构体与构造留在mod.rs,跨文件用到的私有方法标pub(super)。 - 文件长度。 单文件不超过 800 行,目标 500 行以内;测试超过 200 行搬到
tests.rs(多时tests/按主题分文件)。 - 导入写精确路径。 不用
use super::*/use foo::*(#[cfg(test)] mod tests里的use super::*除外)。 子文件从父模块拿的类型写use super::{Engine, Candidate};,父模块没定义的直接use crate::…或use <crate>::…,不绕super::转手。 存量的 glob 改到那个文件时顺手换掉,不专门清扫。
- 代码标识符一律英文,注释与文档用中文;
thiserror的#[error]文案用英文,日志与 UI 文案用中文。 - 新文件都要有
//!文件头;结构体 / 枚举字段之间空一行,字段名说不清的加///(r/g/b、width这类不用,a「255 为不透明」这类要)。 - 不写装饰性分隔注释(
// ====),提交钩子会拦。
- 依赖用
cargo add加,共用包提到根[workspace.dependencies];错误用thiserror不用anyhow;日志用tracing门面。 - 快捷键一律进
[shortcut]可配置,不写死键码。
crates/*用version.workspace = true;apps/*各壳是独立发布的产品,写死自己的version(Windows 读server/Cargo.toml)。- 发版之间带
-dev(两端都是0.1.3-dev),打包脚本再接 git 短哈希成0.1.3-dev-1a2b3c4(脏加+,Cargo.toml 里只写-dev)。 - 发版提交去掉
-dev打标签(macos-v<版本>/windows-v<版本>),标签后再改成下一个-dev;带-dev的标签 CI 拒绝;pkg / Inno 只认数字点号。
- Conventional Commits:第一行
<类型>(<范围>): <说明>,类型与范围英文小写,说明用中文,例如fix(core): 修自绘输入框吞数字、feat(windows): 三进程日志统一到 %LOCALAPPDATA%\Qingjian\logs、docs(changelog): 补 0.1.3 条目。- 类型:
feat新功能 /fix修 bug /docs只改文档 /refactor不改行为的整理 /perf性能 /test只改测试 /build打包与构建脚本 /ci工作流 /chore版本号、依赖、仓库杂务 /style只改格式 /revert还原。 - 范围:crate 或壳的名字——
coreplatformrenderdictionarytranslatelearningpredictlmneuralformatclimacoswindows(Server / DLL / 设置程序细分时用servertsfsettings)installerlinuxtoolsdocscidepsrelease; 跨好几处的可以省略。不兼容的改动在范围后加!。 - 正文写「为什么」与取舍,一行一条;不加 AI 署名。
.githooks/commit-msg会拦第一行不合格式的提交。 - 2026-09-16 之前的历史是「
macOS:……/Core:……」的中文冒号格式,不重写。
- 类型:
- 实现与规划分歧时以代码为准并改文档。技术方向写
docs/design/,计划写docs/plan/,工程记录写docs/notes/;不往 README 里加技术内容。 - 用户能感知的行为改了(按键、菜单、偏好设置、配置文件、数据文件),同一个提交里改
docs/user/对应的页,按键改动同时改keys.md。 - 改了某个 crate 的实现要点(数据文件、常数、生成命令),同步
docs/notes/crate-notes.md。
- 钩子:
.githooks/pre-commit(禁装饰性分隔注释 + fmt + clippy)、.githooks/commit-msg(提交信息格式)、.githooks/pre-push(全 workspace 测试);git config core.hooksPath .githooks启用一次。 两个跑编译的钩子按uname划平台范围:非 Apple 平台排除qingjian-macos(IMK 壳依赖 objc2,在别的平台上是硬compile_error!, 排除不掉就整条命令失败),与 ci.yml 三个 job 的划分一致;Windows 上 pre-push 另设QINGJIAN_UIACCESS=0(Server 的 build.rs 嵌 uiAccess manifest,没签名的测试二进制起不来,os error 740)。 - 排序 / 整句 / 纠错的改动先跑
apps/cli再合。
- CI 三个 job(Linux 全量 / macOS 壳 / Windows 三 crate)都
--locked;Dependabot 升 actions;每周cargo audit。 - 发版:推
<平台>-v<版本>标签触发release.yml,门禁是版本号 = 标签且不带 -dev、标签在 main 上、产品数据按 SHA256SUMS 校验。 - CHANGELOG 手写、发版时由维护者统一改(PR 不动它)。流程与 Secrets 见 notes/release.md。
- 从 main 开分支,一个 PR 只做一件事、只碰一个平台(Core 改动单独一个)。
- 维护者对着 main 审,squash 合并保留作者署名。PR 模板里的合并前清单就是审核标准。
- 主题与自绘渲染器(
crates/qingjian-render、各壳的贴图路径、主题文件)还在测试,这部分暂不接受 PR;稳定一版后再开。