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:
+5
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -433,6 +433,11 @@ export type MagicTodoItem = {
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type MagicTodoUpdateResult = {
|
||||
todo: MagicTodoItem
|
||||
note: MagicNoteDetail
|
||||
}
|
||||
|
||||
export type MagicTodosSnapshot = {
|
||||
todos: MagicTodoItem[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user