diff --git a/docs/README.md b/docs/README.md index 056265c..e346a21 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,6 +27,7 @@ - [后端接口契约](contracts/后端接口契约-开发版.md) - [第二阶段接口契约](contracts/第二阶段接口契约-开发版.md) - [前端页面需求说明](contracts/前端页面需求说明-开发版.md) +- [Tauri / Rust 桌面客户端需求说明(第三阶段,计划)](contracts/Tauri-Rust桌面客户端需求说明-第三阶段.md) 运行中的后端以 `/openapi.json` 为机器可读事实来源。接口契约用于描述设计意图、联调约束和实现状态;两者不一致时,应先确认代码行为,再在同一个 PR 中同步修正文档或实现。 diff --git a/docs/contracts/Tauri-Rust桌面客户端需求说明-第三阶段.md b/docs/contracts/Tauri-Rust桌面客户端需求说明-第三阶段.md new file mode 100644 index 0000000..ce79308 --- /dev/null +++ b/docs/contracts/Tauri-Rust桌面客户端需求说明-第三阶段.md @@ -0,0 +1,69 @@ +# Tauri / Rust 桌面客户端需求说明(第三阶段) + +状态:需求预留,尚未实现桌面客户端。本文不表示已有可调用的 Tauri Command 或可发布安装包。 + +基线日期:2026-09-05。 + +## 1. 目标与边界 + +第三阶段在现有 Vue 编辑器和 FastAPI AI Core 上接入 Tauri 2 / Rust Host,提供原生窗口、菜单、多 Vault 文件管理、安全凭据存储和 Sidecar 生命周期管理。 + +- Vue 负责页面、编辑事务、主题和交互状态;通过既有 Service 边界调用能力,不在组件中散布平台判断。 +- Rust Host 负责系统能力、路径权限、原生菜单事件及受控进程生命周期。 +- FastAPI AI Core 保留笔记解析、索引、检索、模型和 Agent 业务职责;同一文件不得同时由 Host 和 AI Core 无协调地写入。 +- Web 模式保留可运行能力;桌面专有功能通过能力检测显隐,不用无响应按钮假装已实现。 + +架构依据:[技术栈说明](../architecture/AI笔记软件技术栈说明-团队版-v2.3.md)、[前端页面需求](前端页面需求说明-开发版.md)、[第二阶段接口契约](第二阶段接口契约-开发版.md)。 + +## 2. 顶部菜单与元数据格式一键导入 + +### 2.1 入口预留 + +桌面客户端顶部菜单栏的 **段落 → 导入为笔记属性…** 预留元数据格式导入功能,与标题、正文、列表等段落操作归组。它处理笔记内容中的元数据,不是主题包安装入口。 + +建议稳定的前端命令标识为 `editor.import-note-properties`,仅为设计标识,尚未注册为 Tauri IPC。原生菜单和编辑器命令面板应分发同一命令,避免两套转换逻辑。快捷键待第三阶段统一分配,不抢占现有编辑快捷键。 + +### 2.2 输入与转换规则 + +1. 无选区时识别当前笔记开头的属性块;有选区时只处理完整的属性块。无活动笔记、加载中、只读或冲突状态下禁用操作,并提供原因。 +2. 支持标准 YAML frontmatter,以及历史编辑器产生的 `***` 开头、`title:` / `tags:` 字段、横线结尾的兼容形式。普通分隔线、代码块和包含冒号的正文不得被误判。 +3. 将识别成功的内容规范化到文件头唯一的 `---` frontmatter 中,正文中的旧属性块仅在转换成功后移除。 +4. 写作模式显示独立标题和可编辑标签;源码模式显示真实 `title` / `tags` 字段。标签必须进入现有保存和索引链路,能被标签筛选使用,不能只创建装饰性标签元素。 +5. 保留未知属性及其类型,特别是 `embedding_local_only` 等行为配置。复杂 YAML 不得用正则拆分后静默丢弃;无法无损处理时说明原因,并保留原文供源码编辑。 +6. 标签支持字符串、逗号分隔值和 YAML 列表,去重并保留顺序;中文、空格、转义字符须正确往返。空标签与删除标签有明确语义。 +7. 已存在 frontmatter 时合并到同一个属性块;字段值冲突时展示差异供用户选择,禁止静默覆盖。重复执行不重复添加标签或属性块。 + +### 2.3 编辑与保存行为 + +- 无歧义转换一次菜单操作完成,并构成一个可撤销的编辑事务;转换失败不得改变文档或保存状态。 +- 转换作用于当前内存文档,不先从磁盘读取旧内容覆盖未保存编辑。操作绑定文件标识和文档版本,异步处理期间切换文件或继续编辑时,应取消或重新校验。 +- 成功后进入现有脏状态和自动保存流程。磁盘保存失败显示可重试状态,撤销/重做同时恢复正文、属性及标签。 +- 属性块不进入正文大纲;标题跳转、引用定位仍使用完整原文件的正确偏移。写作/源码切换、保存后重开不得改变属性语义。 +- 当前分支的 `frontend/src/features/editor/noteMetadata.ts` 仅是简单属性块展示与标签编辑基础;桌面阶段需补齐完整解析、合并冲突、单事务撤销和原生菜单分发,不能直接视为本节已经验收。 + +## 3. 桌面基础需求 + +| 模块 | 第三阶段要求 | 验收要点 | +| --- | --- | --- | +| 窗口与菜单 | 原生窗口控制、顶部菜单、焦点分发、关闭前未保存处理 | 菜单操作针对活动编辑器;多窗口不串文档;取消关闭保留编辑 | +| Vault 与文件系统 | 原生目录选择、多 Vault、最近打开、文件监听、路径规范化 | 未授权目录不可访问;重命名同步树和打开文件;外部修改不静默覆盖 | +| 写入与恢复 | 原子写入、版本/内容摘要校验、失败重试和异常退出恢复 | 不产生半写文件;并发保存不覆盖新版本;恢复流程可验证 | +| AI Core Sidecar | 启停、健康检查、日志、崩溃恢复、退出清理 | 不残留进程;不可用时显示原因;本地通信有访问控制 | +| 凭据 | 按既有架构接入 Stronghold/平台安全存储,制定开发凭据迁移方案 | 前端只持有凭据引用;不回显密钥;失败可恢复且不丢凭据 | +| MCP 与插件 | 按已冻结的 Host 沙箱契约落实文件、网络和子进程授权 | 沿用审批边界,不因桌面集成默认放开权限 | +| 主题 | 复用主题包校验;原生文件选择和下载适配共用检查流程 | 导入不自动启用;安装失败可恢复;ZIP 路径和资源限制继续有效 | +| 外观与导航 | 继承主题、代码配色、相对纸页宽度、文件/大纲切换 | 窗口缩放、高 DPI、深浅主题下无截断;键盘导航完整 | +| 发布 | Windows、macOS、Linux 构建与安装验证;签名、升级及回滚方案 | 未准备好签名和回滚前不启用自动更新;平台差异有说明 | + +云同步服务、移动端和主题社区服务端不因本文自动纳入第三阶段必交范围;需要单独确认范围与接口。 + +## 4. 开发顺序与验收 + +1. 冻结 Host 能力与 Service 适配接口,明确每类数据的写入责任方及权限模型。 +2. 接入窗口、菜单与编辑命令路由,完成“段落 → 导入为笔记属性…”的编辑器事务。 +3. 接入 Vault、文件监听、冲突处理、Sidecar 和凭据迁移。 +4. 完成平台测试、安装包和升级恢复验收。 + +元数据导入专项测试至少覆盖:标准/历史格式、普通正文误判、代码围栏、未知字段、复杂 YAML、同名字段冲突、重复导入、中文标签、撤销重做、未保存文档、处理中切换文件、保存失败、重开后标签检索,以及写作/源码模式的大纲与引用偏移。 + +第三阶段实现 PR 必须补充实际 Command 名称、输入输出类型、错误码、平台差异和测试证据;在此之前本文所有 Host 能力均标为计划实现。 diff --git a/frontend/src/assets/themes/paper-moments.theme b/frontend/src/assets/themes/paper-moments.theme index 109426b..42ebc3a 100644 --- a/frontend/src/assets/themes/paper-moments.theme +++ b/frontend/src/assets/themes/paper-moments.theme @@ -1,6 +1,6 @@ theme_id: paper-moments name: 纸间时光 · Paper Moments -version: 1.2.0 +version: 1.4.0 author: NotesAgent description: 奶油纸张、手帐虚线与粉蓝胶带,把每天的灵感好好收藏。 min_app_version: 0.2.0 @@ -131,6 +131,10 @@ license: MIT border-right: 1px dashed #b5a693; } [data-theme="paper-moments"] .primary-sidebar { background: #f1e9dc; } +[data-theme="paper-moments"] .file-tree-panel { background: #fff9e9; } +[data-theme="paper-moments"] .workspace-tabs { background: #e5eeee; border-bottom: 1px dashed #b5a693; } +[data-theme="paper-moments"] .workspace-tabs button[aria-selected="true"] { background: #f8e9e3; color: #875343; box-shadow: inset 0 -2px #a77a67; } +[data-theme="paper-moments"] .outline-filename { border-bottom: 1px dashed #b5a693; } [data-theme="paper-moments"] .file-tree-panel .toolbar, [data-theme="paper-moments"] .sidebar-header { background: #e5eeee; border-bottom: 1px dashed #b5a693; } [data-theme="paper-moments"] .editor-header { background: #f8e9e3; border-bottom: 1px solid #b5a693; } @@ -219,3 +223,46 @@ license: MIT [data-theme="paper-moments"] .milkdown-code-block .tools { margin-left: 72px; } [data-theme="paper-moments"] .milkdown-code-block .cm-activeLine, [data-theme="paper-moments"] .milkdown-code-block .cm-activeLineGutter { background: color-mix(in srgb, var(--color-code-text) 7%, transparent); } + +[data-theme="paper-moments"] .note-metadata { + position: relative; + width: 90%; + margin: 8px auto 30px; + padding: 24px 30px; + border: 1px solid #887460; + border-radius: 8px 14px 8px 8px; + outline: 1px dashed #c5b9a7; + outline-offset: -8px; + background: linear-gradient(110deg, #fffdf5, #fbf5e4); + box-shadow: 4px 5px 0 #d8e6e2, 8px 9px 0 #f0d8cf; +} +[data-theme="paper-moments"] .note-metadata::before { + content: ''; + position: absolute; + top: -10px; + right: 36px; + width: 78px; + height: 22px; + background: repeating-linear-gradient(45deg, #c5dfe0c0 0 8px, #daecebb0 8px 16px); + transform: rotate(3deg); + pointer-events: none; +} +[data-theme="paper-moments"] .metadata-caption { color: #806b58; letter-spacing: .12em; } +[data-theme="paper-moments"] .note-metadata h1 { + margin: 12px 0 18px; + color: #875343; + font-family: Georgia, 'Noto Serif SC', 'Songti SC', SimSun, serif; + font-size: clamp(20px, 2vw, 28px); + line-height: 1.4; +} +[data-theme="paper-moments"] .metadata-tags { padding-top: 14px; border-top: 1px dashed #c5b9a7; gap: 8px; } +[data-theme="paper-moments"] .metadata-tag { border: 1px solid #d6b5a8; border-radius: 5px; background: #f5e3da; color: #704b3d; } +[data-theme="paper-moments"] .metadata-tag:nth-of-type(2n + 1) { border-color: #b5cdcf; background: #e5eeee; color: #456671; } +[data-theme="paper-moments"] .metadata-tag button { border-radius: 3px; cursor: pointer; } +[data-theme="paper-moments"] .metadata-tag button:hover { background: #ffffff80; } +[data-theme="paper-moments"] .metadata-tags input { border-color: #b5a693; background: #fffdf580; } +[data-theme="paper-moments"] .metadata-tags form button { padding: 4px 10px; border: 1px solid #b5a693; border-radius: 5px; background: #f7edce; color: #704b3d; cursor: pointer; } +[data-theme="paper-moments"] .metadata-tags button:focus-visible { outline: 2px solid #875343; outline-offset: 2px; } +@media (max-width: 720px) { + [data-theme="paper-moments"] .note-metadata { width: 100%; padding: 22px 18px; } +} diff --git a/frontend/src/components/common/SecondarySidebar.vue b/frontend/src/components/common/SecondarySidebar.vue index 564cce3..9f11c96 100644 --- a/frontend/src/components/common/SecondarySidebar.vue +++ b/frontend/src/components/common/SecondarySidebar.vue @@ -33,14 +33,14 @@ const showSkillToggle = computed(() => routeName.value === 'skills' || routeName