diff --git a/docs/ACCEPTANCE.md b/docs/ACCEPTANCE.md deleted file mode 100644 index b3c3c7a..0000000 --- a/docs/ACCEPTANCE.md +++ /dev/null @@ -1,58 +0,0 @@ -# Manual acceptance checklist - -Everything here is verified by hand on the local Windows build. Automated tests cover storage, the search parser/compiler and the export; the UI is verified manually. - -## Startup and workspace - -- [ ] `ykanban ` with a valid workspace opens the board. -- [ ] `ykanban` with no arguments shows the argument error page. -- [ ] `ykanban a b` (more than one argument) shows the argument error page. -- [ ] A path that does not exist shows the folder-missing page. -- [ ] An existing folder without `.ykanban` shows the not-initialized page with an **Initialize Workspace** button; after clicking it the board opens with the preset columns and tags. -- [ ] Opening the same workspace in a second instance shows the lock-conflict page; closing the first instance lets the second open. -- [ ] The window title is ` - YKanBan`, or just `YKanBan` on an error page. - -## Language - -- [ ] A new workspace is seeded with columns and tags in the language active at creation time. -- [ ] Switching the language in Settings shows the "takes effect after restart" notice and the change only applies after a restart (restart-effective item). -- [ ] After restarting in Chinese, the whole UI is Chinese. - -## Theme - -- [ ] Settings offers light / dark / follow system; switching applies immediately and the interface recolors completely (theme three-state item). -- [ ] Cancelling Settings keeps the previous theme; accepting keeps the chosen one. -- [ ] Custom surfaces (top bar, columns, cards, error text) recolor with the theme. -- [ ] Tag colors always show their raw `#RRGGBB` value in both themes. - -## Board - -- [ ] Columns run left to right and cards top to bottom; there is no drag. -- [ ] A card shows the bold `#id` with its title (or just `#id` when untitled), a truncated content preview and colored tag badges; no timestamps. -- [ ] Clicking a card body opens the editor; the card menu opens in place. -- [ ] Add, edit and delete a column and a card; deletes honor the confirmation toggles. -- [ ] The column editor rejects an empty or duplicate title with an inline error. -- [ ] Editing a card with no effective change writes nothing; cancelling writes nothing; closing with unsaved changes asks to discard when that toggle is on. -- [ ] Tag assignment in the card editor adds existing tags and removes them with the ×; the picker only offers tags not already assigned. -- [ ] Move a card to another column; with only one column the picker reports there is nowhere to move. -- [ ] The Display flyout changes column width (narrow/standard/wide/custom) and card/column sort immediately. - -## Search - -- [ ] Pressing the search button and pressing Enter in the search box behave the same. -- [ ] A blank query shows everything; the clear button restores the full board. -- [ ] A valid query filters cards while keeping every column (empty columns stay visible). -- [ ] The filter status icon is dimmed when not filtering and solid while filtering, and its tooltip states the current status. -- [ ] An invalid query opens the error dialog with the localized message plus the English detail; after closing, the typed text and the previous results remain. -- [ ] Search state persists while switching tabs. - -## Tags - -- [ ] The tag tab lists color, name, description and usage count. -- [ ] Add, rename, recolor (with the color picker) and re-describe a tag; the board refreshes after a change. -- [ ] Deleting a tag warns that it will be removed from N cards and honors the confirmation toggle. - -## Export - -- [ ] Export prompts for a file with a language-appropriate default name and writes Markdown with language-appropriate timestamps, matching the current interface language (export-language-follows-UI item). -- [ ] After a successful export a confirmation dialog shows the written path. diff --git a/docs/PLAN.md b/docs/PLAN.md deleted file mode 100644 index 34314f3..0000000 --- a/docs/PLAN.md +++ /dev/null @@ -1,321 +0,0 @@ -# YKanBan 项目计划(v1.8) - -> 本文档是 YKanBan 的完整项目计划,由需求讨论定稿,供执行者照此实施。 -> 所有设计决策均为定稿结论;如实施中发现问题,应回到计划维护者处修订,而不是自行偏离。 - -## 1. 项目概述 - -个人使用的本地/离线看板工具。跨平台桌面应用(Windows / Linux / macOS),类 Git 的工作方式:在被管理项目根目录下创建 `.ykanban` 文件夹存储看板数据。Avalonia UI,MVVM 模式。每个 YKanBan 进程管理一个工作区,可多实例并行(同一工作区由锁文件互斥)。界面默认英文,附简体中文(ResX,切换重启生效,见 §8);主题支持亮色/暗色/跟随系统(默认跟随系统,即时生效,见 §5.9)。 - -## 2. 存储设计 - -### 2.1 存储模型(类 Git) - -- 每个工作区(定义见 §4)= 一个其下存在 `.ykanban` 的路径;`.ykanban/` 内含 `ykanban.db`(SQLite 数据库)与 `ykanban.lock`(锁文件) -- 打开文件夹时**只检查当前目录**是否存在 `.ykanban`,不向上递归查找;不存在则询问用户是否在当前目录初始化 - -### 2.2 数据库配置 - -- `PRAGMA journal_mode=WAL` + `PRAGMA synchronous=NORMAL` -- 每个连接显式 `PRAGMA foreign_keys=ON`(Microsoft.Data.Sqlite 默认关闭,不开则外键与 CASCADE 均不生效) -- 不做退出时 checkpoint -- 备份/迁移必须同时复制 `ykanban.db`、`ykanban.db-wal`、`ykanban.db-shm` 三个文件(此要求写入 USAGE.md) -- `PRAGMA user_version` 管理 schema 版本,启动时按版本增量迁移 - -### 2.3 独占访问 - -- 每个 `.ykanban` 同一时刻仅允许一个 YKanBan 实例打开 -- 机制:独占打开 `ykanban.lock`(.NET `FileStream` + `FileShare.None`)持有至退出;锁文件内写 PID/机器名/时间用于诊断;程序崩溃/断电由 OS 自动释放,无残留问题 -- 锁冲突场景:同机多实例指向同一工作区,或他机经网络共享打开同一 `.ykanban`;界面显示锁冲突页,不提供其他操作 - -### 2.4 与 Git 的关系 - -- `.ykanban` 不应提交进 Git,建议用户将其加入全局 gitignore(写入 USAGE.md,与 WAL 三文件复制要求并列) - -## 3. 看板数据模型 - -单看板,列 → 卡片两级。时间戳一律 INTEGER Unix **秒**。 - -``` -columns: id INTEGER PK AUTOINCREMENT, - title TEXT NOT NULL UNIQUE CHECK (length(title) > 0), - description TEXT NOT NULL DEFAULT '', - created_at INT NOT NULL, updated_at INT NOT NULL -cards: id INTEGER PK AUTOINCREMENT, - column_id INT NOT NULL → columns.id ON DELETE CASCADE, - title TEXT NOT NULL DEFAULT '', - content TEXT NOT NULL DEFAULT '', - created_at INT NOT NULL, updated_at INT NOT NULL -tags: id INTEGER PK AUTOINCREMENT, - name TEXT NOT NULL UNIQUE CHECK (length(name) > 0), - color TEXT NOT NULL CHECK (GLOB '#'+六位十六进制,即 #RRGGBB 形态), - description TEXT NOT NULL DEFAULT '' -card_tags: (card_id, tag_id) 复合主键,双向 ON DELETE CASCADE -``` - -行为约定: - -- 数据校验尽量在 DDL 层完成(CHECK / UNIQUE / FOREIGN KEY),保证入库即合法: - - `columns.title`:非空、全表唯一(BINARY 严格比较),无字符集限制 - - `tags.name`:仅要求非空串,**完全放开**——无字符集/长度限制,允许空格、emoji、保留字符;UI 输入不做 trim - - `tags.color`:仅接受 `#RRGGBB` 形态(GLOB 校验) - - 所有 description 字段无长度上限 -- 时间戳刷新规则: - - `cards.updated_at` 仅在卡片编辑模态点"确认"写入时刷新(新建卡片时 created_at = updated_at) - - 菜单"移动到列"只改 `column_id`,不刷新卡片 updated_at - - `columns.updated_at` 仅在列自身标题或 description 变更时刷新 - - 编辑模态确认时内容无变化 → 不写库、不刷新时间戳 -- 卡片编辑确认涉及 cards + card_tags 多表写入,必须包在单个事务中 -- 级联删除全场景由 DDL 覆盖,无需触发器(前提 `PRAGMA foreign_keys=ON`): - - | 删除操作 | 级联链 | - |---|---| - | 删 column | → 该列所有 cards → 这些卡片的 card_tags(级联链式传导) | - | 删 card | → 该卡的 card_tags | - | 删 tag | → 相关 card_tags(= 卡片卸载该标签,卡片保留) | - -- 禁止手动排序:列内/列间顺序由显示规则计算(id 默认 / 创建时间 / 最后修改时间 / 标题字母序),界面随时可切换、全局一份存 `app.json`(见 §4.3);所有排序一律以 id 升序兜底;标题排序按码点(BINARY collation) -- 无标题的唯一表示是空串(`title TEXT NOT NULL DEFAULT ''`);无标题卡片列内不显示占位标题;按标题排序时视为空串 -- 删列 → 级联删卡(UI 二次确认,提示将删 N 张卡片);卡片硬删除(二次确认) -- tag 永不自动清理(`tags` 表保留无引用标签),配标签管理界面 -- 初始化预置三列:初始化时按当前语言直接从 ResX 资源读取(英文 To Do / In Progress / Done;中文 待办 / 进行中 / 已完成),随**创建时刻**的语言写入,此后即为数据、不再随语言切换变化(见 §8) -- 数量级参考:卡片 ≤ 10000,列 ≤ 100 - -## 4. 应用工作流与主界面骨架 - -**术语**:工作区 = 其下存在 `.ykanban` 的路径 - -### 4.1 实例模型与启动 - -- 每个 YKanBan 进程独立管理一个工作区;多实例可并行运行;同一工作区由锁文件互斥(见 §2.3) -- `ykanban `:本实例打开该工作区(主启动方式) -- `ykanban`(无参数)或非法参数:显示**参数错误页**(全窗口错误页之一,见 §4.4) -- 窗口标题:`工作区名 - YKanBan`;无有效工作区(错误页状态)时仅 `YKanBan` - -### 4.2 连接管理 - -- 单连接 + 单锁:打开工作区成功即持有,进程生命周期内不释放;正常退出时释放,崩溃/断电由 OS 自动释放 - -### 4.3 全局配置 - -全局配置为 `app.json`(Newtonsoft.Json 13.0.4),位置 `%APPDATA%/YKanBan/`(各平台等价目录): - -- 强类型根对象 `AppConfig` 整体序列化,根含 `"version": 1` 迁移钩子 -- 字段:语言(`Language`,`"en"` 默认)、主题(`Theme`,`"FollowSystem"` 默认)、卡片排序选择、列宽、四类确认开关 -- 写入时机:**应用退出时一次性写盘**(直接写,不做原子替换);崩溃/断电丢失当次会话的设置变更(已接受:配置低价值) -- 读取:文件缺失 → 默认值启动照常运行(下次退出自然创建);解析失败 → 备份为 `app.json.bak` → 以默认值启动 - -### 4.4 全窗口错误页体系 - -所有错误页**覆盖整个窗口内容区**(替代一切正常显示): - -| 错误页 | 触发条件 | 内容 | -|---|---|---| -| 参数错误页 | 无参数 / 非法参数启动 | 参数错误提示 + 正确用法说明(`ykanban `) | -| 目录不存在页 | 参数路径不存在 | 提示 | -| 未初始化页 | 路径存在但无 `.ykanban` | 提示 + 初始化按钮(唯一带操作的错误页) | -| 锁冲突页 | 锁文件被占用 | 仅提示,无任何操作 | - -正常视图 = 顶部显示区域 + TabControl(见 §5)。 - -## 5. 看板核心界面 - -### 5.1 顶部显示区域 - -- 工作区名称(= 文件夹名)+ 小一号字显示完整路径 -- 最右侧:**导出、设置、关于**三按钮同排 - -### 5.2 TabControl 两选项卡 - -**标签选项卡**(标签管理器,风格类 GitHub issue labels 页): - -- 条目 = 颜色标记 + 名称 + 描述 + 使用数(N 张卡片) -- 支持新增、删除(提示"将从 N 张卡片移除该标签")、重命名、改颜色、改描述 - -**看板选项卡**: - -- 搜索栏(点击搜索按钮才执行,无实时搜索,带 × 一键清空);右侧依次:新增列按钮、列宽调整按钮、排序 combobox -- 列宽:模态选择预设窄/标准/宽或手动像素值(范围 160–720,默认"标准"= 320),全局生效,持久化到 app.json -- 看板区域(与 GitHub Projects 基本一致):列左→右横排,卡片上→下展开;**无拖拽** -- 列右上角:+新建卡片、⋯菜单(编辑、删除);"编辑" = 标题 + description 编辑模态 -- 卡右上角:⋯菜单(删除、移动到列…);点击卡片本体 → 编辑模态 -- 卡片条目显示 4 样内容:`#id`(加粗)与标题同行(无标题则仅 `#61`;有标题如 `#61 Foo bar`)、内容按固定字数截断(约 200 字)、标签列于内容下方(彩色徽章:颜色 + 名称)。不显示时间戳 - -### 5.3 模态窗口原则 - -非 SPA。新增列 / 新增卡片 / 编辑卡片 / 各类删除确认等操作一律通过模态对话框弹出。 - -- 所有模态窗口在父窗口中央弹出(Avalonia `WindowStartupLocation="CenterOwner"` + `ShowDialog(owner)`) -- 搜索表达式错误对话框:纯提示型模态(无确认语义),同样父窗口居中(见 §6.2) -- 新增列 / 编辑列模态:标题(必填,受 DDL 非空+唯一约束)+ description(可空) - -### 5.4 卡片编辑模态 - -- 字段:标题(可空,空串 = 无标题)、内容(多行)、标签区、只读 `#id` / 创建时间 / 修改时间(新增时未生成则不显示) -- 底部:确认(当前数据一次性写入数据库)/ 取消(丢弃更改) -- 编辑对话框是一个工作单元:确认时一次性落库(单事务,见 §3),取消不产生任何写入;其余操作(删除、移动等)仍即时写库 -- 确认时内容无变化 → 不写库、不刷新时间戳 -- 标签指派:编辑模态标签区显示已指派标签(各带 × 移除),"添加标签"按钮弹**嵌套模态** = 已有标签搜索/列表 + 新建标签入口,选中即添加,可反复打开继续添加下一个 - -### 5.5 确认对话框开关 - -按类型分设独立开关:删除卡片 / 删除列 / 删除标签 / 丢弃编辑确认,承载于设置模态,存 app.json。 - -### 5.6 设置模态 - -入口 = 顶部显示区域右侧"设置"按钮,承载各类全局设置: - -- 语言选择(英文 / 简体中文),切换后显示"重启后生效"提示(提示文本本身为资源键) -- 主题选择(亮色 / 暗色 / 跟随系统),切换后**即时生效**,无重启提示(与语言项的行为差异) -- 四类确认开关(见 §5.5) - -### 5.7 关于模态 - -应用名、版本号、仓库链接、许可证。入口 = 顶部显示区域右侧,与导出、设置按钮同排。 - -### 5.8 导出 - -- 点击后弹系统保存文件对话框,默认文件名按当前语言生成(模式为资源键,**精确到秒**):英文 `MyRepo-export-20260930-142537.md`;中文 `MyRepo-导出-20260930-142537.md` -- 人类可读格式,无回导需求;模板文本直接读取 ResX 资源(见 §8),导出语言跟随当前界面语言: - -```markdown -# 工作区名 -> 导出时间:2026-09-30 14:25:37 (全文时间戳——导出时间与卡片创建/修改时间——格式随语言:英文 yyyy-MM-dd HH:mm:ss;中文 yyyy年M月d日 HH:mm) - -## 列:进行中 (列按 id 序,标题后附卡片数;列 description 紧随标题下显示) - -### #61 Foo bar -正文内容… - -- 标签:`bug` `ui` -- 创建:2026-09-01 10:00 修改:2026-09-28 18:30 - -## 标签总表 -| 名称 | 颜色 | 描述 | 使用数 | -``` - -- 空列也保留(体现看板结构);无标题卡片以 `### #61` 呈现 - -### 5.9 主题(亮 / 暗 / 跟随系统) - -- 三态选项:亮色 / 暗色 / 跟随系统,**默认跟随系统** -- 机制:`Application.Current.RequestedThemeVariant` = `ThemeVariant.Light / Dark / Default`(Default = 继承系统偏好);Semi.Avalonia 两套配色经标准 ThemeVariant 机制生效,运行时切换**即时生效**(含支持平台上窗口标题栏装饰变体) -- 持久化:`AppConfig.Theme`(`"FollowSystem"` 默认 / `"Light"` / `"Dark"`)入 app.json,退出时落盘;启动时在主窗口创建前读取应用 -- **自定义颜色硬规则**:界面中任何自定义 Brush/Color 资源必须以 `ThemeDictionaries` 成对定义 Light/Dark 两份并经 `DynamicResource` 引用,禁止裸用 `StaticResource` 引颜色,保证主题切换时界面完整变色 -- 例外:标签颜色是用户数据(固定 `#RRGGBB`),两种主题下均直接显示原色,不做暗色变体 - -### 5.10 其他 - -- 主窗口尺寸/位置不持久化 -- **窗口规则**:所有窗口直接使用标准 Avalonia Window(系统原生标题栏与装饰);禁止无边框窗口(borderless / chromeless),禁止自绘标题栏(关闭/最小化/最大化、拖拽区域等一律不做自定义实现) - -## 6. 搜索语法 - -实现:Pidgin 库做 Lexer + Parser;语法文档并入根目录 `USAGE.md`(README 引用,见附录)。 - -### 6.1 文法(EBNF) - -``` -query := orExpr -orExpr := andExpr ( OR andExpr )* -andExpr := operand ( (AND)? operand )* ← 空格相邻 = 隐式 AND -operand := atom | '(' orExpr ')' -atom := word | phrase | qualifier -qualifier := key ':' (word | phrase) -key := tag | title | content | id | column -phrase := '"' 任意文本 '"' (内部支持转义:\" → 字面引号,\\ → 字面反斜杠) -``` - -### 6.2 语义 - -- `AND`(含隐式空格)优先于 `OR`,括号强制结合 -- 仅大写 `AND`/`OR` 为运算符,小写即普通裸词;搜索字面量大写 AND/OR 需加引号(写入文档) -- Atom 语义:裸词/短语 → 标题**或**内容子串匹配;`tag:` 标签精确匹配;`column:` 列标题精确匹配(列名 UNIQUE,无歧义);`title:` / `content:` 字段子串;`id:` 精确匹配;全部不区分大小写;引号支持含空格值 -- 短语内部转义:`\"` → 字面引号,`\\` → 字面反斜杠;搜索含空格/保留字符/字面 `AND` 等特殊标签名时,用(必要时转义的)引号值(写入文档) -- **严格校验**:非法表达式弹出错误对话框,不执行搜索。非法集合:括号不配对、运算符悬空(`bug AND`)、连续运算符(`a AND OR b`)、限定符无值(`tag:`)、短语引号未闭合、无效转义(`tag:"a\x"`)、未知限定符 key(合法 key 仅 tag / title / content / id / column) -- 错误对话框:模态、父窗口居中;文案 = 资源模板"查询表达式有误,详情:{0}"(i18n),{0} 处拼接解析器返回的**非 i18n 英文错误描述串**(对位置信息无要求) -- 空查询与纯空白查询 = 合法(显示全部) -- 无时间维度;解析器按可扩展限定符表设计(预留 `created:`、`NOT` 等扩展点) -- 搜索语法保留字**永不本地化** - -### 6.3 交互 - -- 点搜索按钮才执行(校验也仅发生在点击时,无实时报错);空查询 = 全显;筛选后空列保留显示(维持看板结构感);搜索状态在会话内自然存续 -- 表达式非法时:错误对话框关闭后,搜索框保留原文待修改,看板维持上一次筛选结果(错误不破坏现状) - -## 7. 技术选型 - -版本已经 NuGet 实测调研锁定: - -| 包 | 版本 | 备注 | -|---|---|---| -| Avalonia | **11.3.6 锁定** | 不允许升级 | -| Semi.Avalonia | 11.2.1.10 | 11.3.x 全系依赖 Avalonia ≥ 11.3.7 会强制升级,故降用 11.2 线最新;含亮/暗两套配色 | -| IconPacks.Avalonia.Lucide | 2.0.0 | 仅此子库,不装全库 IconPacks.Avalonia | -| Microsoft.Data.Sqlite | 9.0.20 | 与 net9.0 对齐;手写 SQL | -| Pidgin | 3.5.1 | 搜索 Lexer + Parser | -| MSTest | 4.4.1 | 遇问题降 3.11.1 | -| Newtonsoft.Json | 13.0.4 | app.json 序列化 | -| CommunityToolkit.Mvvm | 8.2.1 | 沿用现有 | - -### 7.1 工程结构(单项目原则) - -沿用现有 `YKanBan/` 项目**原地改造**(清理模板内容),重组为(同步更新 `YKanBan.slnx`): - -- `YKanBan`:唯一应用项目——模型、schema/迁移、仓库层、搜索解析、导出、平台服务层、Avalonia UI(MVVM);持有全部 ResX 资源;全部包依赖进同一 csproj -- `YKanBan.Tests`:MSTest 测试库,引用主项目(连带引入 Avalonia 依赖);纪律规则:**不编写任何 UI 交互测试**(窗口、模态、点击流、数据绑定、主题切换、状态页渲染一律人工验证;不引入 headless UI 测试基础设施) - -**测试范围**:解析器(成功文法全分支 + **错误检测全分支**(每个非法输入断言解析失败且错误消息非空)+ SQL 编译)、schema/DDL 约束实测、`user_version` 迁移、PRAGMA 生效、锁文件独占与释放、app.json 缺失/损坏降级、CRUD 与三条级联链、编辑确认事务原子性、**命令行参数解析**(无参/合法/非法三分支)、导出结构与本地化断言(直接设 `Resources.Culture` 断言 en / zh-Hans 真实输出)。一切测试在本地 Windows 运行。 - -### 7.2 平台与发布 - -- 锁文件等平台相关代码集中于跨平台接口的平台服务层(内部组织习惯,非强制架构) -- 发布目标:win-x64 / linux-x64 / osx-x64 / osx-arm64 -- **单一 GitHub workflow**:仅 `workflow_dispatch` 手动触发;4 RID 矩阵,每个平台作业依次执行 **build → test → 测试通过后上传该 RID 的 artifact**(压缩包);测试失败该 RID 不出产物;不创建 GitHub Release -- 开发机访问不到 GitHub:**禁止任何依赖 push 触发的 CI** -- 开发期测试策略:一切验证在**本地 Windows** 进行(`dotnet build` + `dotnet test`);各里程碑的"单测/人工"验证均指本地 Windows 执行;Linux/macOS 行为验证**无限期推迟**至程序完工后(明确接受平台 bug 风险) - -## 8. 国际化(I18N) - -### 8.1 语言与资源 - -- 支持语言:**英文(默认)+ 简体中文(zh-Hans)**,仅此两种 -- ResX 位于唯一主项目:`Resources.resx`(英文 neutral,missing key 回落英文)+ `Resources.zh-Hans.resx`;XAML 用 `{x:Static lang:Resources.Key}` -- 公共 Resources 类用跨平台 MSBuild 方案生成(`Generator=MSBuild:Compile` + `StronglyTypedFileName`(写入 obj/)+ `StronglyTypedNamespace` + `StronglyTypedClassName=Resources` + `PublicClass=true`),不用 VS 专属生成器 -- M4 起硬性规则:**所有 UI 文本一律资源键**,英文文本也只写在 Resources.resx,禁止硬编码 - -### 8.2 资源访问与异常策略 - -- 需要本地化文本的代码(Markdown 导出生成器、初始化预置列名等)**直接读取 ResX 资源**(`Resources` 类全局可用);导出测试可直接设 `Resources.Culture` 断言 en / zh-Hans 真实输出 -- 异常策略:异常消息**一律英文、永不本地化**——异常携带本地化文本是坏实践(抛出与显示时刻 culture 不一致、日志难检索);对需区分展示的场景(锁冲突、未初始化、目录不存在、schema 版本问题等)定义**类型化异常**,UI 按异常类型映射资源键显示本地化文案 - -### 8.3 语言切换与启动时序 - -- `AppConfig.Language`(`"en"` 默认)存 app.json;设置模态中切换(英文/简体中文)并提示"重启后生效" -- 启动时序:读 app.json → 在 `OnFrameworkInitializationCompleted` **创建主窗口之前**设置 `Resources.Culture = new CultureInfo(language)` → 同时按 `CultureInfo.TextInfo.IsRightToLeft` 检测并设置主窗口 `FlowDirection` -- **RTL 检测代码必须实现**——当前两语言均为 LTR 也不许省略,作为未来语言扩展的基础设施 -- 与 app.json 退出写盘策略天然吻合:切换语言 → 退出时落盘 → 重启生效 - -## 9. 里程碑 - -| 阶段 | 内容 | 验证 | -|---|---|---| -| M0 工程重组 | 现有项目原地改造(清理模板内容)+ 新建 `YKanBan.Tests`、按版本表装依赖 | 本地 Windows build + test 通过 | -| M1 基础设施 | 工作区 schema(含 CHECK/UNIQUE 约束)+ user_version 迁移、PRAGMA、锁文件、app.json、命令行参数解析(无参/合法/非法)、初始化预置三列(列名直接读 ResX) | 单测 | -| M2 仓库层 | CRUD/移动/级联/标签指派、单连接生命周期 | 单测 | -| M3 搜索与导出 | Pidgin 解析器(严格校验,非法输入返回英文错误消息)、查询→SQL 编译、Markdown 导出(直接读 ResX,en / zh-Hans 输出均有测试) | 单测(成功与错误全分支) | -| M4 GUI 框架 | 主题/图标接入、主窗口布局(顶部区 + TabControl + **全窗口错误页体系**,含参数错误页)、设置/关于模态;**ResX 基础设施、语言设置项、RTL/FlowDirection 检测、全 UI 文本资源化(硬规则生效);亮/暗/跟随系统主题基础设施与设置项、ThemeDictionaries 颜色硬规则** | 人工 | -| M5 看板界面 | 看板 tab 全部交互与模态 | 人工 | -| M6 标签与搜索集成 | 标签 tab、搜索执行与空列保留、导出接线(含文件名/时间戳 i18n 模式) | 人工 | -| M7 发布与文档 | 单一 workflow(4 RID 矩阵 build→test→artifact)文件就绪(结构自查)、根目录 USAGE.md、README(引用 USAGE.md)、人工验收清单(含语言切换重启生效、导出语言跟随界面、主题三态切换三项) | workflow 结构自查通过;实际触发与产物验证视网络条件推迟 | - -依赖链:M0 → M1 → M2 → M3 → M4 → M5 → M6 → M7 - -## 附录:用户文档(USAGE.md) - -`README.md` 引用根目录 `USAGE.md`;`USAGE.md` 必载以下四项: - -1. `.ykanban` 不应提交进 Git,建议加入全局 gitignore -2. 备份/迁移必须同时复制 `ykanban.db`、`ykanban.db-wal`、`ykanban.db-shm` 三个文件 -3. 搜索语法全文(文法、语义、转义规则、保留字、严格校验与非法表达式清单、错误对话框行为,见 §6) -4. 命令行启动规则(`ykanban `;无参数/非法参数 → 参数错误页,见 §4.1)