From 9e0dcd7af1c266b7a9c3d0abeb08ac5e9a4d0d07 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 15:06:02 +0300 Subject: [PATCH 01/24] feat: add task block utility functions with tests Co-Authored-By: Claude Opus 4.6 (1M context) --- .../__tests__/task-block-utils.test.ts | 156 +++++++++++++++ .../note/content-area/task-block/index.tsx | 45 +++++ .../task-block/task-block-utils.ts | 189 ++++++++++++++++++ 3 files changed, 390 insertions(+) create mode 100644 apps/desktop/src/renderer/src/components/note/content-area/task-block/__tests__/task-block-utils.test.ts create mode 100644 apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx create mode 100644 apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-utils.ts diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/__tests__/task-block-utils.test.ts b/apps/desktop/src/renderer/src/components/note/content-area/task-block/__tests__/task-block-utils.test.ts new file mode 100644 index 000000000..197d7795c --- /dev/null +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/__tests__/task-block-utils.test.ts @@ -0,0 +1,156 @@ +import { describe, it, expect } from 'vitest' +import { + isLikelyTask, + serializeTaskBlock, + parseTaskBlockSuffix, + normalizeTaskBlocks +} from '../task-block-utils' + +describe('isLikelyTask', () => { + it('returns true for text starting with action verbs', () => { + expect(isLikelyTask('Buy groceries')).toBe(true) + expect(isLikelyTask('fix the login bug')).toBe(true) + expect(isLikelyTask('Send email to team')).toBe(true) + expect(isLikelyTask('Review PR #123')).toBe(true) + expect(isLikelyTask('Schedule meeting with design')).toBe(true) + expect(isLikelyTask('Deploy to staging')).toBe(true) + }) + + it('returns false for non-action text', () => { + expect(isLikelyTask('Milk')).toBe(false) + expect(isLikelyTask('Item 1')).toBe(false) + expect(isLikelyTask('Notes from standup')).toBe(false) + expect(isLikelyTask('a')).toBe(false) + expect(isLikelyTask('')).toBe(false) + }) + + it('is case-insensitive', () => { + expect(isLikelyTask('BUY groceries')).toBe(true) + expect(isLikelyTask('Fix Bug')).toBe(true) + }) + + it('handles leading whitespace', () => { + expect(isLikelyTask(' Buy groceries')).toBe(true) + }) + + it('rejects very short or very long text', () => { + expect(isLikelyTask('Go')).toBe(false) + expect(isLikelyTask('x'.repeat(300))).toBe(false) + }) +}) + +describe('serializeTaskBlock', () => { + it('serializes unchecked task', () => { + expect(serializeTaskBlock({ taskId: 'abc-123', title: 'Buy groceries', checked: false })).toBe( + '- [ ] Buy groceries {task:abc-123}' + ) + }) + + it('serializes checked task', () => { + expect(serializeTaskBlock({ taskId: 'def-456', title: 'Send email', checked: true })).toBe( + '- [x] Send email {task:def-456}' + ) + }) +}) + +describe('parseTaskBlockSuffix', () => { + it('parses task reference from text', () => { + expect(parseTaskBlockSuffix('Buy groceries {task:abc-123}')).toEqual({ + taskId: 'abc-123', + title: 'Buy groceries' + }) + }) + + it('returns null for text without task ref', () => { + expect(parseTaskBlockSuffix('Just a regular item')).toBeNull() + }) + + it('handles task ref at end of longer text', () => { + expect(parseTaskBlockSuffix('Send email {task:def-456}')).toEqual({ + taskId: 'def-456', + title: 'Send email' + }) + }) +}) + +describe('normalizeTaskBlocks', () => { + it('converts checkListItem with {task:id} to taskBlock', () => { + const blocks = [ + { + id: 'b1', + type: 'checkListItem', + props: { isChecked: false }, + content: [{ type: 'text', text: 'Buy groceries {task:abc-123}', styles: {} }], + children: [] + } + ] as any[] + + const { blocks: result, didChange } = normalizeTaskBlocks(blocks) + expect(didChange).toBe(true) + expect(result[0].type).toBe('taskBlock') + expect((result[0].props as any).taskId).toBe('abc-123') + expect((result[0].props as any).title).toBe('Buy groceries') + expect((result[0].props as any).checked).toBe(false) + }) + + it('preserves checked state from checkListItem', () => { + const blocks = [ + { + id: 'b2', + type: 'checkListItem', + props: { isChecked: true }, + content: [{ type: 'text', text: 'Done task {task:def-456}', styles: {} }], + children: [] + } + ] as any[] + + const { blocks: result } = normalizeTaskBlocks(blocks) + expect((result[0].props as any).checked).toBe(true) + }) + + it('leaves regular checkListItem untouched', () => { + const blocks = [ + { + id: 'b3', + type: 'checkListItem', + props: { isChecked: false }, + content: [{ type: 'text', text: 'Just a checkbox', styles: {} }], + children: [] + } + ] as any[] + + const { blocks: result, didChange } = normalizeTaskBlocks(blocks) + expect(didChange).toBe(false) + expect(result).toBe(blocks) + }) + + it('leaves non-checkListItem blocks unchanged', () => { + const blocks = [ + { + id: 'b4', + type: 'paragraph', + props: {}, + content: [{ type: 'text', text: 'Some text {task:xyz}', styles: {} }], + children: [] + } + ] as any[] + + const { didChange } = normalizeTaskBlocks(blocks) + expect(didChange).toBe(false) + }) + + it('returns same reference when no {task: found', () => { + const blocks = [ + { + id: 'b5', + type: 'checkListItem', + props: { isChecked: false }, + content: [{ type: 'text', text: 'No task here', styles: {} }], + children: [] + } + ] as any[] + + const { blocks: result } = normalizeTaskBlocks(blocks) + expect(result).toBe(blocks) + }) +}) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx new file mode 100644 index 000000000..0c6c2dc21 --- /dev/null +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx @@ -0,0 +1,45 @@ +import { createReactBlockSpec } from '@blocknote/react' +import { TaskBlockRenderer } from './task-block-renderer' + +export const createTaskBlock = () => + createReactBlockSpec( + { + type: 'taskBlock' as const, + propSchema: { + taskId: { default: '' }, + title: { default: '' }, + checked: { default: false } + }, + content: 'none' + }, + { + render: (props) => ( + + ) + } + ) + +export function getTaskSlashMenuItem(editor: any) { + return { + title: 'Task', + onItemClick: () => { + const currentBlock = editor.getTextCursorPosition().block + editor.updateBlock(currentBlock, { + type: 'taskBlock' as any, + props: { taskId: '', title: '', checked: false } + }) + window.dispatchEvent( + new CustomEvent('task-block:open-creation', { + detail: { blockId: currentBlock.id } + }) + ) + }, + aliases: ['task', 'todo', 'action'], + group: 'Basic blocks', + subtext: 'Create a linked task' + } +} diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-utils.ts b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-utils.ts new file mode 100644 index 000000000..eec2c51aa --- /dev/null +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-utils.ts @@ -0,0 +1,189 @@ +import type { Block } from '@blocknote/core' + +const ACTION_VERBS = new Set([ + 'add', + 'announce', + 'approve', + 'arrange', + 'ask', + 'assign', + 'backup', + 'book', + 'build', + 'buy', + 'call', + 'cancel', + 'check', + 'clean', + 'clear', + 'close', + 'configure', + 'confirm', + 'connect', + 'copy', + 'create', + 'debug', + 'deploy', + 'design', + 'discuss', + 'do', + 'download', + 'draft', + 'drop', + 'edit', + 'email', + 'export', + 'file', + 'fill', + 'find', + 'finish', + 'fix', + 'flush', + 'follow', + 'get', + 'go', + 'implement', + 'import', + 'install', + 'investigate', + 'link', + 'look', + 'make', + 'meet', + 'merge', + 'migrate', + 'move', + 'notify', + 'open', + 'order', + 'organize', + 'pack', + 'patch', + 'pay', + 'pick', + 'pin', + 'plan', + 'post', + 'prepare', + 'print', + 'publish', + 'push', + 'read', + 'refactor', + 'release', + 'remind', + 'remove', + 'renew', + 'replace', + 'research', + 'resolve', + 'respond', + 'restore', + 'return', + 'review', + 'run', + 'scan', + 'schedule', + 'send', + 'set', + 'share', + 'ship', + 'sign', + 'sort', + 'start', + 'stop', + 'submit', + 'swap', + 'sync', + 'tag', + 'talk', + 'test', + 'try', + 'update', + 'upgrade', + 'upload', + 'validate', + 'verify', + 'watch', + 'write' +]) + +export function isLikelyTask(text: string): boolean { + const trimmed = text.trim() + if (trimmed.length < 3 || trimmed.length > 200) return false + const firstWord = trimmed.split(/\s+/)[0].toLowerCase() + return ACTION_VERBS.has(firstWord) +} + +const TASK_BLOCK_SUFFIX_REGEX = /\{task:([^}]+)\}\s*$/ + +export interface TaskBlockProps { + taskId: string + title: string + checked: boolean +} + +export function serializeTaskBlock(props: TaskBlockProps): string { + const check = props.checked ? 'x' : ' ' + return `- [${check}] ${props.title} {task:${props.taskId}}` +} + +export function parseTaskBlockSuffix(text: string): { taskId: string; title: string } | null { + const match = text.match(TASK_BLOCK_SUFFIX_REGEX) + if (!match) return null + return { + taskId: match[1], + title: text.replace(TASK_BLOCK_SUFFIX_REGEX, '').trim() + } +} + +function extractInlineText(content: unknown): string { + if (typeof content === 'string') return content + if (!Array.isArray(content)) return '' + return content + .map((item: unknown) => { + if (typeof item === 'string') return item + if ( + item && + typeof item === 'object' && + 'type' in item && + (item as Record).type === 'text' + ) { + return ((item as Record).text as string) || '' + } + return '' + }) + .join('') +} + +export function normalizeTaskBlocks(blocks: Block[]): { blocks: Block[]; didChange: boolean } { + const blockStr = JSON.stringify(blocks) + if (!blockStr.includes('{task:')) { + return { blocks, didChange: false } + } + + let didChange = false + + const nextBlocks = blocks.map((block) => { + if (block.type !== 'checkListItem') return block + + const text = extractInlineText(block.content) + const parsed = parseTaskBlockSuffix(text) + if (!parsed) return block + + didChange = true + return { + type: 'taskBlock', + props: { + taskId: parsed.taskId, + title: parsed.title, + checked: (block.props as Record).isChecked ?? false + }, + content: undefined, + children: [], + id: block.id + } as unknown as Block + }) + + return { blocks: didChange ? nextBlocks : blocks, didChange } +} From 31c384cbfb059d1e008fbf91a963cabdc76486d3 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 15:07:16 +0300 Subject: [PATCH 02/24] feat: add TaskBlock renderer with live data and useTaskBlockData hook TaskBlockRenderer handles all states: ghost (creating), deleted, loading, and live. Syncs DB task data back to block props, supports toggle complete, priority badge, due date, and project name on hover. useTaskBlockData hook fetches task by ID and subscribes to update/ complete/delete events for real-time reactivity. --- .../task-block/task-block-renderer.tsx | 184 ++++++++++++++++++ .../task-block/use-task-block-data.ts | 73 +++++++ 2 files changed, 257 insertions(+) create mode 100644 apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx create mode 100644 apps/desktop/src/renderer/src/components/note/content-area/task-block/use-task-block-data.ts diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx new file mode 100644 index 000000000..e9dae1c41 --- /dev/null +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx @@ -0,0 +1,184 @@ +import { type FC, useCallback, useEffect, useRef } from 'react' +import { Check, AlertTriangle, Loader2, X } from 'lucide-react' +import { cn } from '@/lib/utils' +import { useTaskBlockData } from './use-task-block-data' +import { useTasksOptional } from '@/contexts/tasks' +import { tasksService } from '@/services/tasks-service' +import { PRIORITY_CSS_VARS, type Priority } from '@/data/sample-tasks' + +interface TaskBlockRendererProps { + block: { id: string; props: { taskId: string; title: string; checked: boolean } } + editor: any + contentRef: React.Ref +} + +const DB_PRIORITY_MAP: Record = { + 0: 'none', + 1: 'low', + 2: 'medium', + 3: 'high', + 4: 'urgent' +} + +export const TaskBlockRenderer: FC = ({ block, editor, contentRef }) => { + const { taskId, title, checked } = block.props + const { task, isLoading, isDeleted } = useTaskBlockData(taskId) + const tasksCtx = useTasksOptional() + const syncingRef = useRef(false) + + const displayTitle = task?.title ?? title + const displayChecked = task ? !!task.completedAt : checked + + useEffect(() => { + if (!task || syncingRef.current) return + const needsUpdate = + task.title !== block.props.title || !!task.completedAt !== block.props.checked + if (needsUpdate) { + syncingRef.current = true + editor.updateBlock(block, { + props: { + ...block.props, + title: task.title, + checked: !!task.completedAt + } + }) + syncingRef.current = false + } + }, [task, block, editor]) + + const handleToggle = useCallback(async () => { + if (!taskId) return + const newChecked = !displayChecked + editor.updateBlock(block, { props: { ...block.props, checked: newChecked } }) + if (newChecked) { + await tasksService.complete({ id: taskId }) + } else { + await tasksService.uncomplete(taskId) + } + }, [taskId, displayChecked, block, editor]) + + const handleRemoveGhost = useCallback(() => { + editor.removeBlocks([block]) + }, [block, editor]) + + const handleTitleClick = useCallback(() => { + if (taskId) { + window.dispatchEvent(new CustomEvent('task-block:open-detail', { detail: { taskId } })) + } + }, [taskId]) + + if (!taskId) { + return ( +
+ + Creating task... +
+ ) + } + + if (isDeleted) { + return ( +
+ + {displayTitle} + Task deleted + +
+ ) + } + + const priorityNum = + typeof task?.priority === 'number' + ? task.priority + : typeof task?.priority === 'string' + ? (({ none: 0, low: 1, medium: 2, high: 3, urgent: 4 } as Record)[ + task.priority + ] ?? 0) + : 0 + const priorityKey = DB_PRIORITY_MAP[priorityNum] ?? 'none' + const priorityVars = PRIORITY_CSS_VARS[priorityKey] + + const projectName = tasksCtx?.projects?.find((p) => p.id === task?.projectId)?.name + + const dueDate = task?.dueDate ? new Date(task.dueDate) : null + const isOverdue = dueDate instanceof Date && dueDate < new Date() && !displayChecked + + const formatDue = (d: Date | null): string | null => { + if (!d || !(d instanceof Date)) return null + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + } + + return ( +
+ + + + +
+ {priorityVars && ( + + )} + {formatDue(dueDate) && ( + + {formatDue(dueDate)} + + )} + {projectName && {projectName}} +
+ + {isLoading && } +
+ ) +} diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/use-task-block-data.ts b/apps/desktop/src/renderer/src/components/note/content-area/task-block/use-task-block-data.ts new file mode 100644 index 000000000..d07697454 --- /dev/null +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/use-task-block-data.ts @@ -0,0 +1,73 @@ +import { useState, useEffect, useCallback } from 'react' +import { + tasksService, + onTaskUpdated, + onTaskDeleted, + onTaskCompleted, + type Task +} from '@/services/tasks-service' + +interface UseTaskBlockDataResult { + task: Task | null + isLoading: boolean + isDeleted: boolean +} + +export function useTaskBlockData(taskId: string): UseTaskBlockDataResult { + const [task, setTask] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const [isDeleted, setIsDeleted] = useState(false) + + const loadTask = useCallback(async (id: string): Promise => { + setIsLoading(true) + try { + const result = await tasksService.get(id) + if (result) { + setTask(result) + setIsDeleted(false) + } else { + setIsDeleted(true) + } + } catch { + setIsDeleted(true) + } finally { + setIsLoading(false) + } + }, []) + + useEffect(() => { + if (!taskId) return + loadTask(taskId) + }, [taskId, loadTask]) + + useEffect(() => { + if (!taskId) return + + const unsubUpdated = onTaskUpdated((event) => { + if (event.id === taskId) { + setTask(event.task) + } + }) + + const unsubCompleted = onTaskCompleted((event) => { + if (event.id === taskId) { + setTask(event.task) + } + }) + + const unsubDeleted = onTaskDeleted((event) => { + if (event.id === taskId) { + setIsDeleted(true) + setTask(null) + } + }) + + return () => { + unsubUpdated() + unsubCompleted() + unsubDeleted() + } + }, [taskId]) + + return { task, isLoading, isDeleted } +} From c6a6f57f6842a924bae0e5c31339eb498067538b Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 15:08:27 +0300 Subject: [PATCH 03/24] feat: integrate taskBlock into editor schema and markdown pipeline --- .../src/components/note/content-area/editor-schema.ts | 4 +++- .../components/note/content-area/hooks/use-editor-sync.ts | 5 +++++ .../src/components/note/content-area/markdown-utils.ts | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/editor-schema.ts b/apps/desktop/src/renderer/src/components/note/content-area/editor-schema.ts index 199a2287f..37ddfce41 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/editor-schema.ts +++ b/apps/desktop/src/renderer/src/components/note/content-area/editor-schema.ts @@ -8,6 +8,7 @@ import { codeBlockOptions } from '@blocknote/code-block' import { createFileBlock } from './file-block' import { createCalloutBlock } from './callout-block' import { createYoutubeEmbedBlock } from './youtube-embed-block' +import { createTaskBlock } from './task-block' import { WikiLink } from './wiki-link' import { HashTag } from './hash-tag' import { LinkMention } from './link-mention' @@ -18,7 +19,8 @@ export const editorSchema = BlockNoteSchema.create({ codeBlock: createCodeBlockSpec(codeBlockOptions), file: createFileBlock(), callout: createCalloutBlock(), - youtubeEmbed: createYoutubeEmbedBlock() + youtubeEmbed: createYoutubeEmbedBlock(), + taskBlock: createTaskBlock() }, inlineContentSpecs: { ...defaultInlineContentSpecs, diff --git a/apps/desktop/src/renderer/src/components/note/content-area/hooks/use-editor-sync.ts b/apps/desktop/src/renderer/src/components/note/content-area/hooks/use-editor-sync.ts index e9609df6b..b733a5745 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/hooks/use-editor-sync.ts +++ b/apps/desktop/src/renderer/src/components/note/content-area/hooks/use-editor-sync.ts @@ -9,6 +9,7 @@ import { normalizeMarkdownHardBreaks } from '../wiki-link-utils' import { normalizeHashTags, extractInlineTags } from '../hash-tag' +import { normalizeTaskBlocks } from '../task-block/task-block-utils' import { FILE_BLOCK_REGEX, createFileBlockContent, serializeFileBlock } from '../file-block' import { parseMarkdownPreservingBlanks, serializeBlocksPreservingBlanks } from '../markdown-utils' import { createLinkMentionContent } from '../link-mention' @@ -162,6 +163,8 @@ export function useEditorSync({ } let normalizedBlocks = normalizeWikiLinks(blocks).blocks + const taskNormalized = normalizeTaskBlocks(normalizedBlocks) + normalizedBlocks = taskNormalized.blocks if (noteTags?.length && tagColorMap) { const tagSet = new Set(noteTags.map((t) => t.toLowerCase())) @@ -177,6 +180,8 @@ export function useEditorSync({ } } else if (Array.isArray(initialContent) && initialContent.length > 0) { let normalizedBlocks = normalizeWikiLinks(initialContent).blocks + const taskNormalized = normalizeTaskBlocks(normalizedBlocks) + normalizedBlocks = taskNormalized.blocks if (noteTags?.length && tagColorMap) { const tagSet = new Set(noteTags.map((t) => t.toLowerCase())) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/markdown-utils.ts b/apps/desktop/src/renderer/src/components/note/content-area/markdown-utils.ts index ef916ae13..e04dec915 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/markdown-utils.ts +++ b/apps/desktop/src/renderer/src/components/note/content-area/markdown-utils.ts @@ -9,6 +9,7 @@ import { import { splitMarkdownByCallouts, serializeCalloutBlock } from './callout-block' import { extractYouTubeVideoId } from '@/lib/youtube-utils' import { serializeYoutubeEmbed } from './youtube-embed-block' +import { serializeTaskBlock } from './task-block/task-block-utils' export function isEmptyParagraph(block: Block): boolean { if (block.type !== 'paragraph') return false @@ -88,7 +89,12 @@ export async function serializeBlocksPreservingBlanks( } for (const block of blocks) { - if ((block.type as string) === 'youtubeEmbed') { + if ((block.type as string) === 'taskBlock') { + await flushContent() + flushGap() + const props = block.props as { taskId: string; title: string; checked: boolean } + segments.push({ type: 'content', text: serializeTaskBlock(props) }) + } else if ((block.type as string) === 'youtubeEmbed') { await flushContent() flushGap() const videoUrl = (block.props as any).videoUrl as string From 7b987360ee26eca805d07205f45e9a586f7d5ed1 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 15:09:49 +0300 Subject: [PATCH 04/24] feat: add TaskCreationPopover for inline task property assignment --- .../task-block/task-creation-popover.tsx | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 apps/desktop/src/renderer/src/components/note/content-area/task-block/task-creation-popover.tsx diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-creation-popover.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-creation-popover.tsx new file mode 100644 index 000000000..a24eca64a --- /dev/null +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-creation-popover.tsx @@ -0,0 +1,187 @@ +import { type FC, useState, useCallback, useEffect, useRef, type RefObject } from 'react' +import { Popover, PopoverContent, PopoverAnchor } from '@/components/ui/popover' +import { Button } from '@/components/ui/button' +import { useTasksOptional } from '@/contexts/tasks' +import { tasksService, type TaskCreateInput } from '@/services/tasks-service' +import { extractErrorMessage } from '@/lib/ipc-error' +import { cn } from '@/lib/utils' + +interface TaskCreationPopoverProps { + isOpen: boolean + anchorRef: RefObject + title: string + noteId?: string + onCreated: (taskId: string, title: string) => void + onCancel: () => void +} + +const PRIORITY_OPTIONS = [ + { value: 0, label: 'None', color: 'bg-stone-300 dark:bg-stone-600' }, + { value: 1, label: 'Low', color: 'bg-sky-400' }, + { value: 2, label: 'Medium', color: 'bg-amber-400' }, + { value: 3, label: 'High', color: 'bg-orange-500' }, + { value: 4, label: 'Urgent', color: 'bg-red-500' } +] as const + +export const TaskCreationPopover: FC = ({ + isOpen, + anchorRef, + title, + noteId, + onCreated, + onCancel +}) => { + const tasksCtx = useTasksOptional() + const projects = tasksCtx?.projects?.filter((p) => !p.isArchived) ?? [] + const inboxProject = projects.find((p) => p.isDefault) + + const [projectId, setProjectId] = useState('') + const [priority, setPriority] = useState(0) + const [dueDate, setDueDate] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState(null) + const createBtnRef = useRef(null) + const prevOpenRef = useRef(false) + + useEffect(() => { + const justOpened = isOpen && !prevOpenRef.current + prevOpenRef.current = isOpen + if (justOpened) { + setProjectId(inboxProject?.id ?? projects[0]?.id ?? '') + setPriority(0) + setDueDate('') + setError(null) + setIsSubmitting(false) + } + }, [isOpen, inboxProject?.id, projects]) + + const handleCreate = useCallback(async () => { + if (!projectId || isSubmitting) return + setIsSubmitting(true) + setError(null) + + const input: TaskCreateInput = { + projectId, + title, + priority, + dueDate: dueDate || null, + linkedNoteIds: noteId ? [noteId] : [] + } + + try { + const res = await tasksService.create(input) + if (res.success && res.task) { + onCreated(res.task.id, res.task.title) + } else { + setError(res.error ?? 'Failed to create task') + setIsSubmitting(false) + } + } catch (err) { + setError(extractErrorMessage(err, 'Failed to create task')) + setIsSubmitting(false) + } + }, [projectId, title, priority, dueDate, noteId, isSubmitting, onCreated]) + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleCreate() + } + if (e.key === 'Escape') { + e.preventDefault() + onCancel() + } + }, + [handleCreate, onCancel] + ) + + return ( + !open && onCancel()}> + + { + e.preventDefault() + createBtnRef.current?.focus() + }} + > +
+
{title}
+ +
+ + +
+ +
+ +
+ {PRIORITY_OPTIONS.map((opt) => ( +
+
+ +
+ + setDueDate(e.target.value)} + className={cn( + 'w-full rounded-md border bg-transparent px-2 py-1.5 text-sm', + 'focus:outline-none focus:ring-1 focus:ring-ring' + )} + /> +
+ + {error &&

{error}

} + +
+ + +
+
+
+
+ ) +} From 1404f4a5c216332fb6c97d83e0e8322afcc5149b Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 15:13:37 +0300 Subject: [PATCH 05/24] feat: add /task slash command, bracket trigger, and right-click promote Wire TaskCreationPopover into ContentArea with three entry points: - /task slash menu item converts current block to taskBlock - Smart detect on checkListItem after 800ms debounce (isLikelyTask) - Right-click on any checkListItem opens creation popover --- .../note/content-area/ContentArea.tsx | 126 +++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index a883d9529..a8f1f1559 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -30,6 +30,9 @@ import { TagSuggestionPopover } from './tag-suggestion-popover' import { WikiLinkPreviewCard } from './wiki-link-preview-card' import { BlockDropIndicator, EmptyDocumentDropIndicator } from './block-drop-indicator' import { getCalloutSlashMenuItem } from './callout-block' +import { getTaskSlashMenuItem } from './task-block' +import { TaskCreationPopover } from './task-block/task-creation-popover' +import { isLikelyTask } from './task-block/task-block-utils' import { editorSchema } from './editor-schema' import { HighlightReminderPopover, @@ -106,6 +109,14 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const { port: aiPort, error: aiError, retry: retryAI } = useAIInlineContext() const [highlightSelection, setHighlightSelection] = useState(null) + const [taskCreation, setTaskCreation] = useState<{ + isOpen: boolean + blockId: string + title: string + } | null>(null) + const taskCreationAnchorRef = useRef(null) + const taskDetectTimeoutRef = useRef | null>(null) + const dismissedBlocksRef = useRef(new Set()) const editorContainerRef = useRef(null) const containerRef = useRef(null) const noteIdRef = useRef(noteId) @@ -277,6 +288,104 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ window.getSelection()?.removeAllRanges() }, []) + useEffect(() => { + const handleOpenCreation = (e: Event): void => { + const { blockId } = (e as CustomEvent).detail + const blockEl = document.querySelector(`[data-id="${blockId}"]`) + taskCreationAnchorRef.current = blockEl as HTMLElement + setTaskCreation({ isOpen: true, blockId, title: '' }) + } + window.addEventListener('task-block:open-creation', handleOpenCreation) + return () => window.removeEventListener('task-block:open-creation', handleOpenCreation) + }, []) + + const handleTaskCreated = useCallback( + (taskId: string, title: string) => { + if (!taskCreation?.blockId) return + const block = editor.getBlock(taskCreation.blockId) + if (!block) return + + if (block.type === 'checkListItem') { + editor.updateBlock(block, { + type: 'taskBlock' as any, + props: { taskId, title, checked: false } + }) + } else { + editor.updateBlock(block, { props: { taskId, title, checked: false } }) + } + setTaskCreation(null) + }, + [editor, taskCreation] + ) + + const handleTaskCreationCancel = useCallback(() => { + if (taskCreation?.blockId) { + dismissedBlocksRef.current.add(taskCreation.blockId) + const block = editor.getBlock(taskCreation.blockId) + if (block && (block.props as any).taskId === '') { + editor.removeBlocks([block]) + } + } + setTaskCreation(null) + }, [editor, taskCreation]) + + const checkForTaskBlock = useCallback(() => { + if (taskCreation?.isOpen) return + + const cursor = editor.getTextCursorPosition() + if (!cursor?.block) return + + const block = cursor.block + if (block.type !== 'checkListItem') return + if (dismissedBlocksRef.current.has(block.id)) return + + const content = block.content as any[] + if (!content?.length) return + + const text = content + .map((c: any) => (typeof c === 'string' ? c : c.text ?? '')) + .join('') + if (!text.trim() || !isLikelyTask(text)) return + + const blockEl = document.querySelector(`[data-id="${block.id}"]`) + if (!blockEl) return + + taskCreationAnchorRef.current = blockEl as HTMLElement + setTaskCreation({ isOpen: true, blockId: block.id, title: text.trim() }) + }, [editor, taskCreation?.isOpen]) + + const handleEditorContextMenu = useCallback( + (e: React.MouseEvent) => { + if (taskCreation?.isOpen) return + + const target = e.target as HTMLElement + const checkListBlock = target.closest('[data-content-type="checkListItem"]') + if (!checkListBlock) return + + const blockId = checkListBlock.getAttribute('data-id') + if (!blockId) return + + const block = editor.getBlock(blockId) + if (!block || block.type !== 'checkListItem') return + + const content = block.content as any[] + const text = + content?.map((c: any) => (typeof c === 'string' ? c : c.text ?? '')).join('') ?? '' + if (!text.trim()) return + + e.preventDefault() + taskCreationAnchorRef.current = checkListBlock as HTMLElement + setTaskCreation({ isOpen: true, blockId, title: text.trim() }) + }, + [editor, taskCreation?.isOpen] + ) + + useEffect(() => { + return () => { + if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) + } + }, []) + return (
{ void handleChange() + if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) + taskDetectTimeoutRef.current = setTimeout(checkForTaskBlock, 800) }} theme={editorTheme} formattingToolbar={!stickyToolbar} @@ -328,7 +440,8 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const defaults = getDefaultReactSlashMenuItems(editor) const aiItems = aiReady ? getAISlashMenuItems(editor) : [] const calloutItem = getCalloutSlashMenuItem(editor) - const all = [...defaults, calloutItem, ...aiItems] + const taskItem = getTaskSlashMenuItem(editor) + const all = [...defaults, calloutItem, taskItem, ...aiItems] if (!query) return all const lower = query.toLowerCase() return all.filter( @@ -380,6 +493,17 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ selectedIndex={pasteLinkState.selectedIndex} onSelect={handlePasteLinkOptionSelect} /> + + {taskCreation?.isOpen && ( + + )}
) From 64c3bb937f012aae964817ee61df241dc2dd29d7 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 15:21:13 +0300 Subject: [PATCH 06/24] fix: resolve type errors in taskBlock spec and popover anchor ref --- .../note/content-area/ContentArea.tsx | 6 +-- .../note/content-area/task-block/index.tsx | 39 +++++++++---------- .../task-block/task-creation-popover.tsx | 2 +- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index a8f1f1559..73b155f03 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -342,9 +342,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const content = block.content as any[] if (!content?.length) return - const text = content - .map((c: any) => (typeof c === 'string' ? c : c.text ?? '')) - .join('') + const text = content.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))).join('') if (!text.trim() || !isLikelyTask(text)) return const blockEl = document.querySelector(`[data-id="${block.id}"]`) @@ -370,7 +368,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const content = block.content as any[] const text = - content?.map((c: any) => (typeof c === 'string' ? c : c.text ?? '')).join('') ?? '' + content?.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))).join('') ?? '' if (!text.trim()) return e.preventDefault() diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx index 0c6c2dc21..0c9315baf 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx @@ -1,27 +1,26 @@ import { createReactBlockSpec } from '@blocknote/react' import { TaskBlockRenderer } from './task-block-renderer' -export const createTaskBlock = () => - createReactBlockSpec( - { - type: 'taskBlock' as const, - propSchema: { - taskId: { default: '' }, - title: { default: '' }, - checked: { default: false } - }, - content: 'none' +export const createTaskBlock = createReactBlockSpec( + { + type: 'taskBlock' as const, + propSchema: { + taskId: { default: '' }, + title: { default: '' }, + checked: { default: false } }, - { - render: (props) => ( - - ) - } - ) + content: 'none' + }, + { + render: (props) => ( + + ) + } +) export function getTaskSlashMenuItem(editor: any) { return { diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-creation-popover.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-creation-popover.tsx index a24eca64a..e4907cdd6 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-creation-popover.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-creation-popover.tsx @@ -98,7 +98,7 @@ export const TaskCreationPopover: FC = ({ return ( !open && onCancel()}> - + } /> Date: Sun, 5 Apr 2026 21:48:34 +0300 Subject: [PATCH 07/24] refactor: auto-create tasks from brackets, inline editable controls - Bracket trigger now auto-creates task with defaults (no dialog) - Renderer shows always-visible priority, due date, project controls - Click any badge to change inline (dropdown/date picker) - Removed hover-only badge visibility --- .../src/main/ipc/generated-ipc-invoke-map.ts | 3122 +++++++++++++++-- .../note/content-area/ContentArea.tsx | 33 +- .../task-block/task-block-renderer.tsx | 212 +- 3 files changed, 3022 insertions(+), 345 deletions(-) diff --git a/apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts b/apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts index 77c11f3b2..0c5e30721 100644 --- a/apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts +++ b/apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts @@ -2,315 +2,2819 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ export interface MainIpcInvokeHandlers { - "account:getInfo": (...args: []) => Awaited - "account:getRecoveryKey": (...args: []) => Awaited> - "account:signOut": (...args: []) => Awaited> - "ai-inline:get-server-port": (...args: []) => Awaited - "ai-inline:get-settings": (...args: []) => Awaited - "ai-inline:set-settings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> - "ai-inline:start-server": (...args: []) => Awaited> - "ai-inline:stop-server": (...args: []) => Awaited> - "auth:init-oauth": (...args: [{ provider: "google"; }]) => Awaited> - "auth:refresh-token": (...args: []) => Awaited> - "auth:request-otp": (...args: [{ email: string; }]) => Awaited> - "auth:resend-otp": (...args: [{ email: string; }]) => Awaited> - "auth:verify-otp": (...args: [{ email: string; code: string; }]) => Awaited> - "bookmarks:bulk-create": (...args: [{ items: { itemType: string; itemId: string; }[]; }]) => Awaited> - "bookmarks:bulk-delete": (...args: [{ bookmarkIds: string[]; }]) => Awaited> - "bookmarks:create": (...args: [{ itemType: string; itemId: string; }]) => Awaited> - "bookmarks:delete": (...args: [string]) => Awaited> - "bookmarks:get": (...args: [string]) => Awaited> - "bookmarks:get-by-item": (...args: [{ itemType: string; itemId: string; }]) => Awaited> - "bookmarks:is-bookmarked": (...args: [{ itemType: string; itemId: string; }]) => Awaited> - "bookmarks:list": (...args: [{ itemType?: string | undefined; sortBy?: "createdAt" | "position" | undefined; sortOrder?: "asc" | "desc" | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> - "bookmarks:list-by-type": (...args: [string]) => Awaited> - "bookmarks:reorder": (...args: [{ bookmarkIds: string[]; }]) => Awaited> - "bookmarks:toggle": (...args: [{ itemType: string; itemId: string; }]) => Awaited> - "context-menu:show": (...args: [{ id: string; label: string; accelerator?: string | undefined; disabled?: boolean | undefined; type?: "normal" | "separator" | undefined; }[]]) => Awaited> - "crdt:apply-update": (...args: [unknown]) => Awaited> - "crdt:close-doc": (...args: [unknown]) => Awaited> - "crdt:open-doc": (...args: [unknown]) => Awaited> - "crdt:sync-step-1": (...args: [{ noteId: string; stateVector: number[]; }]) => Awaited> - "crdt:sync-step-2": (...args: [{ noteId: string; diff: number[]; }]) => Awaited> - "crypto:decrypt-item": (...args: [{ itemId: string; type: "note" | "filter" | "task" | "project" | "journal" | "settings" | "inbox" | "tag_definition"; encryptedKey: string; keyNonce: string; encryptedData: string; dataNonce: string; signature: string; operation?: "create" | "update" | "delete" | undefined; deletedAt?: number | undefined; metadata?: Record | undefined; }]) => Awaited> - "crypto:encrypt-item": (...args: [{ itemId: string; type: "note" | "filter" | "task" | "project" | "journal" | "settings" | "inbox" | "tag_definition"; content: Record; operation?: "create" | "update" | "delete" | undefined; deletedAt?: number | undefined; metadata?: Record | undefined; }]) => Awaited> - "crypto:get-rotation-progress": (...args: []) => Awaited - "crypto:rotate-keys": (...args: [{ confirm: boolean; }]) => Awaited> - "crypto:verify-signature": (...args: [{ itemId: string; type: "note" | "filter" | "task" | "project" | "journal" | "settings" | "inbox" | "tag_definition"; encryptedKey: string; keyNonce: string; encryptedData: string; dataNonce: string; signature: string; operation?: "create" | "update" | "delete" | undefined; deletedAt?: number | undefined; metadata?: Record | undefined; }]) => Awaited> - "folder-view:delete-view": (...args: [{ folderPath: string; viewName: string; }]) => Awaited> - "folder-view:folder-exists": (...args: [string]) => Awaited - "folder-view:get-available-properties": (...args: [{ folderPath: string; }]) => Awaited> - "folder-view:get-config": (...args: [{ folderPath: string; }]) => Awaited> - "folder-view:get-folder-suggestions": (...args: [{ noteId: string; }]) => Awaited> - "folder-view:get-views": (...args: [{ folderPath: string; }]) => Awaited> - "folder-view:list-with-properties": (...args: [{ folderPath: string; properties?: string[] | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> - "folder-view:set-config": (...args: [{ folderPath: string; config: { path?: string | undefined; template?: string | undefined; inherit?: boolean | undefined; formulas?: Record | undefined; properties?: Record | undefined; summaries?: Record | undefined; views?: { name: string; type?: "table" | "grid" | "list" | "kanban" | undefined; default?: boolean | undefined; columns?: { id: string; width?: number | undefined; displayName?: string | undefined; showSummary?: boolean | undefined; }[] | undefined; filters?: unknown; order?: { property: string; direction: "asc" | "desc"; }[] | undefined; groupBy?: { property: string; direction?: "asc" | "desc" | undefined; collapsed?: boolean | undefined; showSummary?: boolean | undefined; } | undefined; limit?: number | undefined; showSummaries?: boolean | undefined; }[] | undefined; }; }]) => Awaited> - "folder-view:set-view": (...args: [{ folderPath: string; view: { name: string; type?: "table" | "grid" | "list" | "kanban" | undefined; default?: boolean | undefined; columns?: { id: string; width?: number | undefined; displayName?: string | undefined; showSummary?: boolean | undefined; }[] | undefined; filters?: unknown; order?: { property: string; direction: "asc" | "desc"; }[] | undefined; groupBy?: { property: string; direction?: "asc" | "desc" | undefined; collapsed?: boolean | undefined; showSummary?: boolean | undefined; } | undefined; limit?: number | undefined; showSummaries?: boolean | undefined; }; }]) => Awaited> - "graph:get-graph-data": (...args: []) => Awaited<{ nodes: { id: string; type: "note" | "task" | "project" | "journal"; label: string; tags: string[]; wordCount: number; connectionCount: number; emoji: string | null; color: string; isOrphan: boolean; isUnresolved: boolean; }[]; edges: { id: string; source: string; target: string; type: "wikilink" | "task-note" | "project-task" | "tag-cooccurrence"; weight: number; }[]; }> - "graph:get-local-graph": (...args: [{ noteId: string; depth?: number | undefined; }]) => Awaited<{ nodes: { id: string; type: "note" | "task" | "project" | "journal"; label: string; tags: string[]; wordCount: number; connectionCount: number; emoji: string | null; color: string; isOrphan: boolean; isUnresolved: boolean; }[]; edges: { id: string; source: string; target: string; type: "wikilink" | "task-note" | "project-task" | "tag-cooccurrence"; weight: number; }[]; }> - "inbox:add-tag": (...args: [any, any]) => Awaited> - "inbox:archive": (...args: [any]) => Awaited> - "inbox:bulk-archive": (...args: [any]) => Awaited> - "inbox:bulk-file": (...args: [any]) => Awaited> - "inbox:bulk-snooze": (...args: [any]) => Awaited> - "inbox:bulk-tag": (...args: [any]) => Awaited> - "inbox:capture-clip": (...args: [any]) => Awaited> - "inbox:capture-image": (...args: [any]) => Awaited> - "inbox:capture-link": (...args: [any]) => Awaited> - "inbox:capture-pdf": (...args: [any]) => Awaited> - "inbox:capture-text": (...args: [any]) => Awaited> - "inbox:capture-voice": (...args: [any]) => Awaited> - "inbox:convert-to-note": (...args: [any]) => Awaited> - "inbox:convert-to-task": (...args: [any]) => Awaited> - "inbox:delete-permanent": (...args: [any]) => Awaited> - "inbox:file": (...args: [any]) => Awaited> - "inbox:file-all-stale": (...args: []) => Awaited> - "inbox:get": (...args: [any]) => Awaited> - "inbox:get-filing-history": (...args: [any]) => Awaited> - "inbox:get-patterns": (...args: []) => Awaited> - "inbox:get-snoozed": (...args: []) => Awaited> - "inbox:get-stale-threshold": (...args: []) => Awaited> - "inbox:get-stats": (...args: []) => Awaited> - "inbox:get-suggestions": (...args: [any]) => Awaited> - "inbox:get-tags": (...args: []) => Awaited> - "inbox:link-to-note": (...args: [any, any, any]) => Awaited> - "inbox:list": (...args: [any]) => Awaited> - "inbox:list-archived": (...args: [any]) => Awaited> - "inbox:mark-viewed": (...args: [any]) => Awaited> - "inbox:preview-link": (...args: [string]) => Awaited> - "inbox:remove-tag": (...args: [any, any]) => Awaited> - "inbox:retry-metadata": (...args: [any]) => Awaited> - "inbox:retry-transcription": (...args: [any]) => Awaited> - "inbox:set-stale-threshold": (...args: [any]) => Awaited> - "inbox:snooze": (...args: [any]) => Awaited> - "inbox:track-suggestion": (...args: [any, any, any, any, any, any, any]) => Awaited> - "inbox:unarchive": (...args: [any]) => Awaited> - "inbox:undo-archive": (...args: [any]) => Awaited> - "inbox:undo-file": (...args: [any]) => Awaited> - "inbox:unsnooze": (...args: [any]) => Awaited> - "inbox:update": (...args: [any]) => Awaited> - "journal:createEntry": (...args: [{ date: string; content?: string | undefined; tags?: string[] | undefined; properties?: Record | undefined; }]) => Awaited | undefined; }>> - "journal:deleteEntry": (...args: [{ date: string; }]) => Awaited> - "journal:getAllTags": (...args: []) => Awaited> - "journal:getDayContext": (...args: [{ date: string; }]) => Awaited> - "journal:getEntry": (...args: [{ date: string; }]) => Awaited | undefined; } | null>> - "journal:getHeatmap": (...args: [{ year: number; }]) => Awaited> - "journal:getMonthEntries": (...args: [{ year: number; month: number; }]) => Awaited> - "journal:getStreak": (...args: []) => Awaited> - "journal:getYearStats": (...args: [{ year: number; }]) => Awaited> - "journal:updateEntry": (...args: [{ date: string; content?: string | undefined; tags?: string[] | undefined; properties?: Record | undefined; }]) => Awaited | undefined; }>> - "notes:add-property-option": (...args: [{ propertyName: string; option: { value: string; color: string; }; }]) => Awaited> - "notes:add-status-option": (...args: [{ propertyName: string; categoryKey: "todo" | "in_progress" | "done"; option: { value: string; color: string; }; }]) => Awaited> - "notes:create": (...args: [{ title: string; content?: string | undefined; folder?: string | undefined; tags?: string[] | undefined; template?: string | undefined; }]) => Awaited> - "notes:create-folder": (...args: [string]) => Awaited> - "notes:create-property-definition": (...args: [{ name: string; type: "number" | "date" | "text" | "select" | "checkbox" | "url" | "status" | "multiselect"; options?: { value: string; color: string; default?: boolean | undefined; }[] | undefined; defaultValue?: unknown; color?: string | undefined; }]) => Awaited> - "notes:delete": (...args: [string]) => Awaited> - "notes:delete-attachment": (...args: [{ noteId: string; filename: string; }]) => Awaited> - "notes:delete-folder": (...args: [string]) => Awaited> - "notes:delete-property-definition": (...args: [{ name: string; }]) => Awaited> - "notes:delete-version": (...args: [string]) => Awaited> - "notes:ensure-property-definition": (...args: [{ name: string; type: "select" | "status" | "multiselect"; }]) => Awaited> - "notes:exists": (...args: [string]) => Awaited> - "notes:export-html": (...args: [{ noteId: string; includeMetadata?: boolean | undefined; pageSize?: "A4" | "Letter" | "Legal" | undefined; }]) => Awaited> - "notes:export-pdf": (...args: [{ noteId: string; includeMetadata?: boolean | undefined; pageSize?: "A4" | "Letter" | "Legal" | undefined; }]) => Awaited> - "notes:get": (...args: [string]) => Awaited> - "notes:get-all-positions": (...args: []) => Awaited; }>> - "notes:get-by-path": (...args: [string]) => Awaited> - "notes:get-file": (...args: [string]) => Awaited> - "notes:get-folder-config": (...args: [string]) => Awaited> - "notes:get-folder-template": (...args: [string]) => Awaited> - "notes:get-folders": (...args: []) => Awaited> - "notes:get-links": (...args: [string]) => Awaited> - "notes:get-local-only-count": (...args: []) => Awaited> - "notes:get-positions": (...args: [{ folderPath: string; }]) => Awaited> - "notes:get-property-definitions": (...args: []) => Awaited> - "notes:get-tags": (...args: []) => Awaited> - "notes:get-version": (...args: [string]) => Awaited> - "notes:get-versions": (...args: [string]) => Awaited> - "notes:import-files": (...args: [{ sourcePaths: string[]; targetFolder?: string | undefined; }]) => Awaited> - "notes:list": (...args: [{ folder?: string | undefined; tags?: string[] | undefined; sortBy?: "title" | "modified" | "created" | "position" | undefined; sortOrder?: "asc" | "desc" | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> - "notes:list-attachments": (...args: [string]) => Awaited> - "notes:move": (...args: [{ id: string; newFolder: string; }]) => Awaited> - "notes:open-external": (...args: [string]) => Awaited> - "notes:preview-by-title": (...args: [string]) => Awaited> - "notes:remove-property-option": (...args: [{ propertyName: string; optionValue: string; }]) => Awaited> - "notes:rename": (...args: [{ id: string; newTitle: string; }]) => Awaited> - "notes:rename-folder": (...args: [{ oldPath: string; newPath: string; }]) => Awaited> - "notes:rename-property-option": (...args: [{ propertyName: string; oldValue: string; newValue: string; }]) => Awaited> - "notes:reorder": (...args: [{ folderPath: string; notePaths: string[]; }]) => Awaited> - "notes:resolve-by-title": (...args: [string]) => Awaited> - "notes:restore-version": (...args: [string]) => Awaited> - "notes:reveal-in-finder": (...args: [string]) => Awaited> - "notes:set-folder-config": (...args: [{ folderPath: string; config: { icon?: string | null | undefined; template?: string | undefined; inherit?: boolean | undefined; }; }]) => Awaited> - "notes:set-local-only": (...args: [{ id: string; localOnly: boolean; }]) => Awaited> - "notes:show-import-dialog": (...args: []) => Awaited> - "notes:update": (...args: [{ id: string; title?: string | undefined; content?: string | undefined; tags?: string[] | undefined; frontmatter?: Record | undefined; emoji?: string | null | undefined; }]) => Awaited> - "notes:update-option-color": (...args: [{ propertyName: string; optionValue: string; newColor: string; }]) => Awaited> - "notes:update-property-definition": (...args: [{ name: string; type?: "number" | "date" | "text" | "select" | "checkbox" | "url" | "status" | "multiselect" | undefined; options?: { value: string; color: string; default?: boolean | undefined; }[] | undefined; defaultValue?: unknown; color?: string | undefined; }]) => Awaited> - "notes:upload-attachment": (...args: [{ noteId: string; filename: string; data: number[] | ArrayBuffer; }]) => Awaited> - "properties:get": (...args: [{ entityId: string; }]) => Awaited> - "properties:rename": (...args: [{ entityId: string; oldName: string; newName: string; }]) => Awaited> - "properties:set": (...args: [{ entityId: string; properties: Record; }]) => Awaited> - "quick-capture:get-clipboard": (...args: []) => Awaited - "reminder:bulk-dismiss": (...args: [{ reminderIds: string[]; }]) => Awaited> - "reminder:count-pending": (...args: []) => Awaited> - "reminder:create": (...args: [{ targetType: "note"; targetId: string; remindAt: string; title?: string | undefined; note?: string | undefined; } | { targetType: "journal"; targetId: string; remindAt: string; title?: string | undefined; note?: string | undefined; } | { targetType: "highlight"; targetId: string; highlightText: string; highlightStart: number; highlightEnd: number; remindAt: string; title?: string | undefined; note?: string | undefined; }]) => Awaited> - "reminder:delete": (...args: [string]) => Awaited> - "reminder:dismiss": (...args: [string]) => Awaited> - "reminder:get": (...args: [string]) => Awaited> - "reminder:get-due": (...args: []) => Awaited> - "reminder:get-for-target": (...args: [{ targetType: "note" | "journal" | "highlight"; targetId: string; }]) => Awaited> - "reminder:get-upcoming": (...args: [number | undefined]) => Awaited> - "reminder:list": (...args: [{ targetType?: "note" | "journal" | "highlight" | undefined; targetId?: string | undefined; status?: "pending" | "triggered" | "dismissed" | "snoozed" | ("pending" | "triggered" | "dismissed" | "snoozed")[] | undefined; fromDate?: string | undefined; toDate?: string | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> - "reminder:snooze": (...args: [{ id: string; snoozeUntil: string; }]) => Awaited> - "reminder:update": (...args: [{ id: string; remindAt?: string | undefined; title?: string | null | undefined; note?: string | null | undefined; }]) => Awaited> - "saved-filters:create": (...args: [{ name: string; config: { filters: { search?: string | undefined; projectIds?: string[] | undefined; priorities?: ("urgent" | "high" | "medium" | "low" | "none")[] | undefined; dueDate?: { type: "custom" | "any" | "none" | "overdue" | "today" | "tomorrow" | "this-week" | "next-week" | "this-month"; customStart?: string | null | undefined; customEnd?: string | null | undefined; } | undefined; statusIds?: string[] | undefined; completion?: "active" | "completed" | "all" | undefined; repeatType?: "all" | "repeating" | "one-time" | undefined; hasTime?: "all" | "with-time" | "without-time" | undefined; }; sort?: { field: "title" | "createdAt" | "priority" | "dueDate" | "completedAt" | "project"; direction: "asc" | "desc"; } | undefined; starred?: boolean | undefined; }; }]) => Awaited> - "saved-filters:delete": (...args: [{ id: string; }]) => Awaited> - "saved-filters:list": (...args: []) => Awaited> - "saved-filters:reorder": (...args: [{ ids: string[]; positions: number[]; }]) => Awaited> - "saved-filters:update": (...args: [{ id: string; name?: string | undefined; config?: { filters: { search?: string | undefined; projectIds?: string[] | undefined; priorities?: ("urgent" | "high" | "medium" | "low" | "none")[] | undefined; dueDate?: { type: "custom" | "any" | "none" | "overdue" | "today" | "tomorrow" | "this-week" | "next-week" | "this-month"; customStart?: string | null | undefined; customEnd?: string | null | undefined; } | undefined; statusIds?: string[] | undefined; completion?: "active" | "completed" | "all" | undefined; repeatType?: "all" | "repeating" | "one-time" | undefined; hasTime?: "all" | "with-time" | "without-time" | undefined; }; sort?: { field: "title" | "createdAt" | "priority" | "dueDate" | "completedAt" | "project"; direction: "asc" | "desc"; } | undefined; starred?: boolean | undefined; } | undefined; position?: number | undefined; }]) => Awaited> - "search:add-reason": (...args: [{ itemId: string; itemType: "note" | "task" | "journal" | "inbox"; itemTitle: string; searchQuery: string; itemIcon?: string | null | undefined; }]) => Awaited> - "search:clear-reasons": (...args: []) => Awaited> - "search:get-all-tags": (...args: []) => Awaited> - "search:get-reasons": (...args: []) => Awaited> - "search:get-stats": (...args: []) => Awaited> - "search:query": (...args: [{ text: string; types?: ("note" | "task" | "journal" | "inbox")[] | undefined; tags?: string[] | undefined; dateRange?: { from: string; to: string; } | null | undefined; projectId?: string | null | undefined; folderPath?: string | null | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> - "search:quick": (...args: [string]) => Awaited> - "search:rebuild-index": (...args: []) => Awaited> - "settings:downloadVoiceModel": (...args: []) => Awaited> - "settings:get": (...args: [string]) => Awaited - "settings:getAIModelStatus": (...args: []) => Awaited> - "settings:getAISettings": (...args: []) => Awaited - "settings:getBackupSettings": (...args: []) => Awaited<{ autoBackup: boolean; frequencyHours: 1 | 6 | 12 | 24; maxBackups: number; lastBackupAt: string | null; }> - "settings:getEditorSettings": (...args: []) => Awaited<{ width: "medium" | "narrow" | "wide"; spellCheck: boolean; autoSaveDelay: number; showWordCount: boolean; toolbarMode: "floating" | "sticky"; }> - "settings:getGeneralSettings": (...args: []) => Awaited<{ theme: "light" | "dark" | "white" | "system"; fontSize: "small" | "medium" | "large"; fontFamily: "system" | "serif" | "sans-serif" | "monospace" | "gelasio" | "geist" | "inter"; accentColor: string; startOnBoot: boolean; language: string; onboardingCompleted: boolean; createInSelectedFolder: boolean; }> - "settings:getGraphSettings": (...args: []) => Awaited<{ layout: "forceatlas2" | "circular" | "random"; showLabels: boolean; showEdgeLabels: boolean; animateLayout: boolean; showTagEdges: boolean; }> - "settings:getJournalSettings": (...args: []) => Awaited<{ defaultTemplate: string | null; showSchedule: boolean; showTasks: boolean; showAIConnections: boolean; showStatsFooter: boolean; }> - "settings:getKeyboardSettings": (...args: []) => Awaited<{ overrides: Record; globalCapture: { key: string; modifiers: { meta?: boolean | undefined; ctrl?: boolean | undefined; shift?: boolean | undefined; alt?: boolean | undefined; }; } | null; }> - "settings:getNoteEditorSettings": (...args: []) => Awaited - "settings:getSyncSettings": (...args: []) => Awaited<{ enabled: boolean; autoSync: boolean; }> - "settings:getTabSettings": (...args: []) => Awaited - "settings:getTaskSettings": (...args: []) => Awaited<{ defaultProjectId: string | null; defaultSortOrder: "createdAt" | "priority" | "dueDate" | "manual"; weekStartDay: "sunday" | "monday"; staleInboxDays: number; }> - "settings:getVoiceModelStatus": (...args: []) => Awaited - "settings:getVoiceRecordingReadiness": (...args: []) => Awaited> - "settings:getVoiceTranscriptionOpenAIKeyStatus": (...args: []) => Awaited> - "settings:getVoiceTranscriptionSettings": (...args: []) => Awaited<{ provider: "local" | "openai"; }> - "settings:loadAIModel": (...args: []) => Awaited> - "settings:registerGlobalCapture": (...args: []) => Awaited> - "settings:reindexEmbeddings": (...args: []) => Awaited> - "settings:resetKeyboardSettings": (...args: []) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> - "settings:set": (...args: [{ key: string; value: string; }]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> - "settings:setAISettings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> - "settings:setBackupSettings": (...args: [Partial<{ autoBackup: boolean; frequencyHours: 1 | 6 | 12 | 24; maxBackups: number; lastBackupAt: string | null; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> - "settings:setEditorSettings": (...args: [Partial<{ width: "medium" | "narrow" | "wide"; spellCheck: boolean; autoSaveDelay: number; showWordCount: boolean; toolbarMode: "floating" | "sticky"; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> - "settings:setGeneralSettings": (...args: [Partial<{ theme: "light" | "dark" | "white" | "system"; fontSize: "small" | "medium" | "large"; fontFamily: "system" | "serif" | "sans-serif" | "monospace" | "gelasio" | "geist" | "inter"; accentColor: string; startOnBoot: boolean; language: string; onboardingCompleted: boolean; createInSelectedFolder: boolean; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> - "settings:setGraphSettings": (...args: [Partial<{ layout: "forceatlas2" | "circular" | "random"; showLabels: boolean; showEdgeLabels: boolean; animateLayout: boolean; showTagEdges: boolean; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> - "settings:setJournalSettings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> - "settings:setKeyboardSettings": (...args: [Partial<{ overrides: Record; globalCapture: { key: string; modifiers: { meta?: boolean | undefined; ctrl?: boolean | undefined; shift?: boolean | undefined; alt?: boolean | undefined; }; } | null; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> - "settings:setNoteEditorSettings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> - "settings:setSyncSettings": (...args: [Partial<{ enabled: boolean; autoSync: boolean; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> - "settings:setTabSettings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> - "settings:setTaskSettings": (...args: [Partial<{ defaultProjectId: string | null; defaultSortOrder: "createdAt" | "priority" | "dueDate" | "manual"; weekStartDay: "sunday" | "monday"; staleInboxDays: number; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> - "settings:setVoiceTranscriptionOpenAIKey": (...args: [{ apiKey: string; }]) => Awaited> - "settings:setVoiceTranscriptionSettings": (...args: [Partial<{ provider: "local" | "openai"; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> - "sync:approve-linking": (...args: [{ sessionId: string; }]) => Awaited> - "sync:check-device-status": (...args: []) => Awaited> - "sync:complete-linking-qr": (...args: [{ sessionId: string; }]) => Awaited> - "sync:confirm-recovery-phrase": (...args: [{ confirmed: boolean; }]) => Awaited> - "sync:download-attachment": (...args: [{ attachmentId: string; targetPath?: string | undefined; }]) => Awaited> - "sync:emergency-wipe": (...args: []) => Awaited> - "sync:generate-linking-qr": (...args: []) => Awaited> - "sync:get-devices": (...args: []) => Awaited> - "sync:get-download-progress": (...args: [{ attachmentId: string; }]) => Awaited> - "sync:get-history": (...args: [{ limit?: number | undefined; offset?: number | undefined; }]) => Awaited> - "sync:get-linking-sas": (...args: [{ sessionId: string; }]) => Awaited> - "sync:get-quarantined-items": (...args: []) => Awaited - "sync:get-queue-size": (...args: []) => Awaited<{ pending: number; failed: number; }> - "sync:get-recovery-phrase": (...args: []) => Awaited - "sync:get-status": (...args: []) => Awaited - "sync:get-storage-breakdown": (...args: []) => Awaited> - "sync:get-synced-settings": (...args: []) => Awaited<{ general?: { theme?: "light" | "dark" | "white" | "system" | undefined; fontSize?: "small" | "medium" | "large" | undefined; fontFamily?: "system" | "serif" | "sans-serif" | "monospace" | "gelasio" | "geist" | "inter" | undefined; accentColor?: string | undefined; startOnBoot?: boolean | undefined; language?: string | undefined; createInSelectedFolder?: boolean | undefined; } | undefined; editor?: { width?: "medium" | "narrow" | "wide" | undefined; spellCheck?: boolean | undefined; autoSaveDelay?: number | undefined; showWordCount?: boolean | undefined; toolbarMode?: "floating" | "sticky" | undefined; } | undefined; tasks?: { defaultProjectId?: string | null | undefined; defaultSortOrder?: "createdAt" | "priority" | "dueDate" | "manual" | undefined; weekStartDay?: "sunday" | "monday" | undefined; staleInboxDays?: number | undefined; showCompleted?: boolean | undefined; sortBy?: string | undefined; } | undefined; keyboard?: { overrides?: Record | undefined; } | undefined; notes?: { defaultFolder?: string | undefined; editorFontSize?: number | undefined; spellCheck?: boolean | undefined; } | undefined; sync?: { autoSync?: boolean | undefined; syncIntervalMinutes?: number | undefined; } | undefined; } | null> - "sync:get-upload-progress": (...args: [{ sessionId: string; }]) => Awaited> - "sync:link-via-qr": (...args: [{ qrData: string; oauthToken?: string | undefined; provider?: string | undefined; }]) => Awaited> - "sync:link-via-recovery": (...args: [{ recoveryPhrase: string; }]) => Awaited> - "sync:logout": (...args: []) => Awaited> - "sync:pause": (...args: []) => Awaited<{ success: boolean; wasPaused: boolean; }> - "sync:remove-device": (...args: [{ deviceId: string; }]) => Awaited> - "sync:rename-device": (...args: [{ deviceId: string; newName: string; }]) => Awaited> - "sync:resume": (...args: []) => Awaited<{ success: boolean; pendingCount: number; }> - "sync:setup-first-device": (...args: [{ oauthToken: string; provider: "google"; state: string; }]) => Awaited> - "sync:setup-new-account": (...args: []) => Awaited> - "sync:trigger-sync": (...args: []) => Awaited> - "sync:update-synced-setting": (...args: [unknown]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> - "sync:upload-attachment": (...args: [{ noteId: string; filePath: string; }]) => Awaited> - "tags:delete": (...args: [string]) => Awaited> - "tags:get-all-with-counts": (...args: []) => Awaited> - "tags:get-notes-by-tag": (...args: [{ tag: string; sortBy?: "title" | "modified" | "created" | undefined; sortOrder?: "asc" | "desc" | undefined; includeDescendants?: boolean | undefined; }]) => Awaited> - "tags:merge": (...args: [{ source: string; target: string; }]) => Awaited> - "tags:pin-note-to-tag": (...args: [{ noteId: string; tag: string; }]) => Awaited> - "tags:remove-from-note": (...args: [{ noteId: string; tag: string; }]) => Awaited> - "tags:rename": (...args: [{ oldName: string; newName: string; }]) => Awaited> - "tags:unpin-note-from-tag": (...args: [{ noteId: string; tag: string; }]) => Awaited> - "tags:update-color": (...args: [{ tag: string; color: string; }]) => Awaited> - "tasks:archive": (...args: [string]) => Awaited> - "tasks:bulk-archive": (...args: [{ ids: string[]; }]) => Awaited> - "tasks:bulk-complete": (...args: [{ ids: string[]; }]) => Awaited> - "tasks:bulk-delete": (...args: [{ ids: string[]; }]) => Awaited> - "tasks:bulk-move": (...args: [{ ids: string[]; projectId: string; }]) => Awaited> - "tasks:complete": (...args: [{ id: string; completedAt?: string | undefined; }]) => Awaited> - "tasks:convert-to-subtask": (...args: [{ taskId: string; parentId: string; }]) => Awaited> - "tasks:convert-to-task": (...args: [string]) => Awaited> - "tasks:create": (...args: [{ projectId: string; title: string; description?: string | null | undefined; priority?: number | undefined; statusId?: string | null | undefined; parentId?: string | null | undefined; dueDate?: string | null | undefined; dueTime?: string | null | undefined; startDate?: string | null | undefined; isRepeating?: boolean | undefined; repeatConfig?: { frequency: "daily" | "weekly" | "monthly" | "yearly"; endType: "date" | "never" | "count"; createdAt: string; interval?: number | undefined; daysOfWeek?: number[] | undefined; monthlyType?: "dayOfMonth" | "weekPattern" | undefined; dayOfMonth?: number | undefined; weekOfMonth?: number | undefined; dayOfWeekForMonth?: number | undefined; endDate?: string | null | undefined; endCount?: number | undefined; completedCount?: number | undefined; } | null | undefined; repeatFrom?: "due" | "completion" | null | undefined; tags?: string[] | undefined; linkedNoteIds?: string[] | undefined; sourceNoteId?: string | null | undefined; position?: number | undefined; }]) => Awaited> - "tasks:delete": (...args: [string]) => Awaited> - "tasks:duplicate": (...args: [string]) => Awaited> - "tasks:get": (...args: [string]) => Awaited> - "tasks:get-linked-tasks": (...args: [string]) => Awaited> - "tasks:get-overdue": (...args: []) => Awaited> - "tasks:get-stats": (...args: []) => Awaited> - "tasks:get-subtasks": (...args: [string]) => Awaited> - "tasks:get-tags": (...args: []) => Awaited> - "tasks:get-today": (...args: []) => Awaited> - "tasks:get-upcoming": (...args: [{ days?: number | undefined; }]) => Awaited> - "tasks:list": (...args: [{ projectId?: string | undefined; statusId?: string | null | undefined; parentId?: string | null | undefined; includeCompleted?: boolean | undefined; includeArchived?: boolean | undefined; dueBefore?: string | undefined; dueAfter?: string | undefined; tags?: string[] | undefined; search?: string | undefined; sortBy?: "modified" | "created" | "position" | "priority" | "dueDate" | undefined; sortOrder?: "asc" | "desc" | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> - "tasks:move": (...args: [{ taskId: string; position: number; targetProjectId?: string | undefined; targetStatusId?: string | null | undefined; targetParentId?: string | null | undefined; }]) => Awaited> - "tasks:project-archive": (...args: [string]) => Awaited> - "tasks:project-create": (...args: [{ name: string; description?: string | null | undefined; color?: string | undefined; icon?: string | null | undefined; statuses?: { name: string; type: "todo" | "in_progress" | "done"; order: number; color?: string | undefined; }[] | undefined; }]) => Awaited> - "tasks:project-delete": (...args: [string]) => Awaited> - "tasks:project-get": (...args: [string]) => Awaited> - "tasks:project-list": (...args: []) => Awaited> - "tasks:project-reorder": (...args: [{ projectIds: string[]; positions: number[]; }]) => Awaited> - "tasks:project-update": (...args: [{ id: string; name?: string | undefined; description?: string | null | undefined; color?: string | undefined; icon?: string | null | undefined; statuses?: { name: string; type: "todo" | "in_progress" | "done"; order: number; id?: string | undefined; color?: string | undefined; }[] | undefined; }]) => Awaited> - "tasks:reorder": (...args: [{ taskIds: string[]; positions: number[]; }]) => Awaited> - "tasks:seed-demo": (...args: []) => Awaited> - "tasks:seed-performance-test": (...args: []) => Awaited> - "tasks:status-create": (...args: [{ projectId: string; name: string; color?: string | undefined; isDone?: boolean | undefined; }]) => Awaited> - "tasks:status-delete": (...args: [string]) => Awaited> - "tasks:status-list": (...args: [string]) => Awaited> - "tasks:status-reorder": (...args: [{ statusIds: string[]; positions: number[]; }]) => Awaited> - "tasks:status-update": (...args: [{ id: string; name?: string | undefined; color?: string | undefined; position?: number | undefined; isDefault?: boolean | undefined; isDone?: boolean | undefined; }]) => Awaited> - "tasks:unarchive": (...args: [string]) => Awaited> - "tasks:uncomplete": (...args: [string]) => Awaited> - "tasks:update": (...args: [{ id: string; title?: string | undefined; description?: string | null | undefined; priority?: number | undefined; projectId?: string | undefined; statusId?: string | null | undefined; parentId?: string | null | undefined; dueDate?: string | null | undefined; dueTime?: string | null | undefined; startDate?: string | null | undefined; isRepeating?: boolean | undefined; repeatConfig?: { frequency: "daily" | "weekly" | "monthly" | "yearly"; endType: "date" | "never" | "count"; createdAt: string; interval?: number | undefined; daysOfWeek?: number[] | undefined; monthlyType?: "dayOfMonth" | "weekPattern" | undefined; dayOfMonth?: number | undefined; weekOfMonth?: number | undefined; dayOfWeekForMonth?: number | undefined; endDate?: string | null | undefined; endCount?: number | undefined; completedCount?: number | undefined; } | null | undefined; repeatFrom?: "due" | "completion" | null | undefined; tags?: string[] | undefined; linkedNoteIds?: string[] | undefined; }]) => Awaited> - "templates:create": (...args: [{ name: string; description?: string | undefined; icon?: string | null | undefined; tags?: string[] | undefined; properties?: { name: string; type: "number" | "date" | "text" | "select" | "checkbox" | "url" | "multiselect" | "rating"; value: unknown; options?: string[] | undefined; }[] | undefined; content?: string | undefined; }]) => Awaited> - "templates:delete": (...args: [string]) => Awaited> - "templates:duplicate": (...args: [{ id: string; newName: string; }]) => Awaited> - "templates:get": (...args: [string]) => Awaited> - "templates:list": (...args: []) => Awaited> - "templates:update": (...args: [{ id: string; name?: string | undefined; description?: string | undefined; icon?: string | null | undefined; tags?: string[] | undefined; properties?: { name: string; type: "number" | "date" | "text" | "select" | "checkbox" | "url" | "multiselect" | "rating"; value: unknown; options?: string[] | undefined; }[] | undefined; content?: string | undefined; }]) => Awaited> - "vault:close": (...args: []) => Awaited> - "vault:get-all": (...args: []) => Awaited> - "vault:get-config": (...args: []) => Awaited> - "vault:get-status": (...args: []) => Awaited> - "vault:reindex": (...args: []) => Awaited> - "vault:remove": (...args: [string]) => Awaited> - "vault:reveal": (...args: []) => Awaited> - "vault:select": (...args: [{ path?: string | undefined; }]) => Awaited> - "vault:switch": (...args: [string]) => Awaited> - "vault:update-config": (...args: [{ excludePatterns?: string[] | undefined; defaultNoteFolder?: string | undefined; journalFolder?: string | undefined; attachmentsFolder?: string | undefined; }]) => Awaited> + 'account:getInfo': (...args: []) => Awaited + 'account:getRecoveryKey': ( + ...args: [] + ) => Awaited< + Promise< + | { success: boolean; error: string; key?: undefined } + | { success: boolean; key: string; error?: undefined } + > + > + 'account:signOut': ( + ...args: [] + ) => Awaited> + 'ai-inline:get-server-port': (...args: []) => Awaited + 'ai-inline:get-settings': ( + ...args: [] + ) => Awaited + 'ai-inline:set-settings': ( + ...args: [ + Partial + ] + ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + 'ai-inline:start-server': ( + ...args: [] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; error: string; port?: undefined } + | { success: boolean; port: number; error?: undefined } + > + > + 'ai-inline:stop-server': (...args: []) => Awaited> + 'auth:init-oauth': (...args: [{ provider: 'google' }]) => Awaited> + 'auth:refresh-token': ( + ...args: [] + ) => Awaited> + 'auth:request-otp': (...args: [{ email: string }]) => Awaited> + 'auth:resend-otp': (...args: [{ email: string }]) => Awaited> + 'auth:verify-otp': (...args: [{ email: string; code: string }]) => Awaited< + Promise<{ + success: boolean + isNewUser: boolean + needsSetup: boolean + needsRecoveryInput: boolean + }> + > + 'bookmarks:bulk-create': ( + ...args: [{ items: { itemType: string; itemId: string }[] }] + ) => Awaited> + 'bookmarks:bulk-delete': ( + ...args: [{ bookmarkIds: string[] }] + ) => Awaited> + 'bookmarks:create': (...args: [{ itemType: string; itemId: string }]) => Awaited< + Promise< + | { success: boolean; bookmark: null; error: string } + | { + success: boolean + bookmark: { + id: string + createdAt: string + position: number + itemType: string + itemId: string + } + error?: undefined + } + > + > + 'bookmarks:delete': ( + ...args: [string] + ) => Awaited< + Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + > + 'bookmarks:get': (...args: [string]) => Awaited< + Promise<{ + id: string + createdAt: string + position: number + itemType: string + itemId: string + } | null> + > + 'bookmarks:get-by-item': (...args: [{ itemType: string; itemId: string }]) => Awaited< + Promise<{ + id: string + createdAt: string + position: number + itemType: string + itemId: string + } | null> + > + 'bookmarks:is-bookmarked': ( + ...args: [{ itemType: string; itemId: string }] + ) => Awaited> + 'bookmarks:list': ( + ...args: [ + { + itemType?: string | undefined + sortBy?: 'createdAt' | 'position' | undefined + sortOrder?: 'asc' | 'desc' | undefined + limit?: number | undefined + offset?: number | undefined + } + ] + ) => Awaited< + Promise + > + 'bookmarks:list-by-type': ( + ...args: [string] + ) => Awaited< + Promise + > + 'bookmarks:reorder': ( + ...args: [{ bookmarkIds: string[] }] + ) => Awaited> + 'bookmarks:toggle': (...args: [{ itemType: string; itemId: string }]) => Awaited< + Promise<{ + success: boolean + isBookmarked: boolean + bookmark: { + id: string + createdAt: string + position: number + itemType: string + itemId: string + } | null + }> + > + 'context-menu:show': ( + ...args: [ + { + id: string + label: string + accelerator?: string | undefined + disabled?: boolean | undefined + type?: 'normal' | 'separator' | undefined + }[] + ] + ) => Awaited> + 'crdt:apply-update': (...args: [unknown]) => Awaited> + 'crdt:close-doc': (...args: [unknown]) => Awaited> + 'crdt:open-doc': ( + ...args: [unknown] + ) => Awaited< + Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + > + 'crdt:sync-step-1': ( + ...args: [{ noteId: string; stateVector: number[] }] + ) => Awaited< + Promise + > + 'crdt:sync-step-2': (...args: [{ noteId: string; diff: number[] }]) => Awaited> + 'crypto:decrypt-item': ( + ...args: [ + { + itemId: string + type: + | 'note' + | 'filter' + | 'task' + | 'project' + | 'journal' + | 'settings' + | 'inbox' + | 'tag_definition' + encryptedKey: string + keyNonce: string + encryptedData: string + dataNonce: string + signature: string + operation?: 'create' | 'update' | 'delete' | undefined + deletedAt?: number | undefined + metadata?: Record | undefined + } + ] + ) => Awaited< + Promise + > + 'crypto:encrypt-item': ( + ...args: [ + { + itemId: string + type: + | 'note' + | 'filter' + | 'task' + | 'project' + | 'journal' + | 'settings' + | 'inbox' + | 'tag_definition' + content: Record + operation?: 'create' | 'update' | 'delete' | undefined + deletedAt?: number | undefined + metadata?: Record | undefined + } + ] + ) => Awaited< + Promise + > + 'crypto:get-rotation-progress': ( + ...args: [] + ) => Awaited + 'crypto:rotate-keys': ( + ...args: [{ confirm: boolean }] + ) => Awaited> + 'crypto:verify-signature': ( + ...args: [ + { + itemId: string + type: + | 'note' + | 'filter' + | 'task' + | 'project' + | 'journal' + | 'settings' + | 'inbox' + | 'tag_definition' + encryptedKey: string + keyNonce: string + encryptedData: string + dataNonce: string + signature: string + operation?: 'create' | 'update' | 'delete' | undefined + deletedAt?: number | undefined + metadata?: Record | undefined + } + ] + ) => Awaited< + Promise + > + 'folder-view:delete-view': ( + ...args: [{ folderPath: string; viewName: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/folder-view-api').DeleteViewResponse + > + > + 'folder-view:folder-exists': (...args: [string]) => Awaited + 'folder-view:get-available-properties': ( + ...args: [{ folderPath: string }] + ) => Awaited< + Promise< + import('../../../../../packages/contracts/src/folder-view-api').GetAvailablePropertiesResponse + > + > + 'folder-view:get-config': ( + ...args: [{ folderPath: string }] + ) => Awaited< + Promise + > + 'folder-view:get-folder-suggestions': ( + ...args: [{ noteId: string }] + ) => Awaited< + Promise< + import('../../../../../packages/contracts/src/folder-view-api').GetFolderSuggestionsResponse + > + > + 'folder-view:get-views': ( + ...args: [{ folderPath: string }] + ) => Awaited< + Promise + > + 'folder-view:list-with-properties': ( + ...args: [ + { + folderPath: string + properties?: string[] | undefined + limit?: number | undefined + offset?: number | undefined + } + ] + ) => Awaited< + Promise< + import('../../../../../packages/contracts/src/folder-view-api').ListWithPropertiesResponse + > + > + 'folder-view:set-config': ( + ...args: [ + { + folderPath: string + config: { + path?: string | undefined + template?: string | undefined + inherit?: boolean | undefined + formulas?: Record | undefined + properties?: + | Record< + string, + { + displayName?: string | undefined + color?: boolean | undefined + dateFormat?: string | undefined + numberFormat?: string | undefined + hidden?: boolean | undefined + } + > + | undefined + summaries?: + | Record< + string, + { + type: + | 'custom' + | 'count' + | 'sum' + | 'average' + | 'min' + | 'max' + | 'countBy' + | 'countUnique' + label?: string | undefined + expression?: string | undefined + } + > + | undefined + views?: + | { + name: string + type?: 'table' | 'grid' | 'list' | 'kanban' | undefined + default?: boolean | undefined + columns?: + | { + id: string + width?: number | undefined + displayName?: string | undefined + showSummary?: boolean | undefined + }[] + | undefined + filters?: unknown + order?: { property: string; direction: 'asc' | 'desc' }[] | undefined + groupBy?: + | { + property: string + direction?: 'asc' | 'desc' | undefined + collapsed?: boolean | undefined + showSummary?: boolean | undefined + } + | undefined + limit?: number | undefined + showSummaries?: boolean | undefined + }[] + | undefined + } + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/folder-view-api').SetConfigResponse + > + > + 'folder-view:set-view': ( + ...args: [ + { + folderPath: string + view: { + name: string + type?: 'table' | 'grid' | 'list' | 'kanban' | undefined + default?: boolean | undefined + columns?: + | { + id: string + width?: number | undefined + displayName?: string | undefined + showSummary?: boolean | undefined + }[] + | undefined + filters?: unknown + order?: { property: string; direction: 'asc' | 'desc' }[] | undefined + groupBy?: + | { + property: string + direction?: 'asc' | 'desc' | undefined + collapsed?: boolean | undefined + showSummary?: boolean | undefined + } + | undefined + limit?: number | undefined + showSummaries?: boolean | undefined + } + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/folder-view-api').SetViewResponse + > + > + 'graph:get-graph-data': (...args: []) => Awaited<{ + nodes: { + id: string + type: 'note' | 'task' | 'project' | 'journal' + label: string + tags: string[] + wordCount: number + connectionCount: number + emoji: string | null + color: string + isOrphan: boolean + isUnresolved: boolean + }[] + edges: { + id: string + source: string + target: string + type: 'wikilink' | 'task-note' | 'project-task' | 'tag-cooccurrence' + weight: number + }[] + }> + 'graph:get-local-graph': (...args: [{ noteId: string; depth?: number | undefined }]) => Awaited<{ + nodes: { + id: string + type: 'note' | 'task' | 'project' | 'journal' + label: string + tags: string[] + wordCount: number + connectionCount: number + emoji: string | null + color: string + isOrphan: boolean + isUnresolved: boolean + }[] + edges: { + id: string + source: string + target: string + type: 'wikilink' | 'task-note' | 'project-task' | 'tag-cooccurrence' + weight: number + }[] + }> + 'inbox:add-tag': ( + ...args: [any, any] + ) => Awaited> + 'inbox:archive': ( + ...args: [any] + ) => Awaited> + 'inbox:bulk-archive': ( + ...args: [any] + ) => Awaited> + 'inbox:bulk-file': ( + ...args: [any] + ) => Awaited> + 'inbox:bulk-snooze': (...args: [any]) => Awaited< + Promise<{ + success: boolean + processedCount: number + errors: { itemId: string; error: string }[] + }> + > + 'inbox:bulk-tag': ( + ...args: [any] + ) => Awaited> + 'inbox:capture-clip': ( + ...args: [any] + ) => Awaited> + 'inbox:capture-image': ( + ...args: [any] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/inbox-api').CaptureResponse + > + > + 'inbox:capture-link': ( + ...args: [any] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/inbox-api').CaptureResponse + > + > + 'inbox:capture-pdf': ( + ...args: [any] + ) => Awaited> + 'inbox:capture-text': ( + ...args: [any] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/inbox-api').CaptureResponse + > + > + 'inbox:capture-voice': ( + ...args: [any] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/inbox-api').CaptureResponse + > + > + 'inbox:convert-to-note': ( + ...args: [any] + ) => Awaited> + 'inbox:convert-to-task': ( + ...args: [any] + ) => Awaited> + 'inbox:delete-permanent': ( + ...args: [any] + ) => Awaited> + 'inbox:file': ( + ...args: [any] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/inbox-api').FileResponse + > + > + 'inbox:file-all-stale': ( + ...args: [] + ) => Awaited> + 'inbox:get': ( + ...args: [any] + ) => Awaited> + 'inbox:get-filing-history': ( + ...args: [any] + ) => Awaited< + Promise + > + 'inbox:get-patterns': ( + ...args: [] + ) => Awaited> + 'inbox:get-snoozed': (...args: []) => Awaited> + 'inbox:get-stale-threshold': (...args: []) => Awaited> + 'inbox:get-stats': ( + ...args: [] + ) => Awaited> + 'inbox:get-suggestions': ( + ...args: [any] + ) => Awaited< + Promise + > + 'inbox:get-tags': (...args: []) => Awaited> + 'inbox:link-to-note': ( + ...args: [any, any, any] + ) => Awaited> + 'inbox:list': ( + ...args: [any] + ) => Awaited> + 'inbox:list-archived': ( + ...args: [any] + ) => Awaited< + Promise + > + 'inbox:mark-viewed': ( + ...args: [any] + ) => Awaited> + 'inbox:preview-link': (...args: [string]) => Awaited< + Promise< + | { + title: string + domain: string + favicon: string | undefined + image: string | undefined + description: string | undefined + } + | { + title: string + domain: string + favicon?: undefined + image?: undefined + description?: undefined + } + > + > + 'inbox:remove-tag': ( + ...args: [any, any] + ) => Awaited> + 'inbox:retry-metadata': ( + ...args: [any] + ) => Awaited< + Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> + > + 'inbox:retry-transcription': ( + ...args: [any] + ) => Awaited< + Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> + > + 'inbox:set-stale-threshold': (...args: [any]) => Awaited> + 'inbox:snooze': ( + ...args: [any] + ) => Awaited< + Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> + > + 'inbox:track-suggestion': ( + ...args: [any, any, any, any, any, any, any] + ) => Awaited< + Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> + > + 'inbox:unarchive': ( + ...args: [any] + ) => Awaited> + 'inbox:undo-archive': ( + ...args: [any] + ) => Awaited> + 'inbox:undo-file': ( + ...args: [any] + ) => Awaited> + 'inbox:unsnooze': ( + ...args: [any] + ) => Awaited< + Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> + > + 'inbox:update': ( + ...args: [any] + ) => Awaited> + 'journal:createEntry': ( + ...args: [ + { + date: string + content?: string | undefined + tags?: string[] | undefined + properties?: Record | undefined + } + ] + ) => Awaited< + Promise<{ + id: string + date: string + content: string + wordCount: number + characterCount: number + tags: string[] + createdAt: string + modifiedAt: string + properties?: Record | undefined + }> + > + 'journal:deleteEntry': (...args: [{ date: string }]) => Awaited> + 'journal:getAllTags': (...args: []) => Awaited> + 'journal:getDayContext': (...args: [{ date: string }]) => Awaited< + Promise<{ + date: string + tasks: { + id: string + title: string + completed: boolean + priority?: 'urgent' | 'high' | 'medium' | 'low' | undefined + isOverdue?: boolean | undefined + }[] + events: { + id: string + time: string + title: string + type: 'meeting' | 'focus' | 'event' + attendeeCount?: number | undefined + }[] + overdueCount: number + }> + > + 'journal:getEntry': (...args: [{ date: string }]) => Awaited< + Promise<{ + id: string + date: string + content: string + wordCount: number + characterCount: number + tags: string[] + createdAt: string + modifiedAt: string + properties?: Record | undefined + } | null> + > + 'journal:getHeatmap': ( + ...args: [{ year: number }] + ) => Awaited> + 'journal:getMonthEntries': (...args: [{ year: number; month: number }]) => Awaited< + Promise< + { + date: string + preview: string + wordCount: number + characterCount: number + activityLevel: 0 | 1 | 2 | 4 | 3 + tags: string[] + }[] + > + > + 'journal:getStreak': ( + ...args: [] + ) => Awaited< + Promise<{ currentStreak: number; longestStreak: number; lastEntryDate: string | null }> + > + 'journal:getYearStats': (...args: [{ year: number }]) => Awaited< + Promise< + { + year: number + month: number + entryCount: number + totalWordCount: number + totalCharacterCount: number + averageLevel: number + }[] + > + > + 'journal:updateEntry': ( + ...args: [ + { + date: string + content?: string | undefined + tags?: string[] | undefined + properties?: Record | undefined + } + ] + ) => Awaited< + Promise<{ + id: string + date: string + content: string + wordCount: number + characterCount: number + tags: string[] + createdAt: string + modifiedAt: string + properties?: Record | undefined + }> + > + 'notes:add-property-option': ( + ...args: [{ propertyName: string; option: { value: string; color: string } }] + ) => Awaited> + 'notes:add-status-option': ( + ...args: [ + { + propertyName: string + categoryKey: 'todo' | 'in_progress' | 'done' + option: { value: string; color: string } + } + ] + ) => Awaited> + 'notes:create': ( + ...args: [ + { + title: string + content?: string | undefined + folder?: string | undefined + tags?: string[] | undefined + template?: string | undefined + } + ] + ) => Awaited< + Promise< + { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } + > + > + 'notes:create-folder': ( + ...args: [string] + ) => Awaited> + 'notes:create-property-definition': ( + ...args: [ + { + name: string + type: 'number' | 'date' | 'text' | 'select' | 'checkbox' | 'url' | 'status' | 'multiselect' + options?: { value: string; color: string; default?: boolean | undefined }[] | undefined + defaultValue?: unknown + color?: string | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { + success: boolean + definition: + | import('../../../../../packages/contracts/src/property-types').PropertyDefinition + | undefined + } + | { + success: boolean + definition: { + type: string + name: string + createdAt: string + options: string | null + defaultValue: string | null + color: string | null + } + } + > + > + 'notes:delete': ( + ...args: [string] + ) => Awaited> + 'notes:delete-attachment': ( + ...args: [{ noteId: string; filename: string }] + ) => Awaited> + 'notes:delete-folder': ( + ...args: [string] + ) => Awaited> + 'notes:delete-property-definition': ( + ...args: [{ name: string }] + ) => Awaited> + 'notes:delete-version': ( + ...args: [string] + ) => Awaited> + 'notes:ensure-property-definition': ( + ...args: [{ name: string; type: 'select' | 'status' | 'multiselect' }] + ) => Awaited> + 'notes:exists': (...args: [string]) => Awaited> + 'notes:export-html': ( + ...args: [ + { + noteId: string + includeMetadata?: boolean | undefined + pageSize?: 'A4' | 'Letter' | 'Legal' | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; error: string; path?: undefined } + | { success: boolean; path: string; error?: undefined } + > + > + 'notes:export-pdf': ( + ...args: [ + { + noteId: string + includeMetadata?: boolean | undefined + pageSize?: 'A4' | 'Letter' | 'Legal' | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; error: string; path?: undefined } + | { success: boolean; path: string; error?: undefined } + > + > + 'notes:get': (...args: [string]) => Awaited> + 'notes:get-all-positions': ( + ...args: [] + ) => Awaited< + Promise< + { success: false; error: string } | { success: boolean; positions: Record } + > + > + 'notes:get-by-path': (...args: [string]) => Awaited> + 'notes:get-file': ( + ...args: [string] + ) => Awaited> + 'notes:get-folder-config': ( + ...args: [string] + ) => Awaited< + Promise + > + 'notes:get-folder-template': (...args: [string]) => Awaited> + 'notes:get-folders': ( + ...args: [] + ) => Awaited> + 'notes:get-links': ( + ...args: [string] + ) => Awaited> + 'notes:get-local-only-count': (...args: []) => Awaited> + 'notes:get-positions': ( + ...args: [{ folderPath: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; positions: { path: string; position: number; folderPath: string }[] } + > + > + 'notes:get-property-definitions': (...args: []) => Awaited< + Promise< + { + type: string + name: string + createdAt: string + options: string | null + defaultValue: string | null + color: string | null + }[] + > + > + 'notes:get-tags': ( + ...args: [] + ) => Awaited> + 'notes:get-version': ( + ...args: [string] + ) => Awaited> + 'notes:get-versions': ( + ...args: [string] + ) => Awaited> + 'notes:import-files': ( + ...args: [{ sourcePaths: string[]; targetFolder?: string | undefined }] + ) => Awaited< + Promise<{ success: false; error: string } | import('../vault/notes').ImportFilesResult> + > + 'notes:list': ( + ...args: [ + { + folder?: string | undefined + tags?: string[] | undefined + sortBy?: 'title' | 'modified' | 'created' | 'position' | undefined + sortOrder?: 'asc' | 'desc' | undefined + limit?: number | undefined + offset?: number | undefined + } + ] + ) => Awaited> + 'notes:list-attachments': ( + ...args: [string] + ) => Awaited> + 'notes:move': ( + ...args: [{ id: string; newFolder: string }] + ) => Awaited< + Promise< + { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } + > + > + 'notes:open-external': (...args: [string]) => Awaited> + 'notes:preview-by-title': (...args: [string]) => Awaited< + Promise<{ + id: string + title: string + emoji: string | null + snippet: string | null + tags: { name: string; color: string }[] + createdAt: string + } | null> + > + 'notes:remove-property-option': ( + ...args: [{ propertyName: string; optionValue: string }] + ) => Awaited> + 'notes:rename': ( + ...args: [{ id: string; newTitle: string }] + ) => Awaited< + Promise< + { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } + > + > + 'notes:rename-folder': ( + ...args: [{ oldPath: string; newPath: string }] + ) => Awaited> + 'notes:rename-property-option': ( + ...args: [{ propertyName: string; oldValue: string; newValue: string }] + ) => Awaited> + 'notes:reorder': ( + ...args: [{ folderPath: string; notePaths: string[] }] + ) => Awaited> + 'notes:resolve-by-title': (...args: [string]) => Awaited< + Promise<{ + id: string + path: string + title: string + fileType: import('../../../../../packages/shared/src/file-types').FileType + } | null> + > + 'notes:restore-version': ( + ...args: [string] + ) => Awaited< + Promise< + { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } + > + > + 'notes:reveal-in-finder': (...args: [string]) => Awaited> + 'notes:set-folder-config': ( + ...args: [ + { + folderPath: string + config: { + icon?: string | null | undefined + template?: string | undefined + inherit?: boolean | undefined + } + } + ] + ) => Awaited> + 'notes:set-local-only': ( + ...args: [{ id: string; localOnly: boolean }] + ) => Awaited< + Promise< + { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } + > + > + 'notes:show-import-dialog': ( + ...args: [] + ) => Awaited> + 'notes:update': ( + ...args: [ + { + id: string + title?: string | undefined + content?: string | undefined + tags?: string[] | undefined + frontmatter?: Record | undefined + emoji?: string | null | undefined + } + ] + ) => Awaited< + Promise< + { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } + > + > + 'notes:update-option-color': ( + ...args: [{ propertyName: string; optionValue: string; newColor: string }] + ) => Awaited> + 'notes:update-property-definition': ( + ...args: [ + { + name: string + type?: + | 'number' + | 'date' + | 'text' + | 'select' + | 'checkbox' + | 'url' + | 'status' + | 'multiselect' + | undefined + options?: { value: string; color: string; default?: boolean | undefined }[] | undefined + defaultValue?: unknown + color?: string | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; definition: null; error: string } + | { + success: boolean + definition: + | import('../../../../../packages/contracts/src/property-types').PropertyDefinition + | undefined + error?: undefined + } + | { + success: boolean + definition: + | { + type: string + name: string + createdAt: string + options: string | null + defaultValue: string | null + color: string | null + } + | undefined + error?: undefined + } + > + > + 'notes:upload-attachment': ( + ...args: [{ noteId: string; filename: string; data: number[] | ArrayBuffer }] + ) => Awaited> + 'properties:get': ( + ...args: [{ entityId: string }] + ) => Awaited> + 'properties:rename': ( + ...args: [{ entityId: string; oldName: string; newName: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/properties-api').RenamePropertyResponse + > + > + 'properties:set': ( + ...args: [{ entityId: string; properties: Record }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/properties-api').SetPropertiesResponse + > + > + 'quick-capture:get-clipboard': (...args: []) => Awaited + 'reminder:bulk-dismiss': ( + ...args: [{ reminderIds: string[] }] + ) => Awaited< + Promise<{ success: false; error: string } | { success: boolean; dismissedCount: number }> + > + 'reminder:count-pending': (...args: []) => Awaited> + 'reminder:create': ( + ...args: [ + | { + targetType: 'note' + targetId: string + remindAt: string + title?: string | undefined + note?: string | undefined + } + | { + targetType: 'journal' + targetId: string + remindAt: string + title?: string | undefined + note?: string | undefined + } + | { + targetType: 'highlight' + targetId: string + highlightText: string + highlightStart: number + highlightEnd: number + remindAt: string + title?: string | undefined + note?: string | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { + success: boolean + reminder: import('../../../../../packages/contracts/src/reminders-api').Reminder + } + > + > + 'reminder:delete': ( + ...args: [string] + ) => Awaited< + Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + > + 'reminder:dismiss': (...args: [string]) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; reminder: null; error: string } + | { + success: boolean + reminder: import('../../../../../packages/contracts/src/reminders-api').Reminder + error?: undefined + } + > + > + 'reminder:get': ( + ...args: [string] + ) => Awaited< + Promise + > + 'reminder:get-due': ( + ...args: [] + ) => Awaited< + Promise + > + 'reminder:get-for-target': ( + ...args: [{ targetType: 'note' | 'journal' | 'highlight'; targetId: string }] + ) => Awaited> + 'reminder:get-upcoming': (...args: [number | undefined]) => Awaited< + Promise<{ + reminders: import('../../../../../packages/contracts/src/reminders-api').ReminderWithTarget[] + total: number + hasMore: boolean + }> + > + 'reminder:list': ( + ...args: [ + { + targetType?: 'note' | 'journal' | 'highlight' | undefined + targetId?: string | undefined + status?: + | 'pending' + | 'triggered' + | 'dismissed' + | 'snoozed' + | ('pending' | 'triggered' | 'dismissed' | 'snoozed')[] + | undefined + fromDate?: string | undefined + toDate?: string | undefined + limit?: number | undefined + offset?: number | undefined + } + ] + ) => Awaited< + Promise<{ + reminders: import('../../../../../packages/contracts/src/reminders-api').ReminderWithTarget[] + total: number + hasMore: boolean + }> + > + 'reminder:snooze': (...args: [{ id: string; snoozeUntil: string }]) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; reminder: null; error: string } + | { + success: boolean + reminder: import('../../../../../packages/contracts/src/reminders-api').Reminder + error?: undefined + } + > + > + 'reminder:update': ( + ...args: [ + { + id: string + remindAt?: string | undefined + title?: string | null | undefined + note?: string | null | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; reminder: null; error: string } + | { + success: boolean + reminder: import('../../../../../packages/contracts/src/reminders-api').Reminder + error?: undefined + } + > + > + 'saved-filters:create': ( + ...args: [ + { + name: string + config: { + filters: { + search?: string | undefined + projectIds?: string[] | undefined + priorities?: ('urgent' | 'high' | 'medium' | 'low' | 'none')[] | undefined + dueDate?: + | { + type: + | 'custom' + | 'any' + | 'none' + | 'overdue' + | 'today' + | 'tomorrow' + | 'this-week' + | 'next-week' + | 'this-month' + customStart?: string | null | undefined + customEnd?: string | null | undefined + } + | undefined + statusIds?: string[] | undefined + completion?: 'active' | 'completed' | 'all' | undefined + repeatType?: 'all' | 'repeating' | 'one-time' | undefined + hasTime?: 'all' | 'with-time' | 'without-time' | undefined + } + sort?: + | { + field: 'title' | 'createdAt' | 'priority' | 'dueDate' | 'completedAt' | 'project' + direction: 'asc' | 'desc' + } + | undefined + starred?: boolean | undefined + } + } + ] + ) => Awaited< + Promise<{ + success: boolean + savedFilter: import('../../../../../packages/contracts/src/saved-filters-api').SavedFilter + }> + > + 'saved-filters:delete': ( + ...args: [{ id: string }] + ) => Awaited< + Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + > + 'saved-filters:list': (...args: []) => Awaited< + Promise<{ + savedFilters: import('../../../../../packages/contracts/src/saved-filters-api').SavedFilter[] + }> + > + 'saved-filters:reorder': ( + ...args: [{ ids: string[]; positions: number[] }] + ) => Awaited> + 'saved-filters:update': ( + ...args: [ + { + id: string + name?: string | undefined + config?: + | { + filters: { + search?: string | undefined + projectIds?: string[] | undefined + priorities?: ('urgent' | 'high' | 'medium' | 'low' | 'none')[] | undefined + dueDate?: + | { + type: + | 'custom' + | 'any' + | 'none' + | 'overdue' + | 'today' + | 'tomorrow' + | 'this-week' + | 'next-week' + | 'this-month' + customStart?: string | null | undefined + customEnd?: string | null | undefined + } + | undefined + statusIds?: string[] | undefined + completion?: 'active' | 'completed' | 'all' | undefined + repeatType?: 'all' | 'repeating' | 'one-time' | undefined + hasTime?: 'all' | 'with-time' | 'without-time' | undefined + } + sort?: + | { + field: + | 'title' + | 'createdAt' + | 'priority' + | 'dueDate' + | 'completedAt' + | 'project' + direction: 'asc' | 'desc' + } + | undefined + starred?: boolean | undefined + } + | undefined + position?: number | undefined + } + ] + ) => Awaited< + Promise< + | { success: boolean; savedFilter: null; error: string } + | { + success: boolean + savedFilter: + | import('../../../../../packages/contracts/src/saved-filters-api').SavedFilter + | null + error?: undefined + } + > + > + 'search:add-reason': ( + ...args: [ + { + itemId: string + itemType: 'note' | 'task' | 'journal' | 'inbox' + itemTitle: string + searchQuery: string + itemIcon?: string | null | undefined + } + ] + ) => Awaited> + 'search:clear-reasons': (...args: []) => Awaited> + 'search:get-all-tags': (...args: []) => Awaited> + 'search:get-reasons': ( + ...args: [] + ) => Awaited> + 'search:get-stats': ( + ...args: [] + ) => Awaited> + 'search:query': ( + ...args: [ + { + text: string + types?: ('note' | 'task' | 'journal' | 'inbox')[] | undefined + tags?: string[] | undefined + dateRange?: { from: string; to: string } | null | undefined + projectId?: string | null | undefined + folderPath?: string | null | undefined + limit?: number | undefined + offset?: number | undefined + } + ] + ) => Awaited> + 'search:quick': ( + ...args: [string] + ) => Awaited< + Promise + > + 'search:rebuild-index': (...args: []) => Awaited< + Promise< + | { + notes: number + tasks: number + inbox: number + durationMs: number + started: true + error?: undefined + } + | { started: false; error: string } + > + > + 'settings:downloadVoiceModel': ( + ...args: [] + ) => Awaited< + Promise<{ success: boolean; error?: undefined } | { success: boolean; error: string }> + > + 'settings:get': (...args: [string]) => Awaited + 'settings:getAIModelStatus': ( + ...args: [] + ) => Awaited> + 'settings:getAISettings': (...args: []) => Awaited + 'settings:getBackupSettings': (...args: []) => Awaited<{ + autoBackup: boolean + frequencyHours: 1 | 6 | 12 | 24 + maxBackups: number + lastBackupAt: string | null + }> + 'settings:getEditorSettings': (...args: []) => Awaited<{ + width: 'medium' | 'narrow' | 'wide' + spellCheck: boolean + autoSaveDelay: number + showWordCount: boolean + toolbarMode: 'floating' | 'sticky' + }> + 'settings:getGeneralSettings': (...args: []) => Awaited<{ + theme: 'light' | 'dark' | 'white' | 'system' + fontSize: 'small' | 'medium' | 'large' + fontFamily: 'system' | 'serif' | 'sans-serif' | 'monospace' | 'gelasio' | 'geist' | 'inter' + accentColor: string + startOnBoot: boolean + language: string + onboardingCompleted: boolean + createInSelectedFolder: boolean + }> + 'settings:getGraphSettings': (...args: []) => Awaited<{ + layout: 'forceatlas2' | 'circular' | 'random' + showLabels: boolean + showEdgeLabels: boolean + animateLayout: boolean + showTagEdges: boolean + }> + 'settings:getJournalSettings': (...args: []) => Awaited<{ + defaultTemplate: string | null + showSchedule: boolean + showTasks: boolean + showAIConnections: boolean + showStatsFooter: boolean + }> + 'settings:getKeyboardSettings': (...args: []) => Awaited<{ + overrides: Record< + string, + { + key: string + modifiers: { + meta?: boolean | undefined + ctrl?: boolean | undefined + shift?: boolean | undefined + alt?: boolean | undefined + } + } + > + globalCapture: { + key: string + modifiers: { + meta?: boolean | undefined + ctrl?: boolean | undefined + shift?: boolean | undefined + alt?: boolean | undefined + } + } | null + }> + 'settings:getNoteEditorSettings': ( + ...args: [] + ) => Awaited + 'settings:getSyncSettings': (...args: []) => Awaited<{ enabled: boolean; autoSync: boolean }> + 'settings:getTabSettings': (...args: []) => Awaited + 'settings:getTaskSettings': (...args: []) => Awaited<{ + defaultProjectId: string | null + defaultSortOrder: 'createdAt' | 'priority' | 'dueDate' | 'manual' + weekStartDay: 'sunday' | 'monday' + staleInboxDays: number + }> + 'settings:getVoiceModelStatus': ( + ...args: [] + ) => Awaited + 'settings:getVoiceRecordingReadiness': ( + ...args: [] + ) => Awaited> + 'settings:getVoiceTranscriptionOpenAIKeyStatus': ( + ...args: [] + ) => Awaited> + 'settings:getVoiceTranscriptionSettings': ( + ...args: [] + ) => Awaited<{ provider: 'local' | 'openai' }> + 'settings:loadAIModel': ( + ...args: [] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; message: string; error?: undefined } + | { success: boolean; error: string; message?: undefined } + | { success: boolean; message?: undefined; error?: undefined } + > + > + 'settings:registerGlobalCapture': ( + ...args: [] + ) => Awaited> + 'settings:reindexEmbeddings': ( + ...args: [] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; computed: number; skipped: number; error?: string | undefined } + > + > + 'settings:resetKeyboardSettings': ( + ...args: [] + ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + 'settings:set': ( + ...args: [{ key: string; value: string }] + ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + 'settings:setAISettings': ( + ...args: [Partial] + ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + 'settings:setBackupSettings': ( + ...args: [ + Partial<{ + autoBackup: boolean + frequencyHours: 1 | 6 | 12 | 24 + maxBackups: number + lastBackupAt: string | null + }> + ] + ) => Awaited<{ success: boolean; error?: string | undefined }> + 'settings:setEditorSettings': ( + ...args: [ + Partial<{ + width: 'medium' | 'narrow' | 'wide' + spellCheck: boolean + autoSaveDelay: number + showWordCount: boolean + toolbarMode: 'floating' | 'sticky' + }> + ] + ) => Awaited<{ success: boolean; error?: string | undefined }> + 'settings:setGeneralSettings': ( + ...args: [ + Partial<{ + theme: 'light' | 'dark' | 'white' | 'system' + fontSize: 'small' | 'medium' | 'large' + fontFamily: 'system' | 'serif' | 'sans-serif' | 'monospace' | 'gelasio' | 'geist' | 'inter' + accentColor: string + startOnBoot: boolean + language: string + onboardingCompleted: boolean + createInSelectedFolder: boolean + }> + ] + ) => Awaited<{ success: boolean; error?: string | undefined }> + 'settings:setGraphSettings': ( + ...args: [ + Partial<{ + layout: 'forceatlas2' | 'circular' | 'random' + showLabels: boolean + showEdgeLabels: boolean + animateLayout: boolean + showTagEdges: boolean + }> + ] + ) => Awaited<{ success: boolean; error?: string | undefined }> + 'settings:setJournalSettings': ( + ...args: [Partial] + ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + 'settings:setKeyboardSettings': ( + ...args: [ + Partial<{ + overrides: Record< + string, + { + key: string + modifiers: { + meta?: boolean | undefined + ctrl?: boolean | undefined + shift?: boolean | undefined + alt?: boolean | undefined + } + } + > + globalCapture: { + key: string + modifiers: { + meta?: boolean | undefined + ctrl?: boolean | undefined + shift?: boolean | undefined + alt?: boolean | undefined + } + } | null + }> + ] + ) => Awaited<{ success: boolean; error?: string | undefined }> + 'settings:setNoteEditorSettings': ( + ...args: [Partial] + ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + 'settings:setSyncSettings': ( + ...args: [Partial<{ enabled: boolean; autoSync: boolean }>] + ) => Awaited<{ success: boolean; error?: string | undefined }> + 'settings:setTabSettings': ( + ...args: [Partial] + ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + 'settings:setTaskSettings': ( + ...args: [ + Partial<{ + defaultProjectId: string | null + defaultSortOrder: 'createdAt' | 'priority' | 'dueDate' | 'manual' + weekStartDay: 'sunday' | 'monday' + staleInboxDays: number + }> + ] + ) => Awaited<{ success: boolean; error?: string | undefined }> + 'settings:setVoiceTranscriptionOpenAIKey': ( + ...args: [{ apiKey: string }] + ) => Awaited< + Promise<{ success: boolean; error?: undefined } | { success: boolean; error: string }> + > + 'settings:setVoiceTranscriptionSettings': ( + ...args: [Partial<{ provider: 'local' | 'openai' }>] + ) => Awaited<{ success: boolean; error?: string | undefined }> + 'sync:approve-linking': ( + ...args: [{ sessionId: string }] + ) => Awaited< + Promise + > + 'sync:check-device-status': (...args: []) => Awaited> + 'sync:complete-linking-qr': ( + ...args: [{ sessionId: string }] + ) => Awaited< + Promise + > + 'sync:confirm-recovery-phrase': ( + ...args: [{ confirmed: boolean }] + ) => Awaited> + 'sync:download-attachment': ( + ...args: [{ attachmentId: string; targetPath?: string | undefined }] + ) => Awaited< + Promise< + | { success: boolean; error: string; filePath?: undefined } + | { success: boolean; filePath: string; error?: undefined } + > + > + 'sync:emergency-wipe': (...args: []) => Awaited> + 'sync:generate-linking-qr': ( + ...args: [] + ) => Awaited< + Promise + > + 'sync:get-devices': (...args: []) => Awaited< + Promise<{ + devices: { + id: string + name: string + platform: 'macos' | 'windows' | 'linux' | 'ios' | 'android' + linkedAt: number + lastSyncAt: number | undefined + isCurrentDevice: boolean + }[] + email: string | undefined + }> + > + 'sync:get-download-progress': (...args: [{ attachmentId: string }]) => Awaited< + Promise<{ + progress: number + downloadedChunks: number + totalChunks: number + status: 'downloading' + } | null> + > + 'sync:get-history': ( + ...args: [{ limit?: number | undefined; offset?: number | undefined }] + ) => Awaited< + Promise<{ + entries: { + id: string + type: 'error' | 'push' | 'pull' + itemCount: number + direction: string | undefined + details: unknown + durationMs: number | undefined + createdAt: number + }[] + total: number + }> + > + 'sync:get-linking-sas': ( + ...args: [{ sessionId: string }] + ) => Awaited> + 'sync:get-quarantined-items': ( + ...args: [] + ) => Awaited + 'sync:get-queue-size': (...args: []) => Awaited<{ pending: number; failed: number }> + 'sync:get-recovery-phrase': (...args: []) => Awaited + 'sync:get-status': ( + ...args: [] + ) => Awaited< + | import('../../../../../packages/contracts/src/ipc-sync-ops').GetSyncStatusResult + | { status: string; pendingCount: number } + > + 'sync:get-storage-breakdown': ( + ...args: [] + ) => Awaited< + Promise< + import('../../../../../packages/contracts/src/ipc-sync-ops').StorageBreakdownResult | null + > + > + 'sync:get-synced-settings': (...args: []) => Awaited<{ + general?: + | { + theme?: 'light' | 'dark' | 'white' | 'system' | undefined + fontSize?: 'small' | 'medium' | 'large' | undefined + fontFamily?: + | 'system' + | 'serif' + | 'sans-serif' + | 'monospace' + | 'gelasio' + | 'geist' + | 'inter' + | undefined + accentColor?: string | undefined + startOnBoot?: boolean | undefined + language?: string | undefined + createInSelectedFolder?: boolean | undefined + } + | undefined + editor?: + | { + width?: 'medium' | 'narrow' | 'wide' | undefined + spellCheck?: boolean | undefined + autoSaveDelay?: number | undefined + showWordCount?: boolean | undefined + toolbarMode?: 'floating' | 'sticky' | undefined + } + | undefined + tasks?: + | { + defaultProjectId?: string | null | undefined + defaultSortOrder?: 'createdAt' | 'priority' | 'dueDate' | 'manual' | undefined + weekStartDay?: 'sunday' | 'monday' | undefined + staleInboxDays?: number | undefined + showCompleted?: boolean | undefined + sortBy?: string | undefined + } + | undefined + keyboard?: { overrides?: Record | undefined } | undefined + notes?: + | { + defaultFolder?: string | undefined + editorFontSize?: number | undefined + spellCheck?: boolean | undefined + } + | undefined + sync?: { autoSync?: boolean | undefined; syncIntervalMinutes?: number | undefined } | undefined + } | null> + 'sync:get-upload-progress': (...args: [{ sessionId: string }]) => Awaited< + Promise<{ + progress: number + uploadedChunks: number + totalChunks: number + status: 'uploading' + } | null> + > + 'sync:link-via-qr': ( + ...args: [{ qrData: string; oauthToken?: string | undefined; provider?: string | undefined }] + ) => Awaited> + 'sync:link-via-recovery': ( + ...args: [{ recoveryPhrase: string }] + ) => Awaited< + Promise< + | { success: boolean; error: string; deviceId?: undefined } + | { success: boolean; deviceId: string; error?: undefined } + > + > + 'sync:logout': ( + ...args: [] + ) => Awaited> + 'sync:pause': (...args: []) => Awaited<{ success: boolean; wasPaused: boolean }> + 'sync:remove-device': ( + ...args: [{ deviceId: string }] + ) => Awaited< + Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + > + 'sync:rename-device': ( + ...args: [{ deviceId: string; newName: string }] + ) => Awaited< + Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + > + 'sync:resume': (...args: []) => Awaited<{ success: boolean; pendingCount: number }> + 'sync:setup-first-device': ( + ...args: [{ oauthToken: string; provider: 'google'; state: string }] + ) => Awaited< + Promise< + | { + success: boolean + needsRecoverySetup: boolean + deviceId: string + needsRecoveryInput?: undefined + } + | { + success: boolean + needsRecoverySetup: boolean + needsRecoveryInput: boolean + deviceId?: undefined + } + > + > + 'sync:setup-new-account': ( + ...args: [] + ) => Awaited< + Promise< + | { success: boolean; error: string; deviceId?: undefined } + | { success: boolean; deviceId: string; error?: undefined } + > + > + 'sync:trigger-sync': ( + ...args: [] + ) => Awaited> + 'sync:update-synced-setting': ( + ...args: [unknown] + ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> + 'sync:upload-attachment': ( + ...args: [{ noteId: string; filePath: string }] + ) => Awaited< + Promise< + | { success: boolean; error: string; attachmentId?: undefined; sessionId?: undefined } + | { success: boolean; attachmentId: string; sessionId: string; error?: undefined } + > + > + 'tags:delete': ( + ...args: [string] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/tags-api').DeleteTagResponse + > + > + 'tags:get-all-with-counts': ( + ...args: [] + ) => Awaited< + Promise + > + 'tags:get-notes-by-tag': ( + ...args: [ + { + tag: string + sortBy?: 'title' | 'modified' | 'created' | undefined + sortOrder?: 'asc' | 'desc' | undefined + includeDescendants?: boolean | undefined + } + ] + ) => Awaited< + Promise + > + 'tags:merge': ( + ...args: [{ source: string; target: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/tags-api').MergeTagResponse + > + > + 'tags:pin-note-to-tag': ( + ...args: [{ noteId: string; tag: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/tags-api').TagOperationResponse + > + > + 'tags:remove-from-note': ( + ...args: [{ noteId: string; tag: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/tags-api').TagOperationResponse + > + > + 'tags:rename': ( + ...args: [{ oldName: string; newName: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/tags-api').RenameTagResponse + > + > + 'tags:unpin-note-from-tag': ( + ...args: [{ noteId: string; tag: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/tags-api').TagOperationResponse + > + > + 'tags:update-color': ( + ...args: [{ tag: string; color: string }] + ) => Awaited< + Promise< + | { success: false; error: string } + | import('../../../../../packages/contracts/src/tags-api').TagOperationResponse + > + > + 'tasks:archive': ( + ...args: [string] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; error: string } + | { success: boolean; error?: undefined } + > + > + 'tasks:bulk-archive': ( + ...args: [{ ids: string[] }] + ) => Awaited> + 'tasks:bulk-complete': ( + ...args: [{ ids: string[] }] + ) => Awaited> + 'tasks:bulk-delete': ( + ...args: [{ ids: string[] }] + ) => Awaited> + 'tasks:bulk-move': ( + ...args: [{ ids: string[]; projectId: string }] + ) => Awaited> + 'tasks:complete': (...args: [{ id: string; completedAt?: string | undefined }]) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; task: null; error: string } + | { + success: boolean + task: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } + error?: undefined + } + > + > + 'tasks:convert-to-subtask': (...args: [{ taskId: string; parentId: string }]) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; task: null; error: string } + | { + success: boolean + task: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } + error?: undefined + } + > + > + 'tasks:convert-to-task': (...args: [string]) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; task: null; error: string } + | { + success: boolean + task: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } + error?: undefined + } + > + > + 'tasks:create': ( + ...args: [ + { + projectId: string + title: string + description?: string | null | undefined + priority?: number | undefined + statusId?: string | null | undefined + parentId?: string | null | undefined + dueDate?: string | null | undefined + dueTime?: string | null | undefined + startDate?: string | null | undefined + isRepeating?: boolean | undefined + repeatConfig?: + | { + frequency: 'daily' | 'weekly' | 'monthly' | 'yearly' + endType: 'date' | 'never' | 'count' + createdAt: string + interval?: number | undefined + daysOfWeek?: number[] | undefined + monthlyType?: 'dayOfMonth' | 'weekPattern' | undefined + dayOfMonth?: number | undefined + weekOfMonth?: number | undefined + dayOfWeekForMonth?: number | undefined + endDate?: string | null | undefined + endCount?: number | undefined + completedCount?: number | undefined + } + | null + | undefined + repeatFrom?: 'due' | 'completion' | null | undefined + tags?: string[] | undefined + linkedNoteIds?: string[] | undefined + sourceNoteId?: string | null | undefined + position?: number | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { + success: boolean + task: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } + } + > + > + 'tasks:delete': ( + ...args: [string] + ) => Awaited> + 'tasks:duplicate': (...args: [string]) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; task: null; error: string } + | { + success: boolean + task: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } + error?: undefined + } + > + > + 'tasks:get': (...args: [string]) => Awaited< + Promise<{ + tags: string[] + linkedNoteIds: string[] + hasSubtasks: boolean + subtaskCount: number + completedSubtaskCount: number + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } | null> + > + 'tasks:get-linked-tasks': (...args: [string]) => Awaited< + Promise< + { + tags: string[] + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + }[] + > + > + 'tasks:get-overdue': (...args: []) => Awaited< + Promise<{ + tasks: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + }[] + total: number + hasMore: boolean + }> + > + 'tasks:get-stats': (...args: []) => Awaited< + Promise<{ + total: number + completed: number + overdue: number + dueToday: number + dueThisWeek: number + }> + > + 'tasks:get-subtasks': (...args: [string]) => Awaited< + Promise< + { + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + }[] + > + > + 'tasks:get-tags': (...args: []) => Awaited> + 'tasks:get-today': (...args: []) => Awaited< + Promise<{ + tasks: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + }[] + total: number + hasMore: boolean + }> + > + 'tasks:get-upcoming': (...args: [{ days?: number | undefined }]) => Awaited< + Promise<{ + tasks: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + }[] + total: number + hasMore: boolean + }> + > + 'tasks:list': ( + ...args: [ + { + projectId?: string | undefined + statusId?: string | null | undefined + parentId?: string | null | undefined + includeCompleted?: boolean | undefined + includeArchived?: boolean | undefined + dueBefore?: string | undefined + dueAfter?: string | undefined + tags?: string[] | undefined + search?: string | undefined + sortBy?: 'modified' | 'created' | 'position' | 'priority' | 'dueDate' | undefined + sortOrder?: 'asc' | 'desc' | undefined + limit?: number | undefined + offset?: number | undefined + } + ] + ) => Awaited< + Promise<{ + tasks: { + tags: string[] + linkedNoteIds: string[] + hasSubtasks: boolean + subtaskCount: number + completedSubtaskCount: number + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + }[] + total: number + hasMore: boolean + }> + > + 'tasks:move': ( + ...args: [ + { + taskId: string + position: number + targetProjectId?: string | undefined + targetStatusId?: string | null | undefined + targetParentId?: string | null | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; task: null; error: string } + | { + success: boolean + task: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } + error?: undefined + } + > + > + 'tasks:project-archive': ( + ...args: [string] + ) => Awaited> + 'tasks:project-create': ( + ...args: [ + { + name: string + description?: string | null | undefined + color?: string | undefined + icon?: string | null | undefined + statuses?: + | { + name: string + type: 'todo' | 'in_progress' | 'done' + order: number + color?: string | undefined + }[] + | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { + success: boolean + project: { + id: string + name: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + color: string + description: string | null + icon: string | null + isInbox: boolean + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + } + } + > + > + 'tasks:project-delete': ( + ...args: [string] + ) => Awaited> + 'tasks:project-get': ( + ...args: [string] + ) => Awaited> + 'tasks:project-list': ( + ...args: [] + ) => Awaited> + 'tasks:project-reorder': ( + ...args: [{ projectIds: string[]; positions: number[] }] + ) => Awaited> + 'tasks:project-update': ( + ...args: [ + { + id: string + name?: string | undefined + description?: string | null | undefined + color?: string | undefined + icon?: string | null | undefined + statuses?: + | { + name: string + type: 'todo' | 'in_progress' | 'done' + order: number + id?: string | undefined + color?: string | undefined + }[] + | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; project: null; error: string } + | { + success: boolean + project: { + id: string + name: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + color: string + description: string | null + icon: string | null + isInbox: boolean + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + } + error?: undefined + } + > + > + 'tasks:reorder': ( + ...args: [{ taskIds: string[]; positions: number[] }] + ) => Awaited> + 'tasks:seed-demo': (...args: []) => Awaited> + 'tasks:seed-performance-test': ( + ...args: [] + ) => Awaited> + 'tasks:status-create': ( + ...args: [ + { projectId: string; name: string; color?: string | undefined; isDone?: boolean | undefined } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { + success: boolean + status: { + id: string + name: string + createdAt: string + position: number + color: string + projectId: string + isDefault: boolean + isDone: boolean + } + } + > + > + 'tasks:status-delete': ( + ...args: [string] + ) => Awaited> + 'tasks:status-list': (...args: [string]) => Awaited< + Promise< + { + id: string + name: string + createdAt: string + position: number + color: string + projectId: string + isDefault: boolean + isDone: boolean + }[] + > + > + 'tasks:status-reorder': ( + ...args: [{ statusIds: string[]; positions: number[] }] + ) => Awaited> + 'tasks:status-update': ( + ...args: [ + { + id: string + name?: string | undefined + color?: string | undefined + position?: number | undefined + isDefault?: boolean | undefined + isDone?: boolean | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; error: string; status?: undefined } + | { + success: boolean + status: { + id: string + name: string + createdAt: string + position: number + color: string + projectId: string + isDefault: boolean + isDone: boolean + } + error?: undefined + } + > + > + 'tasks:unarchive': ( + ...args: [string] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; error: string } + | { success: boolean; error?: undefined } + > + > + 'tasks:uncomplete': (...args: [string]) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; task: null; error: string } + | { + success: boolean + task: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } + error?: undefined + } + > + > + 'tasks:update': ( + ...args: [ + { + id: string + title?: string | undefined + description?: string | null | undefined + priority?: number | undefined + projectId?: string | undefined + statusId?: string | null | undefined + parentId?: string | null | undefined + dueDate?: string | null | undefined + dueTime?: string | null | undefined + startDate?: string | null | undefined + isRepeating?: boolean | undefined + repeatConfig?: + | { + frequency: 'daily' | 'weekly' | 'monthly' | 'yearly' + endType: 'date' | 'never' | 'count' + createdAt: string + interval?: number | undefined + daysOfWeek?: number[] | undefined + monthlyType?: 'dayOfMonth' | 'weekPattern' | undefined + dayOfMonth?: number | undefined + weekOfMonth?: number | undefined + dayOfWeekForMonth?: number | undefined + endDate?: string | null | undefined + endCount?: number | undefined + completedCount?: number | undefined + } + | null + | undefined + repeatFrom?: 'due' | 'completion' | null | undefined + tags?: string[] | undefined + linkedNoteIds?: string[] | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { success: boolean; task: null; error: string } + | { + success: boolean + task: { + linkedNoteIds: string[] + id: string + title: string + clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null + syncedAt: string | null + createdAt: string + modifiedAt: string + position: number + description: string | null + projectId: string + priority: number + statusId: string | null + parentId: string | null + dueDate: string | null + dueTime: string | null + startDate: string | null + repeatConfig: unknown + repeatFrom: string | null + sourceNoteId: string | null + archivedAt: string | null + fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null + completedAt: string | null + } + error?: undefined + } + > + > + 'templates:create': ( + ...args: [ + { + name: string + description?: string | undefined + icon?: string | null | undefined + tags?: string[] | undefined + properties?: + | { + name: string + type: + | 'number' + | 'date' + | 'text' + | 'select' + | 'checkbox' + | 'url' + | 'multiselect' + | 'rating' + value: unknown + options?: string[] | undefined + }[] + | undefined + content?: string | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { + success: boolean + template: import('../../../../../packages/contracts/src/templates-api').Template + } + > + > + 'templates:delete': ( + ...args: [string] + ) => Awaited> + 'templates:duplicate': (...args: [{ id: string; newName: string }]) => Awaited< + Promise< + | { success: false; error: string } + | { + success: boolean + template: import('../../../../../packages/contracts/src/templates-api').Template + } + > + > + 'templates:get': ( + ...args: [string] + ) => Awaited< + Promise + > + 'templates:list': (...args: []) => Awaited< + Promise<{ + templates: import('../../../../../packages/contracts/src/templates-api').TemplateListItem[] + }> + > + 'templates:update': ( + ...args: [ + { + id: string + name?: string | undefined + description?: string | undefined + icon?: string | null | undefined + tags?: string[] | undefined + properties?: + | { + name: string + type: + | 'number' + | 'date' + | 'text' + | 'select' + | 'checkbox' + | 'url' + | 'multiselect' + | 'rating' + value: unknown + options?: string[] | undefined + }[] + | undefined + content?: string | undefined + } + ] + ) => Awaited< + Promise< + | { success: false; error: string } + | { + success: boolean + template: import('../../../../../packages/contracts/src/templates-api').Template + } + > + > + 'vault:close': (...args: []) => Awaited> + 'vault:get-all': ( + ...args: [] + ) => Awaited> + 'vault:get-config': ( + ...args: [] + ) => Awaited> + 'vault:get-status': ( + ...args: [] + ) => Awaited> + 'vault:reindex': (...args: []) => Awaited> + 'vault:remove': (...args: [string]) => Awaited> + 'vault:reveal': (...args: []) => Awaited> + 'vault:select': ( + ...args: [{ path?: string | undefined }] + ) => Awaited< + Promise + > + 'vault:switch': ( + ...args: [string] + ) => Awaited< + Promise + > + 'vault:update-config': ( + ...args: [ + { + excludePatterns?: string[] | undefined + defaultNoteFolder?: string | undefined + journalFolder?: string | undefined + attachmentsFolder?: string | undefined + } + ] + ) => Awaited> } export type MainIpcInvokeChannel = keyof MainIpcInvokeHandlers -export type MainIpcInvokeArgs = - Parameters -export type MainIpcInvokeResult = - ReturnType +export type MainIpcInvokeArgs = Parameters +export type MainIpcInvokeResult = ReturnType< + MainIpcInvokeHandlers[C] +> diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 73b155f03..2434ea238 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -33,6 +33,8 @@ import { getCalloutSlashMenuItem } from './callout-block' import { getTaskSlashMenuItem } from './task-block' import { TaskCreationPopover } from './task-block/task-creation-popover' import { isLikelyTask } from './task-block/task-block-utils' +import { tasksService } from '@/services/tasks-service' +import { useTasksOptional } from '@/contexts/tasks' import { editorSchema } from './editor-schema' import { HighlightReminderPopover, @@ -108,6 +110,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const { openTag } = useSidebarDrillDown() const { port: aiPort, error: aiError, retry: retryAI } = useAIInlineContext() + const tasksCtx = useTasksOptional() const [highlightSelection, setHighlightSelection] = useState(null) const [taskCreation, setTaskCreation] = useState<{ isOpen: boolean @@ -329,7 +332,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ setTaskCreation(null) }, [editor, taskCreation]) - const checkForTaskBlock = useCallback(() => { + const autoCreateTaskFromCheckbox = useCallback(async () => { if (taskCreation?.isOpen) return const cursor = editor.getTextCursorPosition() @@ -345,12 +348,28 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const text = content.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))).join('') if (!text.trim() || !isLikelyTask(text)) return - const blockEl = document.querySelector(`[data-id="${block.id}"]`) - if (!blockEl) return + dismissedBlocksRef.current.add(block.id) - taskCreationAnchorRef.current = blockEl as HTMLElement - setTaskCreation({ isOpen: true, blockId: block.id, title: text.trim() }) - }, [editor, taskCreation?.isOpen]) + const projects = tasksCtx?.projects ?? [] + const defaultProject = projects.find((p: any) => p.isDefault) ?? projects[0] + if (!defaultProject) return + + try { + const result = await tasksService.create({ + projectId: defaultProject.id, + title: text.trim(), + linkedNoteIds: noteId ? [noteId] : [] + }) + if (result.success && result.task) { + editor.updateBlock(block, { + type: 'taskBlock' as any, + props: { taskId: result.task.id, title: text.trim(), checked: false } + }) + } + } catch { + dismissedBlocksRef.current.delete(block.id) + } + }, [editor, taskCreation?.isOpen, noteId]) const handleEditorContextMenu = useCallback( (e: React.MouseEvent) => { @@ -424,7 +443,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ onChange={(): void => { void handleChange() if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) - taskDetectTimeoutRef.current = setTimeout(checkForTaskBlock, 800) + taskDetectTimeoutRef.current = setTimeout(() => void autoCreateTaskFromCheckbox(), 800) }} theme={editorTheme} formattingToolbar={!stickyToolbar} diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx index e9dae1c41..48d1934e7 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx @@ -1,5 +1,5 @@ -import { type FC, useCallback, useEffect, useRef } from 'react' -import { Check, AlertTriangle, Loader2, X } from 'lucide-react' +import { type FC, useCallback, useEffect, useRef, useState } from 'react' +import { Check, AlertTriangle, Loader2, X, Calendar, Flag, Folder } from 'lucide-react' import { cn } from '@/lib/utils' import { useTaskBlockData } from './use-task-block-data' import { useTasksOptional } from '@/contexts/tasks' @@ -20,12 +20,33 @@ const DB_PRIORITY_MAP: Record = { 4: 'urgent' } +const PRIORITY_NUM_MAP: Record = { + none: 0, + low: 1, + medium: 2, + high: 3, + urgent: 4 +} + +const PRIORITY_OPTIONS: { value: number; label: string; color: string | null }[] = [ + { value: 0, label: 'None', color: null }, + { value: 1, label: 'Low', color: 'var(--task-priority-low)' }, + { value: 2, label: 'Medium', color: 'var(--task-priority-medium)' }, + { value: 3, label: 'High', color: 'var(--task-priority-high)' }, + { value: 4, label: 'Urgent', color: 'var(--task-priority-urgent)' } +] + export const TaskBlockRenderer: FC = ({ block, editor, contentRef }) => { const { taskId, title, checked } = block.props const { task, isLoading, isDeleted } = useTaskBlockData(taskId) const tasksCtx = useTasksOptional() const syncingRef = useRef(false) + const [showPriorityPicker, setShowPriorityPicker] = useState(false) + const [showProjectPicker, setShowProjectPicker] = useState(false) + const [showDatePicker, setShowDatePicker] = useState(false) + const dateInputRef = useRef(null) + const displayTitle = task?.title ?? title const displayChecked = task ? !!task.completedAt : checked @@ -67,12 +88,39 @@ export const TaskBlockRenderer: FC = ({ block, editor, c } }, [taskId]) + const handlePriorityChange = useCallback( + async (value: number) => { + if (!taskId) return + setShowPriorityPicker(false) + await tasksService.update({ id: taskId, priority: value }) + }, + [taskId] + ) + + const handleProjectChange = useCallback( + async (projectId: string) => { + if (!taskId) return + setShowProjectPicker(false) + await tasksService.update({ id: taskId, projectId }) + }, + [taskId] + ) + + const handleDueDateChange = useCallback( + async (dateStr: string) => { + if (!taskId) return + setShowDatePicker(false) + await tasksService.update({ id: taskId, dueDate: dateStr || null }) + }, + [taskId] + ) + if (!taskId) { return (
Creating task... @@ -85,7 +133,7 @@ export const TaskBlockRenderer: FC = ({ block, editor, c
{displayTitle} @@ -105,33 +153,39 @@ export const TaskBlockRenderer: FC = ({ block, editor, c typeof task?.priority === 'number' ? task.priority : typeof task?.priority === 'string' - ? (({ none: 0, low: 1, medium: 2, high: 3, urgent: 4 } as Record)[ - task.priority - ] ?? 0) + ? (PRIORITY_NUM_MAP[task.priority] ?? 0) : 0 const priorityKey = DB_PRIORITY_MAP[priorityNum] ?? 'none' const priorityVars = PRIORITY_CSS_VARS[priorityKey] - const projectName = tasksCtx?.projects?.find((p) => p.id === task?.projectId)?.name + const projects = tasksCtx?.projects ?? [] + const projectName = projects.find((p) => p.id === task?.projectId)?.name ?? 'Inbox' const dueDate = task?.dueDate ? new Date(task.dueDate) : null const isOverdue = dueDate instanceof Date && dueDate < new Date() && !displayChecked - const formatDue = (d: Date | null): string | null => { - if (!d || !(d instanceof Date)) return null + const formatDue = (d: Date | null): string => { + if (!d || !(d instanceof Date)) return 'No date' return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) } + const dueDateIso = task?.dueDate + ? typeof task.dueDate === 'string' + ? task.dueDate.slice(0, 10) + : '' + : '' + return (
+ {/* Checkbox */} + {/* Title */} -
- {priorityVars && ( - - )} - {formatDue(dueDate) && ( - + {/* Priority */} +
+ + {showPriorityPicker && ( +
+ {PRIORITY_OPTIONS.map((opt) => ( + + ))} +
+ )} +
+ + {/* Due date */} +
+ + {showDatePicker && ( +
+ handleDueDateChange(e.target.value)} + onBlur={() => setShowDatePicker(false)} + className="rounded-md border border-stone-200 bg-white px-2 py-1 text-xs shadow-lg dark:border-stone-700 dark:bg-stone-800" + /> +
+ )} +
+ + {/* Project */} +
+ + {showProjectPicker && ( +
+ {projects.map((p) => ( + + ))} +
+ )} +
{isLoading && } From 0298d96902b21ab18b3b9fb7fa24104a2aab9394 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 22:39:59 +0300 Subject: [PATCH 08/24] fix: auto-create tasks silently, remove broken popover dialog - Fixed stale closure: added tasksCtx to useCallback dependencies - Added fallback: fetch projects directly via tasksService.listProjects() - /task slash command now auto-creates with defaults (no dialog) - Right-click promote now auto-creates with defaults (no dialog) - Removed TaskCreationPopover from ContentArea entirely - Simplified to shared convertCheckboxToTask helper --- .../note/content-area/ContentArea.tsx | 113 ++++++------------ .../note/content-area/task-block/index.tsx | 30 +++-- 2 files changed, 56 insertions(+), 87 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 2434ea238..8ce29f4c5 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -31,7 +31,6 @@ import { WikiLinkPreviewCard } from './wiki-link-preview-card' import { BlockDropIndicator, EmptyDocumentDropIndicator } from './block-drop-indicator' import { getCalloutSlashMenuItem } from './callout-block' import { getTaskSlashMenuItem } from './task-block' -import { TaskCreationPopover } from './task-block/task-creation-popover' import { isLikelyTask } from './task-block/task-block-utils' import { tasksService } from '@/services/tasks-service' import { useTasksOptional } from '@/contexts/tasks' @@ -112,12 +111,6 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const tasksCtx = useTasksOptional() const [highlightSelection, setHighlightSelection] = useState(null) - const [taskCreation, setTaskCreation] = useState<{ - isOpen: boolean - blockId: string - title: string - } | null>(null) - const taskCreationAnchorRef = useRef(null) const taskDetectTimeoutRef = useRef | null>(null) const dismissedBlocksRef = useRef(new Set()) const editorContainerRef = useRef(null) @@ -291,50 +284,44 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ window.getSelection()?.removeAllRanges() }, []) - useEffect(() => { - const handleOpenCreation = (e: Event): void => { - const { blockId } = (e as CustomEvent).detail - const blockEl = document.querySelector(`[data-id="${blockId}"]`) - taskCreationAnchorRef.current = blockEl as HTMLElement - setTaskCreation({ isOpen: true, blockId, title: '' }) + const resolveDefaultProject = useCallback(async (): Promise<{ id: string } | null> => { + const ctxProjects = tasksCtx?.projects ?? [] + if (ctxProjects.length > 0) { + return ctxProjects.find((p: any) => p.isDefault || p.isInbox) ?? ctxProjects[0] ?? null } - window.addEventListener('task-block:open-creation', handleOpenCreation) - return () => window.removeEventListener('task-block:open-creation', handleOpenCreation) - }, []) + const res = await tasksService.listProjects() + const dbProjects = res.projects ?? [] + return dbProjects.find((p: any) => p.isDefault || p.isInbox) ?? dbProjects[0] ?? null + }, [tasksCtx]) + + const convertCheckboxToTask = useCallback( + async (blockId: string, titleText: string) => { + const project = await resolveDefaultProject() + if (!project) return - const handleTaskCreated = useCallback( - (taskId: string, title: string) => { - if (!taskCreation?.blockId) return - const block = editor.getBlock(taskCreation.blockId) + const block = editor.getBlock(blockId) if (!block) return - if (block.type === 'checkListItem') { - editor.updateBlock(block, { - type: 'taskBlock' as any, - props: { taskId, title, checked: false } + try { + const result = await tasksService.create({ + projectId: project.id, + title: titleText, + linkedNoteIds: noteId ? [noteId] : [] }) - } else { - editor.updateBlock(block, { props: { taskId, title, checked: false } }) + if (result.success && result.task) { + editor.updateBlock(block, { + type: 'taskBlock' as any, + props: { taskId: result.task.id, title: titleText, checked: false } + }) + } + } catch { + dismissedBlocksRef.current.delete(blockId) } - setTaskCreation(null) }, - [editor, taskCreation] + [editor, noteId, resolveDefaultProject] ) - const handleTaskCreationCancel = useCallback(() => { - if (taskCreation?.blockId) { - dismissedBlocksRef.current.add(taskCreation.blockId) - const block = editor.getBlock(taskCreation.blockId) - if (block && (block.props as any).taskId === '') { - editor.removeBlocks([block]) - } - } - setTaskCreation(null) - }, [editor, taskCreation]) - const autoCreateTaskFromCheckbox = useCallback(async () => { - if (taskCreation?.isOpen) return - const cursor = editor.getTextCursorPosition() if (!cursor?.block) return @@ -349,32 +336,11 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ if (!text.trim() || !isLikelyTask(text)) return dismissedBlocksRef.current.add(block.id) - - const projects = tasksCtx?.projects ?? [] - const defaultProject = projects.find((p: any) => p.isDefault) ?? projects[0] - if (!defaultProject) return - - try { - const result = await tasksService.create({ - projectId: defaultProject.id, - title: text.trim(), - linkedNoteIds: noteId ? [noteId] : [] - }) - if (result.success && result.task) { - editor.updateBlock(block, { - type: 'taskBlock' as any, - props: { taskId: result.task.id, title: text.trim(), checked: false } - }) - } - } catch { - dismissedBlocksRef.current.delete(block.id) - } - }, [editor, taskCreation?.isOpen, noteId]) + await convertCheckboxToTask(block.id, text.trim()) + }, [editor, convertCheckboxToTask]) const handleEditorContextMenu = useCallback( - (e: React.MouseEvent) => { - if (taskCreation?.isOpen) return - + async (e: React.MouseEvent) => { const target = e.target as HTMLElement const checkListBlock = target.closest('[data-content-type="checkListItem"]') if (!checkListBlock) return @@ -391,10 +357,10 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ if (!text.trim()) return e.preventDefault() - taskCreationAnchorRef.current = checkListBlock as HTMLElement - setTaskCreation({ isOpen: true, blockId, title: text.trim() }) + dismissedBlocksRef.current.add(blockId) + await convertCheckboxToTask(blockId, text.trim()) }, - [editor, taskCreation?.isOpen] + [editor, convertCheckboxToTask] ) useEffect(() => { @@ -510,17 +476,6 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ selectedIndex={pasteLinkState.selectedIndex} onSelect={handlePasteLinkOptionSelect} /> - - {taskCreation?.isOpen && ( - - )}
) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx index 0c9315baf..7b157808f 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx @@ -1,5 +1,6 @@ import { createReactBlockSpec } from '@blocknote/react' import { TaskBlockRenderer } from './task-block-renderer' +import { tasksService } from '@/services/tasks-service' export const createTaskBlock = createReactBlockSpec( { @@ -25,17 +26,30 @@ export const createTaskBlock = createReactBlockSpec( export function getTaskSlashMenuItem(editor: any) { return { title: 'Task', - onItemClick: () => { + onItemClick: async () => { const currentBlock = editor.getTextCursorPosition().block - editor.updateBlock(currentBlock, { - type: 'taskBlock' as any, - props: { taskId: '', title: '', checked: false } + const content = currentBlock.content as any[] + const text = + content + ?.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))) + .join('') + .trim() || 'New task' + + const res = await tasksService.listProjects() + const projects = res.projects ?? [] + const defaultProject = projects.find((p: any) => p.isDefault || p.isInbox) ?? projects[0] + if (!defaultProject) return + + const result = await tasksService.create({ + projectId: defaultProject.id, + title: text }) - window.dispatchEvent( - new CustomEvent('task-block:open-creation', { - detail: { blockId: currentBlock.id } + if (result.success && result.task) { + editor.updateBlock(currentBlock, { + type: 'taskBlock' as any, + props: { taskId: result.task.id, title: text, checked: false } }) - ) + } }, aliases: ['task', 'todo', 'action'], group: 'Basic blocks', From 3613ce5336d21f721dc3182001aab76270d0c2a6 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 22:49:56 +0300 Subject: [PATCH 09/24] fix: remove action verb gate, make task title editable inline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Every [ ] checkbox now auto-creates a task (no verb detection) - Title is an editable input after conversion — user keeps typing - Title changes debounce-save to DB (600ms) - Enter or blur commits the title - Click title text to re-edit --- .../note/content-area/ContentArea.tsx | 2 +- .../task-block/task-block-renderer.tsx | 103 +++++++++++++++--- 2 files changed, 90 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 8ce29f4c5..60823f643 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -333,7 +333,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ if (!content?.length) return const text = content.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))).join('') - if (!text.trim() || !isLikelyTask(text)) return + if (!text.trim()) return dismissedBlocksRef.current.add(block.id) await convertCheckboxToTask(block.id, text.trim()) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx index 48d1934e7..a0a171b02 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx @@ -45,7 +45,11 @@ export const TaskBlockRenderer: FC = ({ block, editor, c const [showPriorityPicker, setShowPriorityPicker] = useState(false) const [showProjectPicker, setShowProjectPicker] = useState(false) const [showDatePicker, setShowDatePicker] = useState(false) + const [isEditingTitle, setIsEditingTitle] = useState(!title || title === '') + const [editTitle, setEditTitle] = useState(title) + const titleInputRef = useRef(null) const dateInputRef = useRef(null) + const titleSaveTimeoutRef = useRef | null>(null) const displayTitle = task?.title ?? title const displayChecked = task ? !!task.completedAt : checked @@ -63,9 +67,12 @@ export const TaskBlockRenderer: FC = ({ block, editor, c checked: !!task.completedAt } }) + if (!isEditingTitle) { + setEditTitle(task.title) + } syncingRef.current = false } - }, [task, block, editor]) + }, [task, block, editor, isEditingTitle]) const handleToggle = useCallback(async () => { if (!taskId) return @@ -83,10 +90,62 @@ export const TaskBlockRenderer: FC = ({ block, editor, c }, [block, editor]) const handleTitleClick = useCallback(() => { - if (taskId) { - window.dispatchEvent(new CustomEvent('task-block:open-detail', { detail: { taskId } })) + setIsEditingTitle(true) + setEditTitle(displayTitle) + setTimeout(() => titleInputRef.current?.focus(), 0) + }, [displayTitle]) + + const saveTitleToDb = useCallback( + async (newTitle: string) => { + if (!taskId || !newTitle.trim()) return + editor.updateBlock(block, { props: { ...block.props, title: newTitle.trim() } }) + await tasksService.update({ id: taskId, title: newTitle.trim() }) + }, + [taskId, block, editor] + ) + + const handleTitleChange = useCallback( + (value: string) => { + setEditTitle(value) + if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) + titleSaveTimeoutRef.current = setTimeout(() => void saveTitleToDb(value), 600) + }, + [saveTitleToDb] + ) + + const handleTitleBlur = useCallback(() => { + if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) + if (editTitle.trim()) { + void saveTitleToDb(editTitle) } - }, [taskId]) + setIsEditingTitle(false) + }, [editTitle, saveTitleToDb]) + + const handleTitleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault() + handleTitleBlur() + } + }, + [handleTitleBlur] + ) + + useEffect(() => { + if (isEditingTitle && titleInputRef.current) { + titleInputRef.current.focus() + titleInputRef.current.setSelectionRange( + titleInputRef.current.value.length, + titleInputRef.current.value.length + ) + } + }, [isEditingTitle]) + + useEffect(() => { + return () => { + if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) + } + }, []) const handlePriorityChange = useCallback( async (value: number) => { @@ -201,16 +260,32 @@ export const TaskBlockRenderer: FC = ({ block, editor, c {/* Title */} - + {isEditingTitle ? ( + handleTitleChange(e.target.value)} + onBlur={handleTitleBlur} + onKeyDown={handleTitleKeyDown} + className={cn( + 'min-w-0 flex-1 bg-transparent text-sm font-medium outline-none', + 'placeholder:text-muted-foreground' + )} + placeholder="Task name..." + /> + ) : ( + + )} {/* Inline controls — always visible */}
From 8c50d43350761c4f918291b30352090d96db9ef3 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 22:56:27 +0300 Subject: [PATCH 10/24] fix: scan all blocks for checkListItems instead of relying on cursor - Previous approach used editor.getTextCursorPosition() which missed blocks created via /checklist or when cursor moved - Now scans editor.document tree for any checkListItem with text - Handles nested blocks (children) too - Fixes: /checklist then typing, and other creation paths --- .../note/content-area/ContentArea.tsx | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 60823f643..0347d0b19 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -322,21 +322,30 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ ) const autoCreateTaskFromCheckbox = useCallback(async () => { - const cursor = editor.getTextCursorPosition() - if (!cursor?.block) return - - const block = cursor.block - if (block.type !== 'checkListItem') return - if (dismissedBlocksRef.current.has(block.id)) return - - const content = block.content as any[] - if (!content?.length) return + const extractText = (content: any): string => { + if (!content) return '' + if (typeof content === 'string') return content + if (!Array.isArray(content)) return '' + return content.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))).join('') + } - const text = content.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))).join('') - if (!text.trim()) return + const scanBlocks = (blocks: any[]): void => { + for (const block of blocks) { + if (block.type === 'checkListItem' && !dismissedBlocksRef.current.has(block.id)) { + const text = extractText(block.content) + if (text.trim()) { + dismissedBlocksRef.current.add(block.id) + void convertCheckboxToTask(block.id, text.trim()) + return + } + } + if (block.children?.length) { + scanBlocks(block.children) + } + } + } - dismissedBlocksRef.current.add(block.id) - await convertCheckboxToTask(block.id, text.trim()) + scanBlocks(editor.document as any[]) }, [editor, convertCheckboxToTask]) const handleEditorContextMenu = useCallback( From af9eb93a98257a7cd754e531eb2db735a082448e Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 23:05:05 +0300 Subject: [PATCH 11/24] refactor: match task list row styling, reuse InlineStatusPopover and InlinePriorityPopover - Layout matches task-row.tsx: status icon | priority bars | title | due date - Reuses InlineStatusPopover with project-specific statuses - Reuses InlinePriorityPopover with keyboard shortcuts - Status click opens dropdown with all project statuses (not checkbox toggle) - Auto-complete when selecting 'done' type status - Due date on right side only, shows 'Done' when completed - Title editable inline with debounced save - Cursor stays on task line after conversion --- .../note/content-area/ContentArea.tsx | 5 + .../task-block/task-block-renderer.tsx | 355 ++++++------------ 2 files changed, 123 insertions(+), 237 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 0347d0b19..8544c00ed 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -313,6 +313,11 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ type: 'taskBlock' as any, props: { taskId: result.task.id, title: titleText, checked: false } }) + try { + editor.setTextCursorPosition(block.id, 'end') + } catch { + // taskBlock has content:'none', cursor placement may not apply + } } } catch { dismissedBlocksRef.current.delete(blockId) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx index a0a171b02..eb7acd1bd 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx @@ -1,10 +1,14 @@ import { type FC, useCallback, useEffect, useRef, useState } from 'react' -import { Check, AlertTriangle, Loader2, X, Calendar, Flag, Folder } from 'lucide-react' +import { AlertTriangle, Loader2, X } from 'lucide-react' import { cn } from '@/lib/utils' import { useTaskBlockData } from './use-task-block-data' import { useTasksOptional } from '@/contexts/tasks' import { tasksService } from '@/services/tasks-service' -import { PRIORITY_CSS_VARS, type Priority } from '@/data/sample-tasks' +import type { Priority } from '@/data/sample-tasks' +import { defaultStatuses, type Status } from '@/data/tasks-data' +import { InlineStatusPopover } from '@/components/tasks/inline-status-popover' +import { InlinePriorityPopover } from '@/components/tasks/inline-priority-popover' +import { formatDueDate } from '@/lib/task-utils/task-formatting' interface TaskBlockRendererProps { block: { id: string; props: { taskId: string; title: string; checked: boolean } } @@ -20,7 +24,7 @@ const DB_PRIORITY_MAP: Record = { 4: 'urgent' } -const PRIORITY_NUM_MAP: Record = { +const PRIORITY_REVERSE: Record = { none: 0, low: 1, medium: 2, @@ -28,32 +32,49 @@ const PRIORITY_NUM_MAP: Record = { urgent: 4 } -const PRIORITY_OPTIONS: { value: number; label: string; color: string | null }[] = [ - { value: 0, label: 'None', color: null }, - { value: 1, label: 'Low', color: 'var(--task-priority-low)' }, - { value: 2, label: 'Medium', color: 'var(--task-priority-medium)' }, - { value: 3, label: 'High', color: 'var(--task-priority-high)' }, - { value: 4, label: 'Urgent', color: 'var(--task-priority-urgent)' } -] - export const TaskBlockRenderer: FC = ({ block, editor, contentRef }) => { const { taskId, title, checked } = block.props const { task, isLoading, isDeleted } = useTaskBlockData(taskId) const tasksCtx = useTasksOptional() const syncingRef = useRef(false) - const [showPriorityPicker, setShowPriorityPicker] = useState(false) - const [showProjectPicker, setShowProjectPicker] = useState(false) - const [showDatePicker, setShowDatePicker] = useState(false) - const [isEditingTitle, setIsEditingTitle] = useState(!title || title === '') + const [isEditingTitle, setIsEditingTitle] = useState(!title) const [editTitle, setEditTitle] = useState(title) const titleInputRef = useRef(null) - const dateInputRef = useRef(null) const titleSaveTimeoutRef = useRef | null>(null) const displayTitle = task?.title ?? title - const displayChecked = task ? !!task.completedAt : checked + const isCompleted = task ? !!task.completedAt : checked + + const priorityNum = + typeof task?.priority === 'number' + ? task.priority + : typeof task?.priority === 'string' + ? (PRIORITY_REVERSE[task.priority] ?? 0) + : 0 + const priority: Priority = DB_PRIORITY_MAP[priorityNum] ?? 'none' + + const projects = tasksCtx?.projects ?? [] + const project = projects.find((p) => p.id === task?.projectId) + const statuses: Status[] = (project?.statuses as Status[]) ?? defaultStatuses + const statusId = task?.statusId ?? statuses[0]?.id ?? '' + + const dueDate = task?.dueDate ? new Date(task.dueDate) : null + const dueTime = (task as any)?.dueTime ?? null + const formattedDate = formatDueDate(dueDate, dueTime) + const isOverdue = formattedDate?.status === 'overdue' && !isCompleted + const currentStatus = statuses.find((s) => s.id === statusId) + const statusColor = currentStatus?.color || '#6B7280' + + const dueDateDisplay = (() => { + if (isCompleted) return { text: 'Done', colorStyle: statusColor } + if (!formattedDate) return null + if (isOverdue) return { text: formattedDate.label, colorClass: 'text-destructive' } + return { text: formattedDate.label, colorClass: 'text-text-tertiary' } + })() + + // Sync block props with DB useEffect(() => { if (!task || syncingRef.current) return const needsUpdate = @@ -61,40 +82,14 @@ export const TaskBlockRenderer: FC = ({ block, editor, c if (needsUpdate) { syncingRef.current = true editor.updateBlock(block, { - props: { - ...block.props, - title: task.title, - checked: !!task.completedAt - } + props: { ...block.props, title: task.title, checked: !!task.completedAt } }) - if (!isEditingTitle) { - setEditTitle(task.title) - } + if (!isEditingTitle) setEditTitle(task.title) syncingRef.current = false } }, [task, block, editor, isEditingTitle]) - const handleToggle = useCallback(async () => { - if (!taskId) return - const newChecked = !displayChecked - editor.updateBlock(block, { props: { ...block.props, checked: newChecked } }) - if (newChecked) { - await tasksService.complete({ id: taskId }) - } else { - await tasksService.uncomplete(taskId) - } - }, [taskId, displayChecked, block, editor]) - - const handleRemoveGhost = useCallback(() => { - editor.removeBlocks([block]) - }, [block, editor]) - - const handleTitleClick = useCallback(() => { - setIsEditingTitle(true) - setEditTitle(displayTitle) - setTimeout(() => titleInputRef.current?.focus(), 0) - }, [displayTitle]) - + // Title editing const saveTitleToDb = useCallback( async (newTitle: string) => { if (!taskId || !newTitle.trim()) return @@ -115,9 +110,7 @@ export const TaskBlockRenderer: FC = ({ block, editor, c const handleTitleBlur = useCallback(() => { if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) - if (editTitle.trim()) { - void saveTitleToDb(editTitle) - } + if (editTitle.trim()) void saveTitleToDb(editTitle) setIsEditingTitle(false) }, [editTitle, saveTitleToDb]) @@ -131,6 +124,18 @@ export const TaskBlockRenderer: FC = ({ block, editor, c [handleTitleBlur] ) + const handleTitleClick = useCallback(() => { + setIsEditingTitle(true) + setEditTitle(displayTitle) + setTimeout(() => { + titleInputRef.current?.focus() + titleInputRef.current?.setSelectionRange( + titleInputRef.current.value.length, + titleInputRef.current.value.length + ) + }, 0) + }, [displayTitle]) + useEffect(() => { if (isEditingTitle && titleInputRef.current) { titleInputRef.current.focus() @@ -147,39 +152,46 @@ export const TaskBlockRenderer: FC = ({ block, editor, c } }, []) - const handlePriorityChange = useCallback( - async (value: number) => { + // Status change + const handleStatusChange = useCallback( + async (newStatusId: string) => { if (!taskId) return - setShowPriorityPicker(false) - await tasksService.update({ id: taskId, priority: value }) + await tasksService.update({ id: taskId, statusId: newStatusId }) }, [taskId] ) - const handleProjectChange = useCallback( - async (projectId: string) => { - if (!taskId) return - setShowProjectPicker(false) - await tasksService.update({ id: taskId, projectId }) - }, - [taskId] - ) + const handleToggleComplete = useCallback(async () => { + if (!taskId) return + const newChecked = !isCompleted + editor.updateBlock(block, { props: { ...block.props, checked: newChecked } }) + if (newChecked) { + await tasksService.complete({ id: taskId }) + } else { + await tasksService.uncomplete(taskId) + } + }, [taskId, isCompleted, block, editor]) - const handleDueDateChange = useCallback( - async (dateStr: string) => { + // Priority change + const handlePriorityChange = useCallback( + async (newPriority: Priority) => { if (!taskId) return - setShowDatePicker(false) - await tasksService.update({ id: taskId, dueDate: dateStr || null }) + await tasksService.update({ id: taskId, priority: PRIORITY_REVERSE[newPriority] ?? 0 }) }, [taskId] ) + const handleRemoveGhost = useCallback(() => { + editor.removeBlocks([block]) + }, [block, editor]) + + // Loading state if (!taskId) { return (
Creating task... @@ -187,12 +199,13 @@ export const TaskBlockRenderer: FC = ({ block, editor, c ) } + // Ghost state if (isDeleted) { return (
{displayTitle} @@ -208,58 +221,32 @@ export const TaskBlockRenderer: FC = ({ block, editor, c ) } - const priorityNum = - typeof task?.priority === 'number' - ? task.priority - : typeof task?.priority === 'string' - ? (PRIORITY_NUM_MAP[task.priority] ?? 0) - : 0 - const priorityKey = DB_PRIORITY_MAP[priorityNum] ?? 'none' - const priorityVars = PRIORITY_CSS_VARS[priorityKey] - - const projects = tasksCtx?.projects ?? [] - const projectName = projects.find((p) => p.id === task?.projectId)?.name ?? 'Inbox' - - const dueDate = task?.dueDate ? new Date(task.dueDate) : null - const isOverdue = dueDate instanceof Date && dueDate < new Date() && !displayChecked - - const formatDue = (d: Date | null): string => { - if (!d || !(d instanceof Date)) return 'No date' - return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) - } - - const dueDateIso = task?.dueDate - ? typeof task.dueDate === 'string' - ? task.dueDate.slice(0, 10) - : '' - : '' - return (
- {/* Checkbox */} - + {/* Status (cycles through project statuses) */} +
e.stopPropagation()}> + +
+ + {/* Priority */} +
e.stopPropagation()}> + +
- {/* Title */} + {/* Title — editable */} {isEditingTitle ? ( = ({ block, editor, c onBlur={handleTitleBlur} onKeyDown={handleTitleKeyDown} className={cn( - 'min-w-0 flex-1 bg-transparent text-sm font-medium outline-none', - 'placeholder:text-muted-foreground' + 'grow shrink min-w-0 bg-transparent text-[13px] font-medium outline-none', + 'text-foreground/90 placeholder:text-muted-foreground' )} placeholder="Task name..." /> ) : ( - + {displayTitle || 'Untitled task'} + )} - {/* Inline controls — always visible */} -
- {/* Priority */} -
- - {showPriorityPicker && ( -
- {PRIORITY_OPTIONS.map((opt) => ( - - ))} -
- )} -
- - {/* Due date */} -
- - {showDatePicker && ( -
- handleDueDateChange(e.target.value)} - onBlur={() => setShowDatePicker(false)} - className="rounded-md border border-stone-200 bg-white px-2 py-1 text-xs shadow-lg dark:border-stone-700 dark:bg-stone-800" - /> -
- )} -
- - {/* Project */} -
- - {showProjectPicker && ( -
- {projects.map((p) => ( - - ))} -
+ {/* Due date — right side */} + {dueDateDisplay && ( +
+ {dueDateDisplay.text}
-
+ )} - {isLoading && } + {isLoading && }
) } From 52f0a1cc0d3c6673b50dd2f340450e5a3b68c8aa Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 23:17:34 +0300 Subject: [PATCH 12/24] fix: hide toolbar, add project badge, remove border, add navigate icon - Hide empty BlockNote formatting toolbar on taskBlock via scoped + {/* Status (cycles through project statuses) */}
e.stopPropagation()}> = ({ block, editor, c )} + {project && ( +
+
+
+ {project.name} +
+
+ )} + {/* Due date — right side */} {dueDateDisplay && (
= ({ block, editor, c
)} + + {isLoading && }
) From 2518798c9f277bee1a1cd71add1cb8eaf2953d61 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 23:20:56 +0300 Subject: [PATCH 13/24] feat: add quick-add parsing and task delete sync Parse \!today, \!\!high, #project syntax in checkbox-to-task and /task slash command via parseQuickAdd. Delete backend tasks when taskBlock is removed from the editor. --- .../note/content-area/ContentArea.tsx | 55 +++++++++++++------ .../note/content-area/task-block/index.tsx | 14 ++++- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 8544c00ed..6891cf070 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -34,6 +34,8 @@ import { getTaskSlashMenuItem } from './task-block' import { isLikelyTask } from './task-block/task-block-utils' import { tasksService } from '@/services/tasks-service' import { useTasksOptional } from '@/contexts/tasks' +import { parseQuickAdd } from '@/lib/quick-add-parser' +import { formatDateKey } from '@/lib/task-utils' import { editorSchema } from './editor-schema' import { HighlightReminderPopover, @@ -57,6 +59,8 @@ import { extractDomain, fetchLinkPreview } from '@/lib/url-metadata' import { createLinkMentionContent } from './link-mention' import type { PasteLinkOption } from './hooks/use-paste-link-menu' +const PRIORITY_REVERSE: Record = { none: 0, low: 1, medium: 2, high: 3, urgent: 4 } + function findBlockWithLinkMention( blocks: any[], url: string @@ -113,6 +117,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const [highlightSelection, setHighlightSelection] = useState(null) const taskDetectTimeoutRef = useRef | null>(null) const dismissedBlocksRef = useRef(new Set()) + const knownTaskBlockIdsRef = useRef>(new Set()) const editorContainerRef = useRef(null) const containerRef = useRef(null) const noteIdRef = useRef(noteId) @@ -284,34 +289,34 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ window.getSelection()?.removeAllRanges() }, []) - const resolveDefaultProject = useCallback(async (): Promise<{ id: string } | null> => { - const ctxProjects = tasksCtx?.projects ?? [] - if (ctxProjects.length > 0) { - return ctxProjects.find((p: any) => p.isDefault || p.isInbox) ?? ctxProjects[0] ?? null - } - const res = await tasksService.listProjects() - const dbProjects = res.projects ?? [] - return dbProjects.find((p: any) => p.isDefault || p.isInbox) ?? dbProjects[0] ?? null - }, [tasksCtx]) - const convertCheckboxToTask = useCallback( async (blockId: string, titleText: string) => { - const project = await resolveDefaultProject() - if (!project) return + let projects = tasksCtx?.projects ?? [] + if (projects.length === 0) { + const res = await tasksService.listProjects() + projects = res.projects ?? [] + } + + const defaultProject = projects.find((p: any) => p.isDefault || p.isInbox) ?? projects[0] + if (!defaultProject) return + + const parsed = parseQuickAdd(titleText, projects as any[]) const block = editor.getBlock(blockId) if (!block) return try { const result = await tasksService.create({ - projectId: project.id, - title: titleText, + projectId: parsed.projectId ?? defaultProject.id, + title: parsed.title, + priority: PRIORITY_REVERSE[parsed.priority] ?? 0, + dueDate: parsed.dueDate ? formatDateKey(parsed.dueDate) : null, linkedNoteIds: noteId ? [noteId] : [] }) if (result.success && result.task) { editor.updateBlock(block, { type: 'taskBlock' as any, - props: { taskId: result.task.id, title: titleText, checked: false } + props: { taskId: result.task.id, title: parsed.title, checked: false } }) try { editor.setTextCursorPosition(block.id, 'end') @@ -323,7 +328,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ dismissedBlocksRef.current.delete(blockId) } }, - [editor, noteId, resolveDefaultProject] + [editor, noteId, tasksCtx] ) const autoCreateTaskFromCheckbox = useCallback(async () => { @@ -424,6 +429,24 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ void handleChange() if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) taskDetectTimeoutRef.current = setTimeout(() => void autoCreateTaskFromCheckbox(), 800) + + const currentTaskIds = new Set() + const scanTaskIds = (blocks: any[]): void => { + for (const b of blocks) { + if (b.type === 'taskBlock' && b.props?.taskId) { + currentTaskIds.add(b.props.taskId as string) + } + if (b.children?.length) scanTaskIds(b.children) + } + } + scanTaskIds(editor.document as any[]) + + for (const prevId of knownTaskBlockIdsRef.current) { + if (!currentTaskIds.has(prevId)) { + void tasksService.delete(prevId) + } + } + knownTaskBlockIdsRef.current = currentTaskIds }} theme={editorTheme} formattingToolbar={!stickyToolbar} diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx index 7b157808f..6ea0ee6b9 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx @@ -1,6 +1,10 @@ import { createReactBlockSpec } from '@blocknote/react' import { TaskBlockRenderer } from './task-block-renderer' import { tasksService } from '@/services/tasks-service' +import { parseQuickAdd } from '@/lib/quick-add-parser' +import { formatDateKey } from '@/lib/task-utils' + +const PRIORITY_REVERSE: Record = { none: 0, low: 1, medium: 2, high: 3, urgent: 4 } export const createTaskBlock = createReactBlockSpec( { @@ -40,14 +44,18 @@ export function getTaskSlashMenuItem(editor: any) { const defaultProject = projects.find((p: any) => p.isDefault || p.isInbox) ?? projects[0] if (!defaultProject) return + const parsed = parseQuickAdd(text, projects as any[]) + const result = await tasksService.create({ - projectId: defaultProject.id, - title: text + projectId: parsed.projectId ?? defaultProject.id, + title: parsed.title, + priority: PRIORITY_REVERSE[parsed.priority] ?? 0, + dueDate: parsed.dueDate ? formatDateKey(parsed.dueDate) : null }) if (result.success && result.task) { editor.updateBlock(currentBlock, { type: 'taskBlock' as any, - props: { taskId: result.task.id, title: text, checked: false } + props: { taskId: result.task.id, title: parsed.title, checked: false } }) } }, From 64e7240e38999503abfd31704bffcb4892de18c6 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Sun, 5 Apr 2026 23:22:13 +0300 Subject: [PATCH 14/24] fix: resolve ProjectWithStats type mismatch in convertCheckboxToTask --- .../renderer/src/components/note/content-area/ContentArea.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 6891cf070..453c2d552 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -291,7 +291,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const convertCheckboxToTask = useCallback( async (blockId: string, titleText: string) => { - let projects = tasksCtx?.projects ?? [] + let projects: any[] = tasksCtx?.projects ?? [] if (projects.length === 0) { const res = await tasksService.listProjects() projects = res.projects ?? [] From 203756bccaaf9a19fb71e78cf55b71a32af486b3 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Mon, 6 Apr 2026 00:11:25 +0300 Subject: [PATCH 15/24] fix: debounce task deletion scan to prevent race condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deletion scan was running immediately on every onChange, causing false positives during block type transitions. Now debounced to 2 seconds — gives editor.updateBlock time to complete before checking for removed taskBlocks. --- .../note/content-area/ContentArea.tsx | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 453c2d552..3a5d2393b 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -116,6 +116,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const tasksCtx = useTasksOptional() const [highlightSelection, setHighlightSelection] = useState(null) const taskDetectTimeoutRef = useRef | null>(null) + const taskDeleteTimeoutRef = useRef | null>(null) const dismissedBlocksRef = useRef(new Set()) const knownTaskBlockIdsRef = useRef>(new Set()) const editorContainerRef = useRef(null) @@ -385,6 +386,7 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ useEffect(() => { return () => { if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) + if (taskDeleteTimeoutRef.current) clearTimeout(taskDeleteTimeoutRef.current) } }, []) @@ -430,23 +432,26 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) taskDetectTimeoutRef.current = setTimeout(() => void autoCreateTaskFromCheckbox(), 800) - const currentTaskIds = new Set() - const scanTaskIds = (blocks: any[]): void => { - for (const b of blocks) { - if (b.type === 'taskBlock' && b.props?.taskId) { - currentTaskIds.add(b.props.taskId as string) + if (taskDeleteTimeoutRef.current) clearTimeout(taskDeleteTimeoutRef.current) + taskDeleteTimeoutRef.current = setTimeout(() => { + const currentTaskIds = new Set() + const scanTaskIds = (blocks: any[]): void => { + for (const b of blocks) { + if (b.type === 'taskBlock' && b.props?.taskId) { + currentTaskIds.add(b.props.taskId as string) + } + if (b.children?.length) scanTaskIds(b.children) } - if (b.children?.length) scanTaskIds(b.children) } - } - scanTaskIds(editor.document as any[]) + scanTaskIds(editor.document as any[]) - for (const prevId of knownTaskBlockIdsRef.current) { - if (!currentTaskIds.has(prevId)) { - void tasksService.delete(prevId) + for (const prevId of knownTaskBlockIdsRef.current) { + if (!currentTaskIds.has(prevId)) { + void tasksService.delete(prevId) + } } - } - knownTaskBlockIdsRef.current = currentTaskIds + knownTaskBlockIdsRef.current = currentTaskIds + }, 2000) }} theme={editorTheme} formattingToolbar={!stickyToolbar} From c5219d0375555dcabc9368b42ceaeb6c96ed8b7c Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Mon, 6 Apr 2026 00:16:52 +0300 Subject: [PATCH 16/24] =?UTF-8?q?fix:=20stop=20hiding=20block=20content=20?= =?UTF-8?q?=E2=80=94=20only=20hide=20empty=20formatting=20toolbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous CSS rule hid .bn-inline-content which is the container BlockNote uses to render our custom TaskBlockRenderer. This made the entire task block invisible. The formatting toolbar is a separate floating element, not inside .bn-inline-content. --- .../note/content-area/ContentArea.tsx | 31 ++++++++----------- .../task-block/task-block-renderer.tsx | 2 +- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 3a5d2393b..453c2d552 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -116,7 +116,6 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const tasksCtx = useTasksOptional() const [highlightSelection, setHighlightSelection] = useState(null) const taskDetectTimeoutRef = useRef | null>(null) - const taskDeleteTimeoutRef = useRef | null>(null) const dismissedBlocksRef = useRef(new Set()) const knownTaskBlockIdsRef = useRef>(new Set()) const editorContainerRef = useRef(null) @@ -386,7 +385,6 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ useEffect(() => { return () => { if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) - if (taskDeleteTimeoutRef.current) clearTimeout(taskDeleteTimeoutRef.current) } }, []) @@ -432,26 +430,23 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) taskDetectTimeoutRef.current = setTimeout(() => void autoCreateTaskFromCheckbox(), 800) - if (taskDeleteTimeoutRef.current) clearTimeout(taskDeleteTimeoutRef.current) - taskDeleteTimeoutRef.current = setTimeout(() => { - const currentTaskIds = new Set() - const scanTaskIds = (blocks: any[]): void => { - for (const b of blocks) { - if (b.type === 'taskBlock' && b.props?.taskId) { - currentTaskIds.add(b.props.taskId as string) - } - if (b.children?.length) scanTaskIds(b.children) + const currentTaskIds = new Set() + const scanTaskIds = (blocks: any[]): void => { + for (const b of blocks) { + if (b.type === 'taskBlock' && b.props?.taskId) { + currentTaskIds.add(b.props.taskId as string) } + if (b.children?.length) scanTaskIds(b.children) } - scanTaskIds(editor.document as any[]) + } + scanTaskIds(editor.document as any[]) - for (const prevId of knownTaskBlockIdsRef.current) { - if (!currentTaskIds.has(prevId)) { - void tasksService.delete(prevId) - } + for (const prevId of knownTaskBlockIdsRef.current) { + if (!currentTaskIds.has(prevId)) { + void tasksService.delete(prevId) } - knownTaskBlockIdsRef.current = currentTaskIds - }, 2000) + } + knownTaskBlockIdsRef.current = currentTaskIds }} theme={editorTheme} formattingToolbar={!stickyToolbar} diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx index b6d0b56ed..f5d92804b 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx @@ -231,7 +231,7 @@ export const TaskBlockRenderer: FC = ({ block, editor, c )} > From e46265f01f8f375335aa4ec7846963ecf339a7b7 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Mon, 6 Apr 2026 00:20:06 +0300 Subject: [PATCH 17/24] fix: remove focus border on taskBlock, keep cursor in title input - CSS removes border/outline/box-shadow on focused taskBlock at all levels - Title input starts in editing mode so cursor stays after conversion --- .../note/content-area/task-block/task-block-renderer.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx index f5d92804b..7dfc4541e 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx @@ -38,7 +38,7 @@ export const TaskBlockRenderer: FC = ({ block, editor, c const tasksCtx = useTasksOptional() const syncingRef = useRef(false) - const [isEditingTitle, setIsEditingTitle] = useState(!title) + const [isEditingTitle, setIsEditingTitle] = useState(true) const [editTitle, setEditTitle] = useState(title) const titleInputRef = useRef(null) const titleSaveTimeoutRef = useRef | null>(null) @@ -233,6 +233,10 @@ export const TaskBlockRenderer: FC = ({ block, editor, c {/* Status (cycles through project statuses) */} From 3c5fd4f6187c9792edf892bedda2fbbc25ef28e0 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Mon, 6 Apr 2026 00:21:57 +0300 Subject: [PATCH 18/24] fix: only auto-focus title input on freshly created taskBlocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isNewBlockRef tracks first mount — only enters editing mode when taskId exists but task data hasn't loaded yet (= just created) - Existing blocks loaded from markdown start in read mode --- .../content-area/task-block/task-block-renderer.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx index 7dfc4541e..000b4c93b 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx @@ -38,11 +38,22 @@ export const TaskBlockRenderer: FC = ({ block, editor, c const tasksCtx = useTasksOptional() const syncingRef = useRef(false) - const [isEditingTitle, setIsEditingTitle] = useState(true) + const isNewBlockRef = useRef(true) + const [isEditingTitle, setIsEditingTitle] = useState(false) const [editTitle, setEditTitle] = useState(title) const titleInputRef = useRef(null) const titleSaveTimeoutRef = useRef | null>(null) + useEffect(() => { + if (isNewBlockRef.current && taskId && !task) { + setIsEditingTitle(true) + setEditTitle(title) + } + if (task) { + isNewBlockRef.current = false + } + }, [taskId, task, title]) + const displayTitle = task?.title ?? title const isCompleted = task ? !!task.completedAt : checked From 4d1809e490e708fa96b5a9e0c9a3d9a6a8b32d2a Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Tue, 7 Apr 2026 00:55:22 +0300 Subject: [PATCH 19/24] refactor: add renderTitle, actions, and project change props to TaskRow Extract serviceTaskToDisplayTask and PRIORITY_REVERSE to task-block-utils so the renderer can reuse TaskRow instead of duplicating inline controls. Add InteractiveProjectBadge integration when onProjectChange is provided. --- .../task-block/task-block-utils.ts | 54 +++++++++++++++++++ .../src/components/tasks/task-row.tsx | 49 ++++++++++++----- 2 files changed, 89 insertions(+), 14 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-utils.ts b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-utils.ts index eec2c51aa..36cfe6045 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-utils.ts +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-utils.ts @@ -1,4 +1,58 @@ import type { Block } from '@blocknote/core' +import type { + Task as DisplayTask, + Priority, + RepeatConfig as DisplayRepeatConfig +} from '@/data/sample-tasks' +import type { Task as ServiceTask } from '@/services/tasks-service' + +const DB_PRIORITY_MAP: Record = { + 0: 'none', + 1: 'low', + 2: 'medium', + 3: 'high', + 4: 'urgent' +} + +export const PRIORITY_REVERSE: Record = { + none: 0, + low: 1, + medium: 2, + high: 3, + urgent: 4 +} + +export function serviceTaskToDisplayTask(task: ServiceTask, fallbackStatusId: string): DisplayTask { + let repeatConfig: DisplayRepeatConfig | null = null + if (task.repeatConfig) { + const rc = task.repeatConfig + repeatConfig = { + ...rc, + endDate: rc.endDate ? new Date(rc.endDate) : null, + createdAt: new Date(rc.createdAt) + } + } + + return { + id: task.id, + title: task.title, + description: task.description ?? '', + projectId: task.projectId, + statusId: task.statusId ?? fallbackStatusId, + priority: DB_PRIORITY_MAP[task.priority] ?? 'none', + dueDate: task.dueDate ? new Date(task.dueDate) : null, + dueTime: task.dueTime ?? null, + isRepeating: task.repeatConfig !== null, + repeatConfig, + linkedNoteIds: task.linkedNoteIds ?? [], + sourceNoteId: task.sourceNoteId, + parentId: task.parentId, + subtaskIds: [], + createdAt: new Date(task.createdAt), + completedAt: task.completedAt ? new Date(task.completedAt) : null, + archivedAt: task.archivedAt ? new Date(task.archivedAt) : null + } +} const ACTION_VERBS = new Set([ 'add', diff --git a/apps/desktop/src/renderer/src/components/tasks/task-row.tsx b/apps/desktop/src/renderer/src/components/tasks/task-row.tsx index efc089aa8..6a7836b1f 100644 --- a/apps/desktop/src/renderer/src/components/tasks/task-row.tsx +++ b/apps/desktop/src/renderer/src/components/tasks/task-row.tsx @@ -2,6 +2,7 @@ import { cn } from '@/lib/utils' import { formatDueDate, formatDateShort, formatTime } from '@/lib/task-utils' import { InlineStatusPopover } from '@/components/tasks/inline-status-popover' import { InlinePriorityPopover } from '@/components/tasks/inline-priority-popover' +import { InteractiveProjectBadge } from '@/components/tasks/interactive-project-badge' import { SelectionCheckbox } from '@/components/tasks/bulk-actions' import { RepeatIndicator } from '@/components/tasks/repeat-indicator' import type { Task } from '@/data/sample-tasks' @@ -26,6 +27,9 @@ interface TaskRowProps { isCheckedForSelection?: boolean onToggleSelect?: (taskId: string) => void onShiftSelect?: (taskId: string) => void + onProjectChange?: (projectId: string) => void + actions?: React.ReactNode + renderTitle?: () => React.ReactNode } // ============================================================================ @@ -50,7 +54,7 @@ const resolveStatus = ( export const TaskRow = ({ task, project, - projects: _projects, + projects, isCompleted, isSelected = false, showProjectBadge = false, @@ -61,7 +65,10 @@ export const TaskRow = ({ isSelectionMode = false, isCheckedForSelection = false, onToggleSelect, - onShiftSelect + onShiftSelect, + onProjectChange, + actions, + renderTitle }: TaskRowProps): React.JSX.Element => { const formattedDate = formatDueDate(task.dueDate, task.dueTime) const isOverdue = formattedDate?.status === 'overdue' && !isCompleted @@ -147,29 +154,41 @@ export const TaskRow = ({ onPriorityChange={(priority) => onUpdateTask?.(task.id, { priority })} /> - - {task.title} - + {renderTitle ? ( + renderTitle() + ) : ( + + {task.title} + + )} {task.isRepeating && task.repeatConfig && ( )} - {showProjectBadge && ( + {showProjectBadge && onProjectChange ? ( +
e.stopPropagation()}> + +
+ ) : showProjectBadge ? (
{project.name}
- )} + ) : null} {dueDateDisplay && (
)} + + {actions}
) } From 3ebf6292a5d0615dfa94f7d1b9923add594e93e8 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Tue, 7 Apr 2026 00:55:35 +0300 Subject: [PATCH 20/24] feat: add Enter/Escape key handling and focus management for task blocks Rewrite TaskBlockRenderer to use TaskRow with inline title editing. Enter + title creates new task block below with correct focus. Enter + empty converts to paragraph with blinking cursor. Escape exits edit mode without creating new block. Key fixes: - Set isNewBlockRef=false on Enter to prevent auto-enter effect from re-entering edit mode when async task creation completes - Hold syncingRef across async save boundary to prevent sync effect from reverting title with stale DB value - Use removeBlocks+insertBlocks+editor.focus() for paragraph conversion Also refactors ContentArea: convertCheckboxToTask is now sync-first (immediate type conversion, async DB creation), adds createTaskForDraftBlock for Enter-spawned blocks, and detects draft task blocks in onChange. --- .../note/content-area/ContentArea.tsx | 197 +++++--- .../note/content-area/task-block/index.tsx | 6 +- .../task-block/task-block-renderer.tsx | 473 ++++++++++-------- 3 files changed, 388 insertions(+), 288 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index 453c2d552..a96c06e20 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -31,7 +31,6 @@ import { WikiLinkPreviewCard } from './wiki-link-preview-card' import { BlockDropIndicator, EmptyDocumentDropIndicator } from './block-drop-indicator' import { getCalloutSlashMenuItem } from './callout-block' import { getTaskSlashMenuItem } from './task-block' -import { isLikelyTask } from './task-block/task-block-utils' import { tasksService } from '@/services/tasks-service' import { useTasksOptional } from '@/contexts/tasks' import { parseQuickAdd } from '@/lib/quick-add-parser' @@ -115,7 +114,6 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const tasksCtx = useTasksOptional() const [highlightSelection, setHighlightSelection] = useState(null) - const taskDetectTimeoutRef = useRef | null>(null) const dismissedBlocksRef = useRef(new Set()) const knownTaskBlockIdsRef = useRef>(new Set()) const editorContainerRef = useRef(null) @@ -290,76 +288,114 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ }, []) const convertCheckboxToTask = useCallback( - async (blockId: string, titleText: string) => { - let projects: any[] = tasksCtx?.projects ?? [] - if (projects.length === 0) { - const res = await tasksService.listProjects() - projects = res.projects ?? [] - } - - const defaultProject = projects.find((p: any) => p.isDefault || p.isInbox) ?? projects[0] - if (!defaultProject) return - - const parsed = parseQuickAdd(titleText, projects as any[]) + (blockId: string) => { + dismissedBlocksRef.current.add(blockId) const block = editor.getBlock(blockId) if (!block) return - try { - const result = await tasksService.create({ - projectId: parsed.projectId ?? defaultProject.id, - title: parsed.title, - priority: PRIORITY_REVERSE[parsed.priority] ?? 0, - dueDate: parsed.dueDate ? formatDateKey(parsed.dueDate) : null, - linkedNoteIds: noteId ? [noteId] : [] - }) - if (result.success && result.task) { - editor.updateBlock(block, { - type: 'taskBlock' as any, - props: { taskId: result.task.id, title: parsed.title, checked: false } + const content = block.content as any[] | undefined + const text = + content + ?.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))) + .join('') + .trim() ?? '' + + editor.updateBlock(block, { + type: 'taskBlock' as any, + props: { taskId: '', title: text, checked: false } + }) + + void (async () => { + let projects: any[] = tasksCtx?.projects ?? [] + if (projects.length === 0) { + const res = await tasksService.listProjects() + projects = res.projects ?? [] + } + + const defaultProject = projects.find((p: any) => p.isDefault || p.isInbox) ?? projects[0] + if (!defaultProject) return + + const parsed = text + ? parseQuickAdd(text, projects as any[]) + : { title: '', priority: 'none', projectId: null, dueDate: null } + + try { + const result = await tasksService.create({ + projectId: parsed.projectId ?? defaultProject.id, + title: parsed.title, + priority: PRIORITY_REVERSE[parsed.priority] ?? 0, + dueDate: parsed.dueDate ? formatDateKey(parsed.dueDate) : null, + linkedNoteIds: noteId ? [noteId] : [] }) - try { - editor.setTextCursorPosition(block.id, 'end') - } catch { - // taskBlock has content:'none', cursor placement may not apply + if (result.success && result.task) { + const freshBlock = editor.getBlock(blockId) + if (freshBlock) { + const currentTitle = (freshBlock.props as any).title || parsed.title + editor.updateBlock(freshBlock, { + props: { taskId: result.task.id, title: currentTitle, checked: false } + }) + if (currentTitle && currentTitle !== result.task.title) { + void tasksService.update({ id: result.task.id, title: currentTitle }) + } + } } + } catch { + dismissedBlocksRef.current.delete(blockId) } - } catch { - dismissedBlocksRef.current.delete(blockId) - } + })() }, [editor, noteId, tasksCtx] ) - const autoCreateTaskFromCheckbox = useCallback(async () => { - const extractText = (content: any): string => { - if (!content) return '' - if (typeof content === 'string') return content - if (!Array.isArray(content)) return '' - return content.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))).join('') - } + const createTaskForDraftBlock = useCallback( + (blockId: string, title: string) => { + dismissedBlocksRef.current.add(blockId) - const scanBlocks = (blocks: any[]): void => { - for (const block of blocks) { - if (block.type === 'checkListItem' && !dismissedBlocksRef.current.has(block.id)) { - const text = extractText(block.content) - if (text.trim()) { - dismissedBlocksRef.current.add(block.id) - void convertCheckboxToTask(block.id, text.trim()) - return - } - } - if (block.children?.length) { - scanBlocks(block.children) + void (async () => { + let projects: any[] = tasksCtx?.projects ?? [] + if (projects.length === 0) { + const res = await tasksService.listProjects() + projects = res.projects ?? [] } - } - } - scanBlocks(editor.document as any[]) - }, [editor, convertCheckboxToTask]) + const defaultProject = projects.find((p: any) => p.isDefault || p.isInbox) ?? projects[0] + if (!defaultProject) return + + const parsed = title + ? parseQuickAdd(title, projects as any[]) + : { title: '', priority: 'none', projectId: null, dueDate: null } + + try { + const result = await tasksService.create({ + projectId: parsed.projectId ?? defaultProject.id, + title: parsed.title, + priority: PRIORITY_REVERSE[parsed.priority] ?? 0, + dueDate: parsed.dueDate ? formatDateKey(parsed.dueDate) : null, + linkedNoteIds: noteId ? [noteId] : [] + }) + if (result.success && result.task) { + const freshBlock = editor.getBlock(blockId) + if (freshBlock) { + const currentTitle = (freshBlock.props as any).title || parsed.title + editor.updateBlock(freshBlock, { + props: { taskId: result.task.id, title: currentTitle, checked: false } + }) + if (currentTitle && currentTitle !== result.task.title) { + void tasksService.update({ id: result.task.id, title: currentTitle }) + } + } + } + } catch { + dismissedBlocksRef.current.delete(blockId) + } + })() + }, + [editor, noteId, tasksCtx] + ) const handleEditorContextMenu = useCallback( - async (e: React.MouseEvent) => { + (e: React.MouseEvent) => { const target = e.target as HTMLElement const checkListBlock = target.closest('[data-content-type="checkListItem"]') if (!checkListBlock) return @@ -370,24 +406,12 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ const block = editor.getBlock(blockId) if (!block || block.type !== 'checkListItem') return - const content = block.content as any[] - const text = - content?.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))).join('') ?? '' - if (!text.trim()) return - e.preventDefault() - dismissedBlocksRef.current.add(blockId) - await convertCheckboxToTask(blockId, text.trim()) + convertCheckboxToTask(blockId) }, [editor, convertCheckboxToTask] ) - useEffect(() => { - return () => { - if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) - } - }, []) - return (
{ void handleChange() - if (taskDetectTimeoutRef.current) clearTimeout(taskDetectTimeoutRef.current) - taskDetectTimeoutRef.current = setTimeout(() => void autoCreateTaskFromCheckbox(), 800) const currentTaskIds = new Set() - const scanTaskIds = (blocks: any[]): void => { + let firstUndismissedCheckbox: string | null = null + let firstDraftTaskBlock: { id: string; title: string } | null = null + + const scanBlocks = (blocks: any[]): void => { for (const b of blocks) { if (b.type === 'taskBlock' && b.props?.taskId) { currentTaskIds.add(b.props.taskId as string) } - if (b.children?.length) scanTaskIds(b.children) + if ( + b.type === 'taskBlock' && + !b.props?.taskId && + b.props?.title?.trim() && + !firstDraftTaskBlock && + !dismissedBlocksRef.current.has(b.id) + ) { + firstDraftTaskBlock = { id: b.id, title: b.props.title as string } + } + if ( + b.type === 'checkListItem' && + !firstUndismissedCheckbox && + !dismissedBlocksRef.current.has(b.id) + ) { + firstUndismissedCheckbox = b.id + } + if (b.children?.length) scanBlocks(b.children) } } - scanTaskIds(editor.document as any[]) + scanBlocks(editor.document as any[]) + + if (firstUndismissedCheckbox) { + convertCheckboxToTask(firstUndismissedCheckbox) + } + + if (firstDraftTaskBlock) { + createTaskForDraftBlock(firstDraftTaskBlock.id, firstDraftTaskBlock.title) + } for (const prevId of knownTaskBlockIdsRef.current) { if (!currentTaskIds.has(prevId)) { diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx index 6ea0ee6b9..1c5870ffb 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/index.tsx @@ -37,14 +37,16 @@ export function getTaskSlashMenuItem(editor: any) { content ?.map((c: any) => (typeof c === 'string' ? c : (c.text ?? ''))) .join('') - .trim() || 'New task' + .trim() || '' const res = await tasksService.listProjects() const projects = res.projects ?? [] const defaultProject = projects.find((p: any) => p.isDefault || p.isInbox) ?? projects[0] if (!defaultProject) return - const parsed = parseQuickAdd(text, projects as any[]) + const parsed = text + ? parseQuickAdd(text, projects as any[]) + : { title: '', priority: 'none' as const, projectId: null, dueDate: null } const result = await tasksService.create({ projectId: parsed.projectId ?? defaultProject.id, diff --git a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx index 000b4c93b..1cd0000c0 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/task-block/task-block-renderer.tsx @@ -1,14 +1,14 @@ -import { type FC, useCallback, useEffect, useRef, useState } from 'react' +import { type FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { AlertTriangle, ArrowUpRight, Loader2, X } from 'lucide-react' import { cn } from '@/lib/utils' import { useTaskBlockData } from './use-task-block-data' +import { serviceTaskToDisplayTask, PRIORITY_REVERSE } from './task-block-utils' import { useTasksOptional } from '@/contexts/tasks' +import { useTabActions } from '@/contexts/tabs' import { tasksService } from '@/services/tasks-service' -import type { Priority } from '@/data/sample-tasks' +import type { Task as DisplayTask } from '@/data/sample-tasks' import { defaultStatuses, type Status } from '@/data/tasks-data' -import { InlineStatusPopover } from '@/components/tasks/inline-status-popover' -import { InlinePriorityPopover } from '@/components/tasks/inline-priority-popover' -import { formatDueDate } from '@/lib/task-utils/task-formatting' +import { TaskRow } from '@/components/tasks/task-row' interface TaskBlockRendererProps { block: { id: string; props: { taskId: string; title: string; checked: boolean } } @@ -16,76 +16,106 @@ interface TaskBlockRendererProps { contentRef: React.Ref } -const DB_PRIORITY_MAP: Record = { - 0: 'none', - 1: 'low', - 2: 'medium', - 3: 'high', - 4: 'urgent' -} - -const PRIORITY_REVERSE: Record = { - none: 0, - low: 1, - medium: 2, - high: 3, - urgent: 4 -} +const BLOCKNOTE_OVERRIDES = ` + .bn-formatting-toolbar:empty { display: none !important; } + .bn-block-content[data-content-type="taskBlock"] { cursor: default; } + .bn-block[data-id]:has([data-content-type="taskBlock"]) { border: none !important; outline: none !important; box-shadow: none !important; } + .bn-block[data-id]:has([data-content-type="taskBlock"]):focus-within { border: none !important; outline: none !important; box-shadow: none !important; } + .bn-block-content[data-content-type="taskBlock"]:focus { outline: none !important; border: none !important; } + [data-content-type="taskBlock"] * { outline: none !important; } +` export const TaskBlockRenderer: FC = ({ block, editor, contentRef }) => { const { taskId, title, checked } = block.props const { task, isLoading, isDeleted } = useTaskBlockData(taskId) const tasksCtx = useTasksOptional() + const { openTab } = useTabActions() const syncingRef = useRef(false) const isNewBlockRef = useRef(true) - const [isEditingTitle, setIsEditingTitle] = useState(false) + const wasDraftRef = useRef(!taskId) + const [isEditingTitle, setIsEditingTitle] = useState(!taskId) const [editTitle, setEditTitle] = useState(title) const titleInputRef = useRef(null) const titleSaveTimeoutRef = useRef | null>(null) + const skipBlurRef = useRef(false) + + const projects = tasksCtx?.projects ?? [] + const defaultProject = projects.find((p: any) => p.isDefault || p.isInbox) ?? projects[0] + const project = projects.find((p) => p.id === task?.projectId) ?? defaultProject + const statuses: Status[] = (project?.statuses as Status[]) ?? defaultStatuses + const isCompleted = task ? !!task.completedAt : checked + const placeholderTask: import('@/data/sample-tasks').Task = useMemo( + () => ({ + id: '', + title, + description: '', + projectId: project?.id ?? '', + statusId: statuses[0]?.id ?? '', + priority: 'none' as const, + dueDate: null, + dueTime: null, + isRepeating: false, + repeatConfig: null, + linkedNoteIds: [], + sourceNoteId: null, + parentId: null, + subtaskIds: [], + createdAt: new Date(), + completedAt: null, + archivedAt: null + }), + [project?.id, statuses, title] + ) + + const displayTask = useMemo( + () => (task ? serviceTaskToDisplayTask(task, statuses[0]?.id ?? '') : null), + [task, statuses] + ) + + // Auto-enter edit mode for newly created blocks useEffect(() => { if (isNewBlockRef.current && taskId && !task) { setIsEditingTitle(true) - setEditTitle(title) + if (!wasDraftRef.current) setEditTitle(title) } if (task) { isNewBlockRef.current = false + if (wasDraftRef.current) { + wasDraftRef.current = false + if (editTitle.trim() && task.title !== editTitle.trim()) { + void tasksService.update({ id: taskId, title: editTitle.trim() }) + editor.updateBlock(block, { + props: { ...block.props, title: editTitle.trim() } + }) + } + } } - }, [taskId, task, title]) - - const displayTitle = task?.title ?? title - const isCompleted = task ? !!task.completedAt : checked + }, [taskId, task, title, editTitle, block, editor]) - const priorityNum = - typeof task?.priority === 'number' - ? task.priority - : typeof task?.priority === 'string' - ? (PRIORITY_REVERSE[task.priority] ?? 0) - : 0 - const priority: Priority = DB_PRIORITY_MAP[priorityNum] ?? 'none' - - const projects = tasksCtx?.projects ?? [] - const project = projects.find((p) => p.id === task?.projectId) - const statuses: Status[] = (project?.statuses as Status[]) ?? defaultStatuses - const statusId = task?.statusId ?? statuses[0]?.id ?? '' - - const dueDate = task?.dueDate ? new Date(task.dueDate) : null - const dueTime = (task as any)?.dueTime ?? null - const formattedDate = formatDueDate(dueDate, dueTime) - const isOverdue = formattedDate?.status === 'overdue' && !isCompleted - - const currentStatus = statuses.find((s) => s.id === statusId) - const statusColor = currentStatus?.color || '#6B7280' - - const dueDateDisplay = (() => { - if (isCompleted) return { text: 'Done', colorStyle: statusColor } - if (!formattedDate) return null - if (isOverdue) return { text: formattedDate.label, colorClass: 'text-destructive' } - return { text: formattedDate.label, colorClass: 'text-text-tertiary' } - })() + // Focus title input when editing starts (double-rAF to beat ProseMirror focus restoration) + useEffect(() => { + if (!isEditingTitle) return + let cancelled = false + const rafId = requestAnimationFrame(() => { + requestAnimationFrame(() => { + if (!cancelled && titleInputRef.current) { + titleInputRef.current.focus() + titleInputRef.current.setSelectionRange( + titleInputRef.current.value.length, + titleInputRef.current.value.length + ) + } + }) + }) + return () => { + cancelled = true + cancelAnimationFrame(rafId) + } + }, [isEditingTitle]) - // Sync block props with DB + // Sync block props with DB state (for markdown serialization) useEffect(() => { if (!task || syncingRef.current) return const needsUpdate = @@ -100,12 +130,29 @@ export const TaskBlockRenderer: FC = ({ block, editor, c } }, [task, block, editor, isEditingTitle]) - // Title editing + // Cleanup debounce timer + useEffect(() => { + return () => { + if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) + } + }, []) + + // --- Title editing handlers --- + const saveTitleToDb = useCallback( async (newTitle: string) => { - if (!taskId || !newTitle.trim()) return + if (!newTitle.trim()) return + syncingRef.current = true editor.updateBlock(block, { props: { ...block.props, title: newTitle.trim() } }) - await tasksService.update({ id: taskId, title: newTitle.trim() }) + if (taskId) { + try { + await tasksService.update({ id: taskId, title: newTitle.trim() }) + } finally { + syncingRef.current = false + } + } else { + syncingRef.current = false + } }, [taskId, block, editor] ) @@ -120,6 +167,10 @@ export const TaskBlockRenderer: FC = ({ block, editor, c ) const handleTitleBlur = useCallback(() => { + if (skipBlurRef.current) { + skipBlurRef.current = false + return + } if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) if (editTitle.trim()) void saveTitleToDb(editTitle) setIsEditingTitle(false) @@ -127,67 +178,92 @@ export const TaskBlockRenderer: FC = ({ block, editor, c const handleTitleKeyDown = useCallback( (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault() + skipBlurRef.current = true + if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) + if (editTitle.trim()) void saveTitleToDb(editTitle) + setIsEditingTitle(false) + return + } + if (e.key === 'Enter') { e.preventDefault() - handleTitleBlur() + skipBlurRef.current = true + if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) + + const trimmed = editTitle.trim() + if (trimmed) { + isNewBlockRef.current = false + void saveTitleToDb(trimmed) + setIsEditingTitle(false) + editor.insertBlocks( + [{ type: 'taskBlock' as any, props: { taskId: '', title: '', checked: false } }], + block, + 'after' + ) + } else { + isNewBlockRef.current = false + setIsEditingTitle(false) + if (taskId) void tasksService.delete(taskId) + const doc = editor.document as any[] + const blockIdx = doc.findIndex((b: any) => b.id === block.id) + const anchor = blockIdx > 0 ? doc[blockIdx - 1] : null + editor.removeBlocks([block]) + const updatedDoc = editor.document as any[] + if (anchor) { + editor.insertBlocks([{ type: 'paragraph' as any }], anchor, 'after') + } else if (updatedDoc.length > 0) { + editor.insertBlocks([{ type: 'paragraph' as any }], updatedDoc[0], 'before') + } + requestAnimationFrame(() => { + const finalDoc = editor.document as any[] + const para = finalDoc[blockIdx] ?? finalDoc[finalDoc.length - 1] + if (para) { + editor.setTextCursorPosition(para.id, 'start') + editor.focus() + } + }) + } } }, - [handleTitleBlur] + [editor, block, taskId, editTitle, saveTitleToDb] ) - const handleTitleClick = useCallback(() => { - setIsEditingTitle(true) - setEditTitle(displayTitle) - setTimeout(() => { - titleInputRef.current?.focus() - titleInputRef.current?.setSelectionRange( - titleInputRef.current.value.length, - titleInputRef.current.value.length - ) - }, 0) - }, [displayTitle]) - - useEffect(() => { - if (isEditingTitle && titleInputRef.current) { - titleInputRef.current.focus() - titleInputRef.current.setSelectionRange( - titleInputRef.current.value.length, - titleInputRef.current.value.length - ) - } - }, [isEditingTitle]) - - useEffect(() => { - return () => { - if (titleSaveTimeoutRef.current) clearTimeout(titleSaveTimeoutRef.current) - } - }, []) + // --- Task action handlers --- + + const handleToggleComplete = useCallback( + async (taskIdArg: string) => { + if (!taskIdArg) return + const newChecked = !isCompleted + editor.updateBlock(block, { props: { ...block.props, checked: newChecked } }) + if (newChecked) { + await tasksService.complete({ id: taskIdArg }) + } else { + await tasksService.uncomplete(taskIdArg) + } + }, + [isCompleted, block, editor] + ) - // Status change - const handleStatusChange = useCallback( - async (newStatusId: string) => { + const handleUpdateTask = useCallback( + async (_taskId: string, updates: Partial) => { if (!taskId) return - await tasksService.update({ id: taskId, statusId: newStatusId }) + await tasksService.update({ + id: taskId, + ...(updates.statusId !== undefined && { statusId: updates.statusId }), + ...(updates.priority !== undefined && { + priority: PRIORITY_REVERSE[updates.priority] ?? 0 + }) + }) }, [taskId] ) - const handleToggleComplete = useCallback(async () => { - if (!taskId) return - const newChecked = !isCompleted - editor.updateBlock(block, { props: { ...block.props, checked: newChecked } }) - if (newChecked) { - await tasksService.complete({ id: taskId }) - } else { - await tasksService.uncomplete(taskId) - } - }, [taskId, isCompleted, block, editor]) - - // Priority change - const handlePriorityChange = useCallback( - async (newPriority: Priority) => { + const handleProjectChange = useCallback( + async (projectId: string) => { if (!taskId) return - await tasksService.update({ id: taskId, priority: PRIORITY_REVERSE[newPriority] ?? 0 }) + await tasksService.update({ id: taskId, projectId }) }, [taskId] ) @@ -196,30 +272,63 @@ export const TaskBlockRenderer: FC = ({ block, editor, c editor.removeBlocks([block]) }, [block, editor]) - // Loading state - if (!taskId) { - return ( -
( +
- ) - } + + + ), + [openTab, taskId, task?.projectId] + ) + + const titleInput = useCallback( + () => ( + handleTitleChange(e.target.value)} + onBlur={handleTitleBlur} + onKeyDown={handleTitleKeyDown} + className="grow shrink min-w-0 bg-transparent text-[13px] font-medium outline-none text-foreground/90 placeholder:text-muted-foreground" + placeholder="Task name..." + /> + ), + [editTitle, handleTitleChange, handleTitleBlur, handleTitleKeyDown] + ) + + // --- Render states --- - // Ghost state if (isDeleted) { return (
- {displayTitle} + {task?.title ?? title} Task deleted - - {isLoading && } + +
) } From 03b6848a26ac076999569366d868e19530720e5e Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Tue, 7 Apr 2026 00:55:39 +0300 Subject: [PATCH 21/24] fix: apply activeTab from viewState when navigating from task block --- apps/desktop/src/renderer/src/pages/tasks.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/src/pages/tasks.tsx b/apps/desktop/src/renderer/src/pages/tasks.tsx index 5dcfd6725..bfe39534d 100644 --- a/apps/desktop/src/renderer/src/pages/tasks.tsx +++ b/apps/desktop/src/renderer/src/pages/tasks.tsx @@ -195,6 +195,7 @@ export const TasksPage = ({ const lastAppliedTaskId = useRef(null) const incomingTaskId = (activeTab?.viewState?.openTaskId as string) ?? null const incomingProjectId = (activeTab?.viewState?.selectedProjectId as string) ?? null + const incomingActiveTab = (activeTab?.viewState?.activeTab as TasksInternalTab) ?? null useEffect(() => { if (!incomingTaskId || incomingTaskId === lastAppliedTaskId.current) return @@ -204,7 +205,10 @@ export const TasksPage = ({ setSelectedProjectId(incomingProjectId) hasAppliedDefaultProject.current = true } - }, [incomingTaskId, incomingProjectId]) + if (incomingActiveTab) { + setActiveInternalTab(incomingActiveTab) + } + }, [incomingTaskId, incomingProjectId, incomingActiveTab]) // Modal states const [isAddTaskModalOpen, setIsAddTaskModalOpen] = useState(false) From b0abff403b19eb258a351ecf190793763fbc53ff Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Tue, 7 Apr 2026 00:55:42 +0300 Subject: [PATCH 22/24] docs: add inline task blocks changelog entry --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85058a25b..2246efc59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ Format: weekly entries grouped by feature area. --- +## 2026-04-07 — Inline Task Blocks + +### Added +- Add custom task block for BlockNote editor with live DB sync and inline editing +- Add `[] ` bracket shortcut and `/task` slash command to create task blocks +- Add right-click context menu to promote checkboxes to linked tasks +- Add quick-add syntax parsing in task blocks (project, priority, due date) +- Add Enter key continuation: create new task block after typing title +- Add Enter on empty task block to exit to paragraph with cursor focus +- Add Escape key to exit task editing without creating new block +- Add draft task block detection in editor onChange for DB task creation +- Add task block markdown serialization with `{task:id}` suffix round-trip + +### Fixed +- Fix focus jumping to previous task block when pressing Enter before async task creation completes +- Fix sync effect reverting title to stale DB value during save (hold syncingRef across async boundary) +- Fix paragraph conversion from task block not receiving browser focus (add editor.focus() after cursor placement) +- Fix task deletion scan race condition with debounced block scanning + +### Changed +- Add `renderTitle` prop to TaskRow for inline title editing in task blocks + +--- + ## 2026-04-05 — Inbox Detail Panel Polish ### Added From d9b93061e1e27b20321f6dac8da75193104d2915 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Tue, 7 Apr 2026 00:58:57 +0300 Subject: [PATCH 23/24] chore: regenerate IPC invoke map --- .../src/main/ipc/generated-ipc-invoke-map.ts | 3122 ++--------------- 1 file changed, 309 insertions(+), 2813 deletions(-) diff --git a/apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts b/apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts index 0c5e30721..77c11f3b2 100644 --- a/apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts +++ b/apps/desktop/src/main/ipc/generated-ipc-invoke-map.ts @@ -2,2819 +2,315 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ export interface MainIpcInvokeHandlers { - 'account:getInfo': (...args: []) => Awaited - 'account:getRecoveryKey': ( - ...args: [] - ) => Awaited< - Promise< - | { success: boolean; error: string; key?: undefined } - | { success: boolean; key: string; error?: undefined } - > - > - 'account:signOut': ( - ...args: [] - ) => Awaited> - 'ai-inline:get-server-port': (...args: []) => Awaited - 'ai-inline:get-settings': ( - ...args: [] - ) => Awaited - 'ai-inline:set-settings': ( - ...args: [ - Partial - ] - ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - 'ai-inline:start-server': ( - ...args: [] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; error: string; port?: undefined } - | { success: boolean; port: number; error?: undefined } - > - > - 'ai-inline:stop-server': (...args: []) => Awaited> - 'auth:init-oauth': (...args: [{ provider: 'google' }]) => Awaited> - 'auth:refresh-token': ( - ...args: [] - ) => Awaited> - 'auth:request-otp': (...args: [{ email: string }]) => Awaited> - 'auth:resend-otp': (...args: [{ email: string }]) => Awaited> - 'auth:verify-otp': (...args: [{ email: string; code: string }]) => Awaited< - Promise<{ - success: boolean - isNewUser: boolean - needsSetup: boolean - needsRecoveryInput: boolean - }> - > - 'bookmarks:bulk-create': ( - ...args: [{ items: { itemType: string; itemId: string }[] }] - ) => Awaited> - 'bookmarks:bulk-delete': ( - ...args: [{ bookmarkIds: string[] }] - ) => Awaited> - 'bookmarks:create': (...args: [{ itemType: string; itemId: string }]) => Awaited< - Promise< - | { success: boolean; bookmark: null; error: string } - | { - success: boolean - bookmark: { - id: string - createdAt: string - position: number - itemType: string - itemId: string - } - error?: undefined - } - > - > - 'bookmarks:delete': ( - ...args: [string] - ) => Awaited< - Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - > - 'bookmarks:get': (...args: [string]) => Awaited< - Promise<{ - id: string - createdAt: string - position: number - itemType: string - itemId: string - } | null> - > - 'bookmarks:get-by-item': (...args: [{ itemType: string; itemId: string }]) => Awaited< - Promise<{ - id: string - createdAt: string - position: number - itemType: string - itemId: string - } | null> - > - 'bookmarks:is-bookmarked': ( - ...args: [{ itemType: string; itemId: string }] - ) => Awaited> - 'bookmarks:list': ( - ...args: [ - { - itemType?: string | undefined - sortBy?: 'createdAt' | 'position' | undefined - sortOrder?: 'asc' | 'desc' | undefined - limit?: number | undefined - offset?: number | undefined - } - ] - ) => Awaited< - Promise - > - 'bookmarks:list-by-type': ( - ...args: [string] - ) => Awaited< - Promise - > - 'bookmarks:reorder': ( - ...args: [{ bookmarkIds: string[] }] - ) => Awaited> - 'bookmarks:toggle': (...args: [{ itemType: string; itemId: string }]) => Awaited< - Promise<{ - success: boolean - isBookmarked: boolean - bookmark: { - id: string - createdAt: string - position: number - itemType: string - itemId: string - } | null - }> - > - 'context-menu:show': ( - ...args: [ - { - id: string - label: string - accelerator?: string | undefined - disabled?: boolean | undefined - type?: 'normal' | 'separator' | undefined - }[] - ] - ) => Awaited> - 'crdt:apply-update': (...args: [unknown]) => Awaited> - 'crdt:close-doc': (...args: [unknown]) => Awaited> - 'crdt:open-doc': ( - ...args: [unknown] - ) => Awaited< - Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - > - 'crdt:sync-step-1': ( - ...args: [{ noteId: string; stateVector: number[] }] - ) => Awaited< - Promise - > - 'crdt:sync-step-2': (...args: [{ noteId: string; diff: number[] }]) => Awaited> - 'crypto:decrypt-item': ( - ...args: [ - { - itemId: string - type: - | 'note' - | 'filter' - | 'task' - | 'project' - | 'journal' - | 'settings' - | 'inbox' - | 'tag_definition' - encryptedKey: string - keyNonce: string - encryptedData: string - dataNonce: string - signature: string - operation?: 'create' | 'update' | 'delete' | undefined - deletedAt?: number | undefined - metadata?: Record | undefined - } - ] - ) => Awaited< - Promise - > - 'crypto:encrypt-item': ( - ...args: [ - { - itemId: string - type: - | 'note' - | 'filter' - | 'task' - | 'project' - | 'journal' - | 'settings' - | 'inbox' - | 'tag_definition' - content: Record - operation?: 'create' | 'update' | 'delete' | undefined - deletedAt?: number | undefined - metadata?: Record | undefined - } - ] - ) => Awaited< - Promise - > - 'crypto:get-rotation-progress': ( - ...args: [] - ) => Awaited - 'crypto:rotate-keys': ( - ...args: [{ confirm: boolean }] - ) => Awaited> - 'crypto:verify-signature': ( - ...args: [ - { - itemId: string - type: - | 'note' - | 'filter' - | 'task' - | 'project' - | 'journal' - | 'settings' - | 'inbox' - | 'tag_definition' - encryptedKey: string - keyNonce: string - encryptedData: string - dataNonce: string - signature: string - operation?: 'create' | 'update' | 'delete' | undefined - deletedAt?: number | undefined - metadata?: Record | undefined - } - ] - ) => Awaited< - Promise - > - 'folder-view:delete-view': ( - ...args: [{ folderPath: string; viewName: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/folder-view-api').DeleteViewResponse - > - > - 'folder-view:folder-exists': (...args: [string]) => Awaited - 'folder-view:get-available-properties': ( - ...args: [{ folderPath: string }] - ) => Awaited< - Promise< - import('../../../../../packages/contracts/src/folder-view-api').GetAvailablePropertiesResponse - > - > - 'folder-view:get-config': ( - ...args: [{ folderPath: string }] - ) => Awaited< - Promise - > - 'folder-view:get-folder-suggestions': ( - ...args: [{ noteId: string }] - ) => Awaited< - Promise< - import('../../../../../packages/contracts/src/folder-view-api').GetFolderSuggestionsResponse - > - > - 'folder-view:get-views': ( - ...args: [{ folderPath: string }] - ) => Awaited< - Promise - > - 'folder-view:list-with-properties': ( - ...args: [ - { - folderPath: string - properties?: string[] | undefined - limit?: number | undefined - offset?: number | undefined - } - ] - ) => Awaited< - Promise< - import('../../../../../packages/contracts/src/folder-view-api').ListWithPropertiesResponse - > - > - 'folder-view:set-config': ( - ...args: [ - { - folderPath: string - config: { - path?: string | undefined - template?: string | undefined - inherit?: boolean | undefined - formulas?: Record | undefined - properties?: - | Record< - string, - { - displayName?: string | undefined - color?: boolean | undefined - dateFormat?: string | undefined - numberFormat?: string | undefined - hidden?: boolean | undefined - } - > - | undefined - summaries?: - | Record< - string, - { - type: - | 'custom' - | 'count' - | 'sum' - | 'average' - | 'min' - | 'max' - | 'countBy' - | 'countUnique' - label?: string | undefined - expression?: string | undefined - } - > - | undefined - views?: - | { - name: string - type?: 'table' | 'grid' | 'list' | 'kanban' | undefined - default?: boolean | undefined - columns?: - | { - id: string - width?: number | undefined - displayName?: string | undefined - showSummary?: boolean | undefined - }[] - | undefined - filters?: unknown - order?: { property: string; direction: 'asc' | 'desc' }[] | undefined - groupBy?: - | { - property: string - direction?: 'asc' | 'desc' | undefined - collapsed?: boolean | undefined - showSummary?: boolean | undefined - } - | undefined - limit?: number | undefined - showSummaries?: boolean | undefined - }[] - | undefined - } - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/folder-view-api').SetConfigResponse - > - > - 'folder-view:set-view': ( - ...args: [ - { - folderPath: string - view: { - name: string - type?: 'table' | 'grid' | 'list' | 'kanban' | undefined - default?: boolean | undefined - columns?: - | { - id: string - width?: number | undefined - displayName?: string | undefined - showSummary?: boolean | undefined - }[] - | undefined - filters?: unknown - order?: { property: string; direction: 'asc' | 'desc' }[] | undefined - groupBy?: - | { - property: string - direction?: 'asc' | 'desc' | undefined - collapsed?: boolean | undefined - showSummary?: boolean | undefined - } - | undefined - limit?: number | undefined - showSummaries?: boolean | undefined - } - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/folder-view-api').SetViewResponse - > - > - 'graph:get-graph-data': (...args: []) => Awaited<{ - nodes: { - id: string - type: 'note' | 'task' | 'project' | 'journal' - label: string - tags: string[] - wordCount: number - connectionCount: number - emoji: string | null - color: string - isOrphan: boolean - isUnresolved: boolean - }[] - edges: { - id: string - source: string - target: string - type: 'wikilink' | 'task-note' | 'project-task' | 'tag-cooccurrence' - weight: number - }[] - }> - 'graph:get-local-graph': (...args: [{ noteId: string; depth?: number | undefined }]) => Awaited<{ - nodes: { - id: string - type: 'note' | 'task' | 'project' | 'journal' - label: string - tags: string[] - wordCount: number - connectionCount: number - emoji: string | null - color: string - isOrphan: boolean - isUnresolved: boolean - }[] - edges: { - id: string - source: string - target: string - type: 'wikilink' | 'task-note' | 'project-task' | 'tag-cooccurrence' - weight: number - }[] - }> - 'inbox:add-tag': ( - ...args: [any, any] - ) => Awaited> - 'inbox:archive': ( - ...args: [any] - ) => Awaited> - 'inbox:bulk-archive': ( - ...args: [any] - ) => Awaited> - 'inbox:bulk-file': ( - ...args: [any] - ) => Awaited> - 'inbox:bulk-snooze': (...args: [any]) => Awaited< - Promise<{ - success: boolean - processedCount: number - errors: { itemId: string; error: string }[] - }> - > - 'inbox:bulk-tag': ( - ...args: [any] - ) => Awaited> - 'inbox:capture-clip': ( - ...args: [any] - ) => Awaited> - 'inbox:capture-image': ( - ...args: [any] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/inbox-api').CaptureResponse - > - > - 'inbox:capture-link': ( - ...args: [any] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/inbox-api').CaptureResponse - > - > - 'inbox:capture-pdf': ( - ...args: [any] - ) => Awaited> - 'inbox:capture-text': ( - ...args: [any] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/inbox-api').CaptureResponse - > - > - 'inbox:capture-voice': ( - ...args: [any] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/inbox-api').CaptureResponse - > - > - 'inbox:convert-to-note': ( - ...args: [any] - ) => Awaited> - 'inbox:convert-to-task': ( - ...args: [any] - ) => Awaited> - 'inbox:delete-permanent': ( - ...args: [any] - ) => Awaited> - 'inbox:file': ( - ...args: [any] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/inbox-api').FileResponse - > - > - 'inbox:file-all-stale': ( - ...args: [] - ) => Awaited> - 'inbox:get': ( - ...args: [any] - ) => Awaited> - 'inbox:get-filing-history': ( - ...args: [any] - ) => Awaited< - Promise - > - 'inbox:get-patterns': ( - ...args: [] - ) => Awaited> - 'inbox:get-snoozed': (...args: []) => Awaited> - 'inbox:get-stale-threshold': (...args: []) => Awaited> - 'inbox:get-stats': ( - ...args: [] - ) => Awaited> - 'inbox:get-suggestions': ( - ...args: [any] - ) => Awaited< - Promise - > - 'inbox:get-tags': (...args: []) => Awaited> - 'inbox:link-to-note': ( - ...args: [any, any, any] - ) => Awaited> - 'inbox:list': ( - ...args: [any] - ) => Awaited> - 'inbox:list-archived': ( - ...args: [any] - ) => Awaited< - Promise - > - 'inbox:mark-viewed': ( - ...args: [any] - ) => Awaited> - 'inbox:preview-link': (...args: [string]) => Awaited< - Promise< - | { - title: string - domain: string - favicon: string | undefined - image: string | undefined - description: string | undefined - } - | { - title: string - domain: string - favicon?: undefined - image?: undefined - description?: undefined - } - > - > - 'inbox:remove-tag': ( - ...args: [any, any] - ) => Awaited> - 'inbox:retry-metadata': ( - ...args: [any] - ) => Awaited< - Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> - > - 'inbox:retry-transcription': ( - ...args: [any] - ) => Awaited< - Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> - > - 'inbox:set-stale-threshold': (...args: [any]) => Awaited> - 'inbox:snooze': ( - ...args: [any] - ) => Awaited< - Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> - > - 'inbox:track-suggestion': ( - ...args: [any, any, any, any, any, any, any] - ) => Awaited< - Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> - > - 'inbox:unarchive': ( - ...args: [any] - ) => Awaited> - 'inbox:undo-archive': ( - ...args: [any] - ) => Awaited> - 'inbox:undo-file': ( - ...args: [any] - ) => Awaited> - 'inbox:unsnooze': ( - ...args: [any] - ) => Awaited< - Promise<{ success: false; error: string } | { success: boolean; error?: string | undefined }> - > - 'inbox:update': ( - ...args: [any] - ) => Awaited> - 'journal:createEntry': ( - ...args: [ - { - date: string - content?: string | undefined - tags?: string[] | undefined - properties?: Record | undefined - } - ] - ) => Awaited< - Promise<{ - id: string - date: string - content: string - wordCount: number - characterCount: number - tags: string[] - createdAt: string - modifiedAt: string - properties?: Record | undefined - }> - > - 'journal:deleteEntry': (...args: [{ date: string }]) => Awaited> - 'journal:getAllTags': (...args: []) => Awaited> - 'journal:getDayContext': (...args: [{ date: string }]) => Awaited< - Promise<{ - date: string - tasks: { - id: string - title: string - completed: boolean - priority?: 'urgent' | 'high' | 'medium' | 'low' | undefined - isOverdue?: boolean | undefined - }[] - events: { - id: string - time: string - title: string - type: 'meeting' | 'focus' | 'event' - attendeeCount?: number | undefined - }[] - overdueCount: number - }> - > - 'journal:getEntry': (...args: [{ date: string }]) => Awaited< - Promise<{ - id: string - date: string - content: string - wordCount: number - characterCount: number - tags: string[] - createdAt: string - modifiedAt: string - properties?: Record | undefined - } | null> - > - 'journal:getHeatmap': ( - ...args: [{ year: number }] - ) => Awaited> - 'journal:getMonthEntries': (...args: [{ year: number; month: number }]) => Awaited< - Promise< - { - date: string - preview: string - wordCount: number - characterCount: number - activityLevel: 0 | 1 | 2 | 4 | 3 - tags: string[] - }[] - > - > - 'journal:getStreak': ( - ...args: [] - ) => Awaited< - Promise<{ currentStreak: number; longestStreak: number; lastEntryDate: string | null }> - > - 'journal:getYearStats': (...args: [{ year: number }]) => Awaited< - Promise< - { - year: number - month: number - entryCount: number - totalWordCount: number - totalCharacterCount: number - averageLevel: number - }[] - > - > - 'journal:updateEntry': ( - ...args: [ - { - date: string - content?: string | undefined - tags?: string[] | undefined - properties?: Record | undefined - } - ] - ) => Awaited< - Promise<{ - id: string - date: string - content: string - wordCount: number - characterCount: number - tags: string[] - createdAt: string - modifiedAt: string - properties?: Record | undefined - }> - > - 'notes:add-property-option': ( - ...args: [{ propertyName: string; option: { value: string; color: string } }] - ) => Awaited> - 'notes:add-status-option': ( - ...args: [ - { - propertyName: string - categoryKey: 'todo' | 'in_progress' | 'done' - option: { value: string; color: string } - } - ] - ) => Awaited> - 'notes:create': ( - ...args: [ - { - title: string - content?: string | undefined - folder?: string | undefined - tags?: string[] | undefined - template?: string | undefined - } - ] - ) => Awaited< - Promise< - { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } - > - > - 'notes:create-folder': ( - ...args: [string] - ) => Awaited> - 'notes:create-property-definition': ( - ...args: [ - { - name: string - type: 'number' | 'date' | 'text' | 'select' | 'checkbox' | 'url' | 'status' | 'multiselect' - options?: { value: string; color: string; default?: boolean | undefined }[] | undefined - defaultValue?: unknown - color?: string | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { - success: boolean - definition: - | import('../../../../../packages/contracts/src/property-types').PropertyDefinition - | undefined - } - | { - success: boolean - definition: { - type: string - name: string - createdAt: string - options: string | null - defaultValue: string | null - color: string | null - } - } - > - > - 'notes:delete': ( - ...args: [string] - ) => Awaited> - 'notes:delete-attachment': ( - ...args: [{ noteId: string; filename: string }] - ) => Awaited> - 'notes:delete-folder': ( - ...args: [string] - ) => Awaited> - 'notes:delete-property-definition': ( - ...args: [{ name: string }] - ) => Awaited> - 'notes:delete-version': ( - ...args: [string] - ) => Awaited> - 'notes:ensure-property-definition': ( - ...args: [{ name: string; type: 'select' | 'status' | 'multiselect' }] - ) => Awaited> - 'notes:exists': (...args: [string]) => Awaited> - 'notes:export-html': ( - ...args: [ - { - noteId: string - includeMetadata?: boolean | undefined - pageSize?: 'A4' | 'Letter' | 'Legal' | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; error: string; path?: undefined } - | { success: boolean; path: string; error?: undefined } - > - > - 'notes:export-pdf': ( - ...args: [ - { - noteId: string - includeMetadata?: boolean | undefined - pageSize?: 'A4' | 'Letter' | 'Legal' | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; error: string; path?: undefined } - | { success: boolean; path: string; error?: undefined } - > - > - 'notes:get': (...args: [string]) => Awaited> - 'notes:get-all-positions': ( - ...args: [] - ) => Awaited< - Promise< - { success: false; error: string } | { success: boolean; positions: Record } - > - > - 'notes:get-by-path': (...args: [string]) => Awaited> - 'notes:get-file': ( - ...args: [string] - ) => Awaited> - 'notes:get-folder-config': ( - ...args: [string] - ) => Awaited< - Promise - > - 'notes:get-folder-template': (...args: [string]) => Awaited> - 'notes:get-folders': ( - ...args: [] - ) => Awaited> - 'notes:get-links': ( - ...args: [string] - ) => Awaited> - 'notes:get-local-only-count': (...args: []) => Awaited> - 'notes:get-positions': ( - ...args: [{ folderPath: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; positions: { path: string; position: number; folderPath: string }[] } - > - > - 'notes:get-property-definitions': (...args: []) => Awaited< - Promise< - { - type: string - name: string - createdAt: string - options: string | null - defaultValue: string | null - color: string | null - }[] - > - > - 'notes:get-tags': ( - ...args: [] - ) => Awaited> - 'notes:get-version': ( - ...args: [string] - ) => Awaited> - 'notes:get-versions': ( - ...args: [string] - ) => Awaited> - 'notes:import-files': ( - ...args: [{ sourcePaths: string[]; targetFolder?: string | undefined }] - ) => Awaited< - Promise<{ success: false; error: string } | import('../vault/notes').ImportFilesResult> - > - 'notes:list': ( - ...args: [ - { - folder?: string | undefined - tags?: string[] | undefined - sortBy?: 'title' | 'modified' | 'created' | 'position' | undefined - sortOrder?: 'asc' | 'desc' | undefined - limit?: number | undefined - offset?: number | undefined - } - ] - ) => Awaited> - 'notes:list-attachments': ( - ...args: [string] - ) => Awaited> - 'notes:move': ( - ...args: [{ id: string; newFolder: string }] - ) => Awaited< - Promise< - { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } - > - > - 'notes:open-external': (...args: [string]) => Awaited> - 'notes:preview-by-title': (...args: [string]) => Awaited< - Promise<{ - id: string - title: string - emoji: string | null - snippet: string | null - tags: { name: string; color: string }[] - createdAt: string - } | null> - > - 'notes:remove-property-option': ( - ...args: [{ propertyName: string; optionValue: string }] - ) => Awaited> - 'notes:rename': ( - ...args: [{ id: string; newTitle: string }] - ) => Awaited< - Promise< - { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } - > - > - 'notes:rename-folder': ( - ...args: [{ oldPath: string; newPath: string }] - ) => Awaited> - 'notes:rename-property-option': ( - ...args: [{ propertyName: string; oldValue: string; newValue: string }] - ) => Awaited> - 'notes:reorder': ( - ...args: [{ folderPath: string; notePaths: string[] }] - ) => Awaited> - 'notes:resolve-by-title': (...args: [string]) => Awaited< - Promise<{ - id: string - path: string - title: string - fileType: import('../../../../../packages/shared/src/file-types').FileType - } | null> - > - 'notes:restore-version': ( - ...args: [string] - ) => Awaited< - Promise< - { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } - > - > - 'notes:reveal-in-finder': (...args: [string]) => Awaited> - 'notes:set-folder-config': ( - ...args: [ - { - folderPath: string - config: { - icon?: string | null | undefined - template?: string | undefined - inherit?: boolean | undefined - } - } - ] - ) => Awaited> - 'notes:set-local-only': ( - ...args: [{ id: string; localOnly: boolean }] - ) => Awaited< - Promise< - { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } - > - > - 'notes:show-import-dialog': ( - ...args: [] - ) => Awaited> - 'notes:update': ( - ...args: [ - { - id: string - title?: string | undefined - content?: string | undefined - tags?: string[] | undefined - frontmatter?: Record | undefined - emoji?: string | null | undefined - } - ] - ) => Awaited< - Promise< - { success: false; error: string } | { success: boolean; note: import('../vault/notes').Note } - > - > - 'notes:update-option-color': ( - ...args: [{ propertyName: string; optionValue: string; newColor: string }] - ) => Awaited> - 'notes:update-property-definition': ( - ...args: [ - { - name: string - type?: - | 'number' - | 'date' - | 'text' - | 'select' - | 'checkbox' - | 'url' - | 'status' - | 'multiselect' - | undefined - options?: { value: string; color: string; default?: boolean | undefined }[] | undefined - defaultValue?: unknown - color?: string | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; definition: null; error: string } - | { - success: boolean - definition: - | import('../../../../../packages/contracts/src/property-types').PropertyDefinition - | undefined - error?: undefined - } - | { - success: boolean - definition: - | { - type: string - name: string - createdAt: string - options: string | null - defaultValue: string | null - color: string | null - } - | undefined - error?: undefined - } - > - > - 'notes:upload-attachment': ( - ...args: [{ noteId: string; filename: string; data: number[] | ArrayBuffer }] - ) => Awaited> - 'properties:get': ( - ...args: [{ entityId: string }] - ) => Awaited> - 'properties:rename': ( - ...args: [{ entityId: string; oldName: string; newName: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/properties-api').RenamePropertyResponse - > - > - 'properties:set': ( - ...args: [{ entityId: string; properties: Record }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/properties-api').SetPropertiesResponse - > - > - 'quick-capture:get-clipboard': (...args: []) => Awaited - 'reminder:bulk-dismiss': ( - ...args: [{ reminderIds: string[] }] - ) => Awaited< - Promise<{ success: false; error: string } | { success: boolean; dismissedCount: number }> - > - 'reminder:count-pending': (...args: []) => Awaited> - 'reminder:create': ( - ...args: [ - | { - targetType: 'note' - targetId: string - remindAt: string - title?: string | undefined - note?: string | undefined - } - | { - targetType: 'journal' - targetId: string - remindAt: string - title?: string | undefined - note?: string | undefined - } - | { - targetType: 'highlight' - targetId: string - highlightText: string - highlightStart: number - highlightEnd: number - remindAt: string - title?: string | undefined - note?: string | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { - success: boolean - reminder: import('../../../../../packages/contracts/src/reminders-api').Reminder - } - > - > - 'reminder:delete': ( - ...args: [string] - ) => Awaited< - Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - > - 'reminder:dismiss': (...args: [string]) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; reminder: null; error: string } - | { - success: boolean - reminder: import('../../../../../packages/contracts/src/reminders-api').Reminder - error?: undefined - } - > - > - 'reminder:get': ( - ...args: [string] - ) => Awaited< - Promise - > - 'reminder:get-due': ( - ...args: [] - ) => Awaited< - Promise - > - 'reminder:get-for-target': ( - ...args: [{ targetType: 'note' | 'journal' | 'highlight'; targetId: string }] - ) => Awaited> - 'reminder:get-upcoming': (...args: [number | undefined]) => Awaited< - Promise<{ - reminders: import('../../../../../packages/contracts/src/reminders-api').ReminderWithTarget[] - total: number - hasMore: boolean - }> - > - 'reminder:list': ( - ...args: [ - { - targetType?: 'note' | 'journal' | 'highlight' | undefined - targetId?: string | undefined - status?: - | 'pending' - | 'triggered' - | 'dismissed' - | 'snoozed' - | ('pending' | 'triggered' | 'dismissed' | 'snoozed')[] - | undefined - fromDate?: string | undefined - toDate?: string | undefined - limit?: number | undefined - offset?: number | undefined - } - ] - ) => Awaited< - Promise<{ - reminders: import('../../../../../packages/contracts/src/reminders-api').ReminderWithTarget[] - total: number - hasMore: boolean - }> - > - 'reminder:snooze': (...args: [{ id: string; snoozeUntil: string }]) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; reminder: null; error: string } - | { - success: boolean - reminder: import('../../../../../packages/contracts/src/reminders-api').Reminder - error?: undefined - } - > - > - 'reminder:update': ( - ...args: [ - { - id: string - remindAt?: string | undefined - title?: string | null | undefined - note?: string | null | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; reminder: null; error: string } - | { - success: boolean - reminder: import('../../../../../packages/contracts/src/reminders-api').Reminder - error?: undefined - } - > - > - 'saved-filters:create': ( - ...args: [ - { - name: string - config: { - filters: { - search?: string | undefined - projectIds?: string[] | undefined - priorities?: ('urgent' | 'high' | 'medium' | 'low' | 'none')[] | undefined - dueDate?: - | { - type: - | 'custom' - | 'any' - | 'none' - | 'overdue' - | 'today' - | 'tomorrow' - | 'this-week' - | 'next-week' - | 'this-month' - customStart?: string | null | undefined - customEnd?: string | null | undefined - } - | undefined - statusIds?: string[] | undefined - completion?: 'active' | 'completed' | 'all' | undefined - repeatType?: 'all' | 'repeating' | 'one-time' | undefined - hasTime?: 'all' | 'with-time' | 'without-time' | undefined - } - sort?: - | { - field: 'title' | 'createdAt' | 'priority' | 'dueDate' | 'completedAt' | 'project' - direction: 'asc' | 'desc' - } - | undefined - starred?: boolean | undefined - } - } - ] - ) => Awaited< - Promise<{ - success: boolean - savedFilter: import('../../../../../packages/contracts/src/saved-filters-api').SavedFilter - }> - > - 'saved-filters:delete': ( - ...args: [{ id: string }] - ) => Awaited< - Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - > - 'saved-filters:list': (...args: []) => Awaited< - Promise<{ - savedFilters: import('../../../../../packages/contracts/src/saved-filters-api').SavedFilter[] - }> - > - 'saved-filters:reorder': ( - ...args: [{ ids: string[]; positions: number[] }] - ) => Awaited> - 'saved-filters:update': ( - ...args: [ - { - id: string - name?: string | undefined - config?: - | { - filters: { - search?: string | undefined - projectIds?: string[] | undefined - priorities?: ('urgent' | 'high' | 'medium' | 'low' | 'none')[] | undefined - dueDate?: - | { - type: - | 'custom' - | 'any' - | 'none' - | 'overdue' - | 'today' - | 'tomorrow' - | 'this-week' - | 'next-week' - | 'this-month' - customStart?: string | null | undefined - customEnd?: string | null | undefined - } - | undefined - statusIds?: string[] | undefined - completion?: 'active' | 'completed' | 'all' | undefined - repeatType?: 'all' | 'repeating' | 'one-time' | undefined - hasTime?: 'all' | 'with-time' | 'without-time' | undefined - } - sort?: - | { - field: - | 'title' - | 'createdAt' - | 'priority' - | 'dueDate' - | 'completedAt' - | 'project' - direction: 'asc' | 'desc' - } - | undefined - starred?: boolean | undefined - } - | undefined - position?: number | undefined - } - ] - ) => Awaited< - Promise< - | { success: boolean; savedFilter: null; error: string } - | { - success: boolean - savedFilter: - | import('../../../../../packages/contracts/src/saved-filters-api').SavedFilter - | null - error?: undefined - } - > - > - 'search:add-reason': ( - ...args: [ - { - itemId: string - itemType: 'note' | 'task' | 'journal' | 'inbox' - itemTitle: string - searchQuery: string - itemIcon?: string | null | undefined - } - ] - ) => Awaited> - 'search:clear-reasons': (...args: []) => Awaited> - 'search:get-all-tags': (...args: []) => Awaited> - 'search:get-reasons': ( - ...args: [] - ) => Awaited> - 'search:get-stats': ( - ...args: [] - ) => Awaited> - 'search:query': ( - ...args: [ - { - text: string - types?: ('note' | 'task' | 'journal' | 'inbox')[] | undefined - tags?: string[] | undefined - dateRange?: { from: string; to: string } | null | undefined - projectId?: string | null | undefined - folderPath?: string | null | undefined - limit?: number | undefined - offset?: number | undefined - } - ] - ) => Awaited> - 'search:quick': ( - ...args: [string] - ) => Awaited< - Promise - > - 'search:rebuild-index': (...args: []) => Awaited< - Promise< - | { - notes: number - tasks: number - inbox: number - durationMs: number - started: true - error?: undefined - } - | { started: false; error: string } - > - > - 'settings:downloadVoiceModel': ( - ...args: [] - ) => Awaited< - Promise<{ success: boolean; error?: undefined } | { success: boolean; error: string }> - > - 'settings:get': (...args: [string]) => Awaited - 'settings:getAIModelStatus': ( - ...args: [] - ) => Awaited> - 'settings:getAISettings': (...args: []) => Awaited - 'settings:getBackupSettings': (...args: []) => Awaited<{ - autoBackup: boolean - frequencyHours: 1 | 6 | 12 | 24 - maxBackups: number - lastBackupAt: string | null - }> - 'settings:getEditorSettings': (...args: []) => Awaited<{ - width: 'medium' | 'narrow' | 'wide' - spellCheck: boolean - autoSaveDelay: number - showWordCount: boolean - toolbarMode: 'floating' | 'sticky' - }> - 'settings:getGeneralSettings': (...args: []) => Awaited<{ - theme: 'light' | 'dark' | 'white' | 'system' - fontSize: 'small' | 'medium' | 'large' - fontFamily: 'system' | 'serif' | 'sans-serif' | 'monospace' | 'gelasio' | 'geist' | 'inter' - accentColor: string - startOnBoot: boolean - language: string - onboardingCompleted: boolean - createInSelectedFolder: boolean - }> - 'settings:getGraphSettings': (...args: []) => Awaited<{ - layout: 'forceatlas2' | 'circular' | 'random' - showLabels: boolean - showEdgeLabels: boolean - animateLayout: boolean - showTagEdges: boolean - }> - 'settings:getJournalSettings': (...args: []) => Awaited<{ - defaultTemplate: string | null - showSchedule: boolean - showTasks: boolean - showAIConnections: boolean - showStatsFooter: boolean - }> - 'settings:getKeyboardSettings': (...args: []) => Awaited<{ - overrides: Record< - string, - { - key: string - modifiers: { - meta?: boolean | undefined - ctrl?: boolean | undefined - shift?: boolean | undefined - alt?: boolean | undefined - } - } - > - globalCapture: { - key: string - modifiers: { - meta?: boolean | undefined - ctrl?: boolean | undefined - shift?: boolean | undefined - alt?: boolean | undefined - } - } | null - }> - 'settings:getNoteEditorSettings': ( - ...args: [] - ) => Awaited - 'settings:getSyncSettings': (...args: []) => Awaited<{ enabled: boolean; autoSync: boolean }> - 'settings:getTabSettings': (...args: []) => Awaited - 'settings:getTaskSettings': (...args: []) => Awaited<{ - defaultProjectId: string | null - defaultSortOrder: 'createdAt' | 'priority' | 'dueDate' | 'manual' - weekStartDay: 'sunday' | 'monday' - staleInboxDays: number - }> - 'settings:getVoiceModelStatus': ( - ...args: [] - ) => Awaited - 'settings:getVoiceRecordingReadiness': ( - ...args: [] - ) => Awaited> - 'settings:getVoiceTranscriptionOpenAIKeyStatus': ( - ...args: [] - ) => Awaited> - 'settings:getVoiceTranscriptionSettings': ( - ...args: [] - ) => Awaited<{ provider: 'local' | 'openai' }> - 'settings:loadAIModel': ( - ...args: [] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; message: string; error?: undefined } - | { success: boolean; error: string; message?: undefined } - | { success: boolean; message?: undefined; error?: undefined } - > - > - 'settings:registerGlobalCapture': ( - ...args: [] - ) => Awaited> - 'settings:reindexEmbeddings': ( - ...args: [] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; computed: number; skipped: number; error?: string | undefined } - > - > - 'settings:resetKeyboardSettings': ( - ...args: [] - ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - 'settings:set': ( - ...args: [{ key: string; value: string }] - ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - 'settings:setAISettings': ( - ...args: [Partial] - ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - 'settings:setBackupSettings': ( - ...args: [ - Partial<{ - autoBackup: boolean - frequencyHours: 1 | 6 | 12 | 24 - maxBackups: number - lastBackupAt: string | null - }> - ] - ) => Awaited<{ success: boolean; error?: string | undefined }> - 'settings:setEditorSettings': ( - ...args: [ - Partial<{ - width: 'medium' | 'narrow' | 'wide' - spellCheck: boolean - autoSaveDelay: number - showWordCount: boolean - toolbarMode: 'floating' | 'sticky' - }> - ] - ) => Awaited<{ success: boolean; error?: string | undefined }> - 'settings:setGeneralSettings': ( - ...args: [ - Partial<{ - theme: 'light' | 'dark' | 'white' | 'system' - fontSize: 'small' | 'medium' | 'large' - fontFamily: 'system' | 'serif' | 'sans-serif' | 'monospace' | 'gelasio' | 'geist' | 'inter' - accentColor: string - startOnBoot: boolean - language: string - onboardingCompleted: boolean - createInSelectedFolder: boolean - }> - ] - ) => Awaited<{ success: boolean; error?: string | undefined }> - 'settings:setGraphSettings': ( - ...args: [ - Partial<{ - layout: 'forceatlas2' | 'circular' | 'random' - showLabels: boolean - showEdgeLabels: boolean - animateLayout: boolean - showTagEdges: boolean - }> - ] - ) => Awaited<{ success: boolean; error?: string | undefined }> - 'settings:setJournalSettings': ( - ...args: [Partial] - ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - 'settings:setKeyboardSettings': ( - ...args: [ - Partial<{ - overrides: Record< - string, - { - key: string - modifiers: { - meta?: boolean | undefined - ctrl?: boolean | undefined - shift?: boolean | undefined - alt?: boolean | undefined - } - } - > - globalCapture: { - key: string - modifiers: { - meta?: boolean | undefined - ctrl?: boolean | undefined - shift?: boolean | undefined - alt?: boolean | undefined - } - } | null - }> - ] - ) => Awaited<{ success: boolean; error?: string | undefined }> - 'settings:setNoteEditorSettings': ( - ...args: [Partial] - ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - 'settings:setSyncSettings': ( - ...args: [Partial<{ enabled: boolean; autoSync: boolean }>] - ) => Awaited<{ success: boolean; error?: string | undefined }> - 'settings:setTabSettings': ( - ...args: [Partial] - ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - 'settings:setTaskSettings': ( - ...args: [ - Partial<{ - defaultProjectId: string | null - defaultSortOrder: 'createdAt' | 'priority' | 'dueDate' | 'manual' - weekStartDay: 'sunday' | 'monday' - staleInboxDays: number - }> - ] - ) => Awaited<{ success: boolean; error?: string | undefined }> - 'settings:setVoiceTranscriptionOpenAIKey': ( - ...args: [{ apiKey: string }] - ) => Awaited< - Promise<{ success: boolean; error?: undefined } | { success: boolean; error: string }> - > - 'settings:setVoiceTranscriptionSettings': ( - ...args: [Partial<{ provider: 'local' | 'openai' }>] - ) => Awaited<{ success: boolean; error?: string | undefined }> - 'sync:approve-linking': ( - ...args: [{ sessionId: string }] - ) => Awaited< - Promise - > - 'sync:check-device-status': (...args: []) => Awaited> - 'sync:complete-linking-qr': ( - ...args: [{ sessionId: string }] - ) => Awaited< - Promise - > - 'sync:confirm-recovery-phrase': ( - ...args: [{ confirmed: boolean }] - ) => Awaited> - 'sync:download-attachment': ( - ...args: [{ attachmentId: string; targetPath?: string | undefined }] - ) => Awaited< - Promise< - | { success: boolean; error: string; filePath?: undefined } - | { success: boolean; filePath: string; error?: undefined } - > - > - 'sync:emergency-wipe': (...args: []) => Awaited> - 'sync:generate-linking-qr': ( - ...args: [] - ) => Awaited< - Promise - > - 'sync:get-devices': (...args: []) => Awaited< - Promise<{ - devices: { - id: string - name: string - platform: 'macos' | 'windows' | 'linux' | 'ios' | 'android' - linkedAt: number - lastSyncAt: number | undefined - isCurrentDevice: boolean - }[] - email: string | undefined - }> - > - 'sync:get-download-progress': (...args: [{ attachmentId: string }]) => Awaited< - Promise<{ - progress: number - downloadedChunks: number - totalChunks: number - status: 'downloading' - } | null> - > - 'sync:get-history': ( - ...args: [{ limit?: number | undefined; offset?: number | undefined }] - ) => Awaited< - Promise<{ - entries: { - id: string - type: 'error' | 'push' | 'pull' - itemCount: number - direction: string | undefined - details: unknown - durationMs: number | undefined - createdAt: number - }[] - total: number - }> - > - 'sync:get-linking-sas': ( - ...args: [{ sessionId: string }] - ) => Awaited> - 'sync:get-quarantined-items': ( - ...args: [] - ) => Awaited - 'sync:get-queue-size': (...args: []) => Awaited<{ pending: number; failed: number }> - 'sync:get-recovery-phrase': (...args: []) => Awaited - 'sync:get-status': ( - ...args: [] - ) => Awaited< - | import('../../../../../packages/contracts/src/ipc-sync-ops').GetSyncStatusResult - | { status: string; pendingCount: number } - > - 'sync:get-storage-breakdown': ( - ...args: [] - ) => Awaited< - Promise< - import('../../../../../packages/contracts/src/ipc-sync-ops').StorageBreakdownResult | null - > - > - 'sync:get-synced-settings': (...args: []) => Awaited<{ - general?: - | { - theme?: 'light' | 'dark' | 'white' | 'system' | undefined - fontSize?: 'small' | 'medium' | 'large' | undefined - fontFamily?: - | 'system' - | 'serif' - | 'sans-serif' - | 'monospace' - | 'gelasio' - | 'geist' - | 'inter' - | undefined - accentColor?: string | undefined - startOnBoot?: boolean | undefined - language?: string | undefined - createInSelectedFolder?: boolean | undefined - } - | undefined - editor?: - | { - width?: 'medium' | 'narrow' | 'wide' | undefined - spellCheck?: boolean | undefined - autoSaveDelay?: number | undefined - showWordCount?: boolean | undefined - toolbarMode?: 'floating' | 'sticky' | undefined - } - | undefined - tasks?: - | { - defaultProjectId?: string | null | undefined - defaultSortOrder?: 'createdAt' | 'priority' | 'dueDate' | 'manual' | undefined - weekStartDay?: 'sunday' | 'monday' | undefined - staleInboxDays?: number | undefined - showCompleted?: boolean | undefined - sortBy?: string | undefined - } - | undefined - keyboard?: { overrides?: Record | undefined } | undefined - notes?: - | { - defaultFolder?: string | undefined - editorFontSize?: number | undefined - spellCheck?: boolean | undefined - } - | undefined - sync?: { autoSync?: boolean | undefined; syncIntervalMinutes?: number | undefined } | undefined - } | null> - 'sync:get-upload-progress': (...args: [{ sessionId: string }]) => Awaited< - Promise<{ - progress: number - uploadedChunks: number - totalChunks: number - status: 'uploading' - } | null> - > - 'sync:link-via-qr': ( - ...args: [{ qrData: string; oauthToken?: string | undefined; provider?: string | undefined }] - ) => Awaited> - 'sync:link-via-recovery': ( - ...args: [{ recoveryPhrase: string }] - ) => Awaited< - Promise< - | { success: boolean; error: string; deviceId?: undefined } - | { success: boolean; deviceId: string; error?: undefined } - > - > - 'sync:logout': ( - ...args: [] - ) => Awaited> - 'sync:pause': (...args: []) => Awaited<{ success: boolean; wasPaused: boolean }> - 'sync:remove-device': ( - ...args: [{ deviceId: string }] - ) => Awaited< - Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - > - 'sync:rename-device': ( - ...args: [{ deviceId: string; newName: string }] - ) => Awaited< - Promise<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - > - 'sync:resume': (...args: []) => Awaited<{ success: boolean; pendingCount: number }> - 'sync:setup-first-device': ( - ...args: [{ oauthToken: string; provider: 'google'; state: string }] - ) => Awaited< - Promise< - | { - success: boolean - needsRecoverySetup: boolean - deviceId: string - needsRecoveryInput?: undefined - } - | { - success: boolean - needsRecoverySetup: boolean - needsRecoveryInput: boolean - deviceId?: undefined - } - > - > - 'sync:setup-new-account': ( - ...args: [] - ) => Awaited< - Promise< - | { success: boolean; error: string; deviceId?: undefined } - | { success: boolean; deviceId: string; error?: undefined } - > - > - 'sync:trigger-sync': ( - ...args: [] - ) => Awaited> - 'sync:update-synced-setting': ( - ...args: [unknown] - ) => Awaited<{ success: boolean; error: string } | { success: boolean; error?: undefined }> - 'sync:upload-attachment': ( - ...args: [{ noteId: string; filePath: string }] - ) => Awaited< - Promise< - | { success: boolean; error: string; attachmentId?: undefined; sessionId?: undefined } - | { success: boolean; attachmentId: string; sessionId: string; error?: undefined } - > - > - 'tags:delete': ( - ...args: [string] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/tags-api').DeleteTagResponse - > - > - 'tags:get-all-with-counts': ( - ...args: [] - ) => Awaited< - Promise - > - 'tags:get-notes-by-tag': ( - ...args: [ - { - tag: string - sortBy?: 'title' | 'modified' | 'created' | undefined - sortOrder?: 'asc' | 'desc' | undefined - includeDescendants?: boolean | undefined - } - ] - ) => Awaited< - Promise - > - 'tags:merge': ( - ...args: [{ source: string; target: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/tags-api').MergeTagResponse - > - > - 'tags:pin-note-to-tag': ( - ...args: [{ noteId: string; tag: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/tags-api').TagOperationResponse - > - > - 'tags:remove-from-note': ( - ...args: [{ noteId: string; tag: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/tags-api').TagOperationResponse - > - > - 'tags:rename': ( - ...args: [{ oldName: string; newName: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/tags-api').RenameTagResponse - > - > - 'tags:unpin-note-from-tag': ( - ...args: [{ noteId: string; tag: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/tags-api').TagOperationResponse - > - > - 'tags:update-color': ( - ...args: [{ tag: string; color: string }] - ) => Awaited< - Promise< - | { success: false; error: string } - | import('../../../../../packages/contracts/src/tags-api').TagOperationResponse - > - > - 'tasks:archive': ( - ...args: [string] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; error: string } - | { success: boolean; error?: undefined } - > - > - 'tasks:bulk-archive': ( - ...args: [{ ids: string[] }] - ) => Awaited> - 'tasks:bulk-complete': ( - ...args: [{ ids: string[] }] - ) => Awaited> - 'tasks:bulk-delete': ( - ...args: [{ ids: string[] }] - ) => Awaited> - 'tasks:bulk-move': ( - ...args: [{ ids: string[]; projectId: string }] - ) => Awaited> - 'tasks:complete': (...args: [{ id: string; completedAt?: string | undefined }]) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; task: null; error: string } - | { - success: boolean - task: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } - error?: undefined - } - > - > - 'tasks:convert-to-subtask': (...args: [{ taskId: string; parentId: string }]) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; task: null; error: string } - | { - success: boolean - task: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } - error?: undefined - } - > - > - 'tasks:convert-to-task': (...args: [string]) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; task: null; error: string } - | { - success: boolean - task: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } - error?: undefined - } - > - > - 'tasks:create': ( - ...args: [ - { - projectId: string - title: string - description?: string | null | undefined - priority?: number | undefined - statusId?: string | null | undefined - parentId?: string | null | undefined - dueDate?: string | null | undefined - dueTime?: string | null | undefined - startDate?: string | null | undefined - isRepeating?: boolean | undefined - repeatConfig?: - | { - frequency: 'daily' | 'weekly' | 'monthly' | 'yearly' - endType: 'date' | 'never' | 'count' - createdAt: string - interval?: number | undefined - daysOfWeek?: number[] | undefined - monthlyType?: 'dayOfMonth' | 'weekPattern' | undefined - dayOfMonth?: number | undefined - weekOfMonth?: number | undefined - dayOfWeekForMonth?: number | undefined - endDate?: string | null | undefined - endCount?: number | undefined - completedCount?: number | undefined - } - | null - | undefined - repeatFrom?: 'due' | 'completion' | null | undefined - tags?: string[] | undefined - linkedNoteIds?: string[] | undefined - sourceNoteId?: string | null | undefined - position?: number | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { - success: boolean - task: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } - } - > - > - 'tasks:delete': ( - ...args: [string] - ) => Awaited> - 'tasks:duplicate': (...args: [string]) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; task: null; error: string } - | { - success: boolean - task: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } - error?: undefined - } - > - > - 'tasks:get': (...args: [string]) => Awaited< - Promise<{ - tags: string[] - linkedNoteIds: string[] - hasSubtasks: boolean - subtaskCount: number - completedSubtaskCount: number - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } | null> - > - 'tasks:get-linked-tasks': (...args: [string]) => Awaited< - Promise< - { - tags: string[] - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - }[] - > - > - 'tasks:get-overdue': (...args: []) => Awaited< - Promise<{ - tasks: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - }[] - total: number - hasMore: boolean - }> - > - 'tasks:get-stats': (...args: []) => Awaited< - Promise<{ - total: number - completed: number - overdue: number - dueToday: number - dueThisWeek: number - }> - > - 'tasks:get-subtasks': (...args: [string]) => Awaited< - Promise< - { - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - }[] - > - > - 'tasks:get-tags': (...args: []) => Awaited> - 'tasks:get-today': (...args: []) => Awaited< - Promise<{ - tasks: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - }[] - total: number - hasMore: boolean - }> - > - 'tasks:get-upcoming': (...args: [{ days?: number | undefined }]) => Awaited< - Promise<{ - tasks: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - }[] - total: number - hasMore: boolean - }> - > - 'tasks:list': ( - ...args: [ - { - projectId?: string | undefined - statusId?: string | null | undefined - parentId?: string | null | undefined - includeCompleted?: boolean | undefined - includeArchived?: boolean | undefined - dueBefore?: string | undefined - dueAfter?: string | undefined - tags?: string[] | undefined - search?: string | undefined - sortBy?: 'modified' | 'created' | 'position' | 'priority' | 'dueDate' | undefined - sortOrder?: 'asc' | 'desc' | undefined - limit?: number | undefined - offset?: number | undefined - } - ] - ) => Awaited< - Promise<{ - tasks: { - tags: string[] - linkedNoteIds: string[] - hasSubtasks: boolean - subtaskCount: number - completedSubtaskCount: number - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - }[] - total: number - hasMore: boolean - }> - > - 'tasks:move': ( - ...args: [ - { - taskId: string - position: number - targetProjectId?: string | undefined - targetStatusId?: string | null | undefined - targetParentId?: string | null | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; task: null; error: string } - | { - success: boolean - task: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } - error?: undefined - } - > - > - 'tasks:project-archive': ( - ...args: [string] - ) => Awaited> - 'tasks:project-create': ( - ...args: [ - { - name: string - description?: string | null | undefined - color?: string | undefined - icon?: string | null | undefined - statuses?: - | { - name: string - type: 'todo' | 'in_progress' | 'done' - order: number - color?: string | undefined - }[] - | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { - success: boolean - project: { - id: string - name: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - color: string - description: string | null - icon: string | null - isInbox: boolean - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - } - } - > - > - 'tasks:project-delete': ( - ...args: [string] - ) => Awaited> - 'tasks:project-get': ( - ...args: [string] - ) => Awaited> - 'tasks:project-list': ( - ...args: [] - ) => Awaited> - 'tasks:project-reorder': ( - ...args: [{ projectIds: string[]; positions: number[] }] - ) => Awaited> - 'tasks:project-update': ( - ...args: [ - { - id: string - name?: string | undefined - description?: string | null | undefined - color?: string | undefined - icon?: string | null | undefined - statuses?: - | { - name: string - type: 'todo' | 'in_progress' | 'done' - order: number - id?: string | undefined - color?: string | undefined - }[] - | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; project: null; error: string } - | { - success: boolean - project: { - id: string - name: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - color: string - description: string | null - icon: string | null - isInbox: boolean - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - } - error?: undefined - } - > - > - 'tasks:reorder': ( - ...args: [{ taskIds: string[]; positions: number[] }] - ) => Awaited> - 'tasks:seed-demo': (...args: []) => Awaited> - 'tasks:seed-performance-test': ( - ...args: [] - ) => Awaited> - 'tasks:status-create': ( - ...args: [ - { projectId: string; name: string; color?: string | undefined; isDone?: boolean | undefined } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { - success: boolean - status: { - id: string - name: string - createdAt: string - position: number - color: string - projectId: string - isDefault: boolean - isDone: boolean - } - } - > - > - 'tasks:status-delete': ( - ...args: [string] - ) => Awaited> - 'tasks:status-list': (...args: [string]) => Awaited< - Promise< - { - id: string - name: string - createdAt: string - position: number - color: string - projectId: string - isDefault: boolean - isDone: boolean - }[] - > - > - 'tasks:status-reorder': ( - ...args: [{ statusIds: string[]; positions: number[] }] - ) => Awaited> - 'tasks:status-update': ( - ...args: [ - { - id: string - name?: string | undefined - color?: string | undefined - position?: number | undefined - isDefault?: boolean | undefined - isDone?: boolean | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; error: string; status?: undefined } - | { - success: boolean - status: { - id: string - name: string - createdAt: string - position: number - color: string - projectId: string - isDefault: boolean - isDone: boolean - } - error?: undefined - } - > - > - 'tasks:unarchive': ( - ...args: [string] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; error: string } - | { success: boolean; error?: undefined } - > - > - 'tasks:uncomplete': (...args: [string]) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; task: null; error: string } - | { - success: boolean - task: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } - error?: undefined - } - > - > - 'tasks:update': ( - ...args: [ - { - id: string - title?: string | undefined - description?: string | null | undefined - priority?: number | undefined - projectId?: string | undefined - statusId?: string | null | undefined - parentId?: string | null | undefined - dueDate?: string | null | undefined - dueTime?: string | null | undefined - startDate?: string | null | undefined - isRepeating?: boolean | undefined - repeatConfig?: - | { - frequency: 'daily' | 'weekly' | 'monthly' | 'yearly' - endType: 'date' | 'never' | 'count' - createdAt: string - interval?: number | undefined - daysOfWeek?: number[] | undefined - monthlyType?: 'dayOfMonth' | 'weekPattern' | undefined - dayOfMonth?: number | undefined - weekOfMonth?: number | undefined - dayOfWeekForMonth?: number | undefined - endDate?: string | null | undefined - endCount?: number | undefined - completedCount?: number | undefined - } - | null - | undefined - repeatFrom?: 'due' | 'completion' | null | undefined - tags?: string[] | undefined - linkedNoteIds?: string[] | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { success: boolean; task: null; error: string } - | { - success: boolean - task: { - linkedNoteIds: string[] - id: string - title: string - clock: import('../../../../../packages/contracts/src/sync-api').VectorClock | null - syncedAt: string | null - createdAt: string - modifiedAt: string - position: number - description: string | null - projectId: string - priority: number - statusId: string | null - parentId: string | null - dueDate: string | null - dueTime: string | null - startDate: string | null - repeatConfig: unknown - repeatFrom: string | null - sourceNoteId: string | null - archivedAt: string | null - fieldClocks: import('../../../../../packages/contracts/src/sync-api').FieldClocks | null - completedAt: string | null - } - error?: undefined - } - > - > - 'templates:create': ( - ...args: [ - { - name: string - description?: string | undefined - icon?: string | null | undefined - tags?: string[] | undefined - properties?: - | { - name: string - type: - | 'number' - | 'date' - | 'text' - | 'select' - | 'checkbox' - | 'url' - | 'multiselect' - | 'rating' - value: unknown - options?: string[] | undefined - }[] - | undefined - content?: string | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { - success: boolean - template: import('../../../../../packages/contracts/src/templates-api').Template - } - > - > - 'templates:delete': ( - ...args: [string] - ) => Awaited> - 'templates:duplicate': (...args: [{ id: string; newName: string }]) => Awaited< - Promise< - | { success: false; error: string } - | { - success: boolean - template: import('../../../../../packages/contracts/src/templates-api').Template - } - > - > - 'templates:get': ( - ...args: [string] - ) => Awaited< - Promise - > - 'templates:list': (...args: []) => Awaited< - Promise<{ - templates: import('../../../../../packages/contracts/src/templates-api').TemplateListItem[] - }> - > - 'templates:update': ( - ...args: [ - { - id: string - name?: string | undefined - description?: string | undefined - icon?: string | null | undefined - tags?: string[] | undefined - properties?: - | { - name: string - type: - | 'number' - | 'date' - | 'text' - | 'select' - | 'checkbox' - | 'url' - | 'multiselect' - | 'rating' - value: unknown - options?: string[] | undefined - }[] - | undefined - content?: string | undefined - } - ] - ) => Awaited< - Promise< - | { success: false; error: string } - | { - success: boolean - template: import('../../../../../packages/contracts/src/templates-api').Template - } - > - > - 'vault:close': (...args: []) => Awaited> - 'vault:get-all': ( - ...args: [] - ) => Awaited> - 'vault:get-config': ( - ...args: [] - ) => Awaited> - 'vault:get-status': ( - ...args: [] - ) => Awaited> - 'vault:reindex': (...args: []) => Awaited> - 'vault:remove': (...args: [string]) => Awaited> - 'vault:reveal': (...args: []) => Awaited> - 'vault:select': ( - ...args: [{ path?: string | undefined }] - ) => Awaited< - Promise - > - 'vault:switch': ( - ...args: [string] - ) => Awaited< - Promise - > - 'vault:update-config': ( - ...args: [ - { - excludePatterns?: string[] | undefined - defaultNoteFolder?: string | undefined - journalFolder?: string | undefined - attachmentsFolder?: string | undefined - } - ] - ) => Awaited> + "account:getInfo": (...args: []) => Awaited + "account:getRecoveryKey": (...args: []) => Awaited> + "account:signOut": (...args: []) => Awaited> + "ai-inline:get-server-port": (...args: []) => Awaited + "ai-inline:get-settings": (...args: []) => Awaited + "ai-inline:set-settings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> + "ai-inline:start-server": (...args: []) => Awaited> + "ai-inline:stop-server": (...args: []) => Awaited> + "auth:init-oauth": (...args: [{ provider: "google"; }]) => Awaited> + "auth:refresh-token": (...args: []) => Awaited> + "auth:request-otp": (...args: [{ email: string; }]) => Awaited> + "auth:resend-otp": (...args: [{ email: string; }]) => Awaited> + "auth:verify-otp": (...args: [{ email: string; code: string; }]) => Awaited> + "bookmarks:bulk-create": (...args: [{ items: { itemType: string; itemId: string; }[]; }]) => Awaited> + "bookmarks:bulk-delete": (...args: [{ bookmarkIds: string[]; }]) => Awaited> + "bookmarks:create": (...args: [{ itemType: string; itemId: string; }]) => Awaited> + "bookmarks:delete": (...args: [string]) => Awaited> + "bookmarks:get": (...args: [string]) => Awaited> + "bookmarks:get-by-item": (...args: [{ itemType: string; itemId: string; }]) => Awaited> + "bookmarks:is-bookmarked": (...args: [{ itemType: string; itemId: string; }]) => Awaited> + "bookmarks:list": (...args: [{ itemType?: string | undefined; sortBy?: "createdAt" | "position" | undefined; sortOrder?: "asc" | "desc" | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> + "bookmarks:list-by-type": (...args: [string]) => Awaited> + "bookmarks:reorder": (...args: [{ bookmarkIds: string[]; }]) => Awaited> + "bookmarks:toggle": (...args: [{ itemType: string; itemId: string; }]) => Awaited> + "context-menu:show": (...args: [{ id: string; label: string; accelerator?: string | undefined; disabled?: boolean | undefined; type?: "normal" | "separator" | undefined; }[]]) => Awaited> + "crdt:apply-update": (...args: [unknown]) => Awaited> + "crdt:close-doc": (...args: [unknown]) => Awaited> + "crdt:open-doc": (...args: [unknown]) => Awaited> + "crdt:sync-step-1": (...args: [{ noteId: string; stateVector: number[]; }]) => Awaited> + "crdt:sync-step-2": (...args: [{ noteId: string; diff: number[]; }]) => Awaited> + "crypto:decrypt-item": (...args: [{ itemId: string; type: "note" | "filter" | "task" | "project" | "journal" | "settings" | "inbox" | "tag_definition"; encryptedKey: string; keyNonce: string; encryptedData: string; dataNonce: string; signature: string; operation?: "create" | "update" | "delete" | undefined; deletedAt?: number | undefined; metadata?: Record | undefined; }]) => Awaited> + "crypto:encrypt-item": (...args: [{ itemId: string; type: "note" | "filter" | "task" | "project" | "journal" | "settings" | "inbox" | "tag_definition"; content: Record; operation?: "create" | "update" | "delete" | undefined; deletedAt?: number | undefined; metadata?: Record | undefined; }]) => Awaited> + "crypto:get-rotation-progress": (...args: []) => Awaited + "crypto:rotate-keys": (...args: [{ confirm: boolean; }]) => Awaited> + "crypto:verify-signature": (...args: [{ itemId: string; type: "note" | "filter" | "task" | "project" | "journal" | "settings" | "inbox" | "tag_definition"; encryptedKey: string; keyNonce: string; encryptedData: string; dataNonce: string; signature: string; operation?: "create" | "update" | "delete" | undefined; deletedAt?: number | undefined; metadata?: Record | undefined; }]) => Awaited> + "folder-view:delete-view": (...args: [{ folderPath: string; viewName: string; }]) => Awaited> + "folder-view:folder-exists": (...args: [string]) => Awaited + "folder-view:get-available-properties": (...args: [{ folderPath: string; }]) => Awaited> + "folder-view:get-config": (...args: [{ folderPath: string; }]) => Awaited> + "folder-view:get-folder-suggestions": (...args: [{ noteId: string; }]) => Awaited> + "folder-view:get-views": (...args: [{ folderPath: string; }]) => Awaited> + "folder-view:list-with-properties": (...args: [{ folderPath: string; properties?: string[] | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> + "folder-view:set-config": (...args: [{ folderPath: string; config: { path?: string | undefined; template?: string | undefined; inherit?: boolean | undefined; formulas?: Record | undefined; properties?: Record | undefined; summaries?: Record | undefined; views?: { name: string; type?: "table" | "grid" | "list" | "kanban" | undefined; default?: boolean | undefined; columns?: { id: string; width?: number | undefined; displayName?: string | undefined; showSummary?: boolean | undefined; }[] | undefined; filters?: unknown; order?: { property: string; direction: "asc" | "desc"; }[] | undefined; groupBy?: { property: string; direction?: "asc" | "desc" | undefined; collapsed?: boolean | undefined; showSummary?: boolean | undefined; } | undefined; limit?: number | undefined; showSummaries?: boolean | undefined; }[] | undefined; }; }]) => Awaited> + "folder-view:set-view": (...args: [{ folderPath: string; view: { name: string; type?: "table" | "grid" | "list" | "kanban" | undefined; default?: boolean | undefined; columns?: { id: string; width?: number | undefined; displayName?: string | undefined; showSummary?: boolean | undefined; }[] | undefined; filters?: unknown; order?: { property: string; direction: "asc" | "desc"; }[] | undefined; groupBy?: { property: string; direction?: "asc" | "desc" | undefined; collapsed?: boolean | undefined; showSummary?: boolean | undefined; } | undefined; limit?: number | undefined; showSummaries?: boolean | undefined; }; }]) => Awaited> + "graph:get-graph-data": (...args: []) => Awaited<{ nodes: { id: string; type: "note" | "task" | "project" | "journal"; label: string; tags: string[]; wordCount: number; connectionCount: number; emoji: string | null; color: string; isOrphan: boolean; isUnresolved: boolean; }[]; edges: { id: string; source: string; target: string; type: "wikilink" | "task-note" | "project-task" | "tag-cooccurrence"; weight: number; }[]; }> + "graph:get-local-graph": (...args: [{ noteId: string; depth?: number | undefined; }]) => Awaited<{ nodes: { id: string; type: "note" | "task" | "project" | "journal"; label: string; tags: string[]; wordCount: number; connectionCount: number; emoji: string | null; color: string; isOrphan: boolean; isUnresolved: boolean; }[]; edges: { id: string; source: string; target: string; type: "wikilink" | "task-note" | "project-task" | "tag-cooccurrence"; weight: number; }[]; }> + "inbox:add-tag": (...args: [any, any]) => Awaited> + "inbox:archive": (...args: [any]) => Awaited> + "inbox:bulk-archive": (...args: [any]) => Awaited> + "inbox:bulk-file": (...args: [any]) => Awaited> + "inbox:bulk-snooze": (...args: [any]) => Awaited> + "inbox:bulk-tag": (...args: [any]) => Awaited> + "inbox:capture-clip": (...args: [any]) => Awaited> + "inbox:capture-image": (...args: [any]) => Awaited> + "inbox:capture-link": (...args: [any]) => Awaited> + "inbox:capture-pdf": (...args: [any]) => Awaited> + "inbox:capture-text": (...args: [any]) => Awaited> + "inbox:capture-voice": (...args: [any]) => Awaited> + "inbox:convert-to-note": (...args: [any]) => Awaited> + "inbox:convert-to-task": (...args: [any]) => Awaited> + "inbox:delete-permanent": (...args: [any]) => Awaited> + "inbox:file": (...args: [any]) => Awaited> + "inbox:file-all-stale": (...args: []) => Awaited> + "inbox:get": (...args: [any]) => Awaited> + "inbox:get-filing-history": (...args: [any]) => Awaited> + "inbox:get-patterns": (...args: []) => Awaited> + "inbox:get-snoozed": (...args: []) => Awaited> + "inbox:get-stale-threshold": (...args: []) => Awaited> + "inbox:get-stats": (...args: []) => Awaited> + "inbox:get-suggestions": (...args: [any]) => Awaited> + "inbox:get-tags": (...args: []) => Awaited> + "inbox:link-to-note": (...args: [any, any, any]) => Awaited> + "inbox:list": (...args: [any]) => Awaited> + "inbox:list-archived": (...args: [any]) => Awaited> + "inbox:mark-viewed": (...args: [any]) => Awaited> + "inbox:preview-link": (...args: [string]) => Awaited> + "inbox:remove-tag": (...args: [any, any]) => Awaited> + "inbox:retry-metadata": (...args: [any]) => Awaited> + "inbox:retry-transcription": (...args: [any]) => Awaited> + "inbox:set-stale-threshold": (...args: [any]) => Awaited> + "inbox:snooze": (...args: [any]) => Awaited> + "inbox:track-suggestion": (...args: [any, any, any, any, any, any, any]) => Awaited> + "inbox:unarchive": (...args: [any]) => Awaited> + "inbox:undo-archive": (...args: [any]) => Awaited> + "inbox:undo-file": (...args: [any]) => Awaited> + "inbox:unsnooze": (...args: [any]) => Awaited> + "inbox:update": (...args: [any]) => Awaited> + "journal:createEntry": (...args: [{ date: string; content?: string | undefined; tags?: string[] | undefined; properties?: Record | undefined; }]) => Awaited | undefined; }>> + "journal:deleteEntry": (...args: [{ date: string; }]) => Awaited> + "journal:getAllTags": (...args: []) => Awaited> + "journal:getDayContext": (...args: [{ date: string; }]) => Awaited> + "journal:getEntry": (...args: [{ date: string; }]) => Awaited | undefined; } | null>> + "journal:getHeatmap": (...args: [{ year: number; }]) => Awaited> + "journal:getMonthEntries": (...args: [{ year: number; month: number; }]) => Awaited> + "journal:getStreak": (...args: []) => Awaited> + "journal:getYearStats": (...args: [{ year: number; }]) => Awaited> + "journal:updateEntry": (...args: [{ date: string; content?: string | undefined; tags?: string[] | undefined; properties?: Record | undefined; }]) => Awaited | undefined; }>> + "notes:add-property-option": (...args: [{ propertyName: string; option: { value: string; color: string; }; }]) => Awaited> + "notes:add-status-option": (...args: [{ propertyName: string; categoryKey: "todo" | "in_progress" | "done"; option: { value: string; color: string; }; }]) => Awaited> + "notes:create": (...args: [{ title: string; content?: string | undefined; folder?: string | undefined; tags?: string[] | undefined; template?: string | undefined; }]) => Awaited> + "notes:create-folder": (...args: [string]) => Awaited> + "notes:create-property-definition": (...args: [{ name: string; type: "number" | "date" | "text" | "select" | "checkbox" | "url" | "status" | "multiselect"; options?: { value: string; color: string; default?: boolean | undefined; }[] | undefined; defaultValue?: unknown; color?: string | undefined; }]) => Awaited> + "notes:delete": (...args: [string]) => Awaited> + "notes:delete-attachment": (...args: [{ noteId: string; filename: string; }]) => Awaited> + "notes:delete-folder": (...args: [string]) => Awaited> + "notes:delete-property-definition": (...args: [{ name: string; }]) => Awaited> + "notes:delete-version": (...args: [string]) => Awaited> + "notes:ensure-property-definition": (...args: [{ name: string; type: "select" | "status" | "multiselect"; }]) => Awaited> + "notes:exists": (...args: [string]) => Awaited> + "notes:export-html": (...args: [{ noteId: string; includeMetadata?: boolean | undefined; pageSize?: "A4" | "Letter" | "Legal" | undefined; }]) => Awaited> + "notes:export-pdf": (...args: [{ noteId: string; includeMetadata?: boolean | undefined; pageSize?: "A4" | "Letter" | "Legal" | undefined; }]) => Awaited> + "notes:get": (...args: [string]) => Awaited> + "notes:get-all-positions": (...args: []) => Awaited; }>> + "notes:get-by-path": (...args: [string]) => Awaited> + "notes:get-file": (...args: [string]) => Awaited> + "notes:get-folder-config": (...args: [string]) => Awaited> + "notes:get-folder-template": (...args: [string]) => Awaited> + "notes:get-folders": (...args: []) => Awaited> + "notes:get-links": (...args: [string]) => Awaited> + "notes:get-local-only-count": (...args: []) => Awaited> + "notes:get-positions": (...args: [{ folderPath: string; }]) => Awaited> + "notes:get-property-definitions": (...args: []) => Awaited> + "notes:get-tags": (...args: []) => Awaited> + "notes:get-version": (...args: [string]) => Awaited> + "notes:get-versions": (...args: [string]) => Awaited> + "notes:import-files": (...args: [{ sourcePaths: string[]; targetFolder?: string | undefined; }]) => Awaited> + "notes:list": (...args: [{ folder?: string | undefined; tags?: string[] | undefined; sortBy?: "title" | "modified" | "created" | "position" | undefined; sortOrder?: "asc" | "desc" | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> + "notes:list-attachments": (...args: [string]) => Awaited> + "notes:move": (...args: [{ id: string; newFolder: string; }]) => Awaited> + "notes:open-external": (...args: [string]) => Awaited> + "notes:preview-by-title": (...args: [string]) => Awaited> + "notes:remove-property-option": (...args: [{ propertyName: string; optionValue: string; }]) => Awaited> + "notes:rename": (...args: [{ id: string; newTitle: string; }]) => Awaited> + "notes:rename-folder": (...args: [{ oldPath: string; newPath: string; }]) => Awaited> + "notes:rename-property-option": (...args: [{ propertyName: string; oldValue: string; newValue: string; }]) => Awaited> + "notes:reorder": (...args: [{ folderPath: string; notePaths: string[]; }]) => Awaited> + "notes:resolve-by-title": (...args: [string]) => Awaited> + "notes:restore-version": (...args: [string]) => Awaited> + "notes:reveal-in-finder": (...args: [string]) => Awaited> + "notes:set-folder-config": (...args: [{ folderPath: string; config: { icon?: string | null | undefined; template?: string | undefined; inherit?: boolean | undefined; }; }]) => Awaited> + "notes:set-local-only": (...args: [{ id: string; localOnly: boolean; }]) => Awaited> + "notes:show-import-dialog": (...args: []) => Awaited> + "notes:update": (...args: [{ id: string; title?: string | undefined; content?: string | undefined; tags?: string[] | undefined; frontmatter?: Record | undefined; emoji?: string | null | undefined; }]) => Awaited> + "notes:update-option-color": (...args: [{ propertyName: string; optionValue: string; newColor: string; }]) => Awaited> + "notes:update-property-definition": (...args: [{ name: string; type?: "number" | "date" | "text" | "select" | "checkbox" | "url" | "status" | "multiselect" | undefined; options?: { value: string; color: string; default?: boolean | undefined; }[] | undefined; defaultValue?: unknown; color?: string | undefined; }]) => Awaited> + "notes:upload-attachment": (...args: [{ noteId: string; filename: string; data: number[] | ArrayBuffer; }]) => Awaited> + "properties:get": (...args: [{ entityId: string; }]) => Awaited> + "properties:rename": (...args: [{ entityId: string; oldName: string; newName: string; }]) => Awaited> + "properties:set": (...args: [{ entityId: string; properties: Record; }]) => Awaited> + "quick-capture:get-clipboard": (...args: []) => Awaited + "reminder:bulk-dismiss": (...args: [{ reminderIds: string[]; }]) => Awaited> + "reminder:count-pending": (...args: []) => Awaited> + "reminder:create": (...args: [{ targetType: "note"; targetId: string; remindAt: string; title?: string | undefined; note?: string | undefined; } | { targetType: "journal"; targetId: string; remindAt: string; title?: string | undefined; note?: string | undefined; } | { targetType: "highlight"; targetId: string; highlightText: string; highlightStart: number; highlightEnd: number; remindAt: string; title?: string | undefined; note?: string | undefined; }]) => Awaited> + "reminder:delete": (...args: [string]) => Awaited> + "reminder:dismiss": (...args: [string]) => Awaited> + "reminder:get": (...args: [string]) => Awaited> + "reminder:get-due": (...args: []) => Awaited> + "reminder:get-for-target": (...args: [{ targetType: "note" | "journal" | "highlight"; targetId: string; }]) => Awaited> + "reminder:get-upcoming": (...args: [number | undefined]) => Awaited> + "reminder:list": (...args: [{ targetType?: "note" | "journal" | "highlight" | undefined; targetId?: string | undefined; status?: "pending" | "triggered" | "dismissed" | "snoozed" | ("pending" | "triggered" | "dismissed" | "snoozed")[] | undefined; fromDate?: string | undefined; toDate?: string | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> + "reminder:snooze": (...args: [{ id: string; snoozeUntil: string; }]) => Awaited> + "reminder:update": (...args: [{ id: string; remindAt?: string | undefined; title?: string | null | undefined; note?: string | null | undefined; }]) => Awaited> + "saved-filters:create": (...args: [{ name: string; config: { filters: { search?: string | undefined; projectIds?: string[] | undefined; priorities?: ("urgent" | "high" | "medium" | "low" | "none")[] | undefined; dueDate?: { type: "custom" | "any" | "none" | "overdue" | "today" | "tomorrow" | "this-week" | "next-week" | "this-month"; customStart?: string | null | undefined; customEnd?: string | null | undefined; } | undefined; statusIds?: string[] | undefined; completion?: "active" | "completed" | "all" | undefined; repeatType?: "all" | "repeating" | "one-time" | undefined; hasTime?: "all" | "with-time" | "without-time" | undefined; }; sort?: { field: "title" | "createdAt" | "priority" | "dueDate" | "completedAt" | "project"; direction: "asc" | "desc"; } | undefined; starred?: boolean | undefined; }; }]) => Awaited> + "saved-filters:delete": (...args: [{ id: string; }]) => Awaited> + "saved-filters:list": (...args: []) => Awaited> + "saved-filters:reorder": (...args: [{ ids: string[]; positions: number[]; }]) => Awaited> + "saved-filters:update": (...args: [{ id: string; name?: string | undefined; config?: { filters: { search?: string | undefined; projectIds?: string[] | undefined; priorities?: ("urgent" | "high" | "medium" | "low" | "none")[] | undefined; dueDate?: { type: "custom" | "any" | "none" | "overdue" | "today" | "tomorrow" | "this-week" | "next-week" | "this-month"; customStart?: string | null | undefined; customEnd?: string | null | undefined; } | undefined; statusIds?: string[] | undefined; completion?: "active" | "completed" | "all" | undefined; repeatType?: "all" | "repeating" | "one-time" | undefined; hasTime?: "all" | "with-time" | "without-time" | undefined; }; sort?: { field: "title" | "createdAt" | "priority" | "dueDate" | "completedAt" | "project"; direction: "asc" | "desc"; } | undefined; starred?: boolean | undefined; } | undefined; position?: number | undefined; }]) => Awaited> + "search:add-reason": (...args: [{ itemId: string; itemType: "note" | "task" | "journal" | "inbox"; itemTitle: string; searchQuery: string; itemIcon?: string | null | undefined; }]) => Awaited> + "search:clear-reasons": (...args: []) => Awaited> + "search:get-all-tags": (...args: []) => Awaited> + "search:get-reasons": (...args: []) => Awaited> + "search:get-stats": (...args: []) => Awaited> + "search:query": (...args: [{ text: string; types?: ("note" | "task" | "journal" | "inbox")[] | undefined; tags?: string[] | undefined; dateRange?: { from: string; to: string; } | null | undefined; projectId?: string | null | undefined; folderPath?: string | null | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> + "search:quick": (...args: [string]) => Awaited> + "search:rebuild-index": (...args: []) => Awaited> + "settings:downloadVoiceModel": (...args: []) => Awaited> + "settings:get": (...args: [string]) => Awaited + "settings:getAIModelStatus": (...args: []) => Awaited> + "settings:getAISettings": (...args: []) => Awaited + "settings:getBackupSettings": (...args: []) => Awaited<{ autoBackup: boolean; frequencyHours: 1 | 6 | 12 | 24; maxBackups: number; lastBackupAt: string | null; }> + "settings:getEditorSettings": (...args: []) => Awaited<{ width: "medium" | "narrow" | "wide"; spellCheck: boolean; autoSaveDelay: number; showWordCount: boolean; toolbarMode: "floating" | "sticky"; }> + "settings:getGeneralSettings": (...args: []) => Awaited<{ theme: "light" | "dark" | "white" | "system"; fontSize: "small" | "medium" | "large"; fontFamily: "system" | "serif" | "sans-serif" | "monospace" | "gelasio" | "geist" | "inter"; accentColor: string; startOnBoot: boolean; language: string; onboardingCompleted: boolean; createInSelectedFolder: boolean; }> + "settings:getGraphSettings": (...args: []) => Awaited<{ layout: "forceatlas2" | "circular" | "random"; showLabels: boolean; showEdgeLabels: boolean; animateLayout: boolean; showTagEdges: boolean; }> + "settings:getJournalSettings": (...args: []) => Awaited<{ defaultTemplate: string | null; showSchedule: boolean; showTasks: boolean; showAIConnections: boolean; showStatsFooter: boolean; }> + "settings:getKeyboardSettings": (...args: []) => Awaited<{ overrides: Record; globalCapture: { key: string; modifiers: { meta?: boolean | undefined; ctrl?: boolean | undefined; shift?: boolean | undefined; alt?: boolean | undefined; }; } | null; }> + "settings:getNoteEditorSettings": (...args: []) => Awaited + "settings:getSyncSettings": (...args: []) => Awaited<{ enabled: boolean; autoSync: boolean; }> + "settings:getTabSettings": (...args: []) => Awaited + "settings:getTaskSettings": (...args: []) => Awaited<{ defaultProjectId: string | null; defaultSortOrder: "createdAt" | "priority" | "dueDate" | "manual"; weekStartDay: "sunday" | "monday"; staleInboxDays: number; }> + "settings:getVoiceModelStatus": (...args: []) => Awaited + "settings:getVoiceRecordingReadiness": (...args: []) => Awaited> + "settings:getVoiceTranscriptionOpenAIKeyStatus": (...args: []) => Awaited> + "settings:getVoiceTranscriptionSettings": (...args: []) => Awaited<{ provider: "local" | "openai"; }> + "settings:loadAIModel": (...args: []) => Awaited> + "settings:registerGlobalCapture": (...args: []) => Awaited> + "settings:reindexEmbeddings": (...args: []) => Awaited> + "settings:resetKeyboardSettings": (...args: []) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> + "settings:set": (...args: [{ key: string; value: string; }]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> + "settings:setAISettings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> + "settings:setBackupSettings": (...args: [Partial<{ autoBackup: boolean; frequencyHours: 1 | 6 | 12 | 24; maxBackups: number; lastBackupAt: string | null; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> + "settings:setEditorSettings": (...args: [Partial<{ width: "medium" | "narrow" | "wide"; spellCheck: boolean; autoSaveDelay: number; showWordCount: boolean; toolbarMode: "floating" | "sticky"; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> + "settings:setGeneralSettings": (...args: [Partial<{ theme: "light" | "dark" | "white" | "system"; fontSize: "small" | "medium" | "large"; fontFamily: "system" | "serif" | "sans-serif" | "monospace" | "gelasio" | "geist" | "inter"; accentColor: string; startOnBoot: boolean; language: string; onboardingCompleted: boolean; createInSelectedFolder: boolean; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> + "settings:setGraphSettings": (...args: [Partial<{ layout: "forceatlas2" | "circular" | "random"; showLabels: boolean; showEdgeLabels: boolean; animateLayout: boolean; showTagEdges: boolean; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> + "settings:setJournalSettings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> + "settings:setKeyboardSettings": (...args: [Partial<{ overrides: Record; globalCapture: { key: string; modifiers: { meta?: boolean | undefined; ctrl?: boolean | undefined; shift?: boolean | undefined; alt?: boolean | undefined; }; } | null; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> + "settings:setNoteEditorSettings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> + "settings:setSyncSettings": (...args: [Partial<{ enabled: boolean; autoSync: boolean; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> + "settings:setTabSettings": (...args: [Partial]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> + "settings:setTaskSettings": (...args: [Partial<{ defaultProjectId: string | null; defaultSortOrder: "createdAt" | "priority" | "dueDate" | "manual"; weekStartDay: "sunday" | "monday"; staleInboxDays: number; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> + "settings:setVoiceTranscriptionOpenAIKey": (...args: [{ apiKey: string; }]) => Awaited> + "settings:setVoiceTranscriptionSettings": (...args: [Partial<{ provider: "local" | "openai"; }>]) => Awaited<{ success: boolean; error?: string | undefined; }> + "sync:approve-linking": (...args: [{ sessionId: string; }]) => Awaited> + "sync:check-device-status": (...args: []) => Awaited> + "sync:complete-linking-qr": (...args: [{ sessionId: string; }]) => Awaited> + "sync:confirm-recovery-phrase": (...args: [{ confirmed: boolean; }]) => Awaited> + "sync:download-attachment": (...args: [{ attachmentId: string; targetPath?: string | undefined; }]) => Awaited> + "sync:emergency-wipe": (...args: []) => Awaited> + "sync:generate-linking-qr": (...args: []) => Awaited> + "sync:get-devices": (...args: []) => Awaited> + "sync:get-download-progress": (...args: [{ attachmentId: string; }]) => Awaited> + "sync:get-history": (...args: [{ limit?: number | undefined; offset?: number | undefined; }]) => Awaited> + "sync:get-linking-sas": (...args: [{ sessionId: string; }]) => Awaited> + "sync:get-quarantined-items": (...args: []) => Awaited + "sync:get-queue-size": (...args: []) => Awaited<{ pending: number; failed: number; }> + "sync:get-recovery-phrase": (...args: []) => Awaited + "sync:get-status": (...args: []) => Awaited + "sync:get-storage-breakdown": (...args: []) => Awaited> + "sync:get-synced-settings": (...args: []) => Awaited<{ general?: { theme?: "light" | "dark" | "white" | "system" | undefined; fontSize?: "small" | "medium" | "large" | undefined; fontFamily?: "system" | "serif" | "sans-serif" | "monospace" | "gelasio" | "geist" | "inter" | undefined; accentColor?: string | undefined; startOnBoot?: boolean | undefined; language?: string | undefined; createInSelectedFolder?: boolean | undefined; } | undefined; editor?: { width?: "medium" | "narrow" | "wide" | undefined; spellCheck?: boolean | undefined; autoSaveDelay?: number | undefined; showWordCount?: boolean | undefined; toolbarMode?: "floating" | "sticky" | undefined; } | undefined; tasks?: { defaultProjectId?: string | null | undefined; defaultSortOrder?: "createdAt" | "priority" | "dueDate" | "manual" | undefined; weekStartDay?: "sunday" | "monday" | undefined; staleInboxDays?: number | undefined; showCompleted?: boolean | undefined; sortBy?: string | undefined; } | undefined; keyboard?: { overrides?: Record | undefined; } | undefined; notes?: { defaultFolder?: string | undefined; editorFontSize?: number | undefined; spellCheck?: boolean | undefined; } | undefined; sync?: { autoSync?: boolean | undefined; syncIntervalMinutes?: number | undefined; } | undefined; } | null> + "sync:get-upload-progress": (...args: [{ sessionId: string; }]) => Awaited> + "sync:link-via-qr": (...args: [{ qrData: string; oauthToken?: string | undefined; provider?: string | undefined; }]) => Awaited> + "sync:link-via-recovery": (...args: [{ recoveryPhrase: string; }]) => Awaited> + "sync:logout": (...args: []) => Awaited> + "sync:pause": (...args: []) => Awaited<{ success: boolean; wasPaused: boolean; }> + "sync:remove-device": (...args: [{ deviceId: string; }]) => Awaited> + "sync:rename-device": (...args: [{ deviceId: string; newName: string; }]) => Awaited> + "sync:resume": (...args: []) => Awaited<{ success: boolean; pendingCount: number; }> + "sync:setup-first-device": (...args: [{ oauthToken: string; provider: "google"; state: string; }]) => Awaited> + "sync:setup-new-account": (...args: []) => Awaited> + "sync:trigger-sync": (...args: []) => Awaited> + "sync:update-synced-setting": (...args: [unknown]) => Awaited<{ success: boolean; error: string; } | { success: boolean; error?: undefined; }> + "sync:upload-attachment": (...args: [{ noteId: string; filePath: string; }]) => Awaited> + "tags:delete": (...args: [string]) => Awaited> + "tags:get-all-with-counts": (...args: []) => Awaited> + "tags:get-notes-by-tag": (...args: [{ tag: string; sortBy?: "title" | "modified" | "created" | undefined; sortOrder?: "asc" | "desc" | undefined; includeDescendants?: boolean | undefined; }]) => Awaited> + "tags:merge": (...args: [{ source: string; target: string; }]) => Awaited> + "tags:pin-note-to-tag": (...args: [{ noteId: string; tag: string; }]) => Awaited> + "tags:remove-from-note": (...args: [{ noteId: string; tag: string; }]) => Awaited> + "tags:rename": (...args: [{ oldName: string; newName: string; }]) => Awaited> + "tags:unpin-note-from-tag": (...args: [{ noteId: string; tag: string; }]) => Awaited> + "tags:update-color": (...args: [{ tag: string; color: string; }]) => Awaited> + "tasks:archive": (...args: [string]) => Awaited> + "tasks:bulk-archive": (...args: [{ ids: string[]; }]) => Awaited> + "tasks:bulk-complete": (...args: [{ ids: string[]; }]) => Awaited> + "tasks:bulk-delete": (...args: [{ ids: string[]; }]) => Awaited> + "tasks:bulk-move": (...args: [{ ids: string[]; projectId: string; }]) => Awaited> + "tasks:complete": (...args: [{ id: string; completedAt?: string | undefined; }]) => Awaited> + "tasks:convert-to-subtask": (...args: [{ taskId: string; parentId: string; }]) => Awaited> + "tasks:convert-to-task": (...args: [string]) => Awaited> + "tasks:create": (...args: [{ projectId: string; title: string; description?: string | null | undefined; priority?: number | undefined; statusId?: string | null | undefined; parentId?: string | null | undefined; dueDate?: string | null | undefined; dueTime?: string | null | undefined; startDate?: string | null | undefined; isRepeating?: boolean | undefined; repeatConfig?: { frequency: "daily" | "weekly" | "monthly" | "yearly"; endType: "date" | "never" | "count"; createdAt: string; interval?: number | undefined; daysOfWeek?: number[] | undefined; monthlyType?: "dayOfMonth" | "weekPattern" | undefined; dayOfMonth?: number | undefined; weekOfMonth?: number | undefined; dayOfWeekForMonth?: number | undefined; endDate?: string | null | undefined; endCount?: number | undefined; completedCount?: number | undefined; } | null | undefined; repeatFrom?: "due" | "completion" | null | undefined; tags?: string[] | undefined; linkedNoteIds?: string[] | undefined; sourceNoteId?: string | null | undefined; position?: number | undefined; }]) => Awaited> + "tasks:delete": (...args: [string]) => Awaited> + "tasks:duplicate": (...args: [string]) => Awaited> + "tasks:get": (...args: [string]) => Awaited> + "tasks:get-linked-tasks": (...args: [string]) => Awaited> + "tasks:get-overdue": (...args: []) => Awaited> + "tasks:get-stats": (...args: []) => Awaited> + "tasks:get-subtasks": (...args: [string]) => Awaited> + "tasks:get-tags": (...args: []) => Awaited> + "tasks:get-today": (...args: []) => Awaited> + "tasks:get-upcoming": (...args: [{ days?: number | undefined; }]) => Awaited> + "tasks:list": (...args: [{ projectId?: string | undefined; statusId?: string | null | undefined; parentId?: string | null | undefined; includeCompleted?: boolean | undefined; includeArchived?: boolean | undefined; dueBefore?: string | undefined; dueAfter?: string | undefined; tags?: string[] | undefined; search?: string | undefined; sortBy?: "modified" | "created" | "position" | "priority" | "dueDate" | undefined; sortOrder?: "asc" | "desc" | undefined; limit?: number | undefined; offset?: number | undefined; }]) => Awaited> + "tasks:move": (...args: [{ taskId: string; position: number; targetProjectId?: string | undefined; targetStatusId?: string | null | undefined; targetParentId?: string | null | undefined; }]) => Awaited> + "tasks:project-archive": (...args: [string]) => Awaited> + "tasks:project-create": (...args: [{ name: string; description?: string | null | undefined; color?: string | undefined; icon?: string | null | undefined; statuses?: { name: string; type: "todo" | "in_progress" | "done"; order: number; color?: string | undefined; }[] | undefined; }]) => Awaited> + "tasks:project-delete": (...args: [string]) => Awaited> + "tasks:project-get": (...args: [string]) => Awaited> + "tasks:project-list": (...args: []) => Awaited> + "tasks:project-reorder": (...args: [{ projectIds: string[]; positions: number[]; }]) => Awaited> + "tasks:project-update": (...args: [{ id: string; name?: string | undefined; description?: string | null | undefined; color?: string | undefined; icon?: string | null | undefined; statuses?: { name: string; type: "todo" | "in_progress" | "done"; order: number; id?: string | undefined; color?: string | undefined; }[] | undefined; }]) => Awaited> + "tasks:reorder": (...args: [{ taskIds: string[]; positions: number[]; }]) => Awaited> + "tasks:seed-demo": (...args: []) => Awaited> + "tasks:seed-performance-test": (...args: []) => Awaited> + "tasks:status-create": (...args: [{ projectId: string; name: string; color?: string | undefined; isDone?: boolean | undefined; }]) => Awaited> + "tasks:status-delete": (...args: [string]) => Awaited> + "tasks:status-list": (...args: [string]) => Awaited> + "tasks:status-reorder": (...args: [{ statusIds: string[]; positions: number[]; }]) => Awaited> + "tasks:status-update": (...args: [{ id: string; name?: string | undefined; color?: string | undefined; position?: number | undefined; isDefault?: boolean | undefined; isDone?: boolean | undefined; }]) => Awaited> + "tasks:unarchive": (...args: [string]) => Awaited> + "tasks:uncomplete": (...args: [string]) => Awaited> + "tasks:update": (...args: [{ id: string; title?: string | undefined; description?: string | null | undefined; priority?: number | undefined; projectId?: string | undefined; statusId?: string | null | undefined; parentId?: string | null | undefined; dueDate?: string | null | undefined; dueTime?: string | null | undefined; startDate?: string | null | undefined; isRepeating?: boolean | undefined; repeatConfig?: { frequency: "daily" | "weekly" | "monthly" | "yearly"; endType: "date" | "never" | "count"; createdAt: string; interval?: number | undefined; daysOfWeek?: number[] | undefined; monthlyType?: "dayOfMonth" | "weekPattern" | undefined; dayOfMonth?: number | undefined; weekOfMonth?: number | undefined; dayOfWeekForMonth?: number | undefined; endDate?: string | null | undefined; endCount?: number | undefined; completedCount?: number | undefined; } | null | undefined; repeatFrom?: "due" | "completion" | null | undefined; tags?: string[] | undefined; linkedNoteIds?: string[] | undefined; }]) => Awaited> + "templates:create": (...args: [{ name: string; description?: string | undefined; icon?: string | null | undefined; tags?: string[] | undefined; properties?: { name: string; type: "number" | "date" | "text" | "select" | "checkbox" | "url" | "multiselect" | "rating"; value: unknown; options?: string[] | undefined; }[] | undefined; content?: string | undefined; }]) => Awaited> + "templates:delete": (...args: [string]) => Awaited> + "templates:duplicate": (...args: [{ id: string; newName: string; }]) => Awaited> + "templates:get": (...args: [string]) => Awaited> + "templates:list": (...args: []) => Awaited> + "templates:update": (...args: [{ id: string; name?: string | undefined; description?: string | undefined; icon?: string | null | undefined; tags?: string[] | undefined; properties?: { name: string; type: "number" | "date" | "text" | "select" | "checkbox" | "url" | "multiselect" | "rating"; value: unknown; options?: string[] | undefined; }[] | undefined; content?: string | undefined; }]) => Awaited> + "vault:close": (...args: []) => Awaited> + "vault:get-all": (...args: []) => Awaited> + "vault:get-config": (...args: []) => Awaited> + "vault:get-status": (...args: []) => Awaited> + "vault:reindex": (...args: []) => Awaited> + "vault:remove": (...args: [string]) => Awaited> + "vault:reveal": (...args: []) => Awaited> + "vault:select": (...args: [{ path?: string | undefined; }]) => Awaited> + "vault:switch": (...args: [string]) => Awaited> + "vault:update-config": (...args: [{ excludePatterns?: string[] | undefined; defaultNoteFolder?: string | undefined; journalFolder?: string | undefined; attachmentsFolder?: string | undefined; }]) => Awaited> } export type MainIpcInvokeChannel = keyof MainIpcInvokeHandlers -export type MainIpcInvokeArgs = Parameters -export type MainIpcInvokeResult = ReturnType< - MainIpcInvokeHandlers[C] -> +export type MainIpcInvokeArgs = + Parameters +export type MainIpcInvokeResult = + ReturnType From 4c0a9e1a2a640be099cef8d9a6aa9b631bfd2290 Mon Sep 17 00:00:00 2001 From: Kaan Karaca Date: Tue, 7 Apr 2026 01:00:45 +0300 Subject: [PATCH 24/24] fix: resolve TS narrowing error in draft task block detection --- .../src/components/note/content-area/ContentArea.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx index a96c06e20..13e612578 100644 --- a/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx +++ b/apps/desktop/src/renderer/src/components/note/content-area/ContentArea.tsx @@ -486,8 +486,9 @@ const ContentAreaEditor = memo(function ContentAreaEditor({ convertCheckboxToTask(firstUndismissedCheckbox) } - if (firstDraftTaskBlock) { - createTaskForDraftBlock(firstDraftTaskBlock.id, firstDraftTaskBlock.title) + const draft = firstDraftTaskBlock as { id: string; title: string } | null + if (draft) { + createTaskForDraftBlock(draft.id, draft.title) } for (const prevId of knownTaskBlockIdsRef.current) {