From 8d8bbf44f4f9e46a2c8955eb7f15d5c257b67f4c Mon Sep 17 00:00:00 2001 From: Leo Ashton Date: Wed, 24 Jun 2026 22:21:34 +0800 Subject: [PATCH 1/3] fix: pass projectId to update command to avoid full-table task search - Add projectId extraction from options in tasks.update() - Pass --project option from CLI to tasks.update() - Add null guards in resolveTaskId for data.tasks and array elements --- bin/ticktick.js | 1 + lib/tasks.js | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/ticktick.js b/bin/ticktick.js index 6041cb8..661a476 100755 --- a/bin/ticktick.js +++ b/bin/ticktick.js @@ -199,6 +199,7 @@ async function handleTasks() { process.exit(1); } return await tasks.update(args.positional[0], { + projectId: args.options.project, title: args.options.title, content: args.options.content, dueDate: args.options.due, diff --git a/lib/tasks.js b/lib/tasks.js index 18d4900..cf9d69f 100644 --- a/lib/tasks.js +++ b/lib/tasks.js @@ -131,7 +131,8 @@ export async function update(taskId, options = {}, deps = {}) { parseReminder = coreFunctions.parseReminder, parsePriority = coreFunctions.parsePriority, } = deps; - const resolvedTaskId = await resolveTaskId(taskId, null, deps); + const projectId = options.projectId || null; + const resolvedTaskId = await resolveTaskId(taskId, projectId, deps); const input = { id: resolvedTaskId }; if (options.title) input.title = options.title; @@ -466,7 +467,7 @@ async function resolveTaskId(taskId, projectId = null, deps = {}) { // Search within specific project try { const data = await apiRequest('GET', `/project/${encodeURIComponent(projectId)}/data`, undefined, deps); - const match = data.tasks.find((t) => t.id.startsWith(taskId)); + const match = (data.tasks || []).find((t) => t && t.id && t.id.startsWith(taskId)); if (match) { return match.id; } @@ -481,7 +482,7 @@ async function resolveTaskId(taskId, projectId = null, deps = {}) { for (const project of projects) { try { const data = await apiRequest('GET', `/project/${encodeURIComponent(project.id)}/data`, undefined, deps); - const match = data.tasks.find((t) => t.id.startsWith(taskId)); + const match = (data.tasks || []).find((t) => t && t.id && t.id.startsWith(taskId)); if (match) { return match.id; } From 0aea5ecef08f9ae0b3ab7d80fafc8537de6d6ce5 Mon Sep 17 00:00:00 2001 From: Leo Ashton Date: Wed, 24 Jun 2026 22:22:25 +0800 Subject: [PATCH 2/3] fix: pass correct options arg in tasks.due test calls due() signature is (days, options, deps). Tests were passing mock deps as the options argument, causing real API calls. --- test/tasks.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/tasks.test.js b/test/tasks.test.js index b5da8b7..a508b8b 100644 --- a/test/tasks.test.js +++ b/test/tasks.test.js @@ -500,7 +500,7 @@ describe('tasks.due', () => { return {}; }); - const result = await tasks.due(7, makeDeps(mockApiRequest)); + const result = await tasks.due(7, {}, makeDeps(mockApiRequest)); assert.equal(result.days, 7); // Should include tomorrow and next week, but not next month, no due date, or completed @@ -528,7 +528,7 @@ describe('tasks.due', () => { return {}; }); - const result = await tasks.due(7, makeDeps(mockApiRequest)); + const result = await tasks.due(7, {}, makeDeps(mockApiRequest)); assert.equal(result.tasks[0].title, 'Day 1'); assert.equal(result.tasks[1].title, 'Day 2'); @@ -543,7 +543,7 @@ describe('tasks.due', () => { return {}; }); - const result = await tasks.due(undefined, makeDeps(mockApiRequest)); + const result = await tasks.due(undefined, {}, makeDeps(mockApiRequest)); assert.equal(result.days, 7); }); @@ -567,7 +567,7 @@ describe('tasks.due', () => { return {}; }); - const result = await tasks.due(7, makeDeps(mockApiRequest)); + const result = await tasks.due(7, {}, makeDeps(mockApiRequest)); // Overdue tasks should be included - they're the most urgent assert.equal(result.count, 2); From 15815e4e6a5412146581a0ab099d964ad28c5f39 Mon Sep 17 00:00:00 2001 From: Leo Ashton Date: Wed, 24 Jun 2026 22:41:17 +0800 Subject: [PATCH 3/3] fix: handle 204 No Content response from TickTick update API - Add data && data.tasks null guards in resolveTaskId - Add projects || [] guard for API response - Handle 204 empty response in update() - API returns no body on success --- lib/tasks.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/tasks.js b/lib/tasks.js index cf9d69f..054c3c1 100644 --- a/lib/tasks.js +++ b/lib/tasks.js @@ -146,9 +146,10 @@ export async function update(taskId, options = {}, deps = {}) { } const task = await apiRequest('POST', `/task/${encodeURIComponent(resolvedTaskId)}`, input, deps); + // API returns 204 No Content on success, re-fetch if we need details return { success: true, - task: { + task: task ? { id: shortId(task.id), fullId: task.id, projectId: shortId(task.projectId), @@ -156,6 +157,10 @@ export async function update(taskId, options = {}, deps = {}) { dueDate: task.dueDate, priority: formatPriority(task.priority), tags: task.tags || [], + } : { + id: shortId(resolvedTaskId), + fullId: resolvedTaskId, + title: input.title || '(updated)', }, }; } @@ -467,7 +472,7 @@ async function resolveTaskId(taskId, projectId = null, deps = {}) { // Search within specific project try { const data = await apiRequest('GET', `/project/${encodeURIComponent(projectId)}/data`, undefined, deps); - const match = (data.tasks || []).find((t) => t && t.id && t.id.startsWith(taskId)); + const match = (data && data.tasks || []).find((t) => t && t.id && t.id.startsWith(taskId)); if (match) { return match.id; } @@ -479,10 +484,10 @@ async function resolveTaskId(taskId, projectId = null, deps = {}) { // Search all projects const projects = await apiRequest('GET', '/project', undefined, deps); - for (const project of projects) { + for (const project of (projects || [])) { try { const data = await apiRequest('GET', `/project/${encodeURIComponent(project.id)}/data`, undefined, deps); - const match = (data.tasks || []).find((t) => t && t.id && t.id.startsWith(taskId)); + const match = (data && data.tasks || []).find((t) => t && t.id && t.id.startsWith(taskId)); if (match) { return match.id; }