diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..25e75f5 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,59 @@ +# InkStream 领域用语 + +InkStream 面向日常、学术与长篇创作。以下词汇描述内容及其工作状态。 + +## 写作与项目 + +**项目档案(项目)**:关联一个本地内容目录,拥有名称、封面、收藏与独立会话的写作项目。 +_避免_:把项目、最近目录和 Git 仓库当作同一概念。 + +**内容目录(工作区 / Vault)**:一个项目关联的文件夹及其中的文件;可以不是 Git 仓库。 + +**项目会话**:项目的已打开文档、恢复正文、选区、滚动位置、写作模式和面板布局记录。 + +**独立草稿会话**:不归属于某个项目的文稿集合及恢复状态。 + +**文档**:一份正在编辑或阅读的内容,可以是文件或未指定保存位置的草稿。 + +**草稿**:尚未指定磁盘保存位置的文档。 +_避免_:与章节完成程度中的“草稿”混用。 + +**文档标签**:访问当前会话内一份已打开文档的入口。 + +**未保存**:文稿包含尚未成功写入目标文件的修改。 + +**未提交**:磁盘修改尚未进入 Git 历史,与未保存是不同状态。 + +**外部冲突**:未保存文稿与外部程序修改的磁盘版本需要裁决的状态。 + +## 工作台 + +**项目轨**:常态显示项目快捷入口及工作台控制的窄栏。 + +**项目档案浮层**:用于检索、切换和管理项目的临时覆盖区域。 + +**工作区视图**:用户在项目内选择的工作目的地,包括概览、文稿、文献与版本。 +_避免_:把工作区视图当作文档标签或写作模式。 + +**项目内导航**:当前项目文件、章节或资料的组织入口。 + +**上下文工具**:围绕当前文稿显示的大纲、反链、引用、预览、角色资料或场景概要。 + +**写作模式**:通用、学术、创作三种工具与布局预设,不改变或限制文档格式。 + +**简易模式**:收起高级能力、保留基础编辑和阅读的选择,与写作模式独立。 + +## 资料与关联 + +**文献库**:具有明确身份的参考文献集合;离线副本仍属于该库。 + +**选中文献**:当前正在查看详情的文献条目;选中本身不代表已插入文稿。 + +**引用**:文稿对某个文献条目的引用标记及解析结果。 + +**Codex**:项目中的角色、地点与设定资料集合。 +_避免_:与开发 InkStream 的 Codex 工具混用。 + +**双向链接**:文档引用其他文档、标题或文本块,并可从目标反查来源的关系。 + +**索引**:从源文件派生的可重建检索资料,不是正文的权威副本。 diff --git a/README.md b/README.md index c03532c..b4f1778 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ --- -**InkStream(墨流)** 是一款基于 Tauri 2 的桌面写作应用。它只用一个 CodeMirror 6 编辑器,提供 Standard(通用)、Academic(学术)、Creative(长篇创作)三种模式;git 版本管理、双向链接、Zotero 引用和 Typst / LaTeX / KaTeX 数学渲染都内置在应用里,而不是交给一堆插件。 +**InkStream(墨流)** 是一款基于 Tauri 2 的桌面写作应用。它以 CodeMirror 6 承载主文档编辑,提供 Standard(通用)、Academic(学术)、Creative(长篇创作)三种模式;git 版本管理、双向链接、Zotero 引用和 Typst / LaTeX / KaTeX 数学渲染都内置在应用里,而不是交给一堆插件。 如果要一句话概括,它想成为文本编辑器里的 IntelliJ:纯文本的简单,配上 IDE 级的工具。 @@ -55,26 +55,26 @@ ## 特性 -- **单一内核编辑器**:整个应用就一个 CodeMirror 6 实例,纯文本始终是文档的依据。Source 和 Live Preview 随时切换,光标所在行自动展开源码,支持 11 种语言高亮。 +- **单一内核编辑器**:主文档共用一个 CodeMirror 6 编辑视图,纯文本始终是文档的依据。Source 和 Live Preview 可切换,光标所在行展开源码;大文档默认进入基础编辑,可手动启用完整排版。表格单元格、公式和只读比较按需使用独立视图。 - **本机项目档案**:`Ctrl+Alt+P` 打开左侧档案,管理名称、封面、收藏和内容目录。每个项目记住标签、光标与布局;独立草稿也会暂存在本机。切换前保存失败会留在原项目。详见[项目与恢复](docs/projects.md)。 - **三类数学块**:math 走 KaTeX,typst 走 typst.ts(wasm 实时 SVG),latex 走 MathJax。都是懒加载,首屏不碰 wasm 和字体。 - **双向链接知识网络**:`[[wiki-link]]` 支持别名、标题与块定位,SQLite FTS5 索引保存在本机应用数据目录,并支持一至两个字符的短词查找;反链面板列出有上下文的未链接提及。 - **知识图谱**:`Ctrl+G` 打开全库 Graph View,d3-force 力导加 Canvas2D 自绘,布局放到 Worker 里跑。可缩放、拖拽、点击跳转、邻域高亮;右栏能看当前文件的局部图谱。 -- **git 原生**:基于 libgit2 的完整命令集,自绘三栏 git-graph(图谱、提交详情、文件 diff)。支持远程 clone / fetch / push / pull 和 SSH 签名提交。 +- **git 版本管理**:本地操作结合 libgit2 与系统 Git,自绘三栏 git-graph(图谱、提交详情、文件 diff)。提供克隆对话框、fetch / push / pull、本地变基、暂存记录管理和分支完整正文比较,提交支持 SSH 签名。 - **句级 diff 与三向合并**:中英混排按句子比较,看的是「哪句话改了」而不是「哪行变了」。合并冲突可以逐句采纳本方或对方。 - **Zotero 集成**:CAYW 一键插入 `[@citekey]`,Web API 离线缓存,GB/T 7714 / APA / Vancouver 参考文献,引用与 Typst / LaTeX 联动。 -- **GitHub 集成**:PAT 或 gh CLI 登录(token 存进系统钥匙串,不进前端),浏览、评论、创建 Issue 和 PR,内嵌 diff 审阅。 +- **GitHub 集成**:PAT、现有 gh CLI 凭据或配置了 Client ID 的设备授权登录(token 存进系统凭据库,不进前端),浏览、评论、创建 Issue 和 PR,内嵌 diff 审阅与讨论回复。 - **创作模式**:章节场景树(带状态色点和字数)、`Codex/` 角色与设定卡(别名提及高亮、悬停预览)、Focus Mode、今日字数目标、场景概要。 - **写作辅助**:打字机模式让光标行居中,专注模式淡化其余段落,写作 HUD 记码字速度、码字时间和番茄钟。默认都关着,只存在内存里。 - **文件导出**:一键把当前文档导出成 HTML、PDF、DOCX,全本地转换;可加一行自定义水印(默认关)。装了 pandoc 还能导出 ODT、LaTeX、EPUB 等。 -- **沉浸阅读模式**:打开 txt、docx、epub、pdf 全屏阅读,自动认出小说还是文献并换排版,三套配色护眼。PDF 逐页懒渲染,编辑器不卸载,随时切回来。 +- **沉浸阅读模式**:txt、Markdown、docx、epub、pdf 可进入阅读视图;文本阅读提供文体识别、排版、目录、书签与续读,PDF 逐页懒渲染。书架可按设置启用,编辑器不卸载,随时切回来。 - **应用内自动更新**:启动时静默检查新版本,一键下载并重启升级,更新包经签名验证。 - **简易模式**:一键收起高级功能并关闭索引,保留写作、项目档案和草稿恢复。所有模式都不向内容目录写入新的项目元数据或索引库。 -- **中文优先**:中文输入法全程不被预览打断,中英混合字数统计,中文模糊搜索。 +- **中文优先**:组合输入期间暂停会干扰输入的预览更新,提供中英混合字数统计和中文模糊搜索;物理输入法的候选窗与连续上屏仍按[真机回归清单](specs/03-live-preview-ime.spec.md)验收。 ## 下载安装 -前往 **[Releases](https://github.com/KRPCT/InkStream/releases/latest)** 下载对应平台安装包: +**[InkStream 2.1.0 发行页](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0)**。前往 **[Releases](https://github.com/KRPCT/InkStream/releases/latest)** 下载对应平台安装包: | 平台 | 安装包 | |------|--------| @@ -88,11 +88,13 @@ 三模式 = UI 布局预设 + 默认功能集 + 状态栏指标。不限制文件内容、不绑定文件格式,随时切换不丢数据。 -| 模式 | 定位 | 强调色 | 特色 | -|------|------|--------|------| -| Standard | 通用文本编辑 | 石墨灰 | 文件树、大纲 / 反链 / 局部图谱、Live Preview | -| Academic | 学术写作 | 学院深蓝 | Zotero 文献库、Citation Panel、Typst 预览、学术工具栏 | -| Creative | 长篇创作 | 朱砂红 | 章节导航树、Codex、Focus Mode、字数目标进度 | +| 模式 | 定位 | 特色 | +|------|------|------| +| Standard | 通用文本编辑 | 文件树、大纲 / 反链 / 局部图谱、Live Preview | +| Academic | 学术写作 | Zotero 文献库、引用面板、Typst 预览、学术工具栏 | +| Creative | 长篇创作 | 章节导航树、Codex、专注模式、字数目标进度 | + +2.1.0 多栏工作台统一采用纸白、石墨灰和低饱和灰绿,提供概览/文稿/文献/版本导航、多个项目快捷入口,以及先看详情再明确插入的引用流程。发布与验证范围见[当前状态](docs/CURRENT-STATE.md),真实窗口观察见 [ComputerUse 验收](docs/WORKBENCH-ACCEPTANCE.md)。 ## 技术栈 @@ -115,18 +117,20 @@ ```bash corepack enable -pnpm install # 精确版本锁定 +pnpm install --frozen-lockfile pnpm tauri dev # 开发模式启动桌面应用 pnpm tauri build # 打包本平台安装包(产物在 src-tauri/target/release/bundle/) ``` -本地检查:`pnpm typecheck`、`pnpm lint`、`pnpm test:ci`、`pnpm build`。`test:ci` 串行执行全部前端测试,保留每项测试的原有时限,记录进程清理与结果,避免大文档测试和其他 jsdom 实例争用资源。原生测试运行 `node scripts/acceptance/run-rust.mjs`。Linux 构建需先安装 webkit2gtk 等系统依赖(清单见 [.github/workflows/ci.yml](./.github/workflows/ci.yml));三平台安装包经 [.github/workflows/release.yml](./.github/workflows/release.yml) 在 CI 同时构建。 +本地检查:`pnpm typecheck`、`pnpm lint`、`pnpm test:ci`、`pnpm build`。`test:ci` 将功能测试与两份大文档性能测试分阶段串行执行,保留测试自身时限,记录结果与进程清理。原生入口 `node scripts/acceptance/run-rust.mjs` 执行 `cargo test --locked --manifest-path src-tauri/Cargo.toml --all-targets -- --test-threads=1`,覆盖库与应用二进制的编译和测试;CI 不再单独执行 `cargo check`。Unix 权限负控通过独立 runner 执行。 + +`pnpm test:acceptance` 只运行人工映射的 Vitest 检查,不解释 Gherkin,不能据此宣称全部 BDD 场景通过。执行范围、硬超时和证据边界见 [BDD 自动化绑定状态](docs/specs/AUTOMATION.md)。Linux 系统依赖和三平台检查步骤见 [CI 配置](./.github/workflows/ci.yml)。[Release 工作流](./.github/workflows/release.yml)负责打包;标签发布在更新包签名与 `latest.json` 聚合完成后公开,手动触发仅上传构建产物,不创建公开 Release。打包成功不代替产品验收。 ## 路线图 -v1 的 12 个阶段已经全部做完:三模式 Workbench、CM6 单内核与多语言高亮、Live Preview、FTS5 索引与双向链接、三类数学块、git 与 git-graph、句级 diff、Zotero 与学术模式、创作模式、知识图谱、GitHub 集成,以及三向合并和跨平台打包发布。 +2.1.0 包含多栏导航、文献详情与明确插入,并延续三模式工作台、本机项目与草稿恢复、编辑与索引、Git/GitHub、学术与创作、阅读与导出等功能。实现状态和发布状态分别记录;历史阶段的实现结束不表示所有验收结束。 -v1.1 之后又陆续加了简易模式、写作模式升级(打字机 / 专注 / 写作 HUD)、文件导出(含 pandoc 多格式)、沉浸阅读模式,还有应用内自动更新。 +2.1.0 的合并入口为 [PR #49](https://github.com/KRPCT/InkStream/pull/49),软件内更新公告已同步;安装包和自动更新清单见[发行页](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0)。开发提交 `09b32ff` 已通过三平台 CI,发布提交的检查记录以 PR 为准。外部真实账号、物理 IME、跨平台完整视觉与性能验证仍有开放事项,见 [Issues](https://github.com/KRPCT/InkStream/issues)及[验收边界](docs/specs/AUTOMATION.md)。 还想做的: diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..3230cff --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,35 @@ +# 架构现状与工作台边界 + +基线:2.0.0 / main `ca4c214`,2026-09-13。发布与验收状态统一见 [CURRENT-STATE](CURRENT-STATE.md)。 + +| 职责 | 实现入口 | 权威边界 | +| --- | --- | --- | +| 文档编辑 | `src/editor/documentSession.ts`、`editorState.ts`、`useCodeMirror.ts` | 主 EditorView 与文档缓存持有正文、选区、撤销;标签 store 是镜像 | +| 保存/文件身份 | `src/stores/autosave.ts`、编辑 flows、`src-tauri/src/files/` | 按身份和修订承认写入;失败保留文稿,旧结果不清新修改 | +| 项目会话 | `src/projects/session.ts`、`src-tauri/src/projects/` | 协调保存、快照、打开和恢复,每窗口一个活动项目 | +| 项目操作 | `src/projects/actions.ts`、`src/ipc/projects.ts` | UI 经既有入口添加、切换、改名、重新定位及恢复 | +| 工作台 | `useWorkbenchStore`、`effectiveCentralView`、`WorkbenchLayout` | 视图、模式、布局与能力降级,不拥有正文或保存裁决 | +| 索引/关系 | `src/ipc/index*`、`src-tauri/src/index*`、`src/graph/` | 本机可重建派生数据,查询带项目/工作区归属 | +| Git | `src-tauri/src/git/`、Git flows/stores | 仓库状态、操作与恢复,Git 根独立于内容目录 | +| 文献 | `src/academic/useReferenceLibrary.ts`、`src/ipc/zotero.ts`、引用/CSL | 库身份和游标隔离;选择条目只看资料,明确插入才改正文 | +| 创作 | Codex、章节树、场景概要、写作指标 | 用户文件为资料源,经既有文档入口编辑 | + +## 编辑器与多栏 + +主编辑器持续挂载。切换工作区视图可隐藏它,但不重新创建正文实例;摘录/表格辅助视图不是另一份正文权威。新工作台复用现有文档、项目、Git 和引用入口。 + +项目浮层阻止后台编辑并保留纸面几何。常态项目轨直接切换也遵循保存/会话协议,失败需要让错误可见。导航、文档标签、模式分别表达目的地、文档身份和工具预设。 + +## 持久化与恢复 + +项目名称、封面、收藏、会话和索引已位于本机应用数据目录。内容目录保留用户文件,历史 `.inkstream` 未知内容原样保留。会话记录恢复正文、选区、滚动、模式、各模式布局及活动工具;重启不重放撤销历史。 + +项目协调器负责接受目标会话。布局写回只接受用户调整,拒绝启动恢复、项目切换和缩放产生的中间测量。UI 不直接改写这些协议。 + +## 本轮 DDD 切面 + +工作台统一表达概览、文稿、文献和版本,不为相同状态另建平行权威。资料选择与正文插入分离;库切换、失败和过期响应不能串库或误插入。 + +继续使用 React、CSS token 与 Tauri WebView。窄视口改变呈现,保存的宽度不因临时缩放被覆盖。行为规格先于实现,测试后通过 ComputerUse 操作真实窗口验收。 + +工作台实现由 `WorkspaceNavigation` 选择目的地,`CentralArea` 组合概览、文献、版本和保活正文。`ProjectVersions` 复用现有 Git 快照,只将复杂操作交给原全宽版本管理。资料浏览 hook 管理库修订和请求代次,插入仍经过可见可写正文命令边界,并形成独立撤销步骤。右栏始终派生可用工具,处理简易模式双向切换;收起的栏保持实例但退出焦点导航。 diff --git a/docs/AUDIT-2026-09-11.md b/docs/AUDIT-2026-09-11.md new file mode 100644 index 0000000..73c7f93 --- /dev/null +++ b/docs/AUDIT-2026-09-11.md @@ -0,0 +1,70 @@ +# 历史记录:AUDIT-2026-09-11.md + +此文件保留原日期、原工作树与当时证据。2.0.0 的实现/合并/发布现状见 [CURRENT-STATE](CURRENT-STATE.md);下面“尚未实施/未推送/草稿 PR”等文字是历史状态,不作为当前指令。 + +
+查看原始历史记录 + +# InkStream 1.3.0 功能与架构审计 + +核对日期:2026-09-11;基线 `02e0afa3aab27bf91ce35d0270e8815672d4efde`,已 fetch,开始时与 origin/main 无差异。此次没有修复生产功能,没有变更用户工作区、凭据或系统运行时。 + +结论:现有技术栈可以继续用于 UIUX 探索,但文档生命周期、工作区和派生数据缺少一致的身份与完成规则。先登记缺陷、明确项目模型和行为,再重做界面。 + +## 验证证据 + +- CodeGraph 使用本轮从 npm 核验并隔离安装的 `@colbymchenry/codegraph@1.6.0`,未升级全局配置。完整重建覆盖 521 文件、5,478 节点、17,334 条边。旧索引只有 112 文件、1,025 条未解析引用,不能继续作为完整证据。新原型不属于上述产品基线。 +- 基线 TypeScript 检查通过。 +- 全套 Vitest 串行执行:现有 162 个测试文件、1,366 项测试通过;额外两项临时集成复现失败。总结果为 163 文件中 1 个失败、1,368 项中 2 项失败。临时检查使用真实 EditorTabs、CodeMirror 状态及 autosave,仅替换文件 I/O;已从产品测试目录移出并保留审计证据。 +- 第一轮全套测试的 90 秒上限不足,已回收该进程树;随后在 300 秒硬上限内完成,耗时约 257 秒。不能将第一次超时误记为测试通过。 +- 当前提交的三平台 CI 均失败于两处剪贴板直接导入违规,后续步骤被阻断。[该提交 CI](https://github.com/KRPCT/InkStream/actions/runs/28985293456) +- 未运行真实磁盘故障注入、真实账户/远程攻击、Rust 测试、三平台桌面构建或原生 IME 验收。除明确注明的两项临时集成复现外,其余缺陷证据来自完整静态调用链。代码证据不等同于真机故障录像。 + +## 已登记缺陷与改进 + +P1 为可能破坏文档正确性、跨工作区数据隔离或凭据范围的优先项;P2 为恢复、功能与规范不符合项。每个 issue 有触发条件、证据边界与验收条件;本轮不自动实施。 + +| 级别 | 问题与影响 | 主要代码证据 | GitHub | +|---|---|---|---| +| P1 | 关闭活动标签后正文仍属于旧文档,保存对象却换成新文档;已动态复现身份失配 | `EditorTabs.tsx:33`、`useEditorStore.ts:75`、`editorState.ts:231` | [关闭活动文档一致性](https://github.com/KRPCT/InkStream/issues/2) | +| P1 | 保存失败/冻结仍关闭;冲突失败误清状态;在途保存误清新修订 dirty;已动态复现失败关闭丢缓冲 | `autosave.ts:118`、`EditorTabs.tsx:45`、`ExternalChangeBar.tsx:52` | [保存结果与修订](https://github.com/KRPCT/InkStream/issues/3) | +| P1 | 干净后台文档外部更新后仍用旧缓存,可能把磁盘新版本回退 | `externalChange.ts:78`、`editorState.ts:190` | [后台缓存失效](https://github.com/KRPCT/InkStream/issues/4) | +| P1 | 重命名/移动/删除未同步标签、缓存与写入位置,旧文件可能复活 | `fileTreeOps.ts:95`、`autosave.ts:123` | [文件身份迁移](https://github.com/KRPCT/InkStream/issues/5) | +| P1 | 乱序切库可混合根/树/查询;失败没有恢复旧 watcher | `vaultFlow.ts:50`、`:114` | [工作区会话](https://github.com/KRPCT/InkStream/issues/6) | +| P1 | 普通 A→简易→B→普通后保存 B,可把 B 正文写进 A 索引 | `index.rs:34`、`indexService.ts:40`、`useSettingsStore.ts:126` | [索引会话与启停](https://github.com/KRPCT/InkStream/issues/7) | +| P2 | 关闭高级能力后 overlay 和正文均隐藏,中央区空白 | `WorkbenchLayout.tsx:69`、`CentralArea.tsx:12` | [有效导航](https://github.com/KRPCT/InkStream/issues/8) | +| P1 | GitHub PAT 可作为第三方 HTTPS Git 认证凭据 | `git/remote.rs:18`、`:123` | [凭据主机范围](https://github.com/KRPCT/InkStream/issues/9) | +| P2 | Zotero 换账户仍共用旧数据与 library_version | `zotero_sync.rs:99`、`:115`、`:166` | [文献库分区](https://github.com/KRPCT/InkStream/issues/10) | +| P2 | 删除同步失败/分页版本改变仍推进 Zotero 游标 | `zotero_sync.rs:208`、`:228` | [同步提交](https://github.com/KRPCT/InkStream/issues/11) | +| P2 | Unix 原子写 temp+rename 未保留目标权限 | `files.rs:61` | [文件权限](https://github.com/KRPCT/InkStream/issues/12) | +| P2 | 代码示例中的 wiki-link 被计入知识网络 | `index.rs:368`、`livepreview/wikiLink.ts:27` | [链接上下文契约](https://github.com/KRPCT/InkStream/issues/13) | +| P2 | >1MB Channel 声明未落实,最高 100MB 二进制可整块 invoke;未声称测得 OOM | `files.rs:20`、`:136`、`src/ipc/README.md` | [传输与响应性](https://github.com/KRPCT/InkStream/issues/14) | +| P2 | 编辑命令及测试直接导入 Tauri clipboard,违反现行 IPC 规则 | `editCommands.ts:3`、`editCommands.test.ts:4` | [恢复 CI](https://github.com/KRPCT/InkStream/issues/15) | +| P2 | 前端放行 Markdown 阅读,Rust 字节读取白名单拒绝 md/markdown | `reading/openReading.ts:14`、`reading/loadContent.ts:37`、`files.rs:148` | [Markdown 阅读契约](https://github.com/KRPCT/InkStream/issues/20) | +| P2 | Git 远程模式与自定义服务器设置只持久化,执行路径未读取 | `settingsSections.tsx:276`、`useSettingsStore.ts:124` 与远程执行引用核对 | [远程设置生效](https://github.com/KRPCT/InkStream/issues/21) | +| P2 | 同名候选丢失路径身份;标题/块标记未用于精确定位 | `wikiLinkComplete.ts:32`、`wikiTarget.ts:16` | [精确链接目标](https://github.com/KRPCT/InkStream/issues/23) | +| P2 | 关系面板不随索引提交刷新,查询失败可显示为空结果 | `BacklinksPanel.tsx:58`、`indexService.ts` 查询失败路径 | [关系结果的新鲜度与错误](https://github.com/KRPCT/InkStream/issues/24) | + +## 设计漂移与尚未裁定事项 + +旧 PRD 为 v0.2 草案,当前代码为 1.3.0。ORACLE、UI-SPEC、ROADMAP 的旧根引用已失效;Octokit/Device Flow、完整 git2 命令集与现实现不同。rebase 缺失、clone 未有前端入口、反链缺段落上下文,均需要决定“补齐或调整范围”,不能擅自全部列为必须新建的功能。 + +“全应用一个 CodeMirror 实例”与已有摘录/表格辅助编辑视图不符。应先确认文档内容权威与回写规则,不能用实例数量证明不会丢数据。最近 20 个目录路径也不等于永久、海量的项目档案系统。 + +系统 git/pandoc 缺应用级超时/取消是需进一步实验的恢复改进;连接句柄长期占用、特定输入法与材质帧率未在本轮得到运行证据,不列为已复现事故。 + +## 重构前的架构判断 + +优先深化 DocumentSession,把正文身份、修订、保存结果、关闭与外部冲突放到同一可验收 interface。其次是 WorkspaceSession,协调切换目标、监听与索引生命周期;再收敛 WorkbenchNavigation 的有效视图。全部是待讨论候选,不是已经批准的新接口。 + +React/Tauri/CodeMirror 暂时保留,CSS 为材质原型的起点。真正折射、虚拟化数量、三平台帧率与可访问性预算需要后续明确指标;本轮视觉样例只回答布局与材料方向。 + +## 决策入口 + +[UIUX 重构决策地图](https://github.com/KRPCT/InkStream/issues/16) 使用原生子 issue;[文档与开发范围确认](https://github.com/KRPCT/InkStream/issues/19) 原生依赖于[项目含义](https://github.com/KRPCT/InkStream/issues/17)和[原型布局比较](https://github.com/KRPCT/InkStream/issues/18)。未获得用户回答的票据保持开放。 + +第一轮已完成项目含义裁定;会话、记录位置与后续轮次保存在[项目切换与记录位置](https://github.com/KRPCT/InkStream/issues/22)。用户进一步要求先补齐旧 PRD 的全部 v1 用户功能并验收,之后才正式重构 UI;覆盖表见 [LEGACY-PRD-COVERAGE.md](LEGACY-PRD-COVERAGE.md)。18 个缺陷/改进票据不替代全部旧功能的验收清单。 + +用户已整体确认记录并进入DDD/BDD计划,相关方向决策票已关闭;[旧v1功能验收](https://github.com/KRPCT/InkStream/issues/25)与缺陷/功能票仍开放。[光学技术验证](https://github.com/KRPCT/InkStream/issues/39)由旧v1验收阻塞,尚未执行。当前原型类型检查与专用构建通过;浏览器检查了三布局、项目搜索/切换、低透明和档案浮层,浮层前后正文几何一致,浏览器未报告脚本错误。以上不是生产保存、真实折射或原生平台验收。 + +
diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md new file mode 100644 index 0000000..bb09d42 --- /dev/null +++ b/docs/CURRENT-STATE.md @@ -0,0 +1,39 @@ +# 当前状态与证据 + +核对日期:2026-09-13。当前版本配置为 **2.1.0**,用户已确认合并并发布。合并记录见 [PR 49](https://github.com/KRPCT/InkStream/pull/49),安装包和自动更新清单见 [2.1.0 发行页](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0);实际就绪状态以上述页面为准。 + +## 2.0.0 历史发布基线 + +- [PR 40](https://github.com/KRPCT/InkStream/pull/40) 已合并。项目档案、独立会话/草稿、本机索引及文稿/Git/学术流程修复已进入 2.0.0。 +- 图标采用用户选定的双层竖板与墨带 SVG。项目 popup 使用纸白、石墨灰、灰绿,已清理两处装饰英文。 +- 「视图」菜单实时勾选反馈打字机和专注模式,应用内更新公告包含 2.0.0。 +- [合并前 CI](https://github.com/KRPCT/InkStream/actions/runs/34709938076)、[main CI](https://github.com/KRPCT/InkStream/actions/runs/34710877435)、[Release](https://github.com/KRPCT/InkStream/actions/runs/34710891782) 均通过。 +- Windows x64、macOS Apple Silicon、Linux x64 安装包及自动更新清单已发布。自动更新载荷签名不等于操作系统发行者代码签名。 + +## 尚需专项验收 + +24 个等待合并的实现票已关闭。以下 11 个专项票仍开放,发布或 CI 通过不取代其验收。 + +| 范围 | 入口 | 尚需证据 | +| --- | --- | --- | +| 大文档 | [响应性契约](https://github.com/KRPCT/InkStream/issues/14) | 代表性输入的端到端耗时与峰值内存 | +| 整体 v1 | [逐项验收](https://github.com/KRPCT/InkStream/issues/25) | 组合流程、原生输入与平台矩阵 | +| Typst | [编译与预览](https://github.com/KRPCT/InkStream/issues/26) | 连续编辑、切文档、失败修正的真实渲染 | +| 文献 | [格式与联动](https://github.com/KRPCT/InkStream/issues/29) | 类型/缺字段矩阵、真实库联动 | +| 真实账户 | [Device Flow](https://github.com/KRPCT/InkStream/issues/36)、[评论线程](https://github.com/KRPCT/InkStream/issues/37) | OAuth/keyring、真实权限和获授权的发送 | +| 主题 | [导入与回退](https://github.com/KRPCT/InkStream/issues/38) | 真实主题与新工作台可读性 | +| 光学与平台 | [CSS/WebGL 与降级](https://github.com/KRPCT/InkStream/issues/39) | 原生 WebView、GPU、缩放及长时表现 | +| UI | [原型差距](https://github.com/KRPCT/InkStream/issues/41) | 本轮多栏已实现并完成 Windows ComputerUse;严格并排对照和密集项目矩阵继续保留 | +| 图标/popup | [SVG 图标](https://github.com/KRPCT/InkStream/issues/42)、[项目弹窗](https://github.com/KRPCT/InkStream/issues/43) | 已补 Windows 亮暗工作台/浮层证据;安装图标与完整弹窗矩阵继续保留 | + +## 本轮顺序与证据 + +[任务地图](https://github.com/KRPCT/InkStream/issues/45):文档/issues 同步 → DDD/BDD 多栏实现 → 自动测试 → ComputerUse 用户验收。用户已要求实施;旧“仅规划”“全部外部验收后才做 UI”已被后续指令覆盖。 + +历史审计、旧计划和旧验证文件只代表各自日期。原型的示例数字与假数据不构成产品事实。Vitest、原生测试、浏览器和 ComputerUse 分别说明边界;ComputerUse 不自动证明物理 IME 或其它系统已通过。 + +实施工作树为 `D:/Github/InkStream-workbench-v2`;`D:/Github/InkStream` 保留用户测试分支的未提交代码。 + +## 多栏分支完成状态 + +已实现项目轨、概览/文稿/文献/版本目的地、文献详情与明确插入、键盘标签导航、布局和显示收敛。1910 项前端断言、41 项显式绑定、类型/Lint/前端/原生构建通过;三轮 ComputerUse 已完成并清理进程。实际截图、发现与修复、证据限制见 [验收记录](WORKBENCH-ACCEPTANCE.md)。这些工作台行为验收对应开发提交 `09b32ff`,其[三平台 CI](https://github.com/KRPCT/InkStream/actions/runs/34719252134)已通过;2.1.0 追加版本与发行说明。后续发布由用户明确授权,合并与发行入口见本页顶部。 diff --git a/docs/DDD-PLAN.md b/docs/DDD-PLAN.md new file mode 100644 index 0000000..487d36d --- /dev/null +++ b/docs/DDD-PLAN.md @@ -0,0 +1,24 @@ +# DDD 工作台演进计划 + +2026-09-13。2.0.0 的文档会话、项目会话、索引与 Git 修复已合并。本计划描述其后的多栏工作,不把已实现项目档案重新列为未来实体。 + +## 职责与边界 + +术语唯一来源为 [CONTEXT](../CONTEXT.md),代码职责见 [ARCHITECTURE](ARCHITECTURE.md)。主编辑器拥有正文,项目协调器拥有切换与恢复,工作台只管理目的地和布局,文献浏览管理只读库快照与资料选择。正文和项目身份不复制到页面私有模型中。 + +## 纵向切片 + +| 切片 | 用户结果 | 复用边界 | +| --- | --- | --- | +| 项目轨与导航 | 快速选择真实项目,失败保留文稿并可理解原因 | 项目 actions/session,工作台状态 | +| 概览/文稿/版本 | 查看真实项目资料与 Git 历史,再返回原文稿 | 现有文档与 Git stores/flows | +| 文献详情 | 选中只浏览,明确插入才改当前文稿 | Zotero 库身份与已有引用编辑入口 | +| 布局与键盘 | 栏宽、折叠、Tab/方向键和窄视口保持可用 | 现有布局恢复/写回及能力降级 | + +每个切片用 Given/When/Then 说明可观察行为,先证明失败,再完成实现。规格与自动检查逐项映射,ComputerUse 是独立用户层验收。未绑定或未执行的场景不能被其它测试通过覆盖。 + +## 约束 + +主 EditorView 常驻;视图切换不释放正文或清 dirty。项目切换继续先保存与快照,失败停留。库切换使过期查询和选择失效。缩放不覆盖保存的栏宽。保持当前文件格式与元数据版本,本轮不引入通用 DDD 框架或新的正文数据库。 + +实施与完成状态统一见 [EXECUTION-PROGRESS](EXECUTION-PROGRESS.md)。原 2026-09-11 计划保存在本地审计源,不作为当前待实施清单。 diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md new file mode 100644 index 0000000..92e6490 --- /dev/null +++ b/docs/DECISIONS.md @@ -0,0 +1,24 @@ +# 已确认决定与当前范围 + +截至 2026-09-13。词汇见 [CONTEXT](../CONTEXT.md),实现状态见 [CURRENT-STATE](CURRENT-STATE.md)。 + +| 决定 | 当前约束 | 来源 | +| --- | --- | --- | +| B+A+C | B 日常写作、A 左侧档案浮层、C 学术资料与正文相邻的工具布局 | [原型选择](https://github.com/KRPCT/InkStream/issues/18) | +| 项目 | 独立身份关联内容目录,Git 根单独识别,无 Git 可写作 | [项目定义](https://github.com/KRPCT/InkStream/issues/17) | +| 切换 | 先保存/暂存草稿,失败停留,成功恢复独立会话;每窗口一个活动项目 | [项目会话](https://github.com/KRPCT/InkStream/issues/22) | +| 数据位置 | 元数据、会话和索引在本机应用数据目录,正文留在内容目录 | 同上 | +| 材料/配色 | 正文安静纸面;导航/工具可有材料层次;纸白、石墨灰、低饱和灰绿 | 原方向及用户 2026-09-13 选择 | +| 图标 | 用户最终选择双层竖板与墨带,SVG 路径/渐变重绘 | [图标任务](https://github.com/KRPCT/InkStream/issues/42) | +| popup | 背景独立重做、清理英文;“仅图标拟物”限定新增拟物改动,不自动撤销整个工作台材料目标 | [弹窗任务](https://github.com/KRPCT/InkStream/issues/43) | +| 顺序 | 用户已允许先完成本地实现与 UI,真实账号验收独立保留 | 后续交接与发布指令 | +| 2.0.0 | 已完成合并 main、推送标签和公开发布 | [PR 40](https://github.com/KRPCT/InkStream/pull/40)、[Release](https://github.com/KRPCT/InkStream/releases/tag/v2.0.0) | +| 本轮 | 先更新文档/issues,按 DDD/BDD 对齐多栏,测试后 ComputerUse 验收 | 当前请求、[任务地图](https://github.com/KRPCT/InkStream/issues/45) | + +原型累计字数、目标进度、文献与版本记录是演示数据;产品展示真实状态或明确的空/失败状态。 + +当前按单一主编辑器与多个工具栏推进。“对齐现有多栏”不自动增加多个文稿同时编辑;新的正文权威或焦点模型需要明确需求。 + +本轮实现选择:日常版本时间线保留项目导航,高级分支/差异/远端操作仍使用全宽版本管理。新会话采用 220/260px 两侧默认宽度,既有项目记忆不改写。模式菜单使用独立弹出层以兼容状态栏横向滚动;文献详情可滚动,插入操作常驻底部。原生发现与修复见 [验收记录](WORKBENCH-ACCEPTANCE.md)。 + +2026-09-13 后续决定:用户确认将已验收多栏实现合并 main,并采用 **2.1.0** 发布。该授权替代开发阶段暂不合并/发布的范围。版本与公告同步后,发布链路以 [PR 49](https://github.com/KRPCT/InkStream/pull/49) 和 [Release](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0) 留证。 diff --git a/docs/EXECUTION-PLAN.md b/docs/EXECUTION-PLAN.md new file mode 100644 index 0000000..d133ee0 --- /dev/null +++ b/docs/EXECUTION-PLAN.md @@ -0,0 +1,14 @@ +# 多栏工作台执行计划 + +基线 main / 2.0.0 / ca4c214。工作入口为[任务地图](https://github.com/KRPCT/InkStream/issues/45)。 + +| 顺序 | 产出 | 完成判据 | +| --- | --- | --- | +| 1 | 产品文档、领域词汇、架构、覆盖矩阵与 issues | 逐份审计、过时状态清理、链接有效,保留未验收边界 | +| 2 | 工作台行为规格与失败测试 | 用户意图、失败恢复和数据不变条件可追溯 | +| 3 | 多栏导航/概览/文献/版本及布局实现 | 用户动作可到达真实功能,状态与文稿归属一致 | +| 4 | 自动验证 | 相关测试、类型、lint/build及必要回归真实通过 | +| 5 | ComputerUse | 实际窗口中完成写作/资料/版本/布局流程,保存截图、结果与限制 | +| 6 | 文档/issues 收尾 | 只把实际达到判据的任务标记完成,列明未验证项 | + +进程盘点、硬超时、串行重任务与回收沿用现有 acceptance runner。用户原测试工作树保留,当前实现位于 InkStream-workbench-v2。开发验收之后,用户已追加授权合并 main 并发布 2.1.0;合并与资产就绪见 [PR 49](https://github.com/KRPCT/InkStream/pull/49)、[发行页](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0)。 diff --git a/docs/EXECUTION-PROGRESS.md b/docs/EXECUTION-PROGRESS.md new file mode 100644 index 0000000..d44bc90 --- /dev/null +++ b/docs/EXECUTION-PROGRESS.md @@ -0,0 +1,9 @@ +# 当前执行进度 + +2026-09-13。2.0.0 已发布,PR 40 已合并;发布与专项验收边界见 [CURRENT-STATE](CURRENT-STATE.md)。 + +本轮[多栏工作台对齐](https://github.com/KRPCT/InkStream/issues/45)已完成文档/issue 同步、DDD/BDD 实现、自动测试与三轮 ComputerUse 验收。共审阅 42 份文档,保留 102 条旧需求索引,并逐项同步既有 issues。最终 1910 项前端断言、41 项显式绑定及类型/Lint/前端/原生构建通过。 + +ComputerUse 发现并修复模式菜单裁剪、退出简易模式的工具空白、窄文献详情操作被挤出视口等问题。最终截图、真实文件回读与进程清理见 [验收记录](WORKBENCH-ACCEPTANCE.md)。物理 IME、真实账号和完整跨平台矩阵仍由原专项票推进,未标成整体 BDD 完成。 + +当前工作树:D:/Github/InkStream-workbench-v2;分支 codex/workbench-ux-v2。D:/Github/InkStream 保留用户原测试代码与未提交修改;用户后续明确授权将 [PR 49](https://github.com/KRPCT/InkStream/pull/49) 合并到 main 并发布 [2.1.0](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0),软件内更新公告和四份版本声明同步。此授权覆盖原开发阶段的“不自动发布”范围;实际发布结果见发行页。 diff --git a/docs/IMPLEMENTATION-ORDER.md b/docs/IMPLEMENTATION-ORDER.md new file mode 100644 index 0000000..30eea13 --- /dev/null +++ b/docs/IMPLEMENTATION-ORDER.md @@ -0,0 +1,10 @@ +# 当前实施顺序 + +2026-09-13:2.0.0 已合并发布,旧缺陷实现阶段不再列为未开始。发布与未验收边界见 [CURRENT-STATE](CURRENT-STATE.md)。 + +1. 完成[文档与 issue 同步](https://github.com/KRPCT/InkStream/issues/46),清理过时事实并保留历史证据。 +2. 完成[多栏 DDD/BDD 实现](https://github.com/KRPCT/InkStream/issues/47):明确导航/文档/工具职责,以用户行为场景和失败测试驱动。 +3. 运行与改动相称的自动检查,按真实失败修复。 +4. 通过 [ComputerUse 用户验收](https://github.com/KRPCT/InkStream/issues/48),记录实际点击、键盘、拖动与写作结果。 + +真实账户、物理 IME 与跨平台完整矩阵由专项票据推进。当前指令已授权本地 UX 实现,不再以所有外部验收为前置。 diff --git a/docs/LEGACY-PRD-COVERAGE.md b/docs/LEGACY-PRD-COVERAGE.md new file mode 100644 index 0000000..6c5d1ed --- /dev/null +++ b/docs/LEGACY-PRD-COVERAGE.md @@ -0,0 +1,120 @@ +# 旧 PRD 功能覆盖与验收索引 + +更新于 2026-09-13,基线 2.0.0。旧审计中的“缺少 rebase/clone/Codex/项目会话”等实现缺口已经合并发布,不能继续当作当前待开发项。原始用户要求的编号与内容保留在下表;状态区分实现与专项验收,不声称 v1 总验收已完成。 + +| 编号 | 原承诺 | 当前状态与后续 | +| --- | --- | --- | +| US-S1 | 新建默认 `.md`,立即输入并 Live Preview | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-S2 | 输入 `[[` 出现 vault 模糊候选,选择插入文件名 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-S3 | Ctrl+点击 wiki-link 跳转,不存在则提示创建 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-S4 | 反链显示来源文件及引用段落 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-S5 | Ctrl+G 查看全库链接网络 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-A1 | Ctrl+Shift+Z 通过 BBT picker 插入 `[@citekey]` | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| US-A2 | Citation Panel 汇总当前引用,未解析标红 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| US-A3 | `:::typst` 块实时 SVG,位于块右侧 | 两种 Typst 块、预览和状态已有实现;[真实渲染验收](https://github.com/KRPCT/InkStream/issues/26)保留。 | +| US-A4 | Insert Bibliography 插入 ``,展开 GB/T 7714/APA/Vancouver | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| US-A5 | 设置 Web API key 后离线阅读文献缓存 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| US-C1 | 打开项目自动显示章→场景树,带字数及三种状态 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-C2 | F11 Focus Mode 淡化非当前段落 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-C3 | 设置今日目标 2000,状态栏实时进度 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-C4 | 在 Codex 添加角色,提及时高亮并悬停角色卡 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| US-C5 | Git Graph 比较两个 draft 分支并看句级差异 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| US-F1 | `/math` 插入块并实时显示 KaTeX | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| US-F2 | typst fenced block 公式实时 SVG | 两种 Typst 块、预览和状态已有实现;[真实渲染验收](https://github.com/KRPCT/InkStream/issues/26)保留。 | +| US-F3 | LaTeX 公式 Live Preview,并导出 PDF 片段 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| MODE-01 | Standard/Academic/Creative 随时切换;布局、工具及指标预设;不绑定格式、不丢数据 | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| STD-01 | 按 Git 根展示工作区文件树 | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| STD-02 | 右栏 Outline/Backlinks/Local Graph 三 tab | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| STD-03 | 状态栏显示路径、Git 分支、字数、光标、渲染模式 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| ACA-01 | 左栏上方 Zotero 同步状态,下方文件树 | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| ACA-02 | 右栏 Citation/Typst Preview/Outline | 两种 Typst 块、预览和状态已有实现;[真实渲染验收](https://github.com/KRPCT/InkStream/issues/26)保留。 | +| ACA-03 | 学术工具栏:引用、脚注、参考文献、公式编号 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| ACA-04 | 状态栏:引用数、未解析提示、Typst 编译状态 | 两种 Typst 块、预览和状态已有实现;[真实渲染验收](https://github.com/KRPCT/InkStream/issues/26)保留。 | +| CRE-01 | 左栏章→场景树含字数/状态 | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| CRE-02 | 右栏 Codex 角色/地点/设定及场景概要 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| CRE-03 | 编辑器顶部可折叠场景概要卡及 Focus Mode | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| CRE-04 | 状态栏今日字数/目标、章节场景计数、定稿色点 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| EDIT-01 | 纯文本为权威,渲染不偷换内容 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| EDIT-02 | Source/Live Preview 运行时切换,命令与状态栏一致 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| EDIT-03 | 标题、粗体、列表、链接、fenced、wiki-link、citation 最终样式 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| EDIT-04 | 光标所在行展开源代码 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-01 | Markdown/Obsidian 变体 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-02 | LaTeX | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-03 | Typst | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-04 | JavaScript / TypeScript | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-05 | Python | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-06 | Rust | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-07 | JSON | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-08 | YAML | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-09 | HTML | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-10 | CSS | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LANG-11 | Shell | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| FMT-01 | 新建默认 `.md` + UTF-8 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| FMT-02 | frontmatter `language: markdown/latex/typst/richtext` 整体切语言 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| FMT-03 | richtext 简化 Markdown,B/I/U/链接工具栏,物理仍 Markdown | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| `git_status` | `src-tauri/src/git/status.rs`、`src/ipc/git.ts`、`SidebarGitPanel.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_branch_list` | `src-tauri/src/git/branch.rs`、`BranchManager.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_log` | `src-tauri/src/git/log.rs`、`graph/CommitGraphList.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_diff` | `src-tauri/src/git/diff.rs`、`FileDiffPanel.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_commit` | `src-tauri/src/git/commit.rs` 的系统 `git commit -S`、`src/editor/gitActions.ts` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_checkout` | `src-tauri/src/git/refops.rs`、`BranchManager.tsx`、`GitContextMenu.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_merge` | `src-tauri/src/git/commit.rs`、`MergeResolver.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_rebase` | `src/types/ipc.ts`、`src-tauri/src/lib.rs` 与 `src-tauri/src/git/` 未见本地 rebase command | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_cherry_pick` | `src-tauri/src/git/commit.rs`、`GitContextMenu.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_stash` | `src-tauri/src/git/stash.rs` 提供 save/list/pop/drop,`gitActions.ts` 有 stash 动作 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_tag` | `src-tauri/src/git/refops.rs` create/delete、`GitContextMenu.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_reset` | `src-tauri/src/git/refops.rs` soft/mixed/hard、`GitContextMenu.tsx` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_clone` | `src-tauri/src/git/remote.rs`、`src/ipc/git.ts:gitClone`;`docs/git.md` 明示无应用内克隆对话框 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_push` | `src-tauri/src/git/remote.rs`、`src/editor/gitActions.ts` | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_pull` | `src-tauri/src/git/remote.rs` fetch + ff-only merge | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| `git_fetch` | `src-tauri/src/git/remote.rs`、Git 工具条/侧栏 | 补全实现已随 2.0.0 合并,对应实现票已关闭;组合用户流程继续归[总验收](https://github.com/KRPCT/InkStream/issues/25)。 | +| GRAPH-GIT-01 | 图谱/提交详情/文件 diff 三栏 | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| GRAPH-GIT-02 | 右键菜单驱动所有操作 | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| GRAPH-GIT-03 | Find Widget | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| GRAPH-GIT-04 | Filter Branches | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| GRAPH-GIT-05 | Repository Settings | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| GRAPH-GIT-06 | 点击 commit 在编辑器右侧自动打开 diff | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| GRAPH-GIT-07 | 菜单 View→Git Graph 与 Ctrl+Shift+G | 已有发布功能;本轮对齐多栏呈现和交互,[工作台任务](https://github.com/KRPCT/InkStream/issues/47)记录新增证据。 | +| GH-01 | OAuth Device Flow 登录 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| GH-02 | PAT 备用登录 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| GH-03 | 已登录 gh CLI 备用登录 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| GH-04 | Issue 浏览、评论、创建 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| GH-05 | PR 浏览、评论、创建 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| GH-06 | PR diff 内嵌编辑器 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| GH-07 | PR review 评论回复 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| DIFF-01 | 中英句级分词→LCS→段落对齐→语义高亮 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| DIFF-02 | prose-aware 三向冲突解决器 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LINK-01 | wiki-link 基础目标及 alias | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LINK-02 | `#heading` 链接定位 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LINK-03 | `^block-id` 链接定位 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LINK-04 | SQLite FTS5 全库索引 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LINK-05 | 编辑增量索引 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LINK-06 | 反链含未链接提及 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LINK-07 | 全库图谱 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| LINK-08 | 当前文档 Local Graph | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| ZOT-01 | 本地 CAYW HTTP 主路,需 Zotero+BBT | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| ZOT-02 | Web API 增量、离线 SQLite 缓存 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| ZOT-03 | 命令面板 Cite from Zotero + 引用面板 + Insert Bibliography | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| ZOT-04 | Typst `#cite()` / LaTeX `\cite{}` 自动联动 | 已有发布实现;真实账户/文献矩阵见 [文献](https://github.com/KRPCT/InkStream/issues/29)、[登录](https://github.com/KRPCT/InkStream/issues/36)、[评论](https://github.com/KRPCT/InkStream/issues/37)。 | +| USER-01 | §1:研究者可在一 App 完成引用、公式、Git 多稿及 Prose Diff | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| USER-02 | §1:创作者章节、Codex、专注及多稿合并 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| USER-03 | §1/§9:Obsidian 主题导入即用、兼容主题 | 主题变量导入已发布;[真实主题矩阵](https://github.com/KRPCT/InkStream/issues/38)保留,非任意 CSS 兼容声明。 | +| USER-04 | §1:混合 Markdown 与多语言代码写作 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| REL-01 | §2/§3:切模式/操作不丢纯文本 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| REL-02 | §4/§5:工作区身份与索引正确 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| REL-03 | §4:Git 认证和远程动作可信 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| REL-04 | 桌面编辑的文件属性与大文件响应 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| REL-05 | 旧开发标准所需可执行验证基线 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | +| REL-06 | 后续已加入的功能不得因补旧 PRD 回归 | 已有发布实现或保持的行为约束;完整平台/组合场景仍由[总验收](https://github.com/KRPCT/InkStream/issues/25)逐项收口。 | + +## 技术约束的当前解释 + +- 保留主 CodeMirror、纯文本权威与文档身份/保存/恢复边界;辅助视图不是独立正文权威。 +- 配色按当前用户确认的纸白、石墨灰、灰绿;旧固定模式色不阻止 UX 收敛,语法/错误等有功能的颜色仍保留。 +- 保留当前 Tauri/React/Zustand、类型约束与 CSS token;版本从实际配置读取。 +- Git 由 git2 与受管系统 git 共同承担;GitHub REST/Device Flow 使用现有原生实现,不要求旧指定 SDK。 +- GSD 与已缺失旧 ORACLE/UI-SPEC 的历史引用不再是当前强制入口。当前遵循 wayfinder、DDD/BDD 与明确用户要求。 + +## 旧范围边界 + +原路线图的云协作、插件市场、多项目后台并行等未来设想不因发布号为 2.0.0 而自动实现或纳入本轮。已出现的阅读/书架/终端/导出/主题/更新能力按现有产品文档维护。完整原始审计保存在本地 governance-originals,旧失败与旧未验证记录不被改写为通过。 diff --git a/docs/REVIEW-SUMMARY.md b/docs/REVIEW-SUMMARY.md new file mode 100644 index 0000000..517cd9d --- /dev/null +++ b/docs/REVIEW-SUMMARY.md @@ -0,0 +1,12 @@ +# 当前文档审阅入口 + +多栏实现、文档同步和 ComputerUse 验收已完成;用户后续确认合并并发布 2.1.0。当前合并/发行状态见 [PR 49](https://github.com/KRPCT/InkStream/pull/49)和[发行页](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0)。 + +- 当前事实与证据:[CURRENT-STATE](CURRENT-STATE.md)。 +- 产品要求:本地 `PRD.md`(遵守既有忽略规则,不随源码发布);词汇:[CONTEXT](../CONTEXT.md)。 +- 职责与规则:[ARCHITECTURE](ARCHITECTURE.md)、[DECISIONS](DECISIONS.md)。 +- UX 方向:[UIUX-DIRECTION](UIUX-DIRECTION.md)。 +- 执行:[DDD-PLAN](DDD-PLAN.md)、[EXECUTION-PLAN](EXECUTION-PLAN.md)、[EXECUTION-PROGRESS](EXECUTION-PROGRESS.md)。 +- 旧承诺:[LEGACY-PRD-COVERAGE](LEGACY-PRD-COVERAGE.md);行为自动化边界:[AUTOMATION](specs/AUTOMATION.md)。 + +历史审计保留其原日期,不把旧未实施/草稿 PR 状态作为当前指令。 diff --git a/docs/UIUX-DIRECTION.md b/docs/UIUX-DIRECTION.md new file mode 100644 index 0000000..37f0bbd --- /dev/null +++ b/docs/UIUX-DIRECTION.md @@ -0,0 +1,28 @@ +# UIUX 方向与当前对齐范围 + +截至 2026-09-13,B+A+C 方向已确认并部分实现于 2.0.0。2.1.0 补齐本轮多栏 UX 与功能;发布/验收状态见 [CURRENT-STATE](CURRENT-STATE.md)。 + +## 已确认方向 + +B 是日常写作界面:项目轨、项目内导航、大面积文稿和上下文工具。A 是点击或 Ctrl+Alt+P 展开的项目浮层,不推挤正文。C 让学术材料/引用与文稿相邻。三模式同等,默认通用写作。 + +项目有独立名称、封面、收藏及会话;关联一个内容目录,Git 根独立。切换先保存与快照,失败停留,成功恢复目标会话;这些已发布的约束继续保持。 + +## 后续用户修订 + +- 采用最终双层竖板与流动墨带 SVG,原型品牌图和早期波浪档案图标已退役。 +- 配色方向为中性纸白、石墨灰、低饱和灰绿。 +- popup 背景另行设计,2.0.0 已去掉斜纹/模糊底板及两处英文装饰。本轮不把“只调整图标拟物”误读为删除工作台全部既有材料。 +- 先完成本地实现与 UI,真实账号验收独立保留;旧 v1 全验收不再阻塞本轮 UI。 + +## 本轮对齐 + +工作区视图、文档标签、模式的职责要清楚;原型的概览/文稿/文献/版本入口应由真实功能承接。项目轨可见多个真实项目。文献先选中看详情,再明确插入。去除重复控件和无用英文,保留有意义的术语与状态。正文的字体、留白及工具密度按原型节奏收敛。 + +原型中的假字数、进度、文献与提交记录不直接移植。当前 SVG/popup 方向保留,具体差距和通过情况由行为测试及 ComputerUse 记录。 + +## 验收条件 + +覆盖长名称、多个项目、空/加载/失败、三模式、键盘焦点、面板拖动/折叠、缩放和 reduced 偏好。视图切换不丢正文、选区与撤销;项目切换继续受保存协议保护。 + +参考源为用户提供的 inkstream-uiux.html,以及本地 codex/prototype-uiux-direction 分支的原型源码;它们是讨论与对照材料,不是额外用户指令。原型图片或浏览器桩不等同原生/真实账号/物理 IME 验收。 diff --git a/docs/VERIFICATION-2026-09-12.md b/docs/VERIFICATION-2026-09-12.md new file mode 100644 index 0000000..e21119e --- /dev/null +++ b/docs/VERIFICATION-2026-09-12.md @@ -0,0 +1,180 @@ +# 历史记录:VERIFICATION-2026-09-12.md + +此文件保留原日期、原工作树与当时证据。2.0.0 的实现/合并/发布现状见 [CURRENT-STATE](CURRENT-STATE.md);下面“尚未实施/未推送/草稿 PR”等文字是历史状态,不作为当前指令。 + +
+查看原始历史记录 + +# 2026-09-12 续接验证与旧 v1 验收索引 + +本报告记录当前 `codex/con-repair-v1` 的未提交工作区。原分支未改变;没有推送或触发 GitHub Actions。 + +**旧 v1 总验收 #25 仍开放;正式 UIUX、新项目档案与持久草稿尚未开始。** 测试文件关联只用于定位证据,不表示一整行用户需求已经验收。 + +## 已核验结果 + +| 检查 | 结果 | 本机证据 | +|---|---|---| +| typecheck | 通过 | [报告](../coverage/acceptance/ci-typecheck-win32-1789192346084-58100/ci-summary.json) | +| lint | 通过 | [报告](../coverage/acceptance/ci-lint-win32-1789192465348-46236/ci-summary.json) | +| test | 1707/1707,无失败、跳过或超时 | [报告](../coverage/acceptance/ci-test-win32-1789192674321-41828/ci-summary.json) | +| build | 通过 | [报告](../coverage/acceptance/ci-build-win32-1789192471654-55828/ci-summary.json) | +| Windows Rust | 208 通过;1 个进程 helper 由其他测试调用;复用最近一次原生报告 | [报告](../coverage/acceptance/rust-win32-1789187386473-59604/rust-summary.json) | +| Windows 原生学术流程 | 22 个检查点通过;使用合成 BBT 与模拟 composition 事件 | [记录](../coverage/acceptance/native-academic-20260912-135519/academic-checks.json) | +| Windows 原生分支比较 | 15 个已通过检查点;复用此前实际 WebView 证据 | [记录](../coverage/acceptance/native-compare-20260912-114656/browser-checks.json) | + +新增修复:实时预览只隐藏 Markdown 转义前缀,保留 `[1]`、`[J]` 等字面标点;活动行、源码模式、代码块、IME 与撤销保持原文。先复现 4 项失败,再通过 100 项相关回归,最后重建并以原生截图核验。 + +[修复后原生截图](../coverage/acceptance/native-academic-20260912-135519/academic-bibliography.png)。最终源文件校验值与进程/端口结果见[工作区汇总](../coverage/acceptance/continuation-20260912/summary.json)和[源码哈希](../coverage/acceptance/continuation-20260912/source-sha256.json)。 + +生产 `dist` 已检查,不含测试桥接入口。两次本轮原生测试的 HEAD、index、工作树快照一致;其子进程已回收,1420/9229/23119 端口关闭。 + +## 仍未满足的进入条件 + +Linux:本机 WSL 列出 Ubuntu 24.04,但实例启动返回 `Wsl/Service/CreateInstance/CreateVm/HCS/HCS_E_SERVICE_NOT_AVAILABLE`。仅作只读诊断,没有启用系统功能或修改虚拟化服务。macOS:Darwin 进程回收修复仍无该平台执行证据。 + +外部服务:构建环境未提供 `INKSTREAM_GITHUB_CLIENT_ID`。真实 GitHub OAuth、系统钥匙串、Zotero/BBT 与 Web API、授权远程 clone/Git/PR 回复仍未验收;本地合成服务不能替代。此前已提出配置依赖,本轮没有重复索取凭据,也没有发送远程评论。 + +本地完整场景:基础写作、索引/真实 watcher、长篇创作、全部 Git 入口、主题导入与既有能力的贯通流程仍需逐行核对或归档。`scripts/acceptance/bindings.mjs` 中原有 partial/pending 没有被批量改成完成。 + +## 证据组与剩余场景 + +| 组 | 范围 | 相关检查(不等于完整覆盖) | 尚缺证据 | +|---|---|---|---| +| DOC | 文档保存与生命周期 | [document-session.integration.test.tsx](../src/test/document-session.integration.test.tsx)、[documentFileMutations.test.ts](../src/editor/documentFileMutations.test.ts)、[exitGuard.test.ts](../src/editor/exitGuard.test.ts) | DOC-01–08 的完整场景与磁盘往返证据仍须逐条归档。 | +| WIKI | 链接补全与定位 | [wikiLinkComplete.test.ts](../src/editor/livepreview/wikiLinkComplete.test.ts)、[wikiNavigation.test.ts](../src/editor/livepreview/wikiNavigation.test.ts)、[wikiAnchor.test.ts](../src/editor/livepreview/wikiAnchor.test.ts) | 组合输入、补全产物到真实点击、原生可见落点仍须贯通核对。 | +| INDEX | 索引与关系查询 | [indexLifecycle.test.ts](../src/ipc/indexLifecycle.test.ts)、[indexRefresh.test.ts](../src/ipc/indexRefresh.test.ts)、[BacklinksPanel.paragraph.test.tsx](../src/components/workbench/BacklinksPanel.paragraph.test.tsx)、[render.test.ts](../src/graph/render.test.ts) | WS-01–08 的真实 watcher、跨库索引及面板联动仍须归档。 | +| CITE | 引用与学术命令 | [academicActions.integration.test.ts](../src/editor/academicActions.integration.test.ts)、[academicActions.test.ts](../src/editor/academicActions.test.ts)、[citations.test.ts](../src/editor/citations.test.ts) | 22 个原生检查使用本地合成 BBT;真实 Zotero/BBT picker 与引用面板全场景待验收。 | +| BIB | 参考文献与转义显示 | [bibliography.integration.test.ts](../src/editor/bibliography.integration.test.ts)、[cslFormat.standard.test.ts](../src/editor/cslFormat.standard.test.ts)、[escapePreview.test.ts](../src/editor/livepreview/escapePreview.test.ts) | 真实文献库、多文献类型与三种样式的端到端符合性仍须核验。 | +| ZOT | 文献库账户与离线同步 | [ZoteroLibraryPanel.account.test.tsx](../src/components/workbench/ZoteroLibraryPanel.account.test.tsx)、[ZoteroLibraryPanel.test.tsx](../src/components/workbench/ZoteroLibraryPanel.test.tsx) | 真实 Web API 同步、断网重启、系统凭据库待验收;合成 HTTP/SQLite 不替代真实账户。 | +| TYP | Typst 预览与首次加载 | [TypstFlow.test.tsx](../src/components/workbench/TypstFlow.test.tsx)、[languages.typst.test.ts](../src/editor/languages.typst.test.ts)、[typstClient.lifecycle.test.ts](../src/editor/livepreview/typst/typstClient.lifecycle.test.ts) | 首次 .typ 原生语法树已有证据;完整双语法、多修订、故障恢复场景仍须逐条归档。 | +| EQ | 公式、编号与导出 | [equationNumbering.flow.test.tsx](../src/editor/equationNumbering.flow.test.tsx)、[equationPdf.flow.test.tsx](../src/editor/export/equationPdf.flow.test.tsx)、[slashCommand.test.ts](../src/editor/livepreview/slashCommand.test.ts) | 公式导出核心已有历史实机证据;原生保存对话框、重开与各编号场景仍须归档。 | +| CRE | 长篇创作 | [chapterTree.test.ts](../src/editor/chapterTree.test.ts)、[writingMetrics.test.ts](../src/editor/writingMetrics.test.ts)、[CodexPanel.lifecycle.test.tsx](../src/components/workbench/CodexPanel.lifecycle.test.tsx)、[focusMode.test.ts](../src/editor/livepreview/focusMode.test.ts) | 章节→Codex→目标→比较/合并→保存重开及物理中文 IME 的贯通验收未完成。 | +| CMP | 完整分支比较 | [BranchCompareView.test.tsx](../src/components/git/BranchCompareView.test.tsx)、[branchComparisonClient.test.ts](../src/editor/branchComparisonClient.test.ts)、[comparisonView.test.ts](../src/editor/comparisonView.test.ts)、[compareText.test.ts](../src/diff/compareText.test.ts) | Windows 双分支、长文、增删改名、分页与未保存缓冲场景已有原生证据;跨平台仍未验证。 | +| SHELL | 模式与工作区入口 | [totalAssembly.test.ts](../src/modes/totalAssembly.test.ts)、[WorkbenchLayout.test.tsx](../src/components/workbench/WorkbenchLayout.test.tsx)、[StatusBar.document.test.tsx](../src/components/workbench/StatusBar.document.test.tsx)、[FileTree.test.tsx](../src/components/workbench/FileTree.test.tsx) | 三模式长会话、能力关闭后继续编辑及全部入口仍须贯通核对。 | +| MD | Markdown 与实时预览 | [inlinePlugin.test.ts](../src/editor/livepreview/inlinePlugin.test.ts)、[escapePreview.test.ts](../src/editor/livepreview/escapePreview.test.ts)、[editorState.renderMode.test.ts](../src/editor/editorState.renderMode.test.ts)、[Toolbar.test.tsx](../src/editor/richtext/Toolbar.test.tsx) | 转义显示已有原生复验;其余语法与模式往返需按旧矩阵逐项归档。 | +| LANG | 多语言与文件格式 | [languages.test.ts](../src/editor/languages.test.ts)、[codeLanguages.test.ts](../src/editor/livepreview/codeLanguages.test.ts)、[frontmatter.test.ts](../src/editor/frontmatter.test.ts) | 各语言真实文件的首次打开、混合写作、保存重开与错误路径仍待逐项验收。 | +| GIT | 本地 Git 生命周期 | [gitActions.test.ts](../src/editor/gitActions.test.ts)、[gitWorktreeMutation.test.ts](../src/editor/gitWorktreeMutation.test.ts) | status/log/branch/diff 有真实 Rust 证据;全部菜单、右键、merge/cherry-pick/tag/reset 的完整生命周期仍需核验。 | +| REB | 本地 rebase | [BranchManager.rebase.test.tsx](../src/components/git/BranchManager.rebase.test.tsx)、[RebaseControls.test.tsx](../src/components/git/RebaseControls.test.tsx)、[gitWorktreeMutation.test.ts](../src/editor/gitWorktreeMutation.test.ts) | Windows Rust 成功/冲突/签名失败恢复通过;Linux/macOS 进程回收与原生入口全流程待验收。 | +| STA | Stash 管理 | [gitStashActions.test.ts](../src/editor/gitStashActions.test.ts)、[StashEntry.test.tsx](../src/components/git/StashEntry.test.tsx) | Rust 临时仓库恢复/冲突/身份通过;真实应用完整保存/恢复/删除手势链待归档。 | +| CLONE | 克隆与打开工作区 | [GitCloneDialog.test.tsx](../src/components/git/GitCloneDialog.test.tsx)、[CloneEntry.test.tsx](../src/components/workbench/CloneEntry.test.tsx) | 本地 Git 克隆与排他发布通过;授权远程、原生进度/取消/成功打开仍需验收。 | +| REMOTE | 远程 Git 与凭据目标 | [git.remote.test.ts](../src/ipc/git.remote.test.ts)、[gitCredential.test.ts](../src/ipc/gitCredential.test.ts)、[gitActions.test.ts](../src/editor/gitActions.test.ts) | 真实授权远程 Git、PAT/gh 与系统凭据链待验收;不代替用户注册或授权。 | +| GITUI | Git 图谱交互 | [layoutGraph.test.ts](../src/components/git/graph/layoutGraph.test.ts)、[builtins.test.ts](../src/commands/builtins.test.ts)、[menuConfig.test.ts](../src/components/workbench/menuConfig.test.ts) | Find、Filter Branches、仓库设置及各入口组合仍需逐项实际操作验收。 | +| AUTH | GitHub Device Flow | [GithubDeviceFlow.test.tsx](../src/components/settings/GithubDeviceFlow.test.tsx) | 需要 InkStream 自有公开 Client ID、真实授权与钥匙串读回;当前环境未配置该变量。 | +| PR | Issue / PR / review | [PrReply.test.tsx](../src/components/git/PrReply.test.tsx)、[CommentThread.scope.test.tsx](../src/components/git/CommentThread.scope.test.tsx) | 授权仓库真实读取、回复后读回、分页权限待验收;远程发送还需明确授权。 | +| MERGE | 三向冲突 | [parseConflicts.test.ts](../src/diff/parseConflicts.test.ts)、[MergeResolver.scope.test.tsx](../src/components/git/MergeResolver.scope.test.tsx) | base/ours/theirs 与全部非冲突上下文的原生合并/取消/保存重开链待验收。 | +| THEME | 主题导入兼容 | [themeImport.test.ts](../src/styles/themeImport.test.ts)、[ImportedTheme.test.tsx](../src/components/settings/ImportedTheme.test.tsx) | 代表性第三方主题、重启保持、取消及失败回退的原生证据仍需逐项归档。 | +| QUALITY | 传输、权限与检查基线 | [documentBudget.integration.test.tsx](../src/editor/documentBudget.integration.test.tsx)、[files.streaming.test.ts](../src/ipc/files.streaming.test.ts)、[files.writing.test.ts](../src/ipc/files.writing.test.ts) | Windows 检查与 Raw 传输已有证据;Unix 文件属性和 macOS 清理未验证,旧 CI 不替代验收。 | +| EXISTING | 后续既有能力 | [HtmlReader.transfer.test.tsx](../src/components/reading/HtmlReader.transfer.test.tsx)、[TerminalPanel.test.tsx](../src/components/terminal/TerminalPanel.test.tsx)、[replaceAll.test.ts](../src/editor/multibuffer/replaceAll.test.ts)、[useUpdaterStore.test.ts](../src/stores/useUpdaterStore.test.ts) | 简易模式、阅读/书架、终端、更新、multibuffer、缩放的完整组合仍需回归归档。 | +| E2E | 跨领域用户流程 | 须复用各子流程并实际贯通 | 前置子流程与真实外部依赖仍开放;不能由测试总数替代完整用户故事。 | + +## 原矩阵逐行索引 + +共 102 行,覆盖原矩阵第 1–7 节全部功能编号;第 8 节技术条款与第 9 节非目标维持原裁定。每行完整接受仍为 false,不能由本表关联自动关闭 #25。详细机器记录见 [legacy-v1-rows.json](../coverage/acceptance/continuation-20260912/legacy-v1-rows.json)。 + +| 原编号 | 证据组 | 完整场景验收 | +|---|---|---| +| US-S1 | DOC | 待完整核验;缺口见对应组 | +| US-S2 | WIKI | 待完整核验;缺口见对应组 | +| US-S3 | WIKI | 待完整核验;缺口见对应组 | +| US-S4 | INDEX | 待完整核验;缺口见对应组 | +| US-S5 | INDEX | 待完整核验;缺口见对应组 | +| US-A1 | CITE | 待完整核验;缺口见对应组 | +| US-A2 | CITE | 待完整核验;缺口见对应组 | +| US-A3 | TYP | 待完整核验;缺口见对应组 | +| US-A4 | BIB | 待完整核验;缺口见对应组 | +| US-A5 | ZOT | 待完整核验;缺口见对应组 | +| US-C1 | CRE | 待完整核验;缺口见对应组 | +| US-C2 | CRE | 待完整核验;缺口见对应组 | +| US-C3 | CRE | 待完整核验;缺口见对应组 | +| US-C4 | CRE | 待完整核验;缺口见对应组 | +| US-C5 | CMP | 待完整核验;缺口见对应组 | +| US-F1 | EQ | 待完整核验;缺口见对应组 | +| US-F2 | TYP | 待完整核验;缺口见对应组 | +| US-F3 | EQ | 待完整核验;缺口见对应组 | +| MODE-01 | SHELL | 待完整核验;缺口见对应组 | +| STD-01 | SHELL | 待完整核验;缺口见对应组 | +| STD-02 | SHELL | 待完整核验;缺口见对应组 | +| STD-03 | SHELL | 待完整核验;缺口见对应组 | +| ACA-01 | ZOT | 待完整核验;缺口见对应组 | +| ACA-02 | TYP | 待完整核验;缺口见对应组 | +| ACA-03 | EQ | 待完整核验;缺口见对应组 | +| ACA-04 | TYP | 待完整核验;缺口见对应组 | +| CRE-01 | CRE | 待完整核验;缺口见对应组 | +| CRE-02 | CRE | 待完整核验;缺口见对应组 | +| CRE-03 | CRE | 待完整核验;缺口见对应组 | +| CRE-04 | CRE | 待完整核验;缺口见对应组 | +| EDIT-01 | DOC | 待完整核验;缺口见对应组 | +| EDIT-02 | MD | 待完整核验;缺口见对应组 | +| EDIT-03 | MD | 待完整核验;缺口见对应组 | +| EDIT-04 | MD | 待完整核验;缺口见对应组 | +| LANG-01 | MD | 待完整核验;缺口见对应组 | +| LANG-02 | LANG | 待完整核验;缺口见对应组 | +| LANG-03 | TYP | 待完整核验;缺口见对应组 | +| LANG-04 | LANG | 待完整核验;缺口见对应组 | +| LANG-05 | LANG | 待完整核验;缺口见对应组 | +| LANG-06 | LANG | 待完整核验;缺口见对应组 | +| LANG-07 | LANG | 待完整核验;缺口见对应组 | +| LANG-08 | LANG | 待完整核验;缺口见对应组 | +| LANG-09 | LANG | 待完整核验;缺口见对应组 | +| LANG-10 | LANG | 待完整核验;缺口见对应组 | +| LANG-11 | LANG | 待完整核验;缺口见对应组 | +| FMT-01 | DOC | 待完整核验;缺口见对应组 | +| FMT-02 | LANG | 待完整核验;缺口见对应组 | +| FMT-03 | MD | 待完整核验;缺口见对应组 | +| git_status | GIT | 待完整核验;缺口见对应组 | +| git_branch_list | GIT | 待完整核验;缺口见对应组 | +| git_log | GIT | 待完整核验;缺口见对应组 | +| git_diff | GIT | 待完整核验;缺口见对应组 | +| git_commit | GIT | 待完整核验;缺口见对应组 | +| git_checkout | GIT | 待完整核验;缺口见对应组 | +| git_merge | GIT | 待完整核验;缺口见对应组 | +| git_rebase | REB | 待完整核验;缺口见对应组 | +| git_cherry_pick | GIT | 待完整核验;缺口见对应组 | +| git_stash | STA | 待完整核验;缺口见对应组 | +| git_tag | GIT | 待完整核验;缺口见对应组 | +| git_reset | GIT | 待完整核验;缺口见对应组 | +| git_clone | CLONE | 待完整核验;缺口见对应组 | +| git_push | REMOTE | 待完整核验;缺口见对应组 | +| git_pull | REMOTE | 待完整核验;缺口见对应组 | +| git_fetch | REMOTE | 待完整核验;缺口见对应组 | +| GRAPH-GIT-01 | GITUI | 待完整核验;缺口见对应组 | +| GRAPH-GIT-02 | GIT | 待完整核验;缺口见对应组 | +| GRAPH-GIT-03 | GITUI | 待完整核验;缺口见对应组 | +| GRAPH-GIT-04 | GITUI | 待完整核验;缺口见对应组 | +| GRAPH-GIT-05 | REMOTE | 待完整核验;缺口见对应组 | +| GRAPH-GIT-06 | CMP | 待完整核验;缺口见对应组 | +| GRAPH-GIT-07 | GITUI | 待完整核验;缺口见对应组 | +| GH-01 | AUTH | 待完整核验;缺口见对应组 | +| GH-02 | REMOTE | 待完整核验;缺口见对应组 | +| GH-03 | REMOTE | 待完整核验;缺口见对应组 | +| GH-04 | PR | 待完整核验;缺口见对应组 | +| GH-05 | PR | 待完整核验;缺口见对应组 | +| GH-06 | PR | 待完整核验;缺口见对应组 | +| GH-07 | PR | 待完整核验;缺口见对应组 | +| DIFF-01 | CMP | 待完整核验;缺口见对应组 | +| DIFF-02 | MERGE | 待完整核验;缺口见对应组 | +| LINK-01 | WIKI | 待完整核验;缺口见对应组 | +| LINK-02 | WIKI | 待完整核验;缺口见对应组 | +| LINK-03 | WIKI | 待完整核验;缺口见对应组 | +| LINK-04 | INDEX | 待完整核验;缺口见对应组 | +| LINK-05 | INDEX | 待完整核验;缺口见对应组 | +| LINK-06 | INDEX | 待完整核验;缺口见对应组 | +| LINK-07 | INDEX | 待完整核验;缺口见对应组 | +| LINK-08 | INDEX | 待完整核验;缺口见对应组 | +| ZOT-01 | CITE | 待完整核验;缺口见对应组 | +| ZOT-02 | ZOT | 待完整核验;缺口见对应组 | +| ZOT-03 | CITE | 待完整核验;缺口见对应组 | +| ZOT-04 | CITE | 待完整核验;缺口见对应组 | +| USER-01 | E2E | 待完整核验;缺口见对应组 | +| USER-02 | E2E | 待完整核验;缺口见对应组 | +| USER-03 | THEME | 待完整核验;缺口见对应组 | +| USER-04 | LANG | 待完整核验;缺口见对应组 | +| REL-01 | DOC | 待完整核验;缺口见对应组 | +| REL-02 | INDEX | 待完整核验;缺口见对应组 | +| REL-03 | REMOTE | 待完整核验;缺口见对应组 | +| REL-04 | QUALITY | 待完整核验;缺口见对应组 | +| REL-05 | QUALITY | 待完整核验;缺口见对应组 | +| REL-06 | EXISTING | 待完整核验;缺口见对应组 | + +
diff --git a/docs/WORKBENCH-ACCEPTANCE.md b/docs/WORKBENCH-ACCEPTANCE.md new file mode 100644 index 0000000..ca09ce8 --- /dev/null +++ b/docs/WORKBENCH-ACCEPTANCE.md @@ -0,0 +1,63 @@ +# 多栏工作台验收记录 + +2026-09-13,实施分支 `codex/workbench-ux-v2`,发布基线 `v2.0.0 / ca4c214`。本轮记录对应[地图 45](https://github.com/KRPCT/InkStream/issues/45)、[实现 47](https://github.com/KRPCT/InkStream/issues/47)、[ComputerUse 48](https://github.com/KRPCT/InkStream/issues/48)。该开发阶段验收已完成;其实现纳入 2.1.0,用户随后授权合并与发布,见 [PR 49](https://github.com/KRPCT/InkStream/pull/49)及[发行页](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0)。以下截图与行为证据对应开发提交 `09b32ff`,不将版本说明更新当作新的行为验收。 + +## 方法和边界 + +先写 [WB-01 至 WB-08](specs/workbench-ux.feature) 行为规格,再用真实 React、CodeMirror、项目会话与受控 IPC 边界验证。首个失败测试证明旧列表点击会立即插入;原生验收发现问题后继续补回归、修复和复验。 + +ComputerUse 使用 Windows 的 `@oai/sky`,实际操作普通 Tauri 构建的窗口、系统目录选择器、菜单、键盘和分隔线。此层没有 DOM 查询、页面脚本、store 注入或应用内自动化桥。独立应用标识为 `com.krpct.inkstream.workbenchcheck20260913`,不使用用户正式配置。 + +两个项目和文稿为本次创建的真实目录,Git 时间线读取测试仓库实际提交。文献经原生 HTTP/IPC 读取本机受控 BBT 响应,用于核对界面到插入的完整通路;不是 Zotero 真实账号验收。中文通过 ComputerUse 文本输入接口输入,不声称物理拼音输入法已验收。没有执行真实远端评论、推送或账号授权。 + +## 逐项观察 + +| 场景 | 实际观察 | 自动与原生证据边界 | +| --- | --- | --- | +| WB-01 概览与正文 | 项目文件数 3、已开文稿数随会话变化;概览往返保留正文选区和滚动,返回正文后可撤销;重启保留已保存内容与标签 | 实例身份由集成测试验证;原生观察见 03–07、26 | +| WB-02 项目切换 | 项目轨显示两个项目,长名称省略但完整名称可读;切回恢复两份标签和写入内容 | 38–39;保存失败由项目会话测试验证 | +| WB-02 目标不可用 | 暂时移动本次测试目录后,从项目轨打开它会显示错误,原项目仍为当前项目;关闭提示后可继续输入和保存 | 40–41;目录随后恢复,实际文件回读包含两段验收文字 | +| WB-03 文献 | 选择条目显示作者、年份、引用键、DOI 和摘要,正文计数不变;明确插入返回原文稿,引用落到原光标;一次撤销只移除该引用 | 08–11、29–30、48–49;最终窄栏插入按钮无需滚动即可使用,所用文献明确标为验收样本 | +| WB-04 库身份 | 旧列表与详情的迟到响应不能覆盖新库或形成插入目标 | 自动竞争时序测试;原生已验空库/失败呈现(50–51),错误后旧插入入口消失;不替代账号切换 | +| WB-05 键盘 | Home/方向键改变工作区焦点;文档方向键实际切换文稿;工具方向键实际显示反链内容 | 13、15–16;关闭的保存/冲突规则由相应集成测试覆盖 | +| WB-06 简易模式 | 在版本页启用简易模式后回到原文稿,文献/版本入口隐藏,正文保留 | 33–34;关闭简易模式后 Codex 工具恢复选中并显示内容,最终复验见 44–45 | +| WB-07 栏宽与缩放 | 文件栏拖宽后,两侧折叠互不影响;缩放复原保持拖过的宽度;放大后工具转为抽屉,收起即可继续输入 | 17–24、54–58,覆盖 100%、125%、130%、150%;临时几何不写回的来源判定另有自动回归 | +| WB-08 版本 | 实际提交信息与本地仓库一致;日常版本页保留两侧导航 | 12、33;无仓库空态与过期仓库过滤另有自动回归 | +| 三模式与主题 | 通用、学术、创作提供各自工具;暗色及减少动效、减少透明度设置可操作 | 28–36、42、54、56;54 明确显示减少效果开关开启;完整跨平台、GPU/长时与第三方主题矩阵不在这些观察中 | + +## 原生验收发现并修复 + +1. 状态栏滚动容器裁剪模式菜单。菜单改为锚定到按钮的独立弹出层;缩放/滚动/项目阻塞时关闭。150% 放大后的原生复验已显示完整菜单(27)。 +2. 简易模式退出后,创作工具仍记着不再可用的大纲,导致空白。右栏先派生当前可用目标,再收敛保存的活动工具;已复现 RED 并加入 WB-06 回归。 +3. 长文献详情会把插入按钮推到窄侧栏底部之外。资料独立滚动,明确插入操作留在固定的底部区域。 +4. 折叠面板退出键盘与辅助功能访问;设置开关补充名称。暗色采用原生暗色滚动条,并提升应用内 SVG 标记对比度;SVG 母版和桌面资源保持用户确认的版本。 + +## 最后验证状态 + +最终检查通过:243 份功能测试文件的 1908 项断言,加两份独立性能文件,共 1910 项,零失败、零跳过。41 项显式 BDD 绑定全部通过;Gherkin 未被解释执行,旧专项场景仍按各自证据保留 partial/pending。 + +最终 TypeScript、ESLint、Vite 与 Tauri 原生构建均通过。三轮 ComputerUse 已完成,最后一轮复验确认工具回退、常驻插入操作、暗色滚动条/标记、文献错误/空态和 125% 浮层。新母版未替换:仓库 SVG SHA-256 仍为 `58ffe1787e8802565d9ac5e5112e270a488954e00b1ecbc37ce11385a4d2510a`。 + +三轮应用进程 PID 分别为 50188、60288、61500,均经应用退出并完成所属 Job 清理;本次 WebView 子进程和本地 23119 监听均已回收。回读测试文件确认两段验收文字只写入原项目,另一项目不含它们,撤销后的引用也未留在文件中。 + +本地完整记录位于实施工作树的 `coverage/acceptance/workbench-v2/`:有界命令、进程盘点、三轮原生进程退出记录,以及 `cua/<序号>-<观察>.json/.jpg`。只将少量代表截图收入产品文档,原始日志、测试配置、测试项目和原生包留在忽略目录。 + +## 仍独立保留的验收 + +[41](https://github.com/KRPCT/InkStream/issues/41) 的严格同内容并排对照、密集项目矩阵,[42](https://github.com/KRPCT/InkStream/issues/42) 的安装图标/系统缓存,[43](https://github.com/KRPCT/InkStream/issues/43) 的完整弹窗矩阵,以及真实账户、跨平台和物理 IME 验收不能由本轮有限的 Windows 操作代替。相关票按已补证和剩余项继续管理。 + +## 最终构建截图 + +文稿、文献详情与大纲同时可见;插入操作保持在窄侧栏底部。 + +![最终多栏写作界面](assets/workbench/writing.jpg) + +暗色主题及可识别的 SVG 标记: + +![暗色工作台](assets/workbench/dark.jpg) + +125% 缩放、减少效果开启时的项目浮层,保留长项目名称: + +![项目档案](assets/workbench/archive-125.jpg) + +另见[暗色文献详情与常驻插入操作](assets/workbench/reference-action.jpg)。图片均来自真实窗口,文献内容是明确标示的验收样本。 diff --git a/docs/academic.md b/docs/academic.md index 3224fef..a557bd2 100644 --- a/docs/academic.md +++ b/docs/academic.md @@ -7,14 +7,16 @@ Academic(学术)模式把 Zotero 文献库、引用插入、参考文献编 InkStream 有三种写作模式(Standard / Academic / Creative),切换不会改变文件格式,只是调整界面布局与功能集。 - 按 `Ctrl+Shift+P` 打开命令面板,输入「学术」或「Academic」,选择「模式:切换到 Academic(学术)」。 -- 切换后,三处界面会随之变化:左侧边栏顶部出现「Zotero 文献库」,编辑器上方出现学术工具栏,右侧面板自动切到「引用」标签页。 +- 切换后,三处界面会随之变化:左侧分类可切换到「文献」,编辑器上方出现学术工具栏,右侧面板自动切到「引用」标签页。 ## 学术工具栏 -进入学术模式后,编辑器正上方常驻一条工具栏,按钮从左到右为: +进入学术模式后,编辑器上方的工具栏提供: - **引用**:等同于 `Ctrl+Shift+Z`,触发 Zotero 选择器插入引用。 - **脚注**:在光标处插入脚注标记 `[^N]`,并在文末追加对应的脚注定义行,光标自动移到定义处等你填写(编号自动取文档内未用过的最小数字)。 +- **公式编号**:为 Markdown 块公式启用编号并补充稳定标签。 +- **公式 PDF**:导出当前 LaTeX 围栏中的公式片段,范围与限制见[公式编号与引用](./equations.md)。 - **参考文献**:下拉菜单,含「插入 / 刷新(默认 GB/T 7714)」以及 GB/T 7714、APA、Vancouver 三种样式(详见下文)。 - **公式**:插入数学公式块。 @@ -38,11 +40,11 @@ InkStream 有三种写作模式(Standard / Academic / Creative),切换不 ## 从侧边栏文献库插入 -学术模式下左侧边栏顶部的「Zotero 文献库」直接列出 Zotero 库中的条目: +可从正文上方的「文献」进入完整文献工作区,也可在学术模式左侧分类选择「文献」,在正文旁查阅: -- 顶部输入框可按标题、作者或 citekey 过滤。 -- 点击任意一条,即在编辑器光标处插入对应的 `[@citekey]`(同样会按文档语言自动适配格式),无需再弹选择器。 -- 右上角的刷新按钮可重新拉取文献库。 +- 顶部输入框可按标题、作者、年份或 citekey 过滤。 +- 点击条目只选择并显示标题、作者、年份、引用键,以及可取得的出版物、DOI、摘要。按「插入所选引用」才在原文稿的当前选区插入,格式随语言适配;完整工作区会自动回到文稿。一次撤销只撤掉这次插入。长详情单独滚动,插入按钮保留在详情区底部。没有打开文稿时插入按钮不可用。 +- 右上角刷新重新读取文献库并清除旧选择。库身份变化会使在途结果失效,不能把旧库条目插入新会话。 - 当 Zotero 未运行时,若已配置过 Web API 同步,面板会自动改读本地离线缓存,并在标题旁显示一个离线小图标。 ## 引用面板与未解析标红 @@ -83,8 +85,12 @@ InkStream 有三种写作模式(Standard / Academic / Creative),切换不 注意事项: -- API Key 仅保存在本机操作系统的凭据库,不会上传,也不会回显到界面。 +- API Key 保存在本机操作系统的凭据库,仅用于向 Zotero API 认证,不回显到界面。 - 同步是增量的:每次只拉取上次同步以来的改动。 +- 缓存按 Zotero 用户库隔离。切换账户后读取各自缓存;旧版离线数据保留,需要为当前账户重新同步。 +- 同步期间库版本持续变化或下载失败时,不以不完整结果推进缓存与游标,按提示稍后重试。 - 离线时为「在线优先、离线回退」:先尝试连 Zotero,连不上再读缓存;缓存也为空时才报错。 +当前实现包含本地模块及协议回归;真实 Zotero/BBT 选择器、真实账户同步和跨平台排版仍需各自验证,不因 2.0 发布或 CI 成功而自动完成。 + ← 返回 [功能文档总览](./index.md) diff --git a/docs/assets/workbench/archive-125.jpg b/docs/assets/workbench/archive-125.jpg new file mode 100644 index 0000000..ca4ca9b Binary files /dev/null and b/docs/assets/workbench/archive-125.jpg differ diff --git a/docs/assets/workbench/dark.jpg b/docs/assets/workbench/dark.jpg new file mode 100644 index 0000000..bc61d66 Binary files /dev/null and b/docs/assets/workbench/dark.jpg differ diff --git a/docs/assets/workbench/reference-action.jpg b/docs/assets/workbench/reference-action.jpg new file mode 100644 index 0000000..1c65f5b Binary files /dev/null and b/docs/assets/workbench/reference-action.jpg differ diff --git a/docs/assets/workbench/writing.jpg b/docs/assets/workbench/writing.jpg new file mode 100644 index 0000000..8ae35a3 Binary files /dev/null and b/docs/assets/workbench/writing.jpg differ diff --git a/docs/creative.md b/docs/creative.md index 6d91a78..c9fbd8a 100644 --- a/docs/creative.md +++ b/docs/creative.md @@ -4,7 +4,7 @@ ## 开启创作模式 -模式切换入口在窗口底部状态栏左侧的模式指示器(一个圆点加模式名)。 +模式切换入口在窗口底部状态栏右侧的模式指示器(一个圆点加模式名),也可从「视图 → 模式」或命令面板切换。 1. 点击状态栏的模式指示器。 2. 在弹出菜单中选择「Creative · 长篇创作」。 @@ -46,6 +46,10 @@ Codex 是你作品的设定库。角色、地点、世界观设定各占一个 ### 建立 Codex 条目 +在右侧「Codex」面板点击「添加条目」,选择角色/地点/设定,填写名称、别名和概要后点击「创建条目」。应用在 `Codex/` 下保存普通文档。已有条目右侧的铅笔按钮可编辑这四项元数据,保留正文和其他字段;取消不保存。 + +也可直接编辑文件: + 1. 在工作区根目录下建一个名为 `Codex` 的文件夹。 2. 在 `Codex/` 里为每个条目建一个 Markdown 文件。 3. 在文件 frontmatter 中至少写两项:`type` 和 `name`。 @@ -54,7 +58,7 @@ frontmatter 字段约定: - `type:`:条目类型,取 `character`(角色)、`location`(地点)或 `lore`(设定)之一。其他值的条目会被忽略。 - `name:`:条目的规范名称,也是正文里被识别的触发词。必填,缺失则该条目不生效。 -- `aliases:`:别名,写成单行、用英文逗号分隔(例如 `aliases: 阿深, 深哥`)。每个别名也会成为触发词。 +- `aliases:`:支持单行逗号分隔文本(例如 `aliases: 阿深, 深哥`)或 JSON 字符串数组。表单保存时使用数组,每个别名都可成为触发词。 - `summary:`:悬停卡上显示的简介。不写时自动取正文的第一段。 只有同时具备有效 `type` 和 `name` 的文件才会被当作 Codex 条目。 @@ -67,7 +71,7 @@ frontmatter 字段约定: - 编辑完设定文件后,点面板右上角的刷新按钮重新扫描 `Codex/`,正文里的提及高亮会随之更新。 - 切换 / 打开工作区时也会自动重新扫描。 -如果还没有任何条目,面板会提示你在 `Codex/` 文件夹放入条目。 +没有条目时显示「Codex 还是空的」,可直接点击添加。读取失败、字段重复或无效、名称/别名属于多个条目时会显示问题;有歧义的提及不会猜测悬停目标。 ### 提及高亮与悬停卡 @@ -79,7 +83,7 @@ frontmatter 字段约定: ## Focus Mode 段落聚焦 -按 **F11** 开启或关闭 Focus Mode。开启后,光标所在的段落保持正常显示,其余段落被淡化,帮助你专注于当前正在写的这一段。 +编辑器聚焦时按 **F11**,或从「视图 → 专注模式」开启/关闭 Focus Mode。开启后,光标所在段落保持正常显示,其余段落淡化;菜单显示当前勾选状态。 - 段落以空行为界划分。光标移动到哪一段,哪一段就高亮,其余淡化。 - 这是一个全局开关,对所有打开的文档都生效,再次按 F11 即可关闭。 @@ -103,6 +107,8 @@ frontmatter 字段约定: - 切换不同场景继续写,进度会跨文档累计。 - 达到目标后,进度条颜色变为「定稿」色以示完成。 +创作状态栏还显示章节/场景数和当前场景状态。读取失败或大文档暂停统计时会显示相应状态,不把未取得的统计当成零字数。 + 把鼠标停在进度区域上,会显示「今日已写 X / 目标 Y 字」。 ## 场景概要卡 @@ -142,12 +148,12 @@ Codex 文件字段: - `type:`:`character` / `location` / `lore`(必填)。 - `name:`:条目名称与触发词(必填)。 -- `aliases:`:别名,单行逗号分隔。 +- `aliases:`:别名,单行逗号分隔或 JSON 字符串数组。 - `summary:`:悬停卡简介(不写则取正文首段)。 注意事项: - frontmatter 各字段都写成单行(`字段: 值`),值里可以含空格(如英文标题)。 -- frontmatter 由你手写,写错或留空不会报错,只是对应功能回退到默认(如 `status` 缺失按草稿、`summary` 缺失不显示概要卡)。 +- 场景元数据可手写,`status` 缺失按草稿、`summary` 缺失不显示概要卡;当前场景的无效状态会标为未解析。Codex 可用表单编辑,无效字段或读取失败会在面板提示,不静默当作正常条目。 ← 返回 [功能文档总览](./index.md) diff --git a/docs/diff-merge.md b/docs/diff-merge.md index 7f93d5a..3c7353a 100644 --- a/docs/diff-merge.md +++ b/docs/diff-merge.md @@ -53,23 +53,22 @@ InkStream 的版本对比和冲突解决可以按句子来看,而不只是按 解决器以整页视图打开,左栏列出所有待解决的冲突文件,右栏显示选中文件的内容。打开时不会抢走编辑器的输入焦点。 -右栏把文件内容拆成两类: +右栏的「逐处解决」一次显示一处冲突,可用「上一处/下一处」导航。两侧完整正文支持选择、复制与查找;diff3 冲突还显示该处共同基线。也可切换「完整合并原文」「完整共同基线」「完整本方版本」「完整对方版本」核对上下文,缺失版本会明确标出。 -- 干净段:git 已经合好的部分,以灰色原样显示,不需要你处理。 -- 冲突块:每一处真冲突显示为一张带边框的卡片,标题为「冲突(本方 ↔ 对方)」,卡片内用句级对比展示本方与对方的差异。 +每处冲突都要显式选择,初始不默认采纳任何一方: -对每个冲突块,卡片右上角有三个按钮,逐块选择: - -- 采纳本方:保留你这一侧的内容(默认选中)。 +- 采纳本方:保留你这一侧的内容。 - 采纳对方:采用合并进来的另一侧内容。 - 两者都要:把本方和对方的内容都保留下来。 +变基期间会改用「采纳目标分支」「采纳正在重放的提交」标签,避免混淆两侧含义。尚有未选择的冲突或冲突标记损坏时不能保存;标记损坏时仍可读取完整原文。磁盘或 Git 冲突版本已经改变时,需重新读取再处理。 + 顶部状态条会显示「N 个文件待解决」,右栏底部会显示当前文件有「N 处冲突」。 ## 保存、标记解决与提交 - 为当前文件的每个冲突块选好之后,点右栏底部的「保存并标记解决」。系统会按你的选择组装出最终内容写回文件并加入暂存(标记为已解决),该文件随即从左栏冲突列表中消失。 -- 逐个文件解决,直到列表清空。全部解决后会提示「全部冲突已解决,请在左下角 git 面板提交合并结果」,此时回到 git 面板提交即可完成本次合并。 +- 逐个文件解决,直到列表清空。普通合并提示「全部冲突已解决,请在源代码管理面板提交。」;变基提示「全部冲突已标记解决,请继续变基。」并由上方变基控制区继续。 - 注意:解决器只负责把冲突标记处理掉,它不会自动提交。最终需要你自己提交。提交时若仍检测到未解决的冲突,会先二次确认,避免把 `<<<<<<<` 等冲突标记误提交进历史。 ## 中止合并 diff --git a/docs/editor.md b/docs/editor.md index 333301a..3179282 100644 --- a/docs/editor.md +++ b/docs/editor.md @@ -1,10 +1,12 @@ # 编辑器与实时预览 +当前分支新增「概览/文稿/文献/版本」工作区导航。切换视图保留当前正文、选区和撤销历史;文件导航与大纲定位会回到文稿。引用插入采用独立撤销步骤。新正文排版使用系统宋体栈和 2.05 倍行高,字号仍由设置控制。本轮功能状态及验收见 [CURRENT-STATE](CURRENT-STATE.md)。 + InkStream 用同一个编辑器内核同时承载写源码和看排版。你的文档是纯文本 Markdown,不会被锁进私有格式。 ## 单内核:纯文本就是真相源 -InkStream 没有「富文本编辑区」和「源码编辑区」两套东西,整个编辑器只有一个内核。无论你看到的是带样式的排版还是裸 Markdown 标记,底层保存的都是同一份纯文本:标题就是 `#`、加粗就是 `**...**`、链接就是 `[文字](地址)`。 +主文档的富文本排版与源码共用 CodeMirror 内核和同一份正文。表格单元格、公式编辑与只读比较可使用独立视图,但文档仍保存为纯文本:标题就是 `#`、加粗就是 `**...**`、链接就是 `[文字](地址)`。 这意味着: @@ -27,11 +29,15 @@ InkStream 没有「富文本编辑区」和「源码编辑区」两套东西, - **状态栏指示器**:编辑器底部状态栏有一个模式标签(显示 `Live Preview` 或 `Source`),直接点击即可在两种模式间切换。处于 Live Preview 时标签左侧有一个圆点,Source 时无圆点。 - **命令面板**:按 `Ctrl+Shift+P` 打开命令面板,执行「视图:切换渲染模式」。 -每个文件单独记住自己的模式选择:你把 A 文件切到 Source、再切到 B 文件,B 不受影响;切回 A 仍是 Source。该记忆在当前会话内有效,关闭标签页即清除。 +每个文件单独记住自己的模式选择:你把 A 文件切到 Source、再切到 B 文件,B 不受影响;切回 A 仍是 Source。项目会话会保存标签的渲染模式,恢复已暂存会话时一并恢复;关闭标签后,该标签的当前模式记忆会释放。会话恢复范围见[项目与恢复](./projects.md)。 ### 仅对 Markdown 文档生效 -显示模式只对 Markdown 和 richtext 文档有意义。对 LaTeX、Typst、Python 等其他语言的文档,状态栏的模式指示器会隐藏,`Ctrl+E` 不产生任何效果。 +显示模式只对 Markdown 和 richtext 文档有意义。普通编辑档中,对 LaTeX、Typst、Python 等其他语言的文档,状态栏的模式指示器会隐藏,`Ctrl+E` 不产生任何效果。基础编辑档暂停实时预览,不能通过 `Ctrl+E` 自动开启完整排版。 + +## 大文档基础编辑 + +正文达到 1,000,000 个 UTF-16 单位时,默认使用基础编辑:完整正文仍可编辑、查找和保存,实时排版、语法高亮、大纲、引用、字数与场景概要等派生信息暂停。状态栏提供「启用完整排版(可能较慢)」和「恢复基础编辑」,切换保留正文、选区与撤销历史。此阈值不是文件大小或性能验收承诺。 ## 光标所在行自动展开源码 @@ -83,6 +89,6 @@ InkStream 没有「富文本编辑区」和「源码编辑区」两套东西, ## 中文输入 -编辑器对中文输入法(IME)做了专门处理。在 Live Preview 模式下连续输入中文、长句合成、切换模式或自动保存时,不会出现吞字、重复字的问题。 +编辑器在中文输入法组合期间冻结会干扰输入的装饰更新,并延后渲染模式切换等操作。模拟事件测试不能证明真实候选窗、连续上屏或不同输入法均正常;这些行为按[中文 IME 真机清单](../specs/03-live-preview-ime.spec.md)逐项验证。 ← 返回 [功能文档总览](./index.md) diff --git a/docs/export.md b/docs/export.md index 355b653..bd91a35 100644 --- a/docs/export.md +++ b/docs/export.md @@ -11,16 +11,17 @@ ## 三种格式 -- **HTML**:单文件,内联了一套适合阅读和打印的排版样式,双击就能在浏览器里打开,不引用外部资源。 +- **HTML**:单文件,内联阅读和打印样式,并嵌入可读取的本地图片。文中保留的远程图片仍可能依赖网络,不能把所有导出都视为完全离线自足。 - **PDF**:走系统打印通道生成,版式与 HTML 一致,适合分发和打印。 - **DOCX**:标准 Word 文档,标题、段落、加粗 / 斜体、列表、引用、代码、表格都会映射成对应的 Word 元素,可继续在 Word / WPS 里编辑。 ## 内容如何转换 -- Markdown 的标题、强调、列表、引用、代码块、表格、链接、图片都会正确转换。 -- **数学公式**:`$...$`、`$$...$$`、` ```math ` 经 KaTeX 渲染为公式;`latex` / `typst` 围栏块在导出中以代码块形式保留源码。 -- **Wiki 双向链接** `[[...]]`:导出为其显示文字(有别名取别名)。 -- 安全:导出产物会在浏览器里打开,所以正文里的脚本、`javascript:` / `data:` 等危险链接会在导出时被清理掉,不会执行。 +- Markdown 的标题、强调、列表、引用、代码块、表格和链接映射到目标格式;可读取的本地图片会嵌入,无法读取的图片不能保证完整呈现。 +- **数学公式**:HTML/打印中的 `$...$`、`$$...$$`、` ```math ` 经 KaTeX 输出 MathML;`latex` / `typst` 围栏保留代码源码。DOCX 使用自己的元素转换,不等于 Word 原生公式或完整 TeX 排版。 +- **Wiki 双向链接** `[[...]]`:普通链接导出为显示文字(有别名取别名);合法的公式标签引用在 HTML 中生成文内链接和对应编号。 +- **学术内容**:HTML 公式编号沿用当前文档顺序,引用与参考文献按现有导出流程处理,详见[公式编号与引用](./equations.md)。 +- 正文 HTML 按规则转义,链接只保留允许的协议;`javascript:` 等非准入链接不执行。合法内嵌图片可使用 `data:image/`,与普通链接的协议规则不同。 ## 更多格式(需系统 pandoc) @@ -31,4 +32,6 @@ InkStream 不打包 pandoc(约 150MB),而是检测系统 PATH 上有没有 - **水印页脚**:可在文末附一行自定义水印文字,默认关闭。在 设置 → **通用 → 导出水印页脚** 打开,再到「水印文字」里填想要的内容(如版权信息、作者名);留空就不附页脚。 - **元数据**:无论水印开关如何,生成器标识(含版本号)始终写入文件元数据(HTML 的 `` / DOCX 的核心属性),这是不可见的元信息,不影响正文。 +「文件 → 导出为 → 公式 PDF 片段…」另用于当前 LaTeX 公式,输出范围与整篇文档的「PDF…」不同;支持范围见[公式 PDF 片段](./equations.md#导出-latex-公式-pdf-片段)。 + ← 返回 [功能文档总览](./index.md) diff --git a/docs/git.md b/docs/git.md index 6042896..62b986f 100644 --- a/docs/git.md +++ b/docs/git.md @@ -1,6 +1,6 @@ # Git 版本管理 -InkStream 内置了 git。打开一个 git 仓库文件夹,就能在应用内提交、看历史、切分支、合并、推拉远程,不用切到终端。提交默认带 SSH 签名。 +InkStream 提供 Git 界面,结合 libgit2 与系统 Git 完成本地和远程操作。打开仓库文件夹后,可在应用内提交、看历史、切分支、合并、变基和推拉远程。产生提交的操作使用系统 Git 签名配置,需先准备可用的签名密钥。 ## 让工作区成为 git 工作区 @@ -10,7 +10,7 @@ git 功能只在「当前工作区是一个 git 仓库」时出现,否则相 - 如果你打开的子目录处在某个 git 仓库内部,顶部会出现引导条,询问「打开仓库根 还是 仅此文件夹」。选「打开仓库根」,整个仓库就会进入版本管理。 - 识别成功后,左下角状态栏会显示当前分支名;有未提交改动时分支名旁会亮一个小圆点。 -注意:当前版本不在应用内提供「克隆远程仓库」的对话框。请先用 git 把远程仓库克隆/初始化到本地,再用「打开文件夹」打开它。 +也可从「文件 → 克隆仓库…」或命令面板「克隆仓库…」开始:填写仓库 URL,选择父目录与新目录名。已有同名目录时不会开始克隆。对话框显示进度,支持取消和重试;完成后点击「打开工作区」才切换当前项目。克隆过程需要系统 Git,认证方式受下方远程设置约束。 ## 左侧栏:源代码管理(日常提交) @@ -31,7 +31,7 @@ git 功能只在「当前工作区是一个 git 仓库」时出现,否则相 Git Graph 是中央区的整页视图,分三栏: -- 左栏:提交图谱。顶部可切换「图谱 / 分支 / PR / Issues」四个标签。 +- 左栏:提交图谱。顶部可切换「图谱 / 分支 / 分支比较 / 暂存记录 / PR / Issues」六个标签。 - 中栏:选中提交的详情(信息、作者、时间、父提交、变更文件列表)。 - 右栏:选中文件的 diff,右上角可切换「行」diff 与「句」(句级 prose)diff。 @@ -51,11 +51,14 @@ Git Graph 是中央区的整页视图,分三栏: - 分组显示「当前分支 / 本地分支 / 远程分支」;当前分支卡片显示是否跟踪远程、领先/落后远程的提交数(↑/↓)。 - 右上角「新建」在当前提交创建分支并切过去。 - 把鼠标移到某分支上会出现操作按钮:切换到此分支、合并到当前分支、删除分支(远程分支不显示删除)。 +- 「将当前分支变基到此分支」或命令面板「本地变基…」启动本地变基。状态区提供继续、跳过当前提交、中止及停止正在执行的任务;停止进程不等同于中止整个变基序列。冲突解决后按提示继续,签名失败留下待提交结果时可「提交暂存结果并继续」。 - 切换分支时若有未提交改动导致失败,应用会询问是否「丢弃改动并强制切换」(此操作不可恢复,会再次确认)。 合并:在分支列表点某分支的「合并到当前分支」。产生冲突时会弹出警告,点提示可打开三向解决器逐处采纳本方/对方,或在解决器内中止本次合并。 -暂存(stash):工具条「暂存改动」会把当前改动(含未跟踪文件)暂存起来,可填一句备注。 +暂存(stash):工具条「暂存改动」会把当前改动(含未跟踪文件)暂存起来,可填一句备注。「暂存记录」页提供刷新、恢复并移除、删除记录。恢复失败或发生冲突时保留记录供后续处理;删除记录需要确认。 + +分支比较:打开「分支比较」标签可选择两个分支,固定提交后查看两侧完整正文,不切换工作树。操作说明见[Prose Diff 与合并冲突](./diff-merge.md)。 ## 提交右键菜单 @@ -85,14 +88,14 @@ Git Graph 是中央区的整页视图,分三栏: - 仅本地:只在本机做版本管理,不连任何远程。 - SSH:用 SSH 密钥与远程同步(推荐,支持 ed25519)。需先把公钥加入 GitHub 或你的服务器。 -- GitHub 登录:在「账户」分区填入 GitHub 个人访问令牌(PAT,需含 repo 权限),登录后 HTTPS 远程会自动带上令牌。若本机已安装并登录了 gh CLI,也可以直接用它登录。令牌只存在本机 OS 凭据库,不会上传。 -- 自定义服务器:填写自建或第三方 git 服务器地址。 +- GitHub 登录:在「账户」分区用 PAT、现有 gh CLI 凭据,或已配置 Client ID 的设备授权登录。凭据保存在本机 OS 凭据库,只向通过准入检查的 GitHub HTTPS 目标提供认证;私有仓库需相应权限。详见[GitHub 集成](./github.md)。 +- 自定义服务器:填写自建或第三方 git 服务器地址,只用于本次远程操作,不改写仓库的 remote 配置,也不向自定义地址发送 GitHub 令牌。 「账户」分区底部有「查看多设备同步教程」链接,提供图文同步指引。 ## SSH 签名(Verified) -InkStream 产生新提交的操作(提交、合并、cherry-pick、revert)默认带 SSH 签名,提交在 GitHub 上会显示为 Verified。 +InkStream 的提交、合并、cherry-pick、revert 使用系统 Git 的 `-S` 签名,遵循本机签名配置。GitHub 是否显示 Verified 还取决于签名密钥、账户关联和服务端校验,不能仅凭本地提交成功判定。 - 请确保本机已配置 SSH 签名密钥,并把对应公钥作为「签名密钥」加入 GitHub。 - 配好之后,应用内提交和你在终端手动签名的提交效果一样,不用再做别的设置。 diff --git a/docs/github.md b/docs/github.md index cb0668a..be4175b 100644 --- a/docs/github.md +++ b/docs/github.md @@ -32,7 +32,7 @@ GitHub 的 PR / Issues 视图位于 Git Graph 页面内。 - 按 `Ctrl+Shift+G` 打开 Git Graph,或从命令面板执行「Git Graph」。 - 仅在当前工作区是 git 仓库时可用;否则会提示「当前工作区不是 git 仓库」。 -Git Graph 顶部可切换图谱、分支、暂存记录、PR 与 Issues。点「PR」或「Issues」即可进入对应视图。 +Git Graph 顶部可切换图谱、分支、分支比较、暂存记录、PR 与 Issues。点「PR」或「Issues」即可进入对应视图。 提示:`Ctrl+G` 打开的是知识图谱(双向链接网络),与 Git Graph 不是同一个功能,请勿混用。 @@ -44,6 +44,7 @@ Git Graph 顶部可切换图谱、分支、暂存记录、PR 与 Issues。点「 - 点标题进入 PR 详情(中栏),同时右栏会载入该 PR 的逐文件 diff 供审阅。 - 点条目右侧的外链图标可在浏览器打开该 PR。 - 点顶部「刷新」重新拉取列表。 +- 列表按页加载,使用分页控件查看后续 PR;一页结果不是全部仓库记录。 新建 PR: @@ -77,6 +78,7 @@ PR 详情中的“代码审阅讨论”按文件位置列出原评论和回复 - 顶部有「开放 / 已关闭 / 全部」三个筛选按钮,切换查看不同状态的 Issue。 - 列表每条显示标题、编号、作者与评论数。 - 点「刷新」重新拉取列表。 +- 列表按页加载,筛选或切换仓库时重新读取对应结果。 新建 Issue: @@ -92,4 +94,6 @@ Issue 详情页与 PR 详情页底部都有共用的评论线程: - 上方按顺序列出已有评论,每条显示作者与内容。 - 在底部文本框写下内容,点「评论」即可发表。 +以上是 2.0 已实现的入口与处理流程。真实账户授权、令牌刷新及线上读写仍需对应账号验收;本地替身测试、CI 与 Release 成功不替代这些验证,当前状态见[验收边界](./specs/AUTOMATION.md)。 + ← 返回 [功能文档总览](./index.md) diff --git a/docs/graph.md b/docs/graph.md index aa707ce..f578e7f 100644 --- a/docs/graph.md +++ b/docs/graph.md @@ -4,8 +4,8 @@ ## 这是什么 -- **节点**:工作区内的每一个 `.md` 文件(一个圆点代表一篇笔记)。 -- **连线**:笔记之间的 wiki 链接(`[[目标笔记]]`)。一条链接画一条线。 +- **节点**:工作区内已索引的 `.md` / `.markdown` 文件(扩展名不区分大小写,一个圆点代表一篇笔记)。 +- **连线**:笔记之间唯一解析的 wiki 链接(`[[目标笔记]]`);同一来源到同一目标的重复链接合并,自链接与未解析链接不画线。 - **节点大小**:连接越多的笔记,圆点越大,便于找到中心笔记。 - **没有任何链接的笔记**也会作为孤立圆点出现,提醒你它还没有接入知识网络。 @@ -47,9 +47,10 @@ ## 注意事项 -- 图谱依赖工作区索引:只有被索引到的 `.md` 文件和已解析的链接才会出现。打开新笔记或修改链接后,如果图谱没及时反映,点「重新加载」即可。 -- 当工作区还没有可索引的文件、或尚未建立任何链接时,全库图谱会提示「工作区暂无可索引的文件,或尚未建立链接。」。 +- 图谱依赖本机项目索引:只有已索引的 Markdown 文件和唯一解析的链接才会出现;同名歧义链接不会猜选连线。打开新笔记或修改链接后,如果图谱没及时反映,点「重新加载」即可。 +- 没有已索引的 Markdown 文件时提示「工作区暂无可索引的 Markdown 文件。」;有笔记但没有连线时仍显示孤立节点。 - 局部图谱在没有打开文件、或当前文件还没有任何链接时,会显示空状态提示,请先打开一篇笔记并为它建立 `[[链接]]`。 +- 索引准备中或查询失败时分别显示状态与重试提示,不能把失败当作空图。简易模式会隐藏全库与局部图谱。 - 拖拽时即使经过节点也不会误触发打开,只有真正的点击才会打开笔记。 ← 返回 [功能文档总览](./index.md) diff --git a/docs/index.md b/docs/index.md index 89b382c..bc7a829 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,7 @@ # InkStream 功能文档 +开发与验收的当前状态见 [CURRENT-STATE](CURRENT-STATE.md);领域词汇见 [CONTEXT](../CONTEXT.md),本轮多栏工作见 [执行计划](EXECUTION-PLAN.md)。 + InkStream(墨流)各功能的使用指南。每篇都从用户视角讲怎么用:打开方式、操作步骤、快捷键和注意事项。 > 下载安装与项目概览见 [仓库 README](../README.md)。 @@ -11,10 +13,12 @@ InkStream(墨流)各功能的使用指南。每篇都从用户视角讲怎 - [项目与恢复](./projects.md):本机项目档案、每项目会话、独立草稿、备份恢复与材质设置 - [快捷键与命令面板](./shortcuts.md):Ctrl+Shift+P 命令面板、快捷键表、F11 Focus Mode - [简易模式](./simple-mode.md):精简界面与高级功能开关 +- [导入主题](./themes.md):本地 CSS 预览、兼容报告、应用与恢复内置主题 ## 写作能力 - [数学公式块](./math.md):math (KaTeX) / typst / latex (MathJax)、`/` 触发器、行内与块公式 +- [公式编号与引用](./equations.md):稳定标签、文内跳转、导出编号与 LaTeX 公式 PDF 片段 - [双向链接与知识网络](./links.md):`[[wiki-link]]` 语法、`[[` 补全与跳转、反链面板、全库索引、Ctrl+P 快速打开 - [知识图谱](./graph.md):Ctrl+G 全库 Graph View、邻域高亮、局部图谱 - [学术模式与 Zotero](./academic.md):CAYW 插入引用、Citation Panel、参考文献(GB/T 7714 / APA / Vancouver)、Zotero 同步 @@ -24,15 +28,23 @@ InkStream(墨流)各功能的使用指南。每篇都从用户视角讲怎 ## 导出与阅读 - [文件导出](./export.md):导出当前文档为 HTML / PDF / DOCX(系统装 pandoc 后再加 ODT/RTF/LaTeX/EPUB/Typst/Org)、可定制水印、数学与链接处理 -- [阅读模式](./reading.md):txt / docx / epub / pdf 沉浸阅读、小说 vs 文献自动识别、亮 / 护眼 / 夜间配色 +- [阅读模式](./reading.md):txt / Markdown / docx / epub / pdf 阅读、目录与书签、续读和可选书架 ## 版本与协作 -- [Git 版本管理](./git.md):git-graph 三栏、提交 / 分支 / 合并 / stash、远程同步、SSH 签名 +- [Git 版本管理](./git.md):克隆入口、git-graph 三栏、提交 / 分支 / 本地变基 / stash 管理、远程同步与 SSH 签名 - [Prose Diff 与合并冲突](./diff-merge.md):句级语义 diff、prose 三向合并解决器 - [GitHub 集成](./github.md):PAT / gh CLI 登录、Issue / PR / review、内嵌 diff 审阅 - [自动更新](./update.md):启动静默检查、下载后重启升级、签名验签、手动检查更新 +## 验收与维护 + +- [BDD 自动化绑定状态](./specs/AUTOMATION.md):人工映射 Vitest、CI / Rust 执行范围和仍开放的验证事项 +- [工作台验收清单](../specs/01-workbench.spec.md):当前 2.0 工作台行为及自动、真机验证分工 +- [中文 IME 真机回归清单](../specs/03-live-preview-ime.spec.md):物理输入法矩阵,自动桩与发布结果不能代替签核 + --- ← 返回 [README](../README.md) + +多栏分支的已实现行为、测试与真实窗口观察见 [ComputerUse 验收记录](WORKBENCH-ACCEPTANCE.md)。 diff --git a/docs/links.md b/docs/links.md index 42f8aec..703b45b 100644 --- a/docs/links.md +++ b/docs/links.md @@ -1,6 +1,6 @@ # 双向链接与知识网络 -用 `[[ ]]` 在笔记之间建立链接。支持自动补全和点击跳转,链接目标不存在时会顺手建好;反链面板和全库索引则用来查看哪些笔记引用了当前文件。 +用 `[[ ]]` 在笔记之间建立链接。支持自动补全、定位跳转和确认后创建缺失目标;反链面板和全库索引用来查看哪些笔记引用了当前文件。 ## 什么是 wiki-link @@ -12,12 +12,13 @@ wiki-link 是用双方括号包住目标的链接写法,让一篇笔记指向 [[目标#标题^块|别名]] ``` -其中只有目标是必填的,后面三段都可选,可任意组合或省略: +跨文件链接指定目标,别名、标题与块标识按需填写;同一文档内的标题或块定位可省略文件名: - `[[目标]]`:最简形式,链接到名为「目标」的笔记。 - `|别名`:链接显示成别名,但实际指向「目标」。例如 `[[2024-总结|去年回顾]]` 在正文中显示为「去年回顾」。 - `#标题`:指向目标笔记中的某个标题段落,例如 `[[方法论#实验设计]]`。 - `^块`:指向目标笔记中的某个文本块,例如 `[[读书笔记^p3]]`。 +- 当前文档定位:`[[#实验设计]]`、`[[#^p3]]`;公式标签引用另见[公式编号与引用](./equations.md)。 注意事项: @@ -31,15 +32,15 @@ wiki-link 是用双方括号包住目标的链接写法,让一篇笔记指向 - 列表按文件名相关度排序,单次最多展示 50 个候选。 - 候选项的副标题会显示该文件的相对路径,便于区分同名文件。 -- 选中某项后会插入 `[[文件名]]`(自动去掉 `.md` 后缀,光标落在 `]]` 之后)。 +- 选中某项后保留它的工作区相对路径,避免同名文件丢失身份。例如选择 `资料/笔记.md` 会插入 `[[资料/笔记|笔记]]`,光标落在 `]]` 之后。 -## 点击跳转与「不存在即建」 +## 点击跳转与创建缺失目标 按住 `Ctrl`(macOS 上为 `Cmd`)点击一个 wiki-link 即可跳转: -1. 先在工作区内按精确相对路径查找目标,找不到再按文件名匹配(裸名 `[[笔记名]]` 即可命中,多个同名时取第一个)。 -2. 命中目标时,在当前编辑器内直接打开它。 -3. 目标笔记不存在时,会自动新建 `目标.md`、打开它、刷新文件树,并弹出提示「『目标.md』不存在,已新建并打开。」 +1. 优先匹配精确相对路径;裸文件名可以匹配唯一同名目标。多个同名时提示补全目录路径,不猜选第一个;显式路径不会降级跳到别处的同名文件。 +2. 唯一命中时打开文件,并按标题或块标识定位。定位不存在或不唯一时提示原因,保留文档当前位置。 +3. 目标不存在时弹出「创建链接目标」确认,选择「创建并打开」后才创建空白 Markdown 文档。取消不写文件;父目录缺失等创建失败会提示原因。 补充说明: @@ -54,13 +55,14 @@ wiki-link 是用双方括号包住目标的链接写法,让一篇笔记指向 - 内容随当前打开的文档自动更新,分为两组: - **反向链接**:用 `[[ ]]` 明确链接到当前文件的其他笔记。 - **未链接提及**:正文里提到了当前文件名、但还没有建立 `[[ ]]` 链接的笔记。可据此把零散提及补成正式链接。 -- 每组标题后带数量统计;点击任意一行即可在编辑器中打开对应笔记。 +- 每组标题后带数量统计。反向链接显示来源段落,点击按当前正文重新定位该引用;未链接提及点击打开来源笔记。 - 当前文件没有任何反链与提及时,面板显示「暂无反向链接」的空态提示。 注意事项: -- 「未链接提及」依赖全文索引的三元组匹配,文件名少于 3 个字时不会进行提及检索。 +- 「未链接提及」支持一至两个字符的短词查询,代码、已有链接与公式中的文字会被排除。 - 反链结果会排除文件自身,「未链接提及」也会自动剔除已经建立链接的笔记,避免重复。 +- 索引准备中、关闭或查询失败会分别显示状态,失败不显示成「没有关系」,可按提示重试。 ## 全库索引与重建 @@ -78,4 +80,8 @@ wiki-link 是用双方括号包住目标的链接写法,让一篇笔记指向 - 结果行标题为文件名,副标题为相对路径,便于区分不同目录下的同名文件。 - 选中某项后会在编辑器中打开它。 +## 全库搜索替换 + +按 `Ctrl+Shift+F`,或执行「视图:全库搜索替换」,在项目索引中搜索正文并查看命中片段。此入口依赖完整模式与当前项目索引;切换项目后,旧查询结果不能继续用于新项目的替换。 + ← 返回 [功能文档总览](./index.md) diff --git a/docs/math.md b/docs/math.md index a010b22..6b70dce 100644 --- a/docs/math.md +++ b/docs/math.md @@ -20,6 +20,8 @@ E = mc^2 把光标移出块外,公式即自动渲染;把光标移回块内,块会还原成可编辑的源码。 +Typst 还支持 `:::typst` 容器写法,以单独一行 `:::` 结束,与 Typst 围栏共用公式编辑和编号流程。 + ## 用 /math 触发器插入 在编辑器的空白处或空格后键入 `/`,会弹出补全菜单,可快速插入公式块: @@ -45,11 +47,12 @@ E = mc^2 ## 就地编辑与悬浮工具栏 -公式渲染后,把鼠标移到公式上会出现一个悬浮工具栏,提供三个按钮: +块公式渲染后,把鼠标移到公式上会出现 SVG 图标工具栏,基础操作为: - 编辑(双栏预览):打开一个左右分栏面板,左侧编辑源码、右侧实时预览。 - 复制源码:把该公式的源码复制到剪贴板。 - 删除公式块:移除整个公式块。 +- LaTeX 块额外提供「导出公式 PDF」,用于导出当前公式矢量片段,详见[公式编号与引用](./equations.md)。 进入编辑的方式有两种:点击已渲染的公式,或点工具栏的「编辑」按钮。双栏面板里编辑完成后,点「完成」按钮或按 `Esc` 即退出,恢复就地渲染。 @@ -63,4 +66,6 @@ E = mc^2 注意:Typst 引擎相对较重(首次加载需要拉取较大的 wasm 模块),首个 Typst 块出现时可能会有短暂的「编译中」占位,属正常现象。 +大文档基础编辑档暂停实时预览与完整编号扫描;需显式启用完整排版后使用相关编辑入口。公式编号、标签引用与片段导出不等于完整 TeX 文档或任意宏包编译。 + ← 返回 [功能文档总览](./index.md) diff --git a/docs/modes.md b/docs/modes.md index 5e5f996..7fcc934 100644 --- a/docs/modes.md +++ b/docs/modes.md @@ -14,18 +14,20 @@ InkStream 用同一个编辑器内核提供 Standard(通用)、Academic( - 点击窗口右下角状态栏的**模式指示器**(带圆点的模式名),在弹出菜单中选择目标模式,当前模式行尾会有勾选标记。 - 或打开命令面板(Ctrl+Shift+P),执行「模式:切换到 Standard / Academic / Creative」。 +- 或从标题栏「视图 → 模式」选择目标模式。 -模式没有占用全局快捷键,切换一律通过上面两种入口完成。 +模式没有占用全局快捷键。简易模式会隐藏模式切换入口。 ## 布局与面板 -界面分为三栏:左侧边栏(文件树等)、中央编辑区、右侧面板。 +2.1.0 在 2.0.0 的项目浮层基础上增加常态多项目轨、文件侧栏、概览/文稿/文献/版本四个工作区入口及右侧上下文工具。主编辑器持续挂载。宽度不超过 1100px 时右栏成为抽屉,不超过 760px 时文件栏也成为抽屉。新会话默认导航宽 220px、工具宽 260px;已保存的项目布局保持原值。发布与验证范围见 [CURRENT-STATE](CURRENT-STATE.md)。 - **每种模式各自记忆布局**:你在某个模式下调整的栏宽与折叠状态,会单独保存到该模式;切回来时恢复原样。 -- 切换左侧边栏:Ctrl+\(也可点编辑器标签栏最左侧的面板按钮)。 -- 切换右侧面板:Ctrl+Alt+B(也可点标签栏最右侧的面板按钮)。 +- 切换左侧边栏:Ctrl+\(也可点项目轨底部的文件导航按钮)。 +- 切换右侧面板:Ctrl+Alt+B(也可点项目轨底部的工具面板按钮)。 - 重置当前模式布局:命令面板执行「视图:重置当前模式布局」。 -- 右侧面板顶部按模式列出对应的标签,点击标签切换;当前没有内容时显示该标签的空态提示。 +- 右侧面板顶部按模式列出对应标签。工作区、文稿和工具标签均可用方向键及 Home/End 切换,Tab 只进入一个活动标签。退出简易模式时,右栏自动选中当前模式的可用工具。项目轨的两栏按钮标明展开状态,切换一侧不影响另一侧;空内容显示明确提示。 +- 「视图」菜单的打字机和专注模式带实时勾选标记;状态栏模式选择菜单标出当前写作模式。按钮图标使用 SVG,含项目导航、侧栏切换和模式勾选等入口。 ## 打开文件夹作工作区 @@ -35,7 +37,7 @@ InkStream 以一个文件夹为工作区(同一窗口同时只有一个工作 2. 在弹出的系统目录对话框中选择文件夹,确认后该文件夹即成为当前工作区,文件树随之载入。 3. 侧边栏空态还会列出**最近打开**的工作区,点击任一项即可直接重开。 -切换到另一个工作区时,若当前工作区是 git 仓库且有未提交更改,会先提示「直接切换 / 提交并切换 / 取消」。切换工作区不会动到磁盘上的文件,已打开的文件仍保留在原处。 +切换内容文件夹走项目会话流程:先保存当前已修改文件及草稿、会话,再打开目标项目并恢复它自己的标签和布局。保存、会话快照或目标读取失败时保留原项目;Git 写入或远程操作进行中不能切换。Git 尚未提交的更改与尚未保存的正文是不同状态,详见[项目与恢复](./projects.md)。 ## 文件树操作 diff --git a/docs/projects.md b/docs/projects.md index 5abd259..3f75dc7 100644 --- a/docs/projects.md +++ b/docs/projects.md @@ -1,5 +1,7 @@ # 项目与恢复 +当前分支的项目轨显示多个本机项目及封面,收藏优先、最近打开靠前。直接点击封面走同一保存与恢复入口;切换失败保持原项目并打开档案展示错误。项目概览展示实际文件数、已开文稿和待保存数量,版本页展示真实提交;高级分支、差异和远端操作从「打开版本管理」进入。本轮功能状态及验收见 [CURRENT-STATE](CURRENT-STATE.md)。 + 点击左侧「项目」或按 `Ctrl+Alt+P` 打开项目档案。选择一个内容文件夹后,InkStream 为它建立本机项目记录。内容目录可以不是 Git 仓库,也可以位于某个仓库的子目录中。 项目名称、封面、收藏、文档会话和搜索索引保存在应用数据目录。你的文件仍留在原内容目录;旧目录中已有的 `.inkstream` 或其他未知文件会保留。 diff --git a/docs/reading.md b/docs/reading.md index 87b3ab2..f4dc210 100644 --- a/docs/reading.md +++ b/docs/reading.md @@ -1,10 +1,11 @@ # 阅读模式 -InkStream 除了写作,也能当阅读器用。打开 txt、Word(docx)、EPUB 电子书或 PDF,它会切到一个全屏阅读界面:盖住编辑器和两侧面板,隐藏与阅读无关的功能,只留下正文和一条简单的工具栏。 +InkStream 可阅读 txt、Markdown、Word(docx)、EPUB 电子书和 PDF。阅读视图覆盖工作区中的编辑器与两侧面板,保留正文和阅读工具栏,不切换操作系统全屏。 ## 支持的格式 - **txt**:纯文本 +- **md / markdown**:Markdown 文档的阅读视图 - **docx**:Word 文档(转换为排版后的正文) - **epub**:电子书(按章节顺序连续呈现) - **pdf**:逐页渲染、滚动阅读 @@ -12,15 +13,19 @@ InkStream 除了写作,也能当阅读器用。打开 txt、Word(docx)、E ## 怎么进入 - **docx / epub / pdf**:直接打开即自动进入阅读模式(它们不可编辑)。打开方式包括:文件菜单 → 打开文件、把文件拖进窗口、或在系统里用 InkStream「打开方式」。 -- **txt**:默认仍在编辑器里打开(txt 可编辑);想换成阅读视图时,用命令面板(`Ctrl+Shift+P`)→「视图:阅读模式」把当前 txt 切过去。 +- **txt / md / markdown**:默认在编辑器中打开;通过「视图 → 阅读模式」或命令面板(`Ctrl+Shift+P`)→「视图:阅读模式」进入阅读。阅读视图读取磁盘文件,未保存的正文请先保存;未命名草稿需先另存为文件。 ## 工具栏 阅读界面顶部的工具栏只有几个按钮: - **文体切换**(小说 / 文献):见下节。 -- **字号** −/+:调节正文大小。 -- **配色**:在 亮色 / 护眼(米黄)/ 夜间 三种配色间循环切换,长时间阅读时换护眼或夜间会舒服一些。 +- **排版设置**:文本阅读可调整字体、字号、版心宽度与页边距;PDF 不显示这些文本重排控件。 +- **配色**:用「亮 / 护眼 / 夜间」分段按钮选择,当前项高亮。 +- **目录**:文本提取到标题后显示,点击可定位到对应段落。 +- **书签**:文本阅读可添加当前位置、跳转或删除书签;PDF 当前不提供这个块级书签入口。 +- **上一章 / 下一章**:从书架打开有章节结构的书籍时显示。 +- **加入书架**:启用书架后,对尚未上架的 txt / docx / epub / pdf 显示;Markdown 不加入书架。 - **关闭**:回到编辑器。 ## 自动识别小说 vs 文献 @@ -34,8 +39,14 @@ InkStream 除了写作,也能当阅读器用。打开 txt、Word(docx)、E ## 沉浸与退出 -进入阅读模式后,编辑器、侧边栏、右侧面板都被盖住,相关的编辑功能也就用不到了,状态栏折叠成「阅读模式 · 文件名」。底层编辑器并没有卸载,点关闭(或退出阅读)就回到原来的编辑状态,光标和内容都还在。 +进入阅读模式后,编辑器、侧边栏、右侧面板都被盖住,状态栏折叠成「阅读模式 · 文件名」。底层编辑器没有卸载,关闭阅读后回到原编辑状态。书架已启用且当前文件尚未上架时,关闭前会提示是否加入,可选「暂不」。 -> 提示:阅读模式会把文件一次性读进内存,超大文档(约 100MB 以上)会被拒绝,以免影响性能。 +## 书架与续读 + +在「设置 → 通用」启用书架后,标题栏出现「书架」菜单,可导入书籍文件或文件夹。文件夹支持「书 → 卷 → 章」结构,书卡显示进度,点击可续读。移除书卡只移除书架记录,不删除原文件。 + +文本按正文块记录位置,PDF 按页记录;启用书架持久化后,书籍记录、进度与文本书签保存在本机,关闭书架开关不清除已有数据。阅读配色和排版偏好是本次应用会话状态。 + +单文件读取上限为 100MiB。原生读取使用分块传输,随后仍需在内存解析;PDF 按视口逐页绘制。文件大小上限不代表全部格式和设备已通过相同的性能验收。 ← 返回 [功能文档总览](./index.md) diff --git a/docs/releases/2.1.0.md b/docs/releases/2.1.0.md new file mode 100644 index 0000000..b286e09 --- /dev/null +++ b/docs/releases/2.1.0.md @@ -0,0 +1,19 @@ +# InkStream 2.1.0 + +InkStream 2.1.0 完善了多栏写作工作台与文献浏览体验。 + +- 概览、文稿、文献、版本四个工作区入口,视图切换保留当前文稿、选区与撤销记录。 +- 多项目快捷轨、真实项目概览与版本时间线,项目切换失败后保留原文稿并显示原因。 +- 文献先选择看详情,再明确插入;插入可单独撤销,长详情的操作按钮保持可见。 +- 工作区、文稿及工具标签支持方向键与 Home/End;两侧折叠独立,缩放保留用户栏宽。 +- 统一纸白、石墨灰、低饱和灰绿与宋体风格正文,优化暗色标记、滚动条和工具密度。 +- 修复模式菜单被裁剪、退出简易模式后创作工具空白等问题。软件内更新公告已同步。 + +提供 Windows x64、macOS Apple Silicon、Linux x64 安装包,包含应用内自动更新清单与载荷签名。 + +验证:多栏实现已通过三平台 CI、1910 项前端断言、41 项显式行为绑定和三轮 Windows ComputerUse。文献用户流程使用受控本机 HTTP 数据;真实账号、物理 IME、安装图标及完整平台体验矩阵仍按专项 issues 管理。更新载荷签名与操作系统发行者代码签名是不同环节。 + +合并与变更:https://github.com/KRPCT/InkStream/pull/49 +完整变更:https://github.com/KRPCT/InkStream/compare/v2.0.0...v2.1.0 + +发行资产与签名更新清单见 [2.1.0 Release](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0)。 diff --git a/docs/shortcuts.md b/docs/shortcuts.md index 7c34f8b..c4773b8 100644 --- a/docs/shortcuts.md +++ b/docs/shortcuts.md @@ -1,6 +1,6 @@ # 快捷键与命令面板 -InkStream 的命令都能通过命令面板搜索执行,常用的也有键盘快捷键,不必去翻菜单。 +InkStream 的已注册命令可通过命令面板搜索执行,常用操作也有键盘快捷键。简易模式、书架设置和 pandoc 可用性会影响显示的命令。 ## 命令面板 @@ -31,13 +31,14 @@ InkStream 的命令都能通过命令面板搜索执行,常用的也有键盘 ## 全局快捷键 -以下快捷键在应用任意位置均可使用。 +以下为应用级快捷键;弹窗、组合输入、项目切换和功能开关会限制可用范围。表中按 Windows / Linux 的 Ctrl 标法列出;全局命令解析尚未统一提供 macOS Cmd 等价映射,不能将所有 Ctrl 直接替换为 Cmd。 | 快捷键 | 功能 | |--------|------| | `Ctrl+Shift+P` | 命令面板 | | `Ctrl+P` | 快速打开文件 | | `Ctrl+,` | 打开设置 | +| `Ctrl+Alt+P` | 项目档案 | | `Ctrl+\` | 切换侧边栏 | | `Ctrl+Alt+B` | 切换右侧面板 | | `Ctrl+N` | 新建文档(未命名草稿,保存时另存为转正) | @@ -48,17 +49,22 @@ InkStream 的命令都能通过命令面板搜索执行,常用的也有键盘 | `Ctrl+E` | 切换渲染模式(源码 / 实时预览) | | `Ctrl+G` | 打开知识图谱 | | `Ctrl+Shift+G` | 打开 Git Graph | +| `Ctrl+Shift+F` | 全库搜索替换(需完整模式与项目索引) | +| `` Ctrl+` `` | 内置终端(先在设置中启用) | +| `Ctrl+=` / `Ctrl+-` | 放大 / 缩小界面 | +| `Ctrl+0` | 重置界面缩放;Markdown 编辑器聚焦时由「设为正文」优先处理 | | `Ctrl+Shift+Z` | 插入引用(Zotero) | -| `F11` | 切换专注模式(Focus Mode) | +| `F11` | 编辑器聚焦时切换专注模式(Focus Mode) | ## 渲染模式切换(Ctrl+E) - `Ctrl+E` 在「源码」与「实时预览」之间热切换。 - 仅对 Markdown 家族文档生效;其他类型的文档按下后无变化。 +- 大文档处于基础编辑时不会通过此快捷键启用实时排版,需先点击「启用完整排版(可能较慢)」。 ## 专注模式(F11) -- 按 `F11` 开关专注模式。 +- 编辑器聚焦时按 `F11` 开关专注模式,也可从「视图」菜单或命令面板执行。 - 开启后只有光标所在段落是清晰的,其余段落会淡化。移动光标时清晰的那段会跟着换。 - 再次按 `F11` 关闭,恢复全文正常显示。 @@ -114,9 +120,10 @@ InkStream 的命令都能通过命令面板搜索执行,常用的也有键盘 ## 注意事项 -- 中文输入法(IME)正在组合输入时,回车和快捷键不会误触发命令。 +- 命令分发对 IME 组合状态及 `keyCode 229` 做短路处理;真实候选窗和物理输入法表现仍需[真机回归](../specs/03-live-preview-ime.spec.md)。 - 段落与格式类命令只对 Markdown 家族文档起作用,对其他语言的文档不会有变化。 - `Ctrl+\` 用于切换侧边栏(让位于 `Ctrl+B` 加粗);`Ctrl+O` 打开文件、`Ctrl+Shift+O` 打开文件夹,二者勿混。 - 没有列出快捷键的命令(如插入脚注、插入参考文献、切换文档语言、关于、使用教程等)均可在命令面板中搜索执行。 +- 「视图」菜单中的打字机模式和专注模式会显示当前勾选状态;通过快捷键或命令面板改变后,菜单同步更新。 ← 返回 [功能文档总览](./index.md) diff --git a/docs/simple-mode.md b/docs/simple-mode.md index 088fc51..357ac22 100644 --- a/docs/simple-mode.md +++ b/docs/simple-mode.md @@ -16,7 +16,7 @@ - 侧边栏:隐藏全文搜索、Git 面板、Zotero 文献库、章节-场景树,只保留文件树。 - 右侧面板:只保留大纲,反链与局部图谱隐藏。 -- 状态栏:只留文件路径、光标位置、渲染模式,隐藏 Git 分支、字数目标、引用、模式切换。 +- 状态栏:保留文件路径、光标位置、渲染模式和会话暂存反馈,隐藏 Git 分支、文档字数/目标、引用、模式切换;已处于 Creative 时的写作 HUD 入口仍可用。 - 命令面板 / 菜单 / 快捷键:知识图谱(Ctrl+G)、Git Graph、模式切换、插入引用、切换文档语言等高级命令一并隐藏,即使按对应快捷键也不会触发。 **关闭索引**:简易模式下打开或切换项目不启动索引,反链、未链接提及和图谱工具隐藏。 @@ -25,7 +25,7 @@ ## 随时可逆 -关闭简易模式后,高级功能会恢复;下次打开工作区时索引库会自动重建。简易模式只是一个显示和行为上的开关,不会改动你的文档。 +关闭简易模式后,高级功能会恢复,当前项目按索引会话流程重新打开/刷新索引;切换项目时仍按各项目隔离。简易模式不会改动你的正文。书架由自己的设置开关控制,与简易模式独立。 旧工作区已有的 `.inkstream/` 文件夹会保留;应用使用新的本机项目索引,不自动删除旧目录或其中的未知文件。 diff --git a/docs/specs/AUTOMATION.md b/docs/specs/AUTOMATION.md index 8e40d57..149b3e5 100644 --- a/docs/specs/AUTOMATION.md +++ b/docs/specs/AUTOMATION.md @@ -8,7 +8,9 @@ pnpm test:acceptance 命令只选择映射中的具体测试名,串行运行,测试进程硬上限 300 秒;盘点/终止辅助命令各有 10 秒上限。检查测试文件与场景名称是否仍存在,并解析当次新生成的 Vitest JSON:每个选择项必须恰好出现一次且状态为 `passed`;报告缺失、测试未发现、skip/todo、失败、超时或清理未确认都不会被当作通过。未绑定场景不因同文件其它测试绿色而改变状态。 -当前命令选择五个文件中的 24 项检查:DocumentSession、WorkspaceSession、文件身份迁移、Wiki 实际导航,以及一项同名候选补全检查。既有完整 `pnpm test` 与 CI 的完整 Vitest 步骤保留,不按这份有限映射排除任何测试;CI 不重复运行同一批 Vitest,只新增原生 Rust 验证。 +当前命令选择 14 个文件中的 41 项检查:原有 DocumentSession、WorkspaceSession、文件身份迁移、Wiki 导航与补全共 24 项,以及新增多栏工作台 17 项。工作台映射见 [workbench-bindings.mjs](../../scripts/acceptance/workbench-bindings.mjs),覆盖 WB-01 至 WB-08 的已绑定行为;真实窗口观察见 [ComputerUse 验收](../WORKBENCH-ACCEPTANCE.md)。已校正三条过时的工作区测试名,缺失名称仍会使 runner 失败。`pnpm test` 是直接 `vitest run`;CI 使用 `pnpm test:ci` 的有界 runner,不按本有限映射排除其他测试,也不另行调用 `test:acceptance` 重跑这些选择项。 + +`test:ci` 先串行运行功能测试(排除两份性能文件,进程上限 780 秒),再单独运行 `blockField.performance.test.ts` 与 `perf.test.ts`(fork 进程、显式 GC,进程上限 60 秒)。两阶段均使用单 worker、关闭文件并行,不提高测试自身的时限;阶段结果和清理记录合并后才形成完整前端检查结论。性能文件必须被发现、有实际断言且全部通过,不能用跳过文件换取绿色结果。 这些集成测试使用真实应用模块、状态、CodeMirror 或工作区流程;边界替身按各测试声明使用。它们不能代替原生 WebView/IME、真实磁盘故障、OS watcher 或外部服务验收。运行后只报告“选择的检查通过”,`bddCompletion` 固定为 `false`;全部 BDD 完成仍须逐项补齐下表。 @@ -41,13 +43,13 @@ Save As、删除脏文档、目录移动撤销、旧路径在途写、文件操 ## Unix 权限:负控与生产断言分开 -CI 保留 `cargo check`,并在 Windows、Linux、macOS 实际执行: +Windows、Linux、macOS CI 调用 `node scripts/acceptance/run-rust.mjs`,实际执行: ```text -cargo test --locked --manifest-path src-tauri/Cargo.toml --lib -- --test-threads=1 +cargo test --locked --manifest-path src-tauri/Cargo.toml --all-targets -- --test-threads=1 ``` -[原生 runner](../../scripts/acceptance/run-rust.mjs)给该命令 20 分钟硬上限,并在 Unix 输出中要求观察到正式 `files::tests::write_file_atomic_preserves_existing_unix_permissions` 的通过记录。该正式测试保持原有断言,验证生产 `files.rs` 的写入结果;Windows 的 `cfg(unix)` 排除明确记为未执行,不算权限验证。 +[原生 runner](../../scripts/acceptance/run-rust.mjs)给该命令 20 分钟硬上限。`--all-targets` 同时编译库、测试及应用二进制目标;CI 不再单独跑 `cargo check`,也不缩为 `--lib`。Unix 输出必须出现正式 `files::tests::write_file_atomic_preserves_existing_unix_permissions` 的通过记录。该测试验证生产写入路径;Windows 的 `cfg(unix)` 排除明确记为未执行,不算权限验证。 [独立负控](../../scripts/acceptance/unix-permission-negative.rs)只用 Rust 标准库,在本次新建的临时目录重现旧 `File::create → write → sync → rename` 成功写入策略。它不运行旧应用二进制、不包含旧应用其它组件,也不替换正式测试。它记录 `0600` 与 `0751` 的目标权限和实际权限;至少一个权限谓词被违反时输出确认标记并以专用退出码 `42` 退出。 @@ -61,7 +63,24 @@ cargo test --locked --manifest-path src-tauri/Cargo.toml --lib -- --test-threads - `*-before/after.json`:OS 进程盘点,包含盘点探针自身命令/PID/时间/10 秒上限。OS 没提供的工作目录保留为 unknown,不臆测。 - `*-idle-*.json`:连续 60 秒无输出时的进程诊断。 - `*-after-cleanup.json`:发现属于本次进程组/可追溯父子链的残留后,记录清理复查;既有或归属不明进程不终止。 -- `vitest.json` 与 `acceptance-summary.json`:原始测试结果及逐项匹配,保留 partial/pending 状态。 +- 有限映射 runner 的 `vitest.json` 与 `acceptance-summary.json`:原始测试结果及逐项匹配,保留 partial/pending 状态。 +- 完整前端 runner 的 `functional.json`、`performance.json`、合并 `vitest.json` 与 `tests-summary.json`:分阶段结果、发现的断言和清理结论。 - `rust-summary.json` 与 `negative-control-summary.json`:生产 Rust 结果和隔离旧策略负控各自的证据边界。 -正常退出、失败、超时及中断均检查可观察的所属进程树;Windows 用明确 PID 的 `taskkill /T`,Unix 用该次新建的进程组。主动脱离父子链/进程组的未知进程不被推定为本次所有,相关限制写入记录。此设施尚需主 Agent 实际运行审查;文件存在不构成运行通过声明。 +正常退出、失败、超时及中断均检查可观察的所属进程树;Windows 用明确 PID 的 `taskkill /T`,Unix 用该次新建的进程组。主动脱离父子链/进程组的未知进程不被推定为本次所有,相关限制写入记录。文件存在本身不构成运行通过声明,必须读取对应执行的报告。 + +## 2.0.0 发布记录与剩余验收 + +截至 2026-09-13 已复核的发布基线为 `main` / `v2.0.0` / `ca4c214`,[PR #40](https://github.com/KRPCT/InkStream/pull/40) 已合并,[2.0.0 Release](https://github.com/KRPCT/InkStream/releases/tag/v2.0.0) 已公开: + +| 执行 | 结果 | 证据范围 | +|---|---|---| +| [合并前 CI 34709938076](https://github.com/KRPCT/InkStream/actions/runs/34709938076) | success | 对应提交的三平台 CI 步骤 | +| [main CI 34710877435](https://github.com/KRPCT/InkStream/actions/runs/34710877435) | success | 合并后 main 的三平台 CI 步骤 | +| [Release 34710891782](https://github.com/KRPCT/InkStream/actions/runs/34710891782) | success | 安装包、更新签名条目与清单聚合发布 | + +[CI 工作流](../../.github/workflows/ci.yml)执行锁文件安装、类型检查、lint、`test:ci`、前端 build、独立 Unix 负控与 Rust 全目标测试。[Release 工作流](../../.github/workflows/release.yml)执行打包与发布,不单独承担完整测试或真实用户验收。标签发布先保留草稿,三平台更新签名与 `latest.json` 就绪后公开;手动触发仅上传 run artifacts,不创建公开 Release。 + +24 张已实现票已关闭;仍开放的 validation 票为 [#14](https://github.com/KRPCT/InkStream/issues/14)、[#25](https://github.com/KRPCT/InkStream/issues/25)、[#26](https://github.com/KRPCT/InkStream/issues/26)、[#29](https://github.com/KRPCT/InkStream/issues/29)、[#36](https://github.com/KRPCT/InkStream/issues/36)、[#37](https://github.com/KRPCT/InkStream/issues/37)、[#38](https://github.com/KRPCT/InkStream/issues/38)、[#39](https://github.com/KRPCT/InkStream/issues/39)、[#41](https://github.com/KRPCT/InkStream/issues/41)、[#42](https://github.com/KRPCT/InkStream/issues/42)、[#43](https://github.com/KRPCT/InkStream/issues/43)。实现票关闭不改变本表的 BDD partial/pending 状态。 + +外部真实账号、物理 IME、跨平台完整视觉/性能和安装升级体验需要各自的实际证据。CI success 与公开发布都不能作为这些事项已完成的依据;本规范不将任何未执行的 Gherkin 或真机清单勾为通过。 diff --git a/docs/specs/workbench-ux.feature b/docs/specs/workbench-ux.feature new file mode 100644 index 0000000..766570c --- /dev/null +++ b/docs/specs/workbench-ux.feature @@ -0,0 +1,57 @@ +Feature: 多栏工作台 + 用户在同一项目内组织文稿、查阅资料和查看版本,同时保留当前写作内容。 + + Scenario: WB-01 在概览与文稿之间往返保留写作状态 + Given 当前文稿包含未保存修改和一个选区 + When 用户查看项目概览后返回文稿 + Then 正文、文档身份、选区和撤销历史仍属于原文稿 + And 概览展示真实项目和文档信息 + + Scenario: WB-02 项目轨的快捷切换遵循保存结果 + Given 用户有多个项目且当前文稿未保存 + When 用户从项目轨选择另一个项目 + Then 切换通过现有项目会话入口执行 + And 保存失败时保留原项目并显示错误 + + Scenario: WB-03 文献选择与插入分离 + Given 当前文稿可编辑且文献库有可选条目 + When 用户选择一条文献 + Then 显示该条目的标题作者年份和可用详情 + And 正文不发生变化 + When 用户明确插入所选引用 + Then 引用写入原文稿的当前选区 + + Scenario: WB-04 文献库变化拒绝过期选择 + Given 用户已选择旧库中的条目且一次查询尚未完成 + When 文献库身份发生变化 + Then 清除旧选择并显示当前库状态 + And 旧响应不能覆盖新库或触发正文插入 + + Scenario: WB-05 工具与文档标签可用键盘切换 + Given 用户通过键盘聚焦当前标签 + When 用户按方向键或 Home End + Then 焦点和活动标签按可用项目切换 + And 关闭文档仍遵循原保存和冲突规则 + + Scenario: WB-06 简易模式使高级目的地回到文稿 + Given 用户正在查看文献或版本 + When 用户启用简易模式 + Then 返回仍然存在的文稿 + And 高级入口不再触发隐藏操作 + When 用户关闭简易模式 + Then 当前模式的可用工具重新显示且至少一个工具处于选中状态 + + Scenario: WB-07 面板和缩放保留用户布局 + Given 用户已调整导航和工具栏宽度 + When 用户折叠一侧或调整窗口缩放 + Then 另一侧状态不被翻转 + And 临时窄视口不覆盖保存的栏宽 + And 用户可回到文稿并继续输入 + + Scenario: WB-08 版本视图保留项目导航 + Given 当前项目存在文档与版本记录或明确的无仓库状态 + When 用户查看版本 + Then 当前项目导航仍可见 + And 版本内容使用真实仓库数据或明确的空状态 + When 用户返回文稿 + Then 当前编辑内容仍被保留 diff --git a/docs/update.md b/docs/update.md index 9e949a3..2587407 100644 --- a/docs/update.md +++ b/docs/update.md @@ -2,6 +2,8 @@ InkStream 内置应用内自动更新。启动时会在后台检查新版本,有更新时弹一个不打扰的提示,点一下下载、重启就升级好了,不用再自己去下载页。 +当前发行版本配置为 [2.1.0](https://github.com/KRPCT/InkStream/releases/tag/v2.1.0);安装包与更新清单的就绪状态以发行页为准。发布成功表示对应发布流水线完成,实际安装、升级和平台交互仍按各自验收记录确认。 + ## 怎么用 - **自动检查**:每次启动后台静默检查最新版本。有更新时弹出对话框「InkStream X 现已可用」,可选「现在更新」或「稍后」;无更新或离线时不打扰。 @@ -16,8 +18,14 @@ InkStream 内置应用内自动更新。启动时会在后台检查新版本, ## 注意 -- 自动更新仅在正式安装版中生效,开发版不检查(不会报错)。 +- 自动更新面向正式安装版;启动仍会调用检查入口,开发/未打包环境或网络失败时静默处理。手动检查失败会给出提示,不能写成开发版完全不调用更新器。 - 从 **v1.1.2 起**的安装版才内置更新器;更早版本需手动下载一次新版,此后即可自动更新。 - macOS / Windows 安装包未做系统代码签名,首次安装可能有「未知开发者」提示,属正常。 +## 发布与更新清单 + +标签 `v*` 触发的发布先建立草稿,构建 Windows x64、Linux x64、macOS Apple Silicon 安装包及更新载荷,校验各平台更新签名条目并生成 `latest.json` 后才公开。手动触发 Release 工作流只提供构建产物下载,不创建公开版本。更新载荷签名与 Windows Authenticode/macOS 公证是不同步骤,不能互相替代。 + +CI 的类型检查、前端测试、Rust 全目标测试与 Release 打包各有执行范围;BDD 人工映射、物理 IME、真实账户和跨平台视觉/性能的剩余工作见[自动化绑定与验收边界](./specs/AUTOMATION.md)。 + ← 返回 [功能文档总览](./index.md) diff --git a/docs/writing.md b/docs/writing.md index 1eb84c5..3fdce97 100644 --- a/docs/writing.md +++ b/docs/writing.md @@ -6,14 +6,14 @@ 开启后,光标所在的行会滚动到视口垂直居中,视线固定在屏幕中央,不必随着越写越往下而低头。文首与文末也能居中(编辑器上下留出半屏空白)。 -- 开启:命令面板(`Ctrl+Shift+P`)→「视图:打字机模式」,再执行一次关闭。 -- 中文输入安全:合成中文时不滚动,不会打断输入法(与编辑器其余部分共用同一套 IME 保护)。 +- 开启:「视图 → 打字机模式(光标行居中)」,或命令面板(`Ctrl+Shift+P`)→「视图:打字机模式」,再执行一次关闭。菜单勾选与当前状态同步。 +- 中文组合输入期间暂停居中滚动,组合结束后再更新;真实输入法效果仍按 IME 回归清单验证。 ## 专注模式 开启后,光标所在段落以外的行会被淡化,只有正在写的这一段保持清晰,并随光标移动。 -- 开启:`F11`,或命令面板 →「视图:专注模式」。 +- 开启:编辑器内 `F11`、「视图 → 专注模式(淡化其他段落)」,或命令面板 →「视图:专注模式」。菜单勾选会随这些入口同步。 - 可以和打字机模式叠加使用,居中加淡化。 ## 写作 HUD @@ -25,6 +25,8 @@ - Creative(长篇创作)模式下,状态栏右侧有一个「写作 HUD」入口,点一下即开。 - 命令面板 →「写作:写作 HUD(码字速度 / 时间 / 番茄钟)」。 +状态栏入口会反映 HUD 是否开启;HUD 内的番茄钟按钮按运行状态显示「开始」或「暂停」。 + 面板内容: - **码字速度**:最近 60 秒内敲下的字数(即「字/分」),停笔后自动衰减。粘贴、插入参考文献等程序化写入不计入,中文输入合成期也不计入,统计的是实际手敲的量。 diff --git a/package.json b/package.json index ff2b031..0329a10 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "inkstream", "private": true, - "version": "2.0.0", + "version": "2.1.0", "license": "SEE LICENSE IN LICENSE", "type": "module", "packageManager": "pnpm@11.5.2", diff --git a/scripts/acceptance/bindings.mjs b/scripts/acceptance/bindings.mjs index 00a644a..30d50fd 100644 --- a/scripts/acceptance/bindings.mjs +++ b/scripts/acceptance/bindings.mjs @@ -1,5 +1,7 @@ // Human-reviewed links to named Vitest checks. This file does not parse or execute Gherkin. +import { workbenchGroups, workbenchScenarios } from './workbench-bindings.mjs'; export const groups = [ + ...workbenchGroups, { file: 'src/test/document-session.integration.test.tsx', tests: [ ['doc-close', 'FE-01: closing active A must activate B content before B can be saved'], ['doc-failed-save', 'FE-02: a failed close-save must preserve the dirty background A buffer and tab'], @@ -9,9 +11,9 @@ export const groups = [ ['doc-save-as', 'Save As retains edits made while the chosen file is being written'], ] }, { file: 'src/editor/workspaceSession.integration.test.ts', tests: [ - ['workspace-missing', 'an inaccessible target leaves the original workspace and its watcher operational'], - ['workspace-order', 'prepares the complete target before publishing, and later requests finish consistently'], - ['workspace-watch', 'does not publish a target whose watcher cannot start'], + ['workspace-missing', '目标目录无法打开时保留原文档与监听'], + ['workspace-order', '完整准备目标后才发布,后续切换请求按同一会话队列完成'], + ['workspace-watch', '目标监听启动失败会恢复旧监听和旧编辑会话,失败不伪装成功'], ] }, { file: 'src/editor/documentFileMutations.test.ts', tests: [ ['file-rename', '重命名后活动身份、显示名、缓存和未保存正文一致,后续只写新路径'], @@ -40,6 +42,7 @@ const document = 'docs/specs/document-session.feature'; const workspace = 'docs/specs/workspace-session.feature'; // A passed related check never changes partial/pending into complete. Gaps require additional evidence. export const scenarios = [ + ...workbenchScenarios, { id: 'DOC-01', file: document, name: '关闭活动 A 后继续编辑的是 B', status: 'partial', checks: ['doc-close'], gap: '补充输入后的 B 保存、A 磁盘不变及真实文件往返仍需对应证据。' }, { id: 'DOC-02', file: document, name: '未成功保存时关闭请求保留正文', status: 'partial', checks: ['doc-failed-save'], gap: '仅磁盘失败回归;外部冲突 Examples 行、明确反馈断言仍待绑定。' }, { id: 'DOC-03', file: document, name: '保留我的内容写入失败后仍可继续裁决冲突', status: 'partial', checks: ['doc-conflict'], gap: '绑定冲突提示/冻结/正文;再次裁决和自动保存不得越权的完整流程待绑定。' }, diff --git a/scripts/acceptance/workbench-bindings.mjs b/scripts/acceptance/workbench-bindings.mjs new file mode 100644 index 0000000..ed17b57 --- /dev/null +++ b/scripts/acceptance/workbench-bindings.mjs @@ -0,0 +1,50 @@ +// Named user-behavior checks for the workbench. Gherkin remains a reviewed specification. +export const workbenchGroups = [ + { file: 'src/components/workbench/RightPanel.test.tsx', tests: [ + ['wb-simple-return', 'WB-06 从简易模式返回创作后,选中可用工具并显示内容'], + ] }, + { file: 'src/components/workbench/Workspace.integration.test.tsx', tests: [ + ['wb-overview', 'WB-01 概览往返不卸载真实编辑器,保留修改、选区、撤销;概览数据来自会话'], + ['wb-file-navigation', 'WB-01 从概览使用文件导航会显示所开文稿,复用同一编辑器'], + ['wb-insert', 'WB-03 文献工作区先查看,再明确插入到原文稿选区;撤销可恢复正文'], + ] }, + { file: 'src/components/projects/ProjectRail.test.tsx', tests: [ + ['wb-project-switch', 'WB-02 项目轨使用完整切换入口,失败保持当前项目并展示档案中的错误'], + ['wb-pane-feedback', 'WB-07 两栏开关独立,选中反馈跟随实际折叠状态'], + ] }, + { file: 'src/projects/session.test.ts', tests: [ + ['wb-save-failure', '文件保存失败保留当前完整正文、dirty与所有者,未触碰目标/索引/监听'], + ] }, + { file: 'src/academic/useReferenceLibrary.test.tsx', tests: [ + ['wb-library-detail', 'WB-04 切换库立即清空旧选择,迟到详情不能进入新库或成为插入目标'], + ['wb-library-list', 'WB-04 迟到列表不能覆盖当前库,卸载释放订阅'], + ] }, + { file: 'src/components/workbench/PanelTabs.test.tsx', tests: [ + ['wb-tool-keys', 'WB-05:Tab 只进入活动工具,方向键切换后 Tab 离开标签栏'], + ['wb-tool-content', 'WB-05:键盘切换同时显示对应工具内容,隐藏原内容并保留已挂载面板'], + ] }, + { file: 'src/components/workbench/EditorTabs.test.tsx', tests: [ + ['wb-document-keys', 'WB-05:Tab 只进入活动文档及其可见关闭按钮,然后离开文档栏'], + ['wb-close-guard', 'WB-05:键盘关闭脏草稿仍可取消丢弃,且不调用落盘'], + ] }, + { file: 'src/components/workbench/WorkspaceNavigation.test.tsx', tests: [ + ['wb-simple-references', 'WB-06 简易模式使文献目的地退回原文稿并隐藏高级入口'], + ['wb-simple-versions', 'WB-06 简易模式使版本目的地退回原文稿并隐藏高级入口'], + ] }, + { file: 'src/components/workbench/ProjectVersions.test.tsx', tests: [ + ['wb-versions-empty', 'WB-08 版本空态保留两侧导航及编辑器,返回文稿仍是原视图'], + ['wb-versions-owner', 'WB-08 版本列表只展示当前仓库的数据,切换后不显示旧项目的提交'], + ] }, +]; +const file = 'docs/specs/workbench-ux.feature'; +export const workbenchScenarios = [ + ['WB-01', '在概览与文稿之间往返保留写作状态', ['wb-overview', 'wb-file-navigation']], + ['WB-02', '项目轨的快捷切换遵循保存结果', ['wb-project-switch', 'wb-save-failure']], + ['WB-03', '文献选择与插入分离', ['wb-insert']], + ['WB-04', '文献库变化拒绝过期选择', ['wb-library-detail', 'wb-library-list']], + ['WB-05', '工具与文档标签可用键盘切换', ['wb-tool-keys', 'wb-tool-content', 'wb-document-keys', 'wb-close-guard']], + ['WB-06', '简易模式使高级目的地回到文稿', ['wb-simple-references', 'wb-simple-versions', 'wb-simple-return']], + ['WB-07', '面板和缩放保留用户布局', ['wb-pane-feedback']], + ['WB-08', '版本视图保留项目导航', ['wb-versions-empty', 'wb-versions-owner']], +].map(([id, name, checks]) => ({ id, name: `${id} ${name}`, file, checks, status: 'partial', + gap: '自动检查范围见对应测试。真实窗口、缩放及本轮 ComputerUse 观察另见 docs/WORKBENCH-ACCEPTANCE.md;不代表真实 Zotero 账户或物理 IME 验收。' })); diff --git a/specs/01-workbench.spec.md b/specs/01-workbench.spec.md index a6624e1..7318b6d 100644 --- a/specs/01-workbench.spec.md +++ b/specs/01-workbench.spec.md @@ -1,28 +1,33 @@ -# 01 应用骨架与三模式 Workbench 验收规范 +# 01 三模式 Workbench 验收规范(2.0 基线与多栏分支) > 用户视角 ATDD 验收清单。每项标注验证方式: -> **自动** = 对应单元/组件测试文件(`pnpm test` 全绿即覆盖);**人工** = 真实窗口操作步骤。 -> 本规范是阶段验收的唯一对照表,后续阶段的回归核验也以此为基线。 +> **自动** = 指定测试文件中对应行为的断言,需核对实际发现、执行与结果;**人工** = 真实窗口操作步骤。 +> `pnpm test` / `test:ci` 通过不等于每条验收或全部 BDD 完成;jsdom 不能证明真实 WebView、物理 IME、跨平台视觉与性能通过。 +> 本清单在 `v2.0.0` / `ca4c214` 基线上更新到多栏分支行为,勾选须附对应提交证据。它与 [工作台行为规格](../docs/specs/workbench-ux.feature)、[BDD 绑定状态](../docs/specs/AUTOMATION.md)和 [ComputerUse 记录](../docs/WORKBENCH-ACCEPTANCE.md)共同使用;发布范围以 CURRENT-STATE 为准。 -## 1. 启动与五插槽布局 +## 1. 启动、项目导航与布局 -- [ ] 启动桌面应用后,一屏内可见全部五个插槽:TitleBar(顶部自绘标题栏)/ Sidebar(左侧栏)/ EditorArea(中央编辑区)/ RightPanel(右侧面板)/ StatusBar(底部状态栏)。 - - 验证:自动 `src/components/workbench/WorkbenchLayout.test.tsx`;人工 `pnpm tauri dev` 启动目视核对。 +- [ ] 桌面工作台包含顶部标题栏与菜单、左侧项目导航、文件侧栏、中央编辑区、右侧工具和底部状态栏。项目档案作为浮层打开,不推挤正文;面板按当前模式的折叠状态显示,不要求所有区域始终可见。 + - 验证:自动 `src/components/workbench/WorkbenchLayout.test.tsx`、`src/components/workbench/WorkbenchProjects.test.tsx`;人工用桌面窗口核对。 +- [ ] `Ctrl+Alt+P`、项目导航按钮及「文件 → 项目档案…」打开同一档案入口;归档层打开或项目切换中,背景编辑区不会继续接受输入。 + - 验证:自动 `src/components/projects/ProjectArchive.test.tsx`、`src/components/workbench/WorkbenchProjects.test.tsx`;人工逐入口核对。 - [ ] 拖拽 Sidebar 与 RightPanel 的分隔条可调整面板宽度,宽度在合法区间内(Sidebar 200-480px,RightPanel 240-560px)。 - - 验证:自动 `src/stores/useWorkbenchStore.test.ts`(钳制断言);人工拖拽到极限位置确认不破版。 + - 验证:自动 `src/stores/useWorkbenchStore.test.ts`、`src/components/workbench/WorkbenchLayoutPersistence.test.tsx`、`src/components/workbench/layoutPatch.test.ts`;人工宽窗口拖拽核对。窗口不超过 1100px 时两侧改用抽屉,抽屉几何不得覆盖已记忆栏宽。 - [ ] Sidebar / RightPanel 可折叠与展开,折叠状态不影响其余插槽布局。 - - 验证:自动 `src/stores/useWorkbenchStore.test.ts`;人工 Ctrl+B / Ctrl+Alt+B 往返切换。 -- [ ] EditorArea 在无文档时显示欢迎页(应用名 + 快捷键提示表),无任何乱码或占位英文。 - - 验证:人工目视核对(文案为简体中文)。 + - 验证:自动 `src/stores/useWorkbenchStore.test.ts`;人工 `Ctrl+\` / `Ctrl+Alt+B` 往返切换;`Ctrl+B` 保留为 Markdown 加粗。 +- [ ] 无项目且无标签时显示新建文档/打开文件夹入口;已有项目但无活动文件时显示未打开文件空态;独立草稿打开后可继续编辑。 + - 验证:自动 `src/components/workbench/EditorArea.test.tsx`;人工目视核对可操作入口和简体中文文案。 +- [ ] 项目导航使用 `inkstream-icon.svg`,工具及菜单勾选使用 SVG 图标;图标入口有可识别名称,亮暗主题下清晰。 + - 验证:静态核对 `ProjectRail.tsx`、`Menu.tsx`、`ModeIndicator.tsx`;人工在桌面窗口核对显示与交互,不以资源存在证明视觉验收。 ## 2. 三模式运行时切换 -- [ ] 打开命令面板,执行「模式:切换到 Academic(学术)」,布局预设(面板可见性、RightPanel tab 集)与强调色立即变为学院深蓝;「模式:切换到 Creative(长篇创作)」变为朱砂红;「模式:切换到 Standard(通用)」回到石墨灰。 +- [ ] 打开命令面板,执行「模式:切换到 Academic(学术)」,布局预设(面板可见性、RightPanel tab 集)立即更新;三个模式均沿用纸白、石墨灰与低饱和灰绿配色,身份由模式名称与当前勾选标记表达。 - 验证:自动 `src/modes/presets.test.ts`、`src/commands/builtins.test.ts`;人工依次执行三命令目视核对。 - [ ] 模式切换不重建窗口、不丢失任何已打开内容:切换前在 EditorArea 区域的状态(含面板宽度调整)切换后仍保留各自模式记忆。 - - 验证:自动 `src/components/workbench/WorkbenchLayout.test.tsx`(EditorArea 不卸载断言);人工调宽后往返切换核对。 + - 验证:自动 `src/components/workbench/WorkbenchLayoutPersistence.test.tsx`;人工编辑、调宽后往返切换,核对正文、选区与模式布局。 - [ ] StatusBar 模式指示器实时显示当前模式名,与实际布局一致。 - - 验证:自动 `src/components/workbench/ModeIndicator.test.tsx`;人工切换时目视核对。 + - 验证:自动 `src/components/workbench/ModeIndicator.test.tsx`;人工核对状态栏右侧入口、当前模式勾选,以及「视图 → 模式」和命令面板入口。 - [ ] RightPanel 的 tab 集随模式变化:Standard(大纲/反链/局部图谱)、Academic(引用/Typst 预览/大纲)、Creative(Codex/场景概要),各 tab 空态文案为简体中文。 - 验证:自动 `src/components/workbench/RightPanel.test.tsx`;人工逐模式目视核对。 @@ -30,13 +35,13 @@ - [ ] 主题三态可用:命令面板执行「主题:亮色」「主题:暗色」「主题:跟随系统」三条命令,界面即时响应;跟随系统时随操作系统主题实时变化。 - 验证:自动 `src/stores/useSettingsStore.test.ts`;人工切换系统主题观察跟随。 -- [ ] 3 模式 x 2 亮暗 = 6 组合的强调色与主题变量值全部正确(CSS 变量层,Obsidian 命名习惯,Atom one-dark/one-light 取值)。 +- [ ] 3 模式 x 2 亮暗 = 6 组合的强调色与主题变量值全部正确(CSS 语义变量;当前灰绿强调色见 theme.css,语法高亮保留独立语义色)。 - 验证:自动 `src/styles/theme.test.ts`(6 组合变量断言)。 -- [ ] 右侧标签栏 active 态对比度达标:标签文本 4.5:1 门、accent 指示条 3:1 门(非文本组件),8 项检查全部通过: +- [ ] 内置主题右侧标签栏 active 态对比度达标:标签文本 4.5:1 门、accent 指示条 3:1 门(非文本组件)。以下 8 项计算检查不能代替导入主题、透明材质与真实平台的完整视觉核验: | 检查项 | 门槛 | 验证 | |--------|------|------| - | active tab 标签文本(亮,全模式同值) | 4.5:1 | 自动 `src/styles/contrast.test.ts` | + | active tab 标签文本(亮,全模式同值) | 4.5:1 | 自动 `src/styles/theme.test.ts`(比例计算另见 `contrast.test.ts`) | | active tab 标签文本(暗,全模式同值) | 4.5:1 | 同上 | | Standard 亮 accent 指示条 | 3:1 | 同上 | | Academic 亮 accent 指示条 | 3:1 | 同上 | @@ -60,12 +65,14 @@ - 验证:自动 `src/commands/mru.test.ts`;人工执行后重开面板核对。 - [ ] 命令注册表为统一入口:注册/注销/执行行为正确,重复注册有防御。 - 验证:自动 `src/commands/registry.test.ts`。 -- [ ] 已注册命令全集(12 条)可在面板中检索到:模式 x3、主题 x3、视图 x4(切换侧边栏/切换右侧面板/重置当前模式布局/命令面板)、应用:退出、帮助:关于 InkStream,显示名为「类别:动作」命名法简体中文。 - - 验证:自动 `src/commands/builtins.test.ts`;人工滚动面板全列表核对。 +- [ ] 命令面板按注册表及功能开关展示当前可用命令,包括项目、文件、编辑、视图、Git、学术与帮助入口;不再以早期的 12 条骨架命令作为全集。简易模式、pandoc 可用性和书架开关与菜单使用同一过滤规则。 + - 验证:自动 `src/commands/builtins.test.ts`、`src/components/workbench/menuConfig.test.ts`;人工切换开关后核对命令面板和菜单。 +- [ ] 「视图」菜单的打字机模式、专注模式显示实时勾选;从命令面板或编辑器 `F11` 改变专注状态后,菜单同步更新。写作 HUD 的状态栏入口反映其开启状态。 + - 验证:核对 `MenuBar.tsx` / `menuConfig.tsx` 与对应组件检查、`src/components/workbench/HudHintIndicator.test.tsx`;人工通过各入口往返核对。 ## 5. 中文 IME 防御 -- [ ] 拼音组合输入过程中按 Ctrl+B 等快捷键不触发命令分发(含旧引擎/WebView 的 keyCode 229 路径)。 +- [ ] 拼音组合输入过程中按 `Ctrl+\`、`Ctrl+B` 等快捷键不触发命令分发(含旧引擎/WebView 的 keyCode 229 路径)。 - 验证:自动 `src/commands/keymap.test.ts`(isComposing 与 keyCode 229 短路用例)。 - [ ] 命令面板输入框中,IME 组合上屏的 Enter 不执行选中命令,仅完成组合。 - 验证:自动 `src/components/palette/CommandPalette.test.tsx`(isComposing Enter 与 keyCode 229 Enter 用例);人工以中文输入法在面板内输入并上屏核对。 @@ -73,20 +80,22 @@ ## 6. 持久化与恢复 - [ ] 重启应用后原样恢复:主题三态、上次模式、三模式各自布局(面板宽度/折叠)、命令 MRU 顺序。 - - 验证:自动 `src/stores/persistSettings.test.ts`;人工「改状态 → 重启 → 核对」。 -- [ ] 手写损坏的 settings.json(非法枚举、超界宽度、异型结构)后启动:应用回落默认配置正常可用,并出现错误 toast「无法读取上次的布局配置,已恢复默认布局。」 - - 验证:自动 `src/stores/validateSettings.test.ts`(投毒输入收敛用例);人工手改配置文件后重启核对。 + - 验证:自动 `src/stores/persistSettings.test.ts`、`src/projects/session.test.ts`;人工「改状态 → 重启 → 核对」。项目会话恢复时应用该项目自己的模式与布局,不把全局上次模式当作所有项目的状态。 +- [ ] 设置中的非法枚举、超界宽度或异型结构经校验回落合法值;读取/解析失败时回落默认配置,并提示「无法读取上次的布局配置,已恢复默认布局。」 + - 验证:自动 `src/stores/validateSettings.test.ts` 与 `src/stores/persistSettings.test.ts`;人工仅在专用测试配置副本上核对两类失败,不修改用户实际配置。 - [ ] 配置写盘失败时出现警告 toast「布局配置保存失败,本次更改在重启后可能丢失。」,UI 不中断。 - 验证:自动 `src/stores/persistSettings.test.ts`(写失败路径文案逐字断言)。 - [ ] 频繁调整布局不产生高频磁盘写入(500ms 防抖合并落盘)。 - 验证:自动 `src/stores/persistSettings.test.ts`(防抖窗口内多次变更单次落盘断言)。 +- [ ] 项目档案、封面、会话恢复正文与索引使用本机应用数据目录,内容目录中的既有 `.inkstream` 保留;切换前保存/快照失败时原项目与正文保留。独立草稿、缺失文件恢复副本和「恢复前编辑」按[项目与恢复](../docs/projects.md)处理。 + - 验证:自动 `src/projects/session.test.ts`、`src/projects/session.recovery.test.ts` 和 `src-tauri/src/projects/tests.rs`;人工用测试创建的内容目录核对切换、重启、备份和故障提示。自动替身不代替真实磁盘故障验收。 ## 7. IPC 与 Channel 红线(立约条目) - [ ] 全项目仅 `src/ipc/` 目录触达 `@tauri-apps/api` 与 Tauri 插件 API,其余模块经类型化封装调用。 - - 验证:自动 ESLint `no-restricted-imports` 规则(`pnpm lint` 全绿即覆盖)。 -- [ ] **红线**:单次 invoke 负载 > 1MB(序列化后 1,048,576 字节)必须改走 Channel(`invokeStreamed`),禁止单条 JSON 消息直传。判定与机理见 `src/ipc/README.md`。 - - 验证:code review 引用条目——后续任何引入大负载 IPC 的变更,评审时对照本条与 `src/ipc/README.md`;运行时断言在出现首个真实大负载的阶段落地。 + - 验证:ESLint `no-restricted-imports` 规则只证明导入边界,不证明具体 IPC 行为或整体验收。 +- [ ] 单次 JSON invoke 不超过 1MiB;文件读取走 Channel Raw 分块,写入走 begin / append / commit 会话、逐块确认。数据块最大 256KiB,只有真实提交成功才能清除未保存状态,失败/取消不得误报保存成功。 + - 验证:对照 [IPC 协议](../src/ipc/README.md)、`src/ipc/fileStream.ts`、`src/ipc/fileWrite.ts` 与原生读写测试;真实 WebView 响应性另测。该实现已经存在,不再列作未来大负载阶段的待实现断言。 ## 8. 平台行为 @@ -94,10 +103,10 @@ - 验证:自动 `src/components/workbench/TitleBar.test.tsx`、`src/ipc/window.test.ts`(控制调用断言);人工真窗口逐项操作。 - [ ] 窗口几何持久化且离屏兜底:拔掉外接显示器后启动,窗口自动回到主屏中央而非停留在不可见区域(含负坐标位置)。 - 验证:自动 `src-tauri/src/window_guard.rs` 单元测试(`cargo test`,含负坐标用例);人工改写窗口位置到离屏坐标后启动核对。 -- [ ] 三平台构建冒烟:ubuntu / windows / macos 三平台 CI 上 install → typecheck → lint → test → build → cargo check 全绿。 - - 验证:自动 `.github/workflows/ci.yml`(GitHub Actions matrix 运行结果)。 +- [ ] 三平台 CI 完成锁文件安装 → typecheck → lint → `test:ci` → build → 独立 Unix 负控(仅 Unix)→ `run-rust.mjs` 全目标生产测试。 + - 验证:`.github/workflows/ci.yml` 与对应运行报告。原生实际命令为 `cargo test --locked --manifest-path src-tauri/Cargo.toml --all-targets -- --test-threads=1`,不另跑 `cargo check`;发布记录见 [AUTOMATION.md](../docs/specs/AUTOMATION.md)。三平台 CI 与 Release success 不自动勾选人工或性能项。 ## 9. 已知偏差 - [ ] **macOS Cmd(Meta)修饰键映射推迟**:本阶段快捷键解析仅覆盖 Ctrl / Alt / Shift 修饰键,macOS 上以 Cmd 为主修饰键的等价映射(如 Cmd+Shift+P)暂未实现,待 macOS 实机交互测试阶段补齐后回收本条。代码标记:`src/commands/keymap.ts` 内 `DEVIATION(D-05)` 注释。 - - 验证:存在性核对——`grep "DEVIATION(D-05)" src/commands/keymap.ts` 命中即偏差仍在登记;补齐实现并删除标记后,本条改为常规快捷键验收项。 + - 验证:`rg -n 'DEVIATION\(D-05\)|metaKey|MODIFIER_ORDER' src/commands/keymap.ts` 核对实现;保留偏差直到全局 Cmd 映射与 macOS 真机检查均有证据。编辑器或链接局部支持 Cmd 不代表全局命令已经映射。 diff --git a/specs/03-live-preview-ime.spec.md b/specs/03-live-preview-ime.spec.md index f3be29e..51b24c9 100644 --- a/specs/03-live-preview-ime.spec.md +++ b/specs/03-live-preview-ime.spec.md @@ -1,22 +1,24 @@ -# Phase 3 中文 IME 手测清单(永久回归门 D-13) +# 中文 IME 真机回归清单(2.0 基线,永久回归门 D-13) > **本清单是 EDIT-06 永久回归门。** 它是全项目最高风险件(`view.composing` 冻结纪律)的护栏: > 一旦冻结回退即吞字 / 候选窗跳位 / 上屏错乱。 > -> **本阶段及 Phase 4 / 5 / 8 / 9 的所有装饰类变更,合并前必须逐项勾验通过。** 任何触及 +> **所有触及装饰层的变更,合并前必须逐项勾验通过。** 任何触及 > CodeMirror 装饰层(块级 StateField / 行内 ViewPlugin / atomicRanges)的改动都要重跑本清单。 > > 与 `specs/01-workbench.spec.md` 同构:入库公开可审计、人工逐项勾验。在力所能及范围内已补 > Vitest 模拟 `CompositionEvent` 的自动回归桩以减人工负担,但**人工清单始终是硬门**—— > 自动桩无法复现真实候选窗行为,不可替代真机逐项勾验。 +> +> `v2.0.0` / `ca4c214` 已公开发布,相关 CI 与 Release 成功不代表本矩阵已完成。没有对应提交、平台、输入法版本和实测结果的格子保持未勾选;[开放验证事项](../docs/specs/AUTOMATION.md)继续保留。 ## 0. 范围与平台门 -- **Windows 为本阶段硬门**:下方 4 输入法 × 4 场景类矩阵在 Windows 真机逐项勾验通过,方可合并装饰类变更。 +- **Windows 物理输入法仍是回归硬门**:下方 4 输入法 × 4 场景类矩阵需在 Windows 真机逐项勾验。 - **表格就地编辑(方案 B 子编辑器)IME 专项**:见 §1 专项框 + §4.5——子编辑器是独立 IME 宿主,须单独逐项勾验 (上屏不跳末尾 B1 / 撤销不跳顶 B2 / 跨格续写 / 对齐列编辑 / 空行高),触及 tableCellEditor / TableWidget / blockField 的改动须重跑。 -- **macOS(注音 / 拼音)、Linux(fcitx / ibus)留 Phase 12 打包阶段补测**——本阶段不阻断,届时以同结构矩阵回归。 -- 执行时机:Live Preview 链接 / 装饰落地后(Wave 2 之后)逐项执行;jsdom 无法复现真实候选窗行为,故全部为真机手测。 +- **macOS(注音 / 拼音)、Linux(fcitx / ibus)的物理输入法仍需同结构矩阵**;不能因打包阶段或跨平台 CI 已完成而记为通过。 +- 执行时机:触及主编辑器、表格子编辑器、公式源码或组合输入相关装饰后。需实际启用 OS 输入法并观察候选窗;jsdom 合成事件、直接注入文本或普通自动点击不能代替这一证据。 ## 1. 通用判据(每格勾验四项,全部满足才算通过) @@ -37,7 +39,7 @@ > **表格单元格子编辑器专项(方案 B,TABLE-WYSIWYG / commit 0b451b3)**:表格就地编辑用嵌套子 EditorView > 承载单元格——它是与主编辑器**并存的独立 IME 宿主**(独立 contentDOM、子组合事件经 `stopBubblingToMain` -> 不上达主门、组合期**每个 compositionupdate 即时 commit 回写主 doc**)。此路径不走主编辑器组合门,第 1 节 +> 不上达主门)。当前 `commitSub` 会检查主编辑器和子编辑器的组合状态,组合期间延后回写,不能按“每次 compositionupdate 即时提交”验收。第 1 节 > 四判据之外**额外**须满足(缺一即失败): > 1. **上屏后光标停插入点**:在单元格已有文字**中部**用拼音输入、按空格确认上屏后,光标停在刚上屏文字之后, > **不弹到单元格末尾**(B1 真因:commit 重建子编辑器 + focusSub 落 doc.length;已修为复用子实例 + 保选区)。 @@ -53,7 +55,7 @@ | 代号 | 输入法 | 选取理由 | |------|--------|----------| | IME-A | 微软拼音 | 系统内置基准,最常见路径 | -| IME-B | 搜狗拼音 | 市占第一,第三方候选窗行为差异大 | +| IME-B | 搜狗拼音 | 常见第三方输入法,候选窗行为需独立覆盖 | | IME-C | 双拼模式 | 码长 / 上屏时序与全拼不同 | | IME-D | 手写 / 语音面板 | 非标准一次性上屏路径,绕常规组合事件序列 | @@ -109,39 +111,36 @@ - [ ] 对齐列编辑一致:把某列设居中 / 右对齐后进入该列单元格输入中文,编辑中文字即跟随对齐,空格确认上屏后与同列非活动格渲染一致。(Wave2/3 A) - [ ] 空单元格行高一致:工具条「插入行」后在新空行单元格输入中文上屏,空行高度自始至终与内容行一致(不偏矮)。(Wave2/3 B) -### 4.6 Fenced 公式块源码编辑专项(Phase 5,```math / ```latex / ```typst;至少 IME-A 微软拼音 + IME-B 搜狗) +### 4.6 公式块源码编辑专项(math / latex / typst 围栏及 :::typst 容器;至少 IME-A 微软拼音 + IME-B 搜狗) > 光标进公式块即还原源码(块级 replace 装饰 skip,**主编辑器**直接编辑源码——非子编辑器,走主组合门,与表格子编辑器 > 路径不同)。在源码内输入中文(typst `#text[中文]` / latex `\text{中文}` / 注释等),对照第 1 节四判据 **+** 下列专项: -- [ ] 块内源码输中文不吞字:进 ```typst / ```latex / ```math 块,在源码内连续输入中文(多字词 + 组合中途删改),无吞字、上屏无错乱。 +> 点击公式进入双栏编辑时使用独立源码输入区,须另验该入口的组合输入、完成/Esc 退出与正文回写;不能只验主编辑器中的围栏源码就判定双栏路径通过。 + +- [ ] 块内源码输中文不吞字:进 ```typst / ```latex / ```math 块或 :::typst 容器,在源码内连续输入中文(多字词 + 组合中途删改),无吞字、上屏无错乱。 - [ ] 组合期块不闪渲染:块内输中文组合期间,该块**保持源码态不闪回渲染**(blockField `isComposingTr` 映射旧装饰不重建);`compositionend` 后才刷新。 - [ ] 进/出块切换不被组合打断:光标在「块外渲染 ↔ 块内源码」间移动时,边界重建走 `isComposingTr` 门,组合期不撕装饰吞字。 - [ ] typst 异步编译不干扰输入:typst 块内输中文(200ms 防抖编译)期间输入流畅、无吞字(编译在 Worker,不阻塞主线程 IME)。 - [ ] `/math` `/latex` `/typst` 触发器插块后续输中文:补全 apply 插块 + 光标落空行后直接输中文,无吞字、光标不弹位(apply 零 `view.focus`)。 +- [ ] 双栏公式编辑:在独立源码输入区连续中文组合上屏,完成或 Esc 退出后正文与公式一致,无吞字、重复提交或光标错位。 ## 5. 自动回归桩(减负,非硬门) - [ ] 模拟 `compositionstart` → 输入 → `compositionend` 的 Vitest 桩存在并通过:断言组合期装饰暂停**语法树重算**、 组合期 docChanged 时旧装饰经 `tr.changes` **映射跟随位移**(非返回未映射旧集)、`compositionend` 后推迟一个 微任务统一刷新一次(覆盖 `view.composing` / `isFrozen` 双判 + CM6 原生 `input.type.compose` userEvent 路径)。 - - 验证:自动(对应 CM 装饰扩展的配对测试,`pnpm test` 全绿即覆盖)。 + - 验证:核对 `src/editor/composition.test.ts`、`src/editor/composition.regression.test.ts` 与对应装饰配对测试的实际断言和执行结果;`pnpm test:ci` 汇总绿色不能代替本项逐条证据。 - 说明:此桩仅减少人工负担,**不替代**第 4 节真机矩阵;自动桩绿不构成回归门通过。 - [ ] 表格子编辑器(方案 B)配对桩存在并通过:`tableCellEditor.test.ts` 组合期门桩(组合中子→主不 commit、 end 后落 doc)+ B1/B2 回归桩(连续 commit 复用同一子编辑器实例不重建 / 本地优先撤销 undo(sub) 先撤本格 / 进格锚主选区落表内 / 活动格跟随列对齐)。 - - 验证:自动(`pnpm test` 全绿即覆盖)。 + - 验证:核对 `src/editor/livepreview/tableCellEditor.test.ts` 的具体断言及实际运行结果;这些是组合门、选区和撤销契约的证据。 - 说明:同样**不替代** §4.5 真机专项——jsdom 不驱动子 view 真实组合与「空格确认上屏」时序。 -> **真因结论(2026-06-12,WebView2 平台限制,非装饰层)**:经真机 CDP 直连确证,中文 IME 仅在**真实指针进入 -> 编辑器**时才武装 OS IME/TSF;任何 programmatic 路径(`view.focus()` / 原生 `ICoreWebView2Controller::MoveFocus` / -> EditContext)都不武装 → 打开文件后**首次**程序化聚焦的中文组合丢字。装饰层(freeze/map、Option 2 活动行纯源码) -> 经 CDP 证实**无辜**——IME 一旦工作,装饰层不打断它(即第 5 节自动桩断言的契约成立)。故对策为**不程序化抢焦点** -> (去 auto-focus,由用户点击编辑器自然落焦);该 WebView2 限制记入 `.planning/CONSTRAINTS.md §8`,深层 click-free -> IME 健壮性并入 UX 重做里程碑。 +> **历史排障线索(2026-06-12)**:此前在特定 Windows / WebView2 环境观察到首次程序化聚焦与物理点击进入编辑器的 IME 表现不同,并据此调整聚焦路径。这是旧环境的排障记录,不足以证明所有 WebView2 版本都存在同一限制,也不能排除后续装饰变更的影响。当前回归应分别覆盖首次打开、真实点击聚焦、切换标签和恢复项目后的中文输入。 > -> **诊断脚手架已移除**:定位期间曾加的 DEV-ONLY `[IME-TRACE]` 追踪器(前端 console + Rust `ime_trace_append` 落盘)、 -> 原生 `arm_webview_ime` 武装、程序化 auto-focus 三者在结论确定后一并移除(仓库纯净硬约束)。需复诊按 git 历史还原即可。 +> **当前诊断入口**:`src/App.tsx` 仍按 `import.meta.env.DEV` 挂载 `ImeProbe`。历史 `[IME-TRACE]` 等工具的变迁以 Git 历史为准,不能笼统写成当前没有任何诊断脚手架;开发探针存在或其测试通过也不构成正式安装版 IME 验收。 > **⚠️ 真机为 EDIT-06 唯一验收门(jsdom 无法复现 composition,这正是本 bug 曾经带病上线的原因)。** > jsdom 的 `CompositionEvent` 桩**不驱动**浏览器真实 IME 组合状态机:它无法复现「合成中文本节点」、 @@ -159,14 +158,15 @@ 1. 定位回退点:多为组合期未短路装饰重建(`view.composing` / `isComposing` 漏判)或 `compositionend` 刷新破坏盒模型; 表格子编辑器路径(§4.5)多为子→主 commit 触发重建未保子实例/选区,或撤销委派/锚选区回退。 2. 修复后**重跑整张矩阵**(不可只补失败格——冻结纪律是全局不变量)。 -3. 修复与重验结论记入对应阶段 SUMMARY 的偏差 / 验证小节。 +3. 将修复提交、失败格、平台/输入法版本及重验结果记入对应验证 issue 与下方签核表。 ## 7. 验收签核记录(每次跑矩阵填一行,留审计轨迹) -> 第 4 节 16 格 + §4.5 专项每全过一轮,在此记一行。结果列填「全过」或「失败:<格号/项>」。 +> 第 4 节 16 格 + §4.5 及适用的 §4.6 专项每执行一轮,在此记一行。结果列填「全过」或「失败:<格号/项>」;未执行保持待填。 > 装饰类变更合并前须有一行覆盖该提交且结果为「全过」。 | 日期 | 验证提交(SHA) | 平台 / WebView2 | 输入法 + 版本 | 结果 | 备注 | |------|---------------|-----------------|---------------|------|------| -| (待填) | | Windows / 148 Fixed Runtime | 微软拼音 / 搜狗 | | | -| (待填,Phase 5)| 8849568 / 3286eb7 / 21d33b7 | Windows / 148 Fixed Runtime | 微软拼音 / 搜狗 | | §4.6 fenced 公式块(math/latex/typst)源码 IME 待真机逐项 | +| (待实测) | | Windows / 实际 WebView2 版本 | 实际输入法与版本 | 未执行 | 16 格及表格/公式专项 | +| (待实测) | | macOS / 实际 WebView 版本 | 注音/拼音与版本 | 未执行 | 同结构平台矩阵 | +| (待实测) | | Linux / 实际 WebKit 版本 | fcitx/ibus 与版本 | 未执行 | 同结构平台矩阵 | diff --git a/specs/README.md b/specs/README.md new file mode 100644 index 0000000..3e1402e --- /dev/null +++ b/specs/README.md @@ -0,0 +1,5 @@ +# 行为规格入口 + +当前基线为已发布的 2.0.0。本目录的工作台与 IME 规格按各自证据验收,不因 pnpm test 通过就自动覆盖人工条目。 + +DDD/BDD 本轮顺序见 [执行计划](../docs/EXECUTION-PLAN.md)。会话 Gherkin 及其人工 Vitest 映射见 [自动化说明](../docs/specs/AUTOMATION.md),不能把人工映射称为执行了 Gherkin。多栏场景的自动检查和 ComputerUse 观察见 [验收记录](../docs/WORKBENCH-ACCEPTANCE.md)。 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 33279be..d4610fa 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2735,7 +2735,7 @@ dependencies = [ [[package]] name = "inkstream" -version = "2.0.0" +version = "2.1.0" dependencies = [ "git2", "keyring", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1e1faf2..d518099 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "inkstream" -version = "2.0.0" +version = "2.1.0" description = "InkStream desktop writing application" edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index cd1067b..1198bd3 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "InkStream", - "version": "2.0.0", + "version": "2.1.0", "identifier": "com.krpct.inkstream", "build": { "beforeDevCommand": "pnpm dev", @@ -25,13 +25,21 @@ "csp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost https://ipc.localhost; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: asset: http://asset.localhost https:; font-src 'self'", "assetProtocol": { "enable": true, - "scope": ["**"] + "scope": [ + "**" + ] } } }, "bundle": { "active": true, - "targets": ["deb", "appimage", "nsis", "app", "dmg"], + "targets": [ + "deb", + "appimage", + "nsis", + "app", + "dmg" + ], "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -44,7 +52,10 @@ "longDescription": "InkStream(墨流):基于 Tauri 2 的桌面写作应用,单一 CodeMirror 6 内核,Standard / Academic / Creative 三模式,内置 git 版本管理、Obsidian 式双向链接、Zotero 引用与 Typst/LaTeX/KaTeX 数学排版。", "fileAssociations": [ { - "ext": ["md", "markdown"], + "ext": [ + "md", + "markdown" + ], "name": "Markdown", "description": "Markdown 文档", "role": "Editor", @@ -55,7 +66,9 @@ }, "plugins": { "updater": { - "endpoints": ["https://github.com/KRPCT/InkStream/releases/latest/download/latest.json"], + "endpoints": [ + "https://github.com/KRPCT/InkStream/releases/latest/download/latest.json" + ], "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDM0REU0OTE2NDE5M0IwN0UKUldSK3NKTkJGa25lTkZnNEY0YVVIcUR1Q2xkN0N0eUNTRTdzVHZCM3VIK0FiaFE5emNwcW9ZT1AK" } } diff --git a/src/academic/useReferenceLibrary.test.tsx b/src/academic/useReferenceLibrary.test.tsx new file mode 100644 index 0000000..6f5193b --- /dev/null +++ b/src/academic/useReferenceLibrary.test.tsx @@ -0,0 +1,49 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { CslItem, ZoteroItem } from '../types/zotero'; + +const api = vi.hoisted(() => ({ revision: 0, listeners: new Set<() => void>(), items: vi.fn(), detail: vi.fn() })); +vi.mock('../ipc/zotero', () => ({ + currentZoteroLibraryRevision: () => api.revision, + onZoteroLibraryChanged: (listener: () => void) => { api.listeners.add(listener); return () => api.listeners.delete(listener); }, + zoteroItemsResilient: api.items, zoteroCslResilient: api.detail, +})); +import { useReferenceLibrary } from './useReferenceLibrary'; +const a: ZoteroItem = { citekey: 'A', title: '甲库文献', authors: '甲', year: '2025' }; +const b: ZoteroItem = { citekey: 'B', title: '乙库文献', authors: '乙', year: '2026' }; +function deferred() { let resolve!: (value: T) => void; const promise = new Promise((done) => { resolve = done; }); return { promise, resolve }; } +beforeEach(() => { api.revision = 0; api.listeners.clear(); api.items.mockReset().mockResolvedValue({ items: [a], offline: false }); api.detail.mockReset().mockResolvedValue([]); }); + +describe('文献浏览会话', () => { + it('WB-04 切换库立即清空旧选择,迟到详情不能进入新库或成为插入目标', async () => { + const pending = deferred(); api.detail.mockReturnValue(pending.promise); + const { result } = renderHook(useReferenceLibrary); + await waitFor(() => expect(result.current.items).toEqual([a])); + act(() => { void result.current.select(a); }); + expect(result.current.selectedKey()).toBe('A'); + api.items.mockResolvedValue({ items: [b], offline: true }); + await act(async () => { api.revision++; api.listeners.forEach((listener) => listener()); }); + expect(result.current.selected).toBeNull(); expect(result.current.selectedKey()).toBeNull(); + await act(async () => pending.resolve([{ id: 'A', DOI: 'old-library' }])); + expect(result.current.detail).toBeNull(); expect(result.current.items).toEqual([b]); expect(result.current.offline).toBe(true); + }); + it('WB-04 迟到列表不能覆盖当前库,卸载释放订阅', async () => { + const pending = deferred<{ items: ZoteroItem[]; offline: boolean }>(); api.items.mockReturnValueOnce(pending.promise); + const { result, unmount } = renderHook(useReferenceLibrary); + api.items.mockResolvedValue({ items: [b], offline: false }); + await act(async () => { api.revision++; api.listeners.forEach((listener) => listener()); }); + await act(async () => pending.resolve({ items: [a], offline: false })); + expect(result.current.items).toEqual([b]); + unmount(); expect(api.listeners.size).toBe(0); + }); + it('快速改选时详情始终属于最后选中的文献', async () => { + api.items.mockResolvedValue({ items: [a, b], offline: false }); + const pending = deferred(); api.detail.mockReturnValueOnce(pending.promise).mockResolvedValueOnce([{ id: 'B', DOI: 'new' }]); + const { result } = renderHook(useReferenceLibrary); + await waitFor(() => expect(result.current.items).toHaveLength(2)); + act(() => { void result.current.select(a); }); + await act(async () => result.current.select(b)); + await act(async () => pending.resolve([{ id: 'A', DOI: 'old' }])); + expect(result.current.selectedKey()).toBe('B'); expect(result.current.detail?.DOI).toBe('new'); + }); +}); diff --git a/src/academic/useReferenceLibrary.ts b/src/academic/useReferenceLibrary.ts new file mode 100644 index 0000000..95eadbd --- /dev/null +++ b/src/academic/useReferenceLibrary.ts @@ -0,0 +1,57 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { currentZoteroLibraryRevision, onZoteroLibraryChanged, zoteroCslResilient, zoteroItemsResilient } from '../ipc/zotero'; +import type { CslItem, ZoteroItem } from '../types/zotero'; + +const message = (error: unknown) => error instanceof Error ? error.message : String(error); + +/** 文献浏览会话。库身份、列表与所选详情作为同一生命周期失效;选择不写入文档。 */ +export function useReferenceLibrary() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [offline, setOffline] = useState(false); + const [selected, setSelected] = useState(null); + const [detail, setDetail] = useState(null); + const [detailLoading, setDetailLoading] = useState(false); + const [detailError, setDetailError] = useState(null); + const listRequest = useRef(0); + const detailRequest = useRef(0); + const libraryRevision = useRef(currentZoteroLibraryRevision()); + const clearSelection = useCallback(() => { + detailRequest.current += 1; + setSelected(null); setDetail(null); setDetailError(null); setDetailLoading(false); + }, []); + const load = useCallback(async () => { + const request = ++listRequest.current; + const revision = currentZoteroLibraryRevision(); + libraryRevision.current = revision; + clearSelection(); setItems([]); setLoading(true); setError(null); setOffline(false); + const current = () => request === listRequest.current && revision === currentZoteroLibraryRevision(); + try { + const result = await zoteroItemsResilient(); + if (current()) { setItems(result.items); setOffline(result.offline); } + } catch (failure) { if (current()) setError(message(failure)); } + finally { if (current()) setLoading(false); } + }, [clearSelection]); + useEffect(() => { + const unsubscribe = onZoteroLibraryChanged(() => { void load(); }); + void load(); + return () => { listRequest.current += 1; detailRequest.current += 1; unsubscribe(); }; + }, [load]); + + const select = async (item: ZoteroItem) => { + if (libraryRevision.current !== currentZoteroLibraryRevision() || !items.includes(item)) return; + const request = ++detailRequest.current; + const revision = libraryRevision.current; + setSelected(item); setDetail(null); setDetailError(null); setDetailLoading(true); + const current = () => request === detailRequest.current && revision === currentZoteroLibraryRevision(); + try { + const result = await zoteroCslResilient([item.citekey]); + if (current()) setDetail(result.find((entry) => + String(entry.id) === item.citekey || entry['citation-key'] === item.citekey || entry.citekey === item.citekey) ?? null); + } catch (failure) { if (current()) setDetailError(message(failure)); } + finally { if (current()) setDetailLoading(false); } + }; + const selectedKey = () => libraryRevision.current === currentZoteroLibraryRevision() ? selected?.citekey ?? null : null; + return { items, loading, error, offline, selected, detail, detailLoading, detailError, load, select, selectedKey }; +} diff --git a/src/components/projects/ProjectRail.test.tsx b/src/components/projects/ProjectRail.test.tsx new file mode 100644 index 0000000..e745ed3 --- /dev/null +++ b/src/components/projects/ProjectRail.test.tsx @@ -0,0 +1,30 @@ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { beforeEach, expect, it, vi } from 'vitest'; +import { useProjectStore } from '../../stores/useProjectStore'; +import { useWorkbenchStore } from '../../stores/useWorkbenchStore'; +import type { ProjectRecord } from '../../types/projects'; +const open = vi.hoisted(() => vi.fn()); +vi.mock('../../projects/actions', () => ({ openProject: open })); +import ProjectRail from './ProjectRail'; +const project = (id: string): ProjectRecord => ({ id, name: `项目${id}`, root: `D:/${id}`, favorite: false, cover: null, removed: false, createdAt: 1, lastOpenedAt: 1 }); +beforeEach(() => { + useProjectStore.setState({ phase: 'idle', archiveOpen: false, activeId: 'A', catalog: { version: 1, activeId: 'A', projects: [project('A'), project('B')] } }); + useWorkbenchStore.setState(useWorkbenchStore.getInitialState(), true); + open.mockReset().mockResolvedValue(false); +}); +it('WB-02 项目轨使用完整切换入口,失败保持当前项目并展示档案中的错误', async () => { + render(); + await act(async () => fireEvent.click(screen.getByRole('button', { name: '打开项目:项目B' }))); + expect(open).toHaveBeenCalledWith('B'); + expect(useProjectStore.getState().activeId).toBe('A'); expect(useProjectStore.getState().archiveOpen).toBe(true); + expect(screen.getByRole('button', { name: '打开项目:项目A' })).toHaveAttribute('aria-current', 'true'); +}); +it('WB-07 两栏开关独立,选中反馈跟随实际折叠状态', () => { + render(); + const left = screen.getByRole('button', { name: '展开或收起文件导航' }); + const right = screen.getByRole('button', { name: '展开或收起工具面板' }); + fireEvent.click(left); + expect(left).toHaveAttribute('aria-pressed', 'false'); expect(right).toHaveAttribute('aria-pressed', 'true'); + fireEvent.click(right); fireEvent.click(left); + expect(left).toHaveAttribute('aria-pressed', 'true'); expect(right).toHaveAttribute('aria-pressed', 'false'); +}); diff --git a/src/components/projects/ProjectRail.tsx b/src/components/projects/ProjectRail.tsx index 9536361..aa6b2da 100644 --- a/src/components/projects/ProjectRail.tsx +++ b/src/components/projects/ProjectRail.tsx @@ -2,25 +2,32 @@ import { Archive, PanelLeft, PanelRight, Settings } from 'lucide-react'; import { execute } from '../../commands/registry'; import { useProjectStore } from '../../stores/useProjectStore'; import { useWorkbenchStore } from '../../stores/useWorkbenchStore'; +import { openProject } from '../../projects/actions'; +import ProjectCover from './ProjectCover'; export default function ProjectRail() { const catalog = useProjectStore((state) => state.catalog); const activeId = useProjectStore((state) => state.activeId); const archiveOpen = useProjectStore((state) => state.archiveOpen); const phase = useProjectStore((state) => state.phase); - const project = catalog.projects.find((item) => item.id === activeId && !item.removed); - const count = catalog.projects.filter((item) => !item.removed).length; + const layout = useWorkbenchStore((state) => state.layouts[state.mode]); + const projects = catalog.projects.filter((item) => !item.removed).sort((a, b) => Number(b.favorite) - Number(a.favorite) || b.lastOpenedAt - a.lastOpenedAt); return ; diff --git a/src/components/settings/settingsSections.tsx b/src/components/settings/settingsSections.tsx index 051d4d8..c0f79ca 100644 --- a/src/components/settings/settingsSections.tsx +++ b/src/components/settings/settingsSections.tsx @@ -41,11 +41,12 @@ function SettingRow({ ); } -function Toggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) { +function Toggle({ label, checked, onChange }: { label: string; checked: boolean; onChange: (v: boolean) => void }) { return ( ); + + const activeTab = screen.getByRole('tab', { name: /b\.md/ }); + expect(activeTab).toHaveAttribute('tabindex', '0'); + expect(screen.getByRole('tab', { name: /a\.md/ })).toHaveAttribute('tabindex', '-1'); + expect(screen.getByRole('button', { name: '关闭 a.md' })).toHaveAttribute('tabindex', '-1'); + await user.tab(); + expect(activeTab).toHaveFocus(); + + const closeButton = screen.getByRole('button', { name: '关闭 b.md' }); + expect(closeButton).toBeVisible(); + await user.tab(); + expect(closeButton).toHaveFocus(); + await user.tab(); + expect(screen.getByRole('button', { name: '后续内容' })).toHaveFocus(); + await user.tab({ shift: true }); + expect(closeButton).toHaveFocus(); + expect(closeButton).toBeVisible(); + await user.tab({ shift: true }); + expect(activeTab).toHaveFocus(); + }); + + it.each([ + ['{ArrowRight}', 'b.md'], + ['{ArrowLeft}', 'c.md'], + ['{End}', 'c.md'], + ['{ArrowRight}{Home}', 'a.md'], + ['{End}{ArrowRight}', 'a.md'], + ['{End}{ArrowLeft}', 'b.md'], + ])('WB-05:按 %s 后焦点和活动文档切换到 %s,首尾循环', async (keys, name) => { + const user = userEvent.setup(); + useEditorStore.getState().openTab({ path: 'c.md', name: 'c.md' }); + render(); + await user.tab(); + await user.keyboard(keys); + + const selected = screen.getByRole('tab', { selected: true }); + expect(selected).toHaveTextContent(name); + expect(selected).toHaveFocus(); + expect(selected).toHaveAttribute('tabindex', '0'); + for (const tab of screen.getAllByRole('tab', { selected: false })) { + expect(tab).toHaveAttribute('tabindex', '-1'); + } + expect(useEditorStore.getState().activePath).toBe(name); + expect(switchTab).toHaveBeenLastCalledWith(name); + expect(flushAutosave).not.toHaveBeenCalled(); + }); + + it.each(['{Enter}', ' '])('WB-05:按 %s 激活已聚焦的文档入口', async (key) => { + const user = userEvent.setup(); + render(); + const tab = screen.getByRole('tab', { name: /b\.md/ }); + act(() => tab.focus()); + await user.keyboard(key); + expect(tab).toHaveFocus(); + expect(tab).toHaveAttribute('aria-selected', 'true'); + expect(tab).toHaveAttribute('tabindex', '0'); + expect(switchTab).toHaveBeenCalledTimes(1); + expect(switchTab).toHaveBeenCalledWith('b.md'); + }); + + it('WB-05:关闭按钮上的方向键不切文档,回车只执行关闭请求', async () => { + const user = userEvent.setup(); + flushAutosave.mockResolvedValue({ kind: 'failed' }); + render(); + await user.tab(); + await user.tab(); + const closeButton = screen.getByRole('button', { name: '关闭 a.md' }); + expect(closeButton).toHaveFocus(); + await user.keyboard('{ArrowRight}{Home}{End}'); + expect(closeButton).toHaveFocus(); + await user.keyboard('{Enter}'); + expect(flushAutosave).toHaveBeenCalledTimes(1); + expect(flushAutosave).toHaveBeenCalledWith('a.md'); + expect(switchTab).not.toHaveBeenCalled(); + expect(screen.getByRole('tab', { name: /a\.md/ })).toHaveAttribute('aria-selected', 'true'); + }); + + it.each<{ label: string; outcome: SaveOutcome }>([ + { label: '保存失败', outcome: { kind: 'failed' } }, + { label: '外部冲突', outcome: { kind: 'blocked', reason: 'conflict' } }, + { label: '保存期间修订改变', outcome: { kind: 'changed' } }, + { label: '保存返回后仍有新修改', outcome: { kind: 'saved' } }, + ])('WB-05:键盘关闭遇到$label时保留文档、脏标记及关闭焦点', async ({ outcome }) => { + const user = userEvent.setup(); + useEditorStore.getState().markDirty('a.md'); + if (outcome.kind === 'blocked') { + useEditorStore.getState().freezeAutosave('a.md'); + useEditorStore.getState().markExternalChange('a.md'); + } + flushAutosave.mockResolvedValue(outcome); + render(); + await user.tab(); + await user.tab(); + const closeButton = screen.getByRole('button', { name: '关闭 a.md' }); + expect(closeButton).toHaveFocus(); + expect(closeButton).toBeVisible(); + await user.keyboard('{Enter}'); + + expect(screen.getByRole('tab', { name: /a\.md/ })).toHaveAttribute('aria-selected', 'true'); + expect(screen.getByTestId('dirty-dot-a.md')).toBeInTheDocument(); + expect(closeButton).toHaveFocus(); + expect(disposeStateSpy).not.toHaveBeenCalled(); + expect(useEditorStore.getState().dirty['a.md']).toBe(true); + if (outcome.kind === 'blocked') { + expect(useEditorStore.getState().frozen['a.md']).toBe(true); + expect(useEditorStore.getState().externalChanged['a.md']).toBe(true); + } + }); - it('渲染两端贴边面板开关,默认展开态 aria-pressed=true', () => { + it('WB-05:键盘关闭脏文档在保存成功清除脏标记后才移除标签', async () => { + const user = userEvent.setup(); + useEditorStore.getState().markDirty('a.md'); + flushAutosave.mockImplementationOnce(async (path) => { + useEditorStore.getState().clearDirty(path); + return { kind: 'saved' }; + }); render(); - const left = screen.getByRole('button', { name: /侧边栏/ }); - const right = screen.getByRole('button', { name: /右侧面板/ }); - // DEFAULT_LAYOUT 两侧均展开(collapsed=false)→ pressed=true - expect(left).toHaveAttribute('aria-pressed', 'true'); - expect(right).toHaveAttribute('aria-pressed', 'true'); + await user.tab(); + await user.tab(); + await user.keyboard(' '); + + await waitFor(() => expect(screen.queryByRole('tab', { name: /a\.md/ })).not.toBeInTheDocument()); + expect(flushAutosave).toHaveBeenCalledWith('a.md'); + expect(screen.getByRole('tab', { name: /b\.md/ })).toHaveAttribute('aria-selected', 'true'); + expect(switchTab).not.toHaveBeenCalled(); }); - it('点左开关走 view.toggle-sidebar 命令、右开关走 view.toggle-right-panel', () => { - const exec = vi.spyOn(registry, 'execute').mockResolvedValue(undefined); + it('WB-05:键盘关闭脏草稿仍可取消丢弃,且不调用落盘', async () => { + const user = userEvent.setup(); + useEditorStore.getState().openTab({ path: 'draft://1', name: '未命名-1' }); + useEditorStore.getState().markDirty('draft://1'); render(); - fireEvent.click(screen.getByRole('button', { name: /侧边栏/ })); - fireEvent.click(screen.getByRole('button', { name: /右侧面板/ })); - expect(exec).toHaveBeenCalledWith('view.toggle-sidebar'); - expect(exec).toHaveBeenCalledWith('view.toggle-right-panel'); - exec.mockRestore(); + await user.tab(); + await user.keyboard('{End}'); + await user.tab(); + await user.keyboard('{Enter}'); + + expect(confirmDestructive).toHaveBeenCalledTimes(1); + expect(flushAutosave).not.toHaveBeenCalled(); + expect(screen.getByRole('tab', { name: /未命名-1/ })).toHaveAttribute('aria-selected', 'true'); + expect(screen.getByRole('button', { name: '关闭 未命名-1' })).toHaveFocus(); + expect(useEditorStore.getState().dirty['draft://1']).toBe(true); + expect(disposeStateSpy).not.toHaveBeenCalled(); }); - it('折叠态 → aria-pressed=false 且 aria-label/标题切到“展开”', () => { - useWorkbenchStore.getState().toggleSidebar(); + it('WB-05:键盘可到达外部文档,保留非工作区标记并按原绝对路径关闭', async () => { + const user = userEvent.setup(); + const path = 'D:/outside/reference.md'; + useEditorStore.getState().openTab({ path, name: 'reference.md', external: true }); render(); - const left = screen.getByRole('button', { name: /展开侧边栏/ }); - expect(left).toHaveAttribute('aria-pressed', 'false'); + await user.tab(); + await user.keyboard('{End}'); + const tab = screen.getByRole('tab', { name: /reference\.md/ }); + expect(tab).toHaveFocus(); + expect(tab).toHaveAttribute('aria-selected', 'true'); + expect(within(tab).getByLabelText('非工作区文件')).toBeInTheDocument(); + expect(within(tab).getByText('reference.md')).toHaveAttribute('title', `非工作区文件:${path}`); + await user.tab(); + await user.keyboard('{Enter}'); + await waitFor(() => expect(screen.queryByRole('tab', { name: /reference\.md/ })).not.toBeInTheDocument()); + expect(flushAutosave).toHaveBeenCalledWith(path); + expect(disposeStateSpy).toHaveBeenCalledWith(path); }); }); diff --git a/src/components/workbench/EditorTabs.tsx b/src/components/workbench/EditorTabs.tsx index 668a23b..3e4b597 100644 --- a/src/components/workbench/EditorTabs.tsx +++ b/src/components/workbench/EditorTabs.tsx @@ -1,24 +1,15 @@ -import { - ExternalLink, - PanelLeft, - PanelLeftClose, - PanelRight, - PanelRightClose, - X, - type LucideIcon, -} from 'lucide-react'; -import { execute } from '../../commands/registry'; +import { ExternalLink, X } from 'lucide-react'; +import { useRef } from 'react'; import { closeDocument } from '../../editor/documentSession'; import { switchToTab } from '../../editor/editorState'; import { useEditorStore } from '../../stores/useEditorStore'; -import { useWorkbenchStore } from '../../stores/useWorkbenchStore'; /** * 编辑器 tab 栏(D-01 标签页模型):tab 并存、可关闭、带脏标记。 * * 切 tab 经 switchToTab(path)——单内核 view.setState 换装 + 快照 + 滚动还原(D-03)全在 * editorState 内完成,组件不重复实现。关闭统一交给 documentSession 保存并释放。 - * 高 36 / 内边距 12 / active 2px 底 accent 指示条 + 600 字重;脏态 6px 圆点 ↔ hover 变 x。 + * 高 36 / 内边距 12 / active 2px 底 accent 指示条 + 600 字重;脏态圆点在 hover / focus-within 时变 x。 */ /** @@ -30,74 +21,66 @@ import { useWorkbenchStore } from '../../stores/useWorkbenchStore'; */ const closeTabFlow = closeDocument; -/** - * tab 栏贴边面板开关(R4 §3.2):复用 Sidebar HeaderAction 几何(32px 命中区 / 16px 图标 / - * strokeWidth 1.75 / rounded-[4px])。图标随开关态切换给视觉反馈,面板已开时高亮(aria-pressed)。 - * onClick 走既有 view.toggle-sidebar / view.toggle-right-panel 命令(零新命令,R4 §3.2 接线)。 - */ -function PanelToggle({ - icon: Icon, - label, - commandId, - pressed, -}: { - icon: LucideIcon; - label: string; - commandId: string; - pressed: boolean; -}) { - return ( - - ); -} - export default function EditorTabs() { const tabs = useEditorStore((s) => s.tabs); const activePath = useEditorStore((s) => s.activePath); const dirty = useEditorStore((s) => s.dirty); - const sidebarCollapsed = useWorkbenchStore((s) => s.layouts[s.mode].sidebarCollapsed); - const rightPanelCollapsed = useWorkbenchStore((s) => s.layouts[s.mode].rightPanelCollapsed); + const tabRefs = useRef(new Map()); + const tabbablePath = tabs.some((tab) => tab.path === activePath) ? activePath : tabs[0]?.path; if (tabs.length === 0) return null; return (
- {/* 左端贴边:切换左侧栏(图标随开关态 PanelLeft↔PanelLeftClose) */} -
- -
- {tabs.map((tab) => { + {tabs.map((tab, index) => { const active = tab.path === activePath; const isDirty = dirty[tab.path] === true; return (
{ + if (element) tabRefs.current.set(tab.path, element); + else tabRefs.current.delete(tab.path); + }} role="tab" aria-selected={active} - tabIndex={0} - onClick={() => switchToTab(tab.path)} + tabIndex={tab.path === tabbablePath ? 0 : -1} + onClick={(e) => { + e.currentTarget.focus(); + void switchToTab(tab.path); + }} onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') switchToTab(tab.path); + // 关闭按钮保留原生键盘行为,不让冒泡事件变成标签切换。 + if (e.target !== e.currentTarget || e.altKey || e.ctrlKey || e.metaKey || e.nativeEvent.isComposing || e.keyCode === 229) return; + let nextIndex: number; + switch (e.key) { + case 'ArrowLeft': + nextIndex = (index - 1 + tabs.length) % tabs.length; + break; + case 'ArrowRight': + nextIndex = (index + 1) % tabs.length; + break; + case 'Home': + nextIndex = 0; + break; + case 'End': + nextIndex = tabs.length - 1; + break; + case 'Enter': + case ' ': + nextIndex = index; + break; + default: + return; + } + e.preventDefault(); + const nextTab = tabs[nextIndex]; + tabRefs.current.get(nextTab.path)?.focus(); + void switchToTab(nextTab.path); }} className={ 'group flex shrink-0 cursor-pointer items-center gap-2 border-b-2 px-3 text-[13px] ' + @@ -121,25 +104,28 @@ export default function EditorTabs() { > {tab.name} - {/* 脏态 6px 圆点 ↔ hover 变 x:圆点常显于脏态、hover 时让位关闭按钮 */} + {/* 鼠标悬停或键盘焦点进入标签时,脏圆点让位给可操作的关闭按钮。 */} {isDirty ? ( ) : null}
); })} - {/* 右端贴边:ml-auto 把开关推到 tab 组之后、靠窗口右上(不随 tab 横向滚动消失) */} -
- -
); } diff --git a/src/components/workbench/ModeIndicator.tsx b/src/components/workbench/ModeIndicator.tsx index e6c662a..bab9be8 100644 --- a/src/components/workbench/ModeIndicator.tsx +++ b/src/components/workbench/ModeIndicator.tsx @@ -1,8 +1,10 @@ import { Check } from 'lucide-react'; -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState, type CSSProperties } from 'react'; +import { createPortal } from 'react-dom'; import { execute } from '../../commands/registry'; import { MODE_PRESETS } from '../../modes/presets'; import { useWorkbenchStore } from '../../stores/useWorkbenchStore'; +import { useProjectStore } from '../../stores/useProjectStore'; import type { AppMode } from '../../types/workbench'; import Menu, { type MenuEntry } from '../common/Menu'; @@ -16,7 +18,17 @@ const MODES: AppMode[] = ['standard', 'academic', 'creative']; export default function ModeIndicator() { const mode = useWorkbenchStore((s) => s.mode); const [open, setOpen] = useState(false); + const [position, setPosition] = useState({}); const anchorRef = useRef(null); + const blocked = useProjectStore((s) => s.archiveOpen || s.phase !== 'idle'); + useEffect(() => { + if (!open) return; + const close = () => setOpen(false); + window.addEventListener('resize', close); + window.addEventListener('scroll', close, true); + const unsubscribe = useProjectStore.subscribe((state) => { if (state.archiveOpen || state.phase !== 'idle') close(); }); + return () => { window.removeEventListener('resize', close); window.removeEventListener('scroll', close, true); unsubscribe(); }; + }, [open]); const items: MenuEntry[] = MODES.map((m) => ({ id: m, @@ -49,7 +61,11 @@ export default function ModeIndicator() { data-testid="mode-indicator" aria-haspopup="menu" aria-expanded={open} - onClick={() => setOpen((v) => !v)} + onClick={() => { + const rect = anchorRef.current?.getBoundingClientRect(); + if (rect) setPosition({ right: Math.max(8, window.innerWidth - rect.right), bottom: window.innerHeight - rect.top + 4 }); + setOpen((v) => !v); + }} className={`flex h-full items-center gap-1.5 px-2 text-[12px] font-normal transition-colors duration-[var(--duration-fast)] hover:bg-[var(--background-modifier-hover)] hover:text-[var(--text-normal)] ${ open ? 'bg-[var(--background-modifier-active)] text-[var(--text-normal)]' @@ -59,14 +75,15 @@ export default function ModeIndicator() {