From c98eb69cbdc2fca190be7db00308d745d6854c1d Mon Sep 17 00:00:00 2001 From: mesalogo Date: Sat, 19 Sep 2026 20:56:03 +0800 Subject: [PATCH] feat: refine notes workspace and add canvas zoom Simplify sidebar selection and note record framing, compact the empty composer, and emphasize note titles. Replace redundant AI comment headings with an accessible panel toggle. Add view-only canvas zoom and fit width, correct PDF import/export icons, and allow image files up to 20 MiB. Include regression coverage and updated design documentation. Release note: Notes have clearer navigation and a more compact writing area. Canvases support zoom and fit width, and image imports accept files up to 20 MiB. --- FEATURES.md | 4 +- FEATURES.zh-CN.md | 1 + UI-DESIGN.md | 11 +- docs/features/magic-notes/README.md | 3 + docs/features/magic-notes/progress.md | 50 ++++- docs/features/magic-notes/technical-design.md | 7 + .../canvas-ipc.integration.test.ts | 16 ++ src/renderer/src/MagicNotesWorkspace.test.tsx | 29 ++- src/renderer/src/MagicNotesWorkspace.tsx | 25 +-- src/renderer/src/magic-canvas.css | 7 +- src/renderer/src/magic-canvas/README.md | 30 ++- src/renderer/src/magic-canvas/canvas-flow.mjs | 6 +- .../src/magic-canvas/canvas-flow.test.mjs | 10 + src/renderer/src/magic-canvas/canvas-note.mjs | 101 ++++++++-- .../src/magic-canvas/canvas-wheel.test.mjs | 34 +++- src/renderer/src/styles.css | 178 +++++++++++------- tests/canvas-core.electron.test.ts | 5 + tests/magic-canvas-wheel.electron.mjs | 81 +++++++- tests/magic-notes-layout.electron.test.ts | 33 +++- tests/support/canvas-core-regression.tsx | 26 ++- tests/support/canvas-zoom-regression.mjs | 140 ++++++++++++++ 21 files changed, 677 insertions(+), 120 deletions(-) create mode 100644 tests/support/canvas-zoom-regression.mjs diff --git a/FEATURES.md b/FEATURES.md index 9e0a0dac..543f0ef2 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -632,7 +632,9 @@ records are listed separately and do not introduce another feature status. refresh the open workbench while preserving selection and unsaved drafts. - [x] **Paged canvas notes**: The integrated PeopleLib Fabric + Quill editor combines flowing body text with pen/highlighter, object selection/transforms, - floating text, images and paper templates. Import PDFs as page backgrounds + floating text, images and paper templates. Editors and read-only viewers support + 25%-300% view zoom, 100% reset and responsive Fit width, without changing saved + content or export dimensions. Import PDFs as page backgrounds with native text extraction, then export a raster PDF of text and annotations; exported PDFs do not retain a searchable text layer. Manual saves store entry bodies and binary assets in local note files; SQLite retains metadata, indexes, diff --git a/FEATURES.zh-CN.md b/FEATURES.zh-CN.md index 9bddae6a..3cae72b1 100644 --- a/FEATURES.zh-CN.md +++ b/FEATURES.zh-CN.md @@ -94,6 +94,7 @@ - [x] **魔法笔记 / Magic Notes**:提供本地优先的笔记与待办工作台、范围管理、编辑、筛选和受控 AI 评论。笔记从自适应卡片总览打开;独立待办页提供搜索、状态筛选、分组列表和直接勾选,宽屏列表稳定在左侧,任务说明、来源记录及 AI 评论在右侧同一区域显示,窄屏按需显示详情并提供返回列表。紧凑页签放在页头新建按钮左侧,筛选放在搜索右侧。打开原笔记再返回可恢复任务上下文;切换任务不会混入上一项的来源内容,读取失败使用应用通知并提供就地重试,来源缺失有明确提示。左侧入口可按设置显示未完成待办数量,创建、保存和评论结果使用统一应用通知。Agent / MCP 写入后,已打开的工作台自动更新,并保留当前选择与未保存草稿。 - [x] **连续记录工作区**:笔记详情连续展示全部记录,左侧索引默认 168px,可拖动右边界或用键盘在 140–320px 内调宽,并记住偏好;索引与右侧 AI 评论独立收起。每条画布记录显示真实首页缩略图、页数和时间,文字记录显示摘要;点击索引只滚动定位,保留当前编辑器和草稿。打开笔记就在标题下方显示空白输入区,保留文字/画布选择和保存操作;新增保存后清空,供下一条记录使用,旧记录通过编辑入口修改。空白输入区和保存后未再修改的记录返回时不提示;保存失败和真实修改仍受保护。窄屏索引使用固定宽度折叠抽屉,不显示调宽分隔条,AI 评论移至底部。 - [x] **分页画布笔记**:已集成 PeopleLib Fabric + Quill 编辑器,支持跨页正文、笔迹、高亮、对象选择及变换、浮动文字、图片和纸张模板。PDF 可作为分页底版导入并提取原生文字;正文和批注可导出为栅格 PDF,导出文件不保留可搜索文字层。用户手动保存后,记录正文与二进制资源存入本地笔记文件,SQLite 保留元数据、索引、修订、待办和评论。备份须协调保存 SQLite 与笔记目录,产品无一键备份入口。撤销/重做限当前批注页或 Quill 模式,不提供全局文档撤销、无限画布或 PNG 下载按钮。草稿、已保存记录和来源为画布的待办按默认模型图像输入能力使用页面图像加文字,或明确标注仅文字降级;纯视觉记录/草稿需要支持图像输入的模型。即时评论模式提供手动画布分析,保存后自动分析失败不阻断或回滚保存。提取文字不变时保留文字评论;布局变化使视觉评论失效,并在保存后自动评论模式下重新分析。MCP 禁止以纯文字覆写画布记录。实现已完成,最终验证状态见[功能进展](./docs/features/magic-notes/progress.md)。 + 编辑与只读画布均支持 25%–300% 视图缩放、100% 还原及随视口调整的“适应宽度”,不改变保存内容或导出尺寸。 - [ ] **MCP Server Control Plane**(规划中):统一 MCP 生命周期、健康检查、重连、Schema 缓存、隔离、审批和审计。 - [ ] **可追溯笔记摘录与 AI 编辑**(规划中):从对话、知识和网页收集带来源的摘录,并提供需确认的总结、改写和整理操作。 diff --git a/UI-DESIGN.md b/UI-DESIGN.md index f3769edd..6c8383d9 100644 --- a/UI-DESIGN.md +++ b/UI-DESIGN.md @@ -284,7 +284,8 @@ - 浅色侧栏使用冰蓝灰表面,与白色主内容画布形成明确边界;深色侧栏使用比主画布略亮的蓝灰表面。 - 一级导航与最近会话之间、最近会话与底部账户区之间必须有可见结构分隔线。浅色主题使用 `--border-default`,深色主题可在可辨前提下使用 `--border-subtle`。 -- 当前导航项和当前会话必须同时使用至少三种信号中的两种:强调背景、可见边框、图标或文字强调。浅色主题的当前项优先使用更完整的蓝色选中表面和较高字重。 +- 当前导航项和当前会话使用一致的淡蓝选中背景、强调色文字和中等字重,不额外描边或添加侧边标记;导航图标随选中状态使用强调色,悬停时保留选中状态。浅深主题遵循相同规则,强制颜色模式保留系统选中边框。 +- 固定导航行高至少 `36px`、行间距 `4px`,图标统一为 `16px`;导航与最近会话分隔线上下各留 `16px`。普通待办和待处理建议数量使用中性色徽标,不使用危险红色;徽标高度不得改变导航行高。搜索框获得内部焦点时显示完整焦点环。 - 未选中项保持平整,不为每一行添加卡片边框或阴影。悬停反馈不得强于选中状态。 - 项目选择器顶行保留项目切换与新建按钮;项目设置统一从下拉列表对应行的管理按钮进入,关闭后焦点返回项目选择器。 - 项目选择器下方仅在有运行中、待处理或已完成未查看会话时显示跨项目活动汇总,无活动时隐藏;项目计数与项目、会话级联菜单遵守 @@ -727,6 +728,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证 - 文字记录编辑器保留 Quill 原生列表快捷输入:输入 `-`、`*`、`1.`、`[ ]` 或 `[x]` 后按空格,分别转换为项目符号、编号或待办清单。快捷输入只生成现有 Delta 富文本,不引入 Markdown 源码模式、第二套文字记录持久化格式或第三方 Markdown 插件;编辑区附近持续提供简短提示。 - 新记录可选择“文字 / 画布”;已保存画布提供分页阅读和“展开编辑画布”。画布使用 PeopleLib 的 Fabric 分页批注与 Quill 跨页正文,支持纸张模板、增删页、笔迹、高亮、整对象橡皮、选择及移动缩放旋转、浮动文字、图片和 PDF 底版。画布有固定页面边界;已保存记录、待办来源和编辑态默认容纳完整当前页及工具栏,高度随当前页实际显示尺寸变化,纵向由外层笔记流滚动。多页仍属于一条记录,通过页码翻页;窄窗口工具栏换行,纸张仅在自身视口内横向滚动,不撑宽外层页面。 - 画布通过“保存记录 / 保存修改”手动持久化,离开或切换记录类型前沿用未保存草稿确认。撤销/重做作用于当前批注页或 Quill 正文模式,增删页和 PDF 导入不属于全局撤销历史。删除非空批注页或 PDF 页需要在五秒内再次点击确认。 +- 画布编辑与只读工具栏均提供缩小、当前百分比、放大和“适应宽度”。默认 100%,范围 25%–300%;加减每次调整 25 个百分点,点击百分比还原 100%。画布区域内 Ctrl/Command + 滚轮连续缩放,普通滚轮和 Shift 横滚保持原有方向与边界传递。适应宽度按实际视口计算,窗口或分栏调整后自动更新;翻页保留固定比例或适应宽度策略。缩放仅影响显示,整页高度与横向滚动范围随之变化,不新增内层纵向滚动,也不改变正文分页、保存内容、导出尺寸或未保存状态。控件沿用画布的主题、焦点环与提示,窄屏整组换行,适应宽度通过 `aria-pressed` 和选中样式表达状态。 - “导入 PDF”保留页面并提取原生文字供搜索与 AI 使用,扫描页不自动 OCR。“导出 PDF”将底版、正文和批注逐页合成为栅格 PDF,不保留可搜索文字层;当前 UI 无 PNG 下载按钮,页面 PNG 捕获仅作为程序接口提供。 - 画布新建与编辑态不显示常驻分析提示。操作放在编辑区右上角:新建与类型选择同排,顺序为“分析画布草稿、保存记录”;编辑放在记录头部,顺序为“分析画布草稿、取消、保存修改”。保存为最右侧主按钮,窄屏允许换行;底部不重复操作,新建不增加取消按钮。`immediate` 仍通过按钮手动分析,不随笔画自动请求;`after-save-auto` 在手动保存后分析。设置读取或页面捕获失败不阻断保存,保存成功后通过应用通知说明自动分析失败,可稍后重新分析;模型分析失败也不回滚已保存内容。 - 草稿、已保存画布记录及来源为画布的待办,按默认模型的 `supportsImageInput` 使用全部页面图像与提取文字,或明确标注“仅文字分析(未使用画布图像)”。纯视觉记录/草稿遇到文字模型时提示切换支持图像输入的默认模型,不声称已理解笔迹或布局。待办分析同时保留任务标题、说明和来源画布上下文。 @@ -736,9 +738,12 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证 - 待办内容容器宽于 `700px` 时,任务列表稳定在左侧,右侧显示当前任务说明、来源记录和 AI 评论;未选择时右侧提示选择任务。点击任务只更新右侧,不改变列表宽度、行位置或滚动位置,再次点击取消选择。AI 不增加第三栏。容器不超过 `700px` 时,按需详情替换同一内容区域,并提供“返回待办列表”;列表保持挂载,返回恢复滚动和任务行焦点,不将列表与详情上下堆叠。任务按钮提供 `aria-expanded` 和 `aria-controls`。 - 待办 AI 评论保留方向选择、流式输出、重新分析和已有结果;来源加载失败通过应用通知提示,并提供就地重试。从待办打开原笔记后,“返回待办”恢复任务选择、搜索、筛选和列表滚动;宽屏焦点返回任务行,窄屏返回待办详情中的返回按钮。 - 笔记详情主区按新到旧连续展示全部记录。打开笔记即在标题下、历史记录上显示空白输入区,保留文字/画布类型选择和保存操作,标题栏与索引均不提供“新增记录”按钮。新增保存成功后重建空白输入区,保留所选类型;自动分析结束也不转入旧记录编辑。旧记录通过显式编辑入口修改,保存使用同一 ID 和返回的修订号。点击索引或当前笔记内的 AI 来源只按 entry ID 滚动定位,不卸载编辑器、不清草稿、不询问离开。空白输入区离开不提示,返回总览、打开其他笔记或改为编辑另一条记录时仍保护未保存修改。 +- 详情页的一级标题显示当前笔记已保存的标题,不显示模块说明;全局范围、返回导航和索引开关保持可见。原标题输入框使用正文大小,保留原有编辑、失焦保存与键盘操作,不形成第二个醒目标题。新建文字记录的输入区初始约四行,随内容自然增高,保留原有调整大小与滚动方式;此高度不作用于旧记录编辑器或画布。笔记内部列表、记录流、AI 区域和文字编辑区在精细指针设备上使用细原生滚动条,轨道透明;强制颜色与粗指针设备保留系统默认。说明、摘要和输入提示使用 `--font-body` 与 `--text-secondary`,时间和短标签保留 `--font-caption`。 - 左侧记录索引默认 `168px`,可拖动右边界在 `140–320px` 内调整栏宽,缩略图随列宽等比适配。开关独立位于详情标题左侧,提供 `aria-controls`、展开状态和动作提示;收起后完全移除索引列、分隔条、间隙、边框及轨道,不占宽度,隐藏内容不可聚焦。右侧 AI 评论独立显示、滚动和收起,不与索引共享页签。每条记录一个导航项:画布显示真实首页缩略图及总页数,文字显示摘要,两者均显示类型和时间。两侧分别记住桌面展开状态和宽度,共用原有布局偏好;宽布局分隔条共用样式及 `separator` 语义,方向键每次调整 `16px`,Home、End 跳到当前上下限。两侧宽度按可用空间协调限制,为主区保留至少 `300px`,窗口缩小时不覆盖保存的偏好。内容容器不超过 `800px` 时索引默认隐藏,由同一标题栏按钮开闭标题栏下方固定 `168px` 的抽屉,不显示调宽分隔条;标题栏自身保留控件高度,不保留轨道。选择记录或按 Escape 后关闭,Escape 将焦点返回开关。AI 移至底部,高度不超过可用布局的 40% 且最多 `280px`,不显示分隔条。 -- 笔记详情的标题、正文和记录流不再包裹额外的大外框,记录流容器不加内边距;保留编辑器自身边框、页面安全边距及 AI 区域分隔。此规则仅作用于笔记详情,不改变卡片总览与待办独立布局。 -- 画布新建、编辑和只读态的内框统一保留 `12px` 间距,避免内外边框及圆角重叠。记录头部操作按内容宽度排列,空间不足时自然换行。文字新建的保存区与编辑器属于同一卡片,空白编辑区使用最小高度,不随容器百分比高度拉长。文字工具栏图标、菜单和原生滚动条须跟随主题。 + AI 评论的唯一显隐入口位于页面右上角,使用紧凑面板图标按钮;展开时应用共享选中样式,图标随显示或隐藏动作切换,并提供对应的工具提示、可访问名称、`aria-expanded` 和 `aria-controls`。评论面板内部不再显示重复的“AI 评论”标题或隐藏按钮行,保留区域的可访问名称、评论方向选择及各记录的分析操作。 +- 笔记详情的内部记录索引使用透明底色,与主内容表面连续,仅保留细调宽分隔线。每条索引记录保留 `1px` 的 `--border-subtle` 淡边框,不加卡片底色或阴影,悬停使用轻微次级底色;选中记录使用淡蓝背景和左侧 `3px` 蓝色内侧标记,悬停时保留选中态。缩略图保留纸张边界,键盘焦点环与强制颜色模式选中边框保持可见。窄屏抽屉使用不透明的主内容表面,避免下层正文透出;深色主题沿用语义令牌。 +- 笔记详情的标题、正文和记录流不再包裹额外的大外框,记录流容器不加内边距;每条已保存记录使用一层 `1px` 的 `--border-subtle` 淡边框和 `--radius-control` 圆角,保留透明底色与记录间留白,不加阴影或头部下划线。记录编辑时保留输入区边框、页面安全边距及 AI 区域分隔。此规则仅作用于笔记详情,不改变卡片总览与待办独立布局。 +- 画布新建、编辑和只读态统一保留 `12px` 内容间距;详情中已保存画布去掉预览外框和工具栏底色、下划线,保留灰色画布衬底与白色纸张,编辑态保留编辑器边框。记录头部操作按内容宽度排列,空间不足时自然换行。文字新建的保存区与编辑器属于同一卡片,空白编辑区使用最小高度,不随容器百分比高度拉长。文字工具栏图标、菜单和原生滚动条须跟随主题。 - 整篇笔记的置顶、取消置顶和删除放在卡片的“更多”菜单,标题编辑区不重复提供入口。按钮在悬停、键盘聚焦或菜单展开时显示,触屏设备常显;菜单沿用会话列表样式,通过 Portal 避免滚动区域裁切,支持方向键、Home、End 和 Escape。删除在菜单内二次确认并说明笔记标题及全部记录;打开菜单不进入详情。 - 左侧“魔法笔记”入口可显示未完成待办数量。数量为 0 时隐藏徽标,超过 99 时显示 `99+`,可访问名称仍说明精确数量;勾选、取消勾选以及 Agent / MCP 对笔记清单的写入都必须刷新数量。 - 未完成待办徽标的高度不得超过主导航图标;数量从 0 变为可见或再次隐藏时,主导航项行高和相邻菜单位置必须保持不变。 diff --git a/docs/features/magic-notes/README.md b/docs/features/magic-notes/README.md index e2c0c48a..5308332e 100644 --- a/docs/features/magic-notes/README.md +++ b/docs/features/magic-notes/README.md @@ -56,6 +56,9 @@ object erasing and selection/transforms, floating text, images, PDF backgrounds with native text extraction, and raster PDF export. Saves are manual. Undo/redo belongs to the current annotation page or Quill body mode; it is not a global document history. There is no infinite canvas or PNG download button. +Editors and saved viewers support view zoom and responsive Fit width without +changing saved content or export dimensions; see the +[canvas zoom reference](../../../src/renderer/src/magic-canvas/README.md#view-zoom). Drafts, saved entries and todos sourced from canvases support AI comments using the default model's image-input capability. Image-capable models receive complete diff --git a/docs/features/magic-notes/progress.md b/docs/features/magic-notes/progress.md index cdc80659..34c93047 100644 --- a/docs/features/magic-notes/progress.md +++ b/docs/features/magic-notes/progress.md @@ -1,5 +1,22 @@ # Magic Notes Progress +## 2026-09-19: Canvas Image Import Size + +- Raised the canvas image file limit to 20 MiB inclusive and updated its error + message and derived restoration bound. The authoritative limit is in the + [canvas integration reference](../../../src/renderer/src/magic-canvas/README.md#editor-limits). +- Real Electron exercised File input, FileReader, Fabric insertion, flush, model + conversion and remount: 2 MiB + 1 byte and exactly 20 MiB were retained; + 20 MiB + 1 byte was rejected with the updated message. Fixtures use a valid + one-pixel PNG padded to each byte boundary, not a large decoded pixel image. +- Production preload/registered IPC handlers with mocked Electron transport and + real SQLite/files passed 20 MiB create, update and database reopen checks. + Shared canvas validation and file persistence impose no smaller byte budget. + Rich-text attachment budgets and the PDF import limit are unchanged. +- Validation: `npx vitest run tests/canvas-core.electron.test.ts src/main/magic-notes/canvas-ipc.integration.test.ts src/main/magic-notes/magic-note-storage.test.ts src/main/magic-notes/rich-content.test.ts src/renderer/src/magic-canvas` + passed 66 tests in 8 files. `npm run typecheck` and `npm run lint` passed. + The full suite and a combined native Electron IPC save scenario were not run. + ## 2026-09-19: Resizable Record Index - The record index defaults to 168px and supports pointer resizing from its right @@ -359,7 +376,8 @@ - The shared canvas viewport explicitly handles horizontal and vertical wheel input in annotation, flow-text and read-only modes. Shift with vertical-only - input scrolls horizontally. Ctrl input is left to the existing browser path. + input scrolls horizontally. Ctrl input was initially left to the browser; + the View Zoom update below assigns Ctrl/Command + wheel to canvas zoom. Default scrolling is prevented only when the viewport actually moves; page scrolling remains available at boundaries or without canvas overflow. - Removed scroll-chain containment. The Electron baseline reproduced boundary @@ -370,6 +388,36 @@ - Typecheck and lint passed. The full test run during this fix reported 4,682 passed, 67 skipped and one unrelated Agent package inventory timeout. +## 2026-09-19: Canvas View Zoom + +- Implemented editor/read-only zoom using the shared core. Controls and view-only + invariants are documented in the + [canvas reference](../../../src/renderer/src/magic-canvas/README.md#view-zoom) + and root UI design. The persisted model and export dimensions are unchanged. +- Real PDF plus flow validation reproduced an existing cleared-background defect: + automatic pagination reassigned an unchanged canvas width/height, erasing its + bitmap. Layout now retains the bitmap when dimensions are unchanged. +- Focused validation passed 9 files / 140 tests: `canvas-wheel`, `canvas-flow`, + `canvas-note`, canvas `model`, `MagicCanvasEditor`, `MagicCanvasThumbnail`, + `MagicNoteContent`, `MagicNotesWorkspace`, and `tests/canvas-core.electron.test.ts`. +- `node tests/magic-canvas-wheel.electron.mjs`: 41 checks passed in Electron 43.2.0. + Native input covered pen coordinates and object move/scale at 50% and 200%, + second-page Quill click/typing, Ctrl wheel, ordinary/Shift/diagonal wheel and + boundary propagation. Real PDF background pixels, unchanged PNG captures and + 794-by-1123 output dimensions, stable flow pagination, zero view-only changes, + readonly controls, displayed page heights and fit-width resize all passed. + Dark narrow layout included a real 560px window resize across the responsive + padding breakpoint while the host width remained 360px. +- `npm run typecheck` and focused ESLint on all six changed JavaScript/test files + passed. Final `npm run lint` encountered two `react-hooks/immutability` errors + at lines 18/27 of concurrently added `src/renderer/src/FloatingPortal.tsx`; + those parallel changes were left intact. `git diff --check` passed. +- No full test suite, production build, physical-device test or macOS session was + run in this change. Meta-wheel is covered by focused tests. The Electron fixture + uses production canvas modules/styles with real Fabric, Quill and PDF.js; it + does not claim complete App-shell or release-package validation. No model calls + or commits were made. Deployed Agent paths are unaffected. + ## 2026-09-19: Single Navigation Button and Unframed Note Detail - Replaced the two header tabs with one destination-labelled button and icon, diff --git a/docs/features/magic-notes/technical-design.md b/docs/features/magic-notes/technical-design.md index 3565b76c..10f0505b 100644 --- a/docs/features/magic-notes/technical-design.md +++ b/docs/features/magic-notes/technical-design.md @@ -211,6 +211,13 @@ Editor and viewer heights follow the current paper element's displayed height through natural CSS layout, including wrapped toolbars and viewport padding. No fixed preview height or viewport-height cap clips the page. The outer note stream scrolls vertically; the paper viewport retains horizontal scrolling. +View zoom is local to each mounted core instance. A displayed-size outer box and +one transformed logical-size surface keep PDF, flow and Fabric aligned; Quill +screen measurements are converted back to logical coordinates. Neither zoom nor +fit-width resize calls `onChange`, changes the persisted model, invalidates AI +comments or alters PNG/PDF export dimensions. See the +[view zoom implementation](../../../src/renderer/src/magic-canvas/README.md#view-zoom) +for controls, bounds, page-switch policy and observer cleanup. Paper templates, pen, highlighter, whole-object eraser, selection/transforms, floating text, images and PDF page backgrounds are integrated. Native PDF text diff --git a/src/main/magic-notes/canvas-ipc.integration.test.ts b/src/main/magic-notes/canvas-ipc.integration.test.ts index 17aa2fb4..d599d20d 100644 --- a/src/main/magic-notes/canvas-ipc.integration.test.ts +++ b/src/main/magic-notes/canvas-ipc.integration.test.ts @@ -219,6 +219,22 @@ describe('production preload -> registered IPC -> SQLite canvas persistence', () expect(await api.get(note.id)).toEqual(saved) }) + it('saves and reopens a 20 MiB canvas image through create and update IPC', async () => { + const content = canvas() + const bytes = Buffer.alloc(20 * 1024 * 1024) + Buffer.from(png.split(',')[1]!, 'base64').copy(bytes) + content.assets[0]!.dataUrl = `data:image/png;base64,${bytes.toString('base64')}` + const note = await api.create({ title: 'Large canvas image' }) + const created = await api.createEntry({ noteId: note.id, content }) + await reopen() + const entry = (await api.get(note.id)).entries.find(item => item.id === created.createdEntryId)! + expect(entry.content).toEqual(content) + content.flow!.ops[0] = { insert: 'Edited large image note' } + await api.updateEntry({ entryId: entry.id, expectedRevision: entry.revision, content }) + await reopen() + expect((await api.get(note.id)).entries.find(item => item.id === entry.id)!.content).toEqual(content) + }) + it('preserves a real generated PDF and all page references through three reopen/save cycles', async () => { const { jsPDF } = await import('jspdf') const pdf = new jsPDF() diff --git a/src/renderer/src/MagicNotesWorkspace.test.tsx b/src/renderer/src/MagicNotesWorkspace.test.tsx index 57fac251..94722625 100644 --- a/src/renderer/src/MagicNotesWorkspace.test.tsx +++ b/src/renderer/src/MagicNotesWorkspace.test.tsx @@ -387,6 +387,9 @@ describe('MagicNotesWorkspace overview navigation', () => { const card = screen.getByRole('button', { name: /发布笔记.*条记录/ }) await openNote() expect(overview).not.toBeVisible() + expect(screen.getByRole('heading', { level: 1, name: detail.title })).toBeVisible() + expect(document.querySelector('.page-header__description')).toBeNull() + expect(screen.getByRole('textbox', { name: '笔记标题' })).toHaveValue(detail.title) expect(screen.queryByRole('button', { name: '切换到待办' })).not.toBeInTheDocument() expect(screen.queryByRole('searchbox')).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: '新建笔记' })).not.toBeInTheDocument() @@ -398,6 +401,7 @@ describe('MagicNotesWorkspace overview navigation', () => { await waitFor(() => expect(screen.getByRole('button', { name: '返回总览' })).toHaveFocus()) back() expect(overview).toBeVisible() + expect(screen.getByRole('heading', { level: 1, name: '魔法笔记' })).toBeVisible() expect(search).toHaveValue('发布') expect(overview.scrollTop).toBe(180) expect(screen.queryByRole('region', { name: '笔记记录' })).not.toBeInTheDocument() @@ -802,11 +806,25 @@ describe('MagicNotesWorkspace detail behavior', () => { const view = render() await openNote() showAi() + const toggle = screen.getByRole('button', { name: '隐藏 AI 评论', expanded: true }) + expect(toggle).toHaveClass('icon-button', 'icon-button--active') + expect(toggle).toHaveAttribute('title', '隐藏 AI 评论') + expect(toggle).toHaveAttribute('aria-controls', 'magic-notes-ai-pane') + expect(toggle.textContent).toBe('') + const pane = screen.getByRole('complementary', { name: 'AI 评论' }) + expect(pane).toHaveAttribute('id', toggle.getAttribute('aria-controls')) + expect(within(pane).queryByText('AI 评论', { exact: true })).not.toBeInTheDocument() + expect(pane.querySelector('.magic-notes-pane-heading')).toBeNull() expect(screen.queryByRole('group', { name: 'AI 评论形式' })).not.toBeInTheDocument() expect(screen.getByRole('combobox', { name: 'AI 评论方向' })).toBeVisible() fireEvent.keyDown(screen.getByRole('separator', { name: '调整编辑区与 AI 评论宽度' }), { key: 'End' }) const width = screen.getByRole('separator', { name: '调整编辑区与 AI 评论宽度' }).getAttribute('aria-valuenow') - fireEvent.click(screen.getAllByRole('button', { name: '隐藏 AI 评论' })[0]!) + toggle.focus() + fireEvent.click(toggle) + expect(toggle).toHaveFocus() + expect(screen.getByRole('button', { name: '显示 AI 评论', expanded: false })).toBe(toggle) + expect(toggle).toHaveAttribute('title', '显示 AI 评论') + expect(toggle).not.toHaveClass('icon-button--active') expect(screen.queryByRole('complementary', { name: 'AI 评论' })).not.toBeInTheDocument() back() await openNote() @@ -845,6 +863,13 @@ describe('MagicNotesWorkspace detail behavior', () => { await openNote() expect(screen.getByRole('separator')).toHaveAttribute('aria-disabled', 'true') expect(screen.getByRole('separator')).toHaveAttribute('tabindex', '-1') + const toggle = screen.getByRole('button', { name: '隐藏 AI 评论', expanded: true }) + fireEvent.click(toggle) + expect(screen.queryByRole('complementary', { name: 'AI 评论' })).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: '显示 AI 评论', expanded: false })).toBe(toggle) + fireEvent.click(toggle) + expect(screen.getByRole('complementary', { name: 'AI 评论' })).toBeVisible() + expect(screen.getByRole('combobox', { name: 'AI 评论方向' })).toBeVisible() }) it('resizes the record column, clamps against AI, and preserves preferences across hidden and narrow layouts', async () => { @@ -1158,7 +1183,7 @@ describe('MagicNotesWorkspace canvas integration', () => { expect(document.querySelector('.magic-notes-layout')).toHaveClass('magic-notes-layout--index-hidden') expect(screen.queryByRole('navigation', { name: '记录' })).not.toBeInTheDocument() expect(screen.getByRole('complementary', { name: 'AI 评论' })).toBeVisible() - fireEvent.click(screen.getAllByRole('button', { name: '隐藏 AI 评论' })[0]!) + fireEvent.click(screen.getByRole('button', { name: '隐藏 AI 评论' })) fireEvent.click(screen.getByRole('button', { name: '展开记录索引' })) expect(screen.getByRole('navigation', { name: '记录' })).toBeVisible() expect(screen.queryByRole('complementary', { name: 'AI 评论' })).not.toBeInTheDocument() diff --git a/src/renderer/src/MagicNotesWorkspace.tsx b/src/renderer/src/MagicNotesWorkspace.tsx index 29310f7d..7df49c13 100644 --- a/src/renderer/src/MagicNotesWorkspace.tsx +++ b/src/renderer/src/MagicNotesWorkspace.tsx @@ -1946,8 +1946,10 @@ export function MagicNotesWorkspace({ )} {!detailView && ( @@ -1992,11 +1989,11 @@ export function MagicNotesWorkspace({ )} } - description={t('page.description')} + description={detailView ? undefined : t('page.description')} headingId="magic-notes-title" icon={} scope={{ kind: 'global' }} - title={t('page.title')} + title={detailView && detail ? detail.title : t('page.title')} /> {loadStatus === 'error' ? ( @@ -2882,18 +2879,6 @@ export function MagicNotesWorkspace({ hidden={!aiPaneOpen} id="magic-notes-ai-pane" > -
- {t('comments.paneLabel')} - -