feat: add magic note tags and a two-pane notes layout

Keep the notes list beside the open note, compact the detail view with a full-width sticky toolbar, and make Back to overview return to a full-width all-notes grid. Add manual note tags with AND filtering, a tag manager for rename, merge and delete, schema 51 tag tables, and tag support in the note Agent tools.
This commit is contained in:
mesalogo
2026-09-30 19:04:47 +08:00
parent ab85e72f01
commit 6edfc0046b
28 changed files with 2736 additions and 370 deletions
+8 -4
View File
@@ -6,9 +6,12 @@ metadata and indexes, and local files for entry bodies and binary assets.
The open workbench subscribes to successful writes so users can see external
changes without leaving the page or clicking a refresh button.
The workbench opens on a responsive note-card overview. Opening a note replaces
the overview with a continuous record stream, a narrow left index and independent
right AI comments. Both side panes can collapse. Index items show one real
The desktop workbench keeps a compact all-notes list on the left and the selected
note on the right. The list supports search, creation, pin/delete menus, resizing
and collapse. Switching notes preserves list position and remembers each note's
reading position while the workspace remains mounted. Narrow workspaces switch
between list and detail. Note detail contains a continuous record stream, a narrow
record index and independent AI comments. Both detail side panes can collapse. Index items show one real
first-page canvas thumbnail with page count, or a text summary; clicking scrolls
without replacing the editor or losing a draft. Opening a note shows a blank composer
above the history, with text/canvas selection and Save. Creating a record resets
@@ -19,7 +22,8 @@ comments. The divider can be dragged and remembers its width; each note group
can independently collapse its tasks while retaining its title and count.
Narrow containers use on-demand detail with an explicit return to the
mounted list. One content-sized button switches to the other view in the page
header immediately before New note; status filters sit beside search and wrap
header when note detail is closed; New note lives in the notes list heading or the
To-dos page header. Status filters sit beside search and wrap
when needed. Note detail removes the surrounding frame and stream padding while
keeping editor borders and the AI divider. Returning from a source note restores the task
context and protects unsaved editor changes. A successful save followed by no
+7
View File
@@ -18,6 +18,12 @@
| FR-4 | 通过右侧工作栏“+ → 笔记”打开面板并恢复当前应用会话中最近选择的笔记,首次使用显示列表,不隐式保存当前对话。左侧底部和聊天页头不再提供重复的“快速笔记”入口。 |
| FR-5 | 从会话保存的记录保留结构化来源,显示会话名称、所属项目(如有)和采集时间,支持返回原会话及单条来源消息。源会话删除后保留笔记内容。 |
| FR-6 | 面板、完整工作区和 Agent 写入使用同一份笔记数据,成功写入后更新已打开视图,同时保护未保存草稿。 |
| FR-7 | 笔记支持手动标签。每篇最多 10 个,单个标签最长 24 字,不区分大小写,同名只保留首次写法。在笔记标题下添加或移除标签;添加时提示已有标签,也可以直接新建。 |
| FR-8 | 总览可按标签筛选,多选时只显示同时带有全部所选标签的笔记;搜索框也能匹配标签名。打开笔记后,左侧列表沿用当前筛选,并提供清除入口。 |
| FR-9 | 提供“管理标签”入口,列出全部标签及使用篇数。可以重命名标签;新名称与已有标签相同时,两个标签合并。删除标签时,它会从所有笔记中移除,笔记本身保留。 |
| FR-10 | Agent 笔记工具可在创建、修改笔记时设置标签,并可按标签列出笔记。只在用户要求时修改标签,不自动打标签。 |
标签第一版不提供颜色和层级(如“工作/项目A”),“/”只作为普通字符保存。没有笔记使用的标签会自动删除。
首版不包含自动总结、自动分类、自动选择保存目标、选中文本采集、多条消息勾选、附件文件复制或跨设备同步。“总结后加入”和选区采集作为后续扩展,不预留不可用按钮。
@@ -28,5 +34,6 @@
- 保存成功后显示目标笔记并定位新增记录;取消不写入,失败保留内容和目标供重试。
- 从来源条可返回对应会话;会话已删除时显示“原会话已不存在”,已保存正文仍可阅读和编辑。
- 面板追加后完整工作区可见,Agent 修改后面板可见;后台刷新和切换会话不覆盖未保存文字。
- 给两篇笔记加上“Work”和“work”,只会得到一个标签,计数为 2。选中两个标签后,只显示同时带有这两个标签的笔记。把“Work”重命名为已有的“发布”后,两个标签合并,两篇笔记都不会丢失。删除标签不会删除任何笔记。
状态与内容范围见 [逻辑设计](./logic-design.md),入口和操作细节见 [界面设计](./ui-design.md),接口与存储见 [技术设计](./technical-design.md#conversation-integration)。
+77
View File
@@ -1,5 +1,82 @@
# Magic Notes Progress
## 2026-09-30: Manual Note Tags
Implemented FR-7 to FR-10 as described in
[Note Tags](./technical-design.md#note-tags): schema 51 tag tables, tag
replacement on note update, AND filtering, rename/merge/delete with note
revision bumps, IPC and preload methods, the overview filter row, card chips,
note-header tag editor, the "Manage tags" dialog, and tag support in
`note_create`, `note_update` and `note_list`. No colours or hierarchy.
- New tests: database tag lifecycle (`assistant-database.test.ts`), MCP tag
normalization and filtering (`knowledge-mcp-gateway.test.ts`), and four
workspace tag tests (filtering, search, keyboard add/remove, manager
rename/merge/delete).
- `npx vitest run` over the workspace, panel, `src/main/agent`,
`src/main/magic-notes`, `src/main/assistant`, storage startup, preload, shared
and the five Magic Notes Electron tests: 142 files passed (4 skipped), 1706
tests passed. `App.test.tsx -t note`: 4 passed.
- The Electron layout test checks the overview filter row and card chips fit
their containers; screenshots of the overview, tag manager and note header
checked at 1280px.
- `npm run typecheck` and eslint on the changed files passed. Full `npm test`
was not rerun.
## 2026-09-30: Overview Page And Header Back Button
"Back to overview" returned to the page header's top-right as a labelled button.
It now leads to a full-width all-notes card grid instead of a list beside an
empty detail pane; the split list appears only while a note is open.
`MagicNotesWorkspace.test.tsx` (129) and the Electron layout, analysis and
navigation tests passed; screenshots checked at 1280 and 720px.
## 2026-09-30: Compact Note Detail
The note detail now prioritizes content: the notes list uses two-line rows
(about 54px instead of 88px); note controls moved from text buttons in the page
header to a sticky icon toolbar; the title appears once as the stream heading;
the record index starts collapsed; AI opens only with comment activity or a pin;
the idle composer is one line; entry actions are hover icons. At 1280x800 with
the list open the stream is about 990px wide (content capped at 880px), versus
525px before, and the first entry starts about 240px from the top of the stream.
- Electron screenshots checked at 1280 (light/dark), 1000 and 720px, with and
without the index and AI panes.
- `MagicNotesWorkspace.test.tsx` 129 tests, `App.test.tsx` 315 tests, and the
Electron layout, analysis, navigation, footer and capture tests passed.
The analysis and navigation fixtures now locate icon buttons by `aria-label`.
- `npm run typecheck` and eslint on the changed files passed. Full `npm test`
was not rerun.
## 2026-09-30: Persistent Notes List
Desktop notes now retain the all-notes list beside the current note. The list
supports independent scrolling, selected state, creation, action menus, resizing
and collapse. Narrow workspaces switch between the mounted list and detail.
Switching notes restores reading positions within the mounted workspace and
protects unsaved titles and bodies. Current-note deletion uses the same draft
guard; failed deletion and deleting another note retain current drafts.
- Workspace, Electron layout, navigation and analysis regression tests passed:
4 files, 132 tests. The analysis fixture no longer waits for Cancel after Save
changes has already closed the editor.
- The full-suite run also passed the production App/preload/IPC/SQLite note-capture
scenario, including reopening the database and returning to conversation sources.
- `npm run typecheck`, `npm run lint` and `git diff --check` passed.
- Full `npm test` did not finish within 600 seconds. It reported two analysis
failures subsequently fixed and passed above, and one supervisor-layout failure.
There is no all-green full-suite result for this change.
- Layout fix, same day: with the list occupying 289px, a 930-1100px window left
the detail container under 800px, so AI stacked below a short stream. At about
880px a CSS container query and the React width state disagreed, briefly
rendering the list above the stacked detail. AI now stays beside the editor
down to a 600px detail width; the record index uses a drawer from 800px; the
list/detail switch uses only the measured workspace width. Electron screenshots
were checked at 1280, 1100, 1000, 930, 880, 700 and 560px.
`MagicNotesWorkspace.test.tsx`, the Electron layout test and the analysis
regression passed: 3 files, 131 tests.
## 2026-09-28: Startup Body Read Reuse
Startup repair now reuses the asset references collected during revision
+101 -17
View File
@@ -20,6 +20,18 @@ stream events, error causes and nested release/dispose cleanup.
## Continuous Record Workspace
The outer workspace separates the persistent notes list from the detail container.
The list defaults to 280px, resizes within 240-420px and stores `notesPaneWidth`
and `notesPaneOpen` in `goodbuddy.magic-notes-layout.v1`. At workspace widths up to
900px, selection replaces the visible list with detail while retaining the mounted
list. The right-hand `magic-notes-detail` query container and its ResizeObserver
measure the detail width for two breakpoints: at 800px or less the record index
becomes a drawer while AI stays beside the editor; at 600px or less AI stacks
below the stream. The 900px list/detail switch is driven by the same measured
workspace width in both CSS class and React state, so the two cannot disagree.
Per-note stream scroll positions live in a workspace-local map; explicit entry
navigation takes precedence over reading-position restoration.
The detail mounts every entry in a continuous newest-first stream. The left index
and AI source buttons scroll to an entry ID without changing the active editor,
clearing a composer, flushing content or invoking the draft-switch guard. Opening
@@ -67,22 +79,38 @@ content and streamed draft comments through the shared draft-analysis cleanup.
The context counter rejects late results and prevents dispatch after abandoned
settings preparation. This cleanup does not clear the new-entry composer's content.
The left index defaults to 168px and resizes from its right edge between 140px
The left index is collapsed by default (`recordIndexOpen`, default `false`); when
open it defaults to 168px and resizes from its right edge between 140px
and 320px. It shares the AI pane's pointer-capture handlers, separator styling
and keyboard semantics (16px arrow steps, Home/End limits). Both widths are
stored in `goodbuddy.magic-notes-layout.v1`; missing index width defaults to
168px. Displayed widths clamp together to reserve at least 300px for the stream,
without overwriting saved widths when the window shrinks. The page header places
the index toggle immediately after the back button, followed by the AI toggle.
All three use icons and visible localized text; pane toggle labels reflect their
current show/hide action. Collapsing hides the entire index and removes its grid column,
border, resize separator and occupied width. AI has a separate
right pane with its existing resize separator and header toggle. Both panes scroll
independently and persist independent desktop visibility. At container widths of
800px or less, the index defaults to hidden and the same header toggle opens or
closes a fixed 168px drawer below the header, without reserving a rail or showing
without overwriting saved widths when the window shrinks. The legacy
`indexPaneOpen`/`aiPaneOpen` flags are ignored so existing users get the compact
defaults once. With no note open the workspace renders only the list, full
width, as a card grid (`magic-notes-workspace--overview`); the split layout
(`--notes`) exists only while a note is open on a wide workspace. The page
header shows the library switch in the overview and a labelled "Back to
overview" button while a note is open. Note controls live in a sticky icon
toolbar at the top of the stream (notes list, record index, entry count/updated
time, AI). Collapsing hides the entire
index and removes its grid column, border, resize separator and occupied width.
AI visibility is derived, not stored directly: `aiPaneOpen = aiPanePinned ||
(hasAiActivity && dismissedNoteId !== detail.id)`, where activity means saved
comments, draft analyses, or a running/streaming analysis. Hiding records the
current note as dismissed and clears the pin; starting a new analysis clears the
dismissal. Opening AI with no activity sets `aiPanePinned`, which is persisted.
The new-entry composer stays mounted. It collapses with CSS
(`:not(.magic-note-composer--active):not(:focus-within)`) so drafts, focus
management and the unsaved-draft guard are unchanged; `composerHasDraft` tracks
non-empty text so a draft never collapses after blur. At container widths of
800px or less, the index defaults to hidden and the same toolbar toggle opens or
closes a fixed 168px drawer over the stream, without reserving a rail or showing
an index resize separator; choosing
an item or pressing Escape closes it. AI moves below the stream, limited to 40%
an item or pressing Escape closes it. AI remains a resizable right column until the
detail container is 600px or less, then moves below the stream, limited to 40%
of layout height and 280px. Overview search, scroll, task selection and return
focus stay mounted.
@@ -375,8 +403,9 @@ objects and returns a function removing that listener. The existing
The overview's `libraryView` and the optional note-only `detailView` are independent:
opening a todo's source note does not lose the originating task view.
The overview remains mounted but hidden while detail is open, retaining scroll
position and filters. Returning restores the initiating control's focus and
The notes list remains visible beside detail on desktop unless collapsed. On
narrow screens, or when opening a source note from To-dos, the originating list
remains mounted but hidden, retaining scroll position and filters. Returning restores the initiating control's focus and
invalidates outstanding detail requests. Initial loading fetches summaries and
todos without selecting the first note or task. Navigation away from unsaved content or
an unsaved title uses the existing draft confirmation; active writes finish
@@ -436,7 +465,7 @@ Groups start expanded and can independently hide their mounted rows without
clearing the selected detail. Collapse state lasts while the group is mounted.
Note detail retains its existing AI pane preferences.
A single secondary button lives in `PageHeader.actions` before New note. Its
A single secondary button lives in `PageHeader.actions` while note detail is closed. Its
label and icon describe the destination, and `requestDraftSwitch` retains the
existing draft guard. Focus returns to the same `magic-library-switch` button
after switching. The overview section keeps its localized list `aria-label`;
@@ -446,12 +475,15 @@ search in one wrapping toolbar.
Only note detail applies `magic-notes-layout--detail` to remove the layout frame
and corner radius. Its stream pane has no extra padding; editor borders and the
AI divider remain. Overview cards and the independent todo layout keep their
existing frame and spacing. These changes do not affect Main, preload or storage.
AI divider remain. The compact notes list and independent todo layout retain their
own spacing. These changes do not affect Main, preload or storage.
## Note List Actions
Each overview card has an open-detail button and a sibling action-menu button. The menu
Each notes-list row has an open-detail button and a sibling action-menu button,
available while detail is open. Deleting the selected note passes through the
existing draft guard; drafts clear only after deletion succeeds. Deleting another
note retains current selection and drafts. The menu
uses the conversation action styles and shared `DestructiveConfirmActions`, and
is portalled to `document.body`. Its position follows the trigger on scrolling,
window resizing and confirmation-size changes. Entering detail, filtering out the
@@ -464,6 +496,58 @@ target ID and refreshes the overview after success, without opening another note
These actions are available only in the overview. Database and Agent contracts
are unchanged.
## Note Tags
Implements [FR-7 to FR-10](./prd.md#功能要求). Schema 51 adds two tables:
- `magic_note_tags(id, name, name_key UNIQUE, created_at, updated_at)`. `name_key`
is the NFC-normalized, trimmed, lower-cased name, so `Work` and `work` share one
row and the first spelling is kept.
- `magic_note_tag_links(note_id, tag_id, position, created_at)` with a composite
primary key and `ON DELETE CASCADE` on both sides. `position` keeps the order
the user added tags in.
`magicNoteTagNameSchema` and `magicNoteTagListSchema` in
`src/shared/magic-notes-contracts.ts` hold all name rules: collapse whitespace,
1–24 characters, no commas, `#` or control characters; case-insensitive dedup;
at most 10 per note. IPC and MCP inputs go through these schemas before reaching
the database. The MCP tool schemas stay plain string arrays so the published
JSON schema remains simple; the gateway normalizes them.
Database behavior (`AssistantDatabase`):
- Summaries carry `tags: string[]` via one `json_group_array` subquery shared by
list, get and summary search. The summary search also matches tag names.
- `updateMagicNote({ tags })` replaces the full list inside the same
`BEGIN IMMEDIATE` transaction as the revision check, so a stale revision
changes nothing. `createMagicNote` accepts initial tags.
- Tags with no remaining links are deleted by the write that orphaned them
(tag replacement and note deletion). `listMagicNoteTags()` returns only
linked tags with `noteCount`, ordered by count then name.
- `listMagicNotes({ tags })` is an AND filter on `name_key`.
- `renameMagicNoteTag` updates the name, or merges when the new key belongs to
another tag: links move with `INSERT OR IGNORE`, then the source tag is
deleted. `deleteMagicNoteTag` removes the tag and its links. Both bump the
revision and `updated_at` of every affected note so open editors do not save
over the change, and both emit one `onMagicNotesChanged`.
IPC: `magic-notes:list` now returns `{ notes, tags }`; new channels
`magic-notes:rename-tag` and `magic-notes:delete-tag`; `magic-notes:update`
accepts `tags`. Preload exposes `renameTag` and `removeTag`.
Renderer: tag UI lives in `MagicNoteTags.tsx`. The workspace filters the loaded
summaries on the client (AND by tag key, plus tag names in the search box), so
filter changes do not refetch. Filter keys that no longer exist after a refresh
are ignored; renaming a filtered tag carries the filter to the new key. Tag edits
from the note header use the detail revision and `applyDetail`, so title and
composer drafts are kept.
Agent tools: `note_create` and `note_update` accept `tags` (update replaces the
list; `[]` clears it), `note_list` accepts a `tags` AND filter, and note
summaries returned to tools include `tags`. Descriptions tell the model to change
tags only when the user asks. The conversation capture panel does not edit tags
in this version.
## Agent Search Contract
`note_search` accepts an integer `limit` from 1 to 100, defaulting to 8.
+1 -1
View File
@@ -15,7 +15,7 @@
## 紧凑面板
面板按单列显示列表或笔记详情,不嵌入完整工作区的三栏布局。搜索位于列表顶部,“新建笔记”在列表之后;笔记行显示标题、置顶状态、两行摘要和更新时间。无笔记时提供新建动作;搜索无结果时保留搜索词并允许创建。
面板按单列显示列表或笔记详情,不嵌入完整工作区的多栏布局。搜索位于列表顶部,“新建笔记”在列表之后;笔记行显示标题、置顶状态、两行摘要和更新时间。无笔记时提供新建动作;搜索无结果时保留搜索词并允许创建。
详情顶部提供返回列表、笔记标题和打开完整工作区,不显示“全局”标识。中间以可滚动记录摘要供浏览,每条文字摘要最多显示前 600 个字符,画布显示首页缩略图及页数;记录按钮可在完整工作区定位该记录。底部提供快速追加文字输入区与“加入笔记”按钮,画布编辑、待办管理和 AI 评论阅读仍在完整工作区。