diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 7e72eba..a32676b 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,8 +5,8 @@ import { GripVertical, Inbox, ListChecks, ListTodo, Menu, Pencil, Plus, Search, Settings, Trash2, X, Repeat2, } from 'lucide-vue-next' -import { buildTaskRrule, filterTasks, fromDateTimeLocal, groupTaskTree, moveItemWithinScope, parseTaskRrule, renderMarkdown, toDateTimeLocal, type TaskRepeatConfig } from './lib/task-utils' -import { defaultView, isTaskView, nextTotalAfterLocalTaskAdd, quickTaskFields, shouldToggleRowSwipe, writeCountdownCache } from './lib/mvp-utils' +import { buildTaskRrule, defaultTaskDueAt, filterTasks, fromDateTimeLocal, groupTaskTree, moveItemWithinScope, parseTaskRrule, renderMarkdown, toDateTimeLocal, type TaskRepeatConfig } from './lib/task-utils' +import { defaultView, isTaskView, nextTotalAfterLocalTaskAdd, shouldToggleRowSwipe, writeCountdownCache } from './lib/mvp-utils' import { csrfHeader } from './lib/csrf' import MvpPanel from './MvpPanel.vue' import CountdownPanel from './CountdownPanel.vue' @@ -80,7 +80,7 @@ function openTaskCompose() { const inboxId = lists.value.find((item) => item.is_inbox)?.id || activeList.value composeTitle.value = '' composeListId.value = activeView.value === 'tasks' && activeList.value ? activeList.value : inboxId - composeDueAt.value = activeView.value === 'today' ? toDateTimeLocal(quickTaskFields('today', activeList.value, inboxId).due_at ?? null) : '' + composeDueAt.value = defaultTaskDueAt() composePriority.value = 0 composeDescription.value = '' composeRepeat.value = 'none' diff --git a/frontend/src/lib/task-utils.test.ts b/frontend/src/lib/task-utils.test.ts index cd07e6f..8075609 100644 --- a/frontend/src/lib/task-utils.test.ts +++ b/frontend/src/lib/task-utils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { buildTaskRrule, filterTasks, groupTaskTree, moveItemWithinScope, parseTaskRrule, renderMarkdown, toDateTimeLocal } from './task-utils' +import { buildTaskRrule, defaultTaskDueAt, filterTasks, groupTaskTree, moveItemWithinScope, parseTaskRrule, renderMarkdown, toDateTimeLocal } from './task-utils' type SearchTask = { id: string @@ -63,6 +63,11 @@ describe('task utilities', () => { expect(filterTasks(searchable, '工作').map((task) => task.id)).toEqual(['4']) }) + it('defaults new tasks to today at noon in local time', () => { + const due = defaultTaskDueAt(new Date(2026, 8, 8, 21, 30)) + expect(due).toBe('2026-09-08T12:00') + }) + it('formats API dates in local wall-clock time', () => { const original = process.env.TZ process.env.TZ = 'Asia/Shanghai' diff --git a/frontend/src/lib/task-utils.ts b/frontend/src/lib/task-utils.ts index f7ee3b3..774f5d2 100644 --- a/frontend/src/lib/task-utils.ts +++ b/frontend/src/lib/task-utils.ts @@ -144,6 +144,13 @@ export function parseTaskRrule(rrule = ''): TaskRepeatConfig { } } +export function defaultTaskDueAt(now = new Date()) { + const year = now.getFullYear() + const month = `${now.getMonth() + 1}`.padStart(2, '0') + const day = `${now.getDate()}`.padStart(2, '0') + return `${year}-${month}-${day}T12:00` +} + export function toDateTimeLocal(value: string | null | undefined) { if (!value) return '' const date = new Date(value) diff --git a/frontend/src/style.test.ts b/frontend/src/style.test.ts index b866cdc..60c1b35 100644 --- a/frontend/src/style.test.ts +++ b/frontend/src/style.test.ts @@ -183,6 +183,11 @@ describe('mobile touch targets', () => { }) describe('unified floating add interaction', () => { + it('defaults every new-task composer to today', () => { + expect(app).toContain('composeDueAt.value = defaultTaskDueAt()') + expect(app).not.toContain("activeView.value === 'today' ? toDateTimeLocal(quickTaskFields") + }) + it('lets task create and detail forms configure a repeat rule', () => { expect(app).toContain('const composeRepeat = ref') expect(app).toContain('const selectedTaskRepeat = ref')