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:
@@ -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
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
## 紧凑面板
|
||||
|
||||
面板按单列显示列表或笔记详情,不嵌入完整工作区的三栏布局。搜索位于列表顶部,“新建笔记”在列表之后;笔记行显示标题、置顶状态、两行摘要和更新时间。无笔记时提供新建动作;搜索无结果时保留搜索词并允许创建。
|
||||
面板按单列显示列表或笔记详情,不嵌入完整工作区的多栏布局。搜索位于列表顶部,“新建笔记”在列表之后;笔记行显示标题、置顶状态、两行摘要和更新时间。无笔记时提供新建动作;搜索无结果时保留搜索词并允许创建。
|
||||
|
||||
详情顶部提供返回列表、笔记标题和打开完整工作区,不显示“全局”标识。中间以可滚动记录摘要供浏览,每条文字摘要最多显示前 600 个字符,画布显示首页缩略图及页数;记录按钮可在完整工作区定位该记录。底部提供快速追加文字输入区与“加入笔记”按钮,画布编辑、待办管理和 AI 评论阅读仍在完整工作区。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user