fix: sync todo completion with source notes

Completing a Magic Notes todo updated the stored checklist but left the cached
source note stale until the workspace was remounted. Todo updates now return
the refreshed source note and update the active note cache immediately for
both completion and reopening.

Release note: 修复在待办视图勾选或取消待办后,源笔记必须切换页面才显示最新状态的问题;现在会即时同步。
This commit is contained in:
mesalogo
2026-08-21 23:27:41 +08:00
parent 63f0f7f0b0
commit fee4686750
7 changed files with 114 additions and 20 deletions
+5 -1
View File
@@ -5686,9 +5686,13 @@ export function registerIpcHandlers(
ipcChannels.magicTodosUpdate,
(event, input: unknown) => {
assertTrustedSender(event, window)
return assistantDatabase.updateMagicTodo(
const todo = assistantDatabase.updateMagicTodo(
magicTodoUpdateSchema.parse(input)
)
return {
todo,
note: assistantDatabase.getMagicNote(todo.noteId)
}
}
)
+2 -1
View File
@@ -107,6 +107,7 @@ import type {
MagicNoteDetail,
MagicNotesSnapshot,
MagicTodoItem,
MagicTodoUpdateResult,
MagicTodosSnapshot
} from '../shared/magic-notes-contracts'
import type {
@@ -1113,7 +1114,7 @@ const desktopApi: DesktopApi = {
ipcRenderer.invoke(
ipcChannels.magicTodosUpdate,
input
) as Promise<MagicTodoItem>,
) as Promise<MagicTodoUpdateResult>,
analyzeTodo: (todoId, options) =>
ipcRenderer.invoke(ipcChannels.magicTodosAnalyze, {
todoId,
+78 -7
View File
@@ -14,7 +14,9 @@ import type { ApplicationSettings } from '../../shared/application-settings-cont
import type {
MagicNoteDetail,
MagicNotesSnapshot,
MagicNoteRichContent,
MagicTodoItem,
MagicTodoUpdateResult,
MagicTodosSnapshot
} from '../../shared/magic-notes-contracts'
import { MagicNotesWorkspace } from './MagicNotesWorkspace'
@@ -47,7 +49,22 @@ vi.mock('./MagicNoteEditor', () => ({
}))
vi.mock('./MagicNoteContent', () => ({
MagicNoteContent: () => <div>记录正文</div>
MagicNoteContent: ({ content }: { content: MagicNoteRichContent }) => {
const checklistState =
content.ops.find(
(operation) =>
operation.attributes?.list === 'checked' ||
operation.attributes?.list === 'unchecked'
)?.attributes?.list ?? 'none'
return (
<div>
<span>记录正文</span>
<output data-testid="magic-note-checklist-state">
{checklistState}
</output>
</div>
)
}
}))
const noteId = '00000000-0000-4000-8000-000000000601'
@@ -229,11 +246,41 @@ beforeEach(() => {
revision: entry.revision + 1
}))
})
updateTodo.mockImplementation(async (input) => ({
...noteTodo,
completed: input.completed,
revision: noteTodo.revision + 1
}))
updateTodo.mockImplementation(
async (input): Promise<MagicTodoUpdateResult> => ({
todo: {
...noteTodo,
completed: input.completed,
revision: input.expectedRevision + 1
},
note: {
...detail,
revision: detail.revision + 1,
entries: detail.entries.map((entry) =>
entry.id === noteTodo.entryId
? {
...entry,
content: {
version: 1,
ops: [
{ insert: noteTodo.title },
{
insert: '\n',
attributes: {
list: input.completed ? 'checked' : 'unchecked'
}
}
]
},
comments: [],
analyzedAt: undefined,
revision: entry.revision + 1
}
: entry
)
}
})
)
analyze.mockResolvedValue({
...createdDetail,
entries: createdDetail.entries.map((entry) =>
@@ -957,7 +1004,7 @@ describe('MagicNotesWorkspace', () => {
expect(screen.getByText('准备演示')).toBeInTheDocument()
})
it('marks a todo completed from the standalone todo tab', async () => {
it('synchronizes todo completion with its source note immediately', async () => {
render(<MagicNotesWorkspace onNotify={onNotify} />)
await screen.findByText('记录正文')
@@ -986,6 +1033,30 @@ describe('MagicNotesWorkspace', () => {
message: '待办已完成'
})
)
fireEvent.click(screen.getByRole('tab', { name: '笔记' }))
expect(
screen.getByTestId('magic-note-checklist-state')
).toHaveTextContent('checked')
fireEvent.click(screen.getByRole('tab', { name: '待办' }))
fireEvent.click(
screen.getByRole('button', {
name: `标记为未完成:${noteTodo.title}`
})
)
await waitFor(() =>
expect(updateTodo).toHaveBeenNthCalledWith(2, {
todoId: noteTodo.id,
completed: false,
expectedRevision: noteTodo.revision + 1
})
)
fireEvent.click(screen.getByRole('tab', { name: '笔记' }))
expect(
screen.getByTestId('magic-note-checklist-state')
).toHaveTextContent('unchecked')
})
it('reuses the AI comments pane for selected todos', async () => {
+21 -10
View File
@@ -693,9 +693,7 @@ export function MagicNotesWorkspace({
[]
)
const applyDetail = useCallback((next: MagicNoteDetail) => {
setDetail(next)
setTitleDraft(next.title)
const applyNoteSummary = useCallback((next: MagicNoteDetail) => {
setNotes((current) => {
const summary = noteSummary(next)
const existing = current.some((note) => note.id === next.id)
@@ -710,6 +708,15 @@ export function MagicNotesWorkspace({
})
}, [])
const applyDetail = useCallback(
(next: MagicNoteDetail) => {
setDetail(next)
setTitleDraft(next.title)
applyNoteSummary(next)
},
[applyNoteSummary]
)
const applyTodo = useCallback((next: MagicTodoItem) => {
setTodos((current) =>
[
@@ -1184,13 +1191,17 @@ export function MagicNotesWorkspace({
}
try {
const completed = !todo.completed
applyTodo(
await window.goodbuddy.magicNotes.updateTodo({
todoId: todo.id,
completed,
expectedRevision: todo.revision
})
)
const result = await window.goodbuddy.magicNotes.updateTodo({
todoId: todo.id,
completed,
expectedRevision: todo.revision
})
applyTodo(result.todo)
if (requestedNoteIdRef.current === result.note.id) {
applyDetail(result.note)
} else {
applyNoteSummary(result.note)
}
notifySuccess(
t(
completed
+2 -1
View File
@@ -64,6 +64,7 @@ import type {
MagicNoteUpdateInput,
MagicTodoItem,
MagicTodoUpdateInput,
MagicTodoUpdateResult,
MagicTodosSnapshot
} from './magic-notes-contracts'
import type {
@@ -1704,7 +1705,7 @@ export type DesktopApi = {
listTodos: () => Promise<MagicTodosSnapshot>
updateTodo: (
input: MagicTodoUpdateInput
) => Promise<MagicTodoItem>
) => Promise<MagicTodoUpdateResult>
analyzeTodo: (
todoId: string,
options: MagicNoteAnalysisOptions
+5
View File
@@ -433,6 +433,11 @@ export type MagicTodoItem = {
updatedAt: string
}
export type MagicTodoUpdateResult = {
todo: MagicTodoItem
note: MagicNoteDetail
}
export type MagicTodosSnapshot = {
todos: MagicTodoItem[]
}