diff --git a/apps/mana/apps/web/src/lib/data/time-blocks/recurrence.ts b/apps/mana/apps/web/src/lib/data/time-blocks/recurrence.ts index 275ae4f8d..805bae8f6 100644 --- a/apps/mana/apps/web/src/lib/data/time-blocks/recurrence.ts +++ b/apps/mana/apps/web/src/lib/data/time-blocks/recurrence.ts @@ -195,7 +195,7 @@ export async function cleanupFutureInstances(templateBlockId: string): Promise).isRecurrenceException) continue; + if (instance.isRecurrenceException) continue; if (instance.recurrenceDate && instance.recurrenceDate >= today) { await deleteBlock(instance.id); } @@ -255,7 +255,7 @@ export function expandTemplatesVirtually( const existingKeys = new Set( existingBlocks .filter((b) => b.parentBlockId) - .map((b) => `${b.parentBlockId}|${(b as Record).recurrenceDate}`) + .map((b) => `${b.parentBlockId}|${b.recurrenceDate}`) ); const virtuals: VirtualTimeBlock[] = []; diff --git a/apps/mana/apps/web/src/lib/modules/calendar/stores/events.svelte.ts b/apps/mana/apps/web/src/lib/modules/calendar/stores/events.svelte.ts index dd6f48202..080876520 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/stores/events.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/calendar/stores/events.svelte.ts @@ -377,6 +377,9 @@ export const eventsStore = { icon: null, isLive: false, projectId: null, + linkedBlockId: null, + parentBlockId: null, + recurrenceDate: null, }; return draftEvent; }, diff --git a/apps/mana/apps/web/src/lib/modules/calendar/types.ts b/apps/mana/apps/web/src/lib/modules/calendar/types.ts index 71d61fd37..45a971234 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/types.ts +++ b/apps/mana/apps/web/src/lib/modules/calendar/types.ts @@ -59,7 +59,7 @@ export interface CalendarEvent { projectId: string | null; linkedBlockId: string | null; parentBlockId: string | null; - recurrenceRule: string | null; + recurrenceDate: string | null; } export interface Calendar { @@ -107,5 +107,6 @@ export function timeBlockToCalendarEvent( projectId: block.projectId, linkedBlockId: block.linkedBlockId, parentBlockId: block.parentBlockId, + recurrenceDate: block.recurrenceDate, }; } diff --git a/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts b/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts index bbda717a9..a2529d7f3 100644 --- a/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts @@ -19,8 +19,6 @@ import { materializeRecurringBlocks, regenerateForBlock, } from '$lib/data/time-blocks/recurrence'; -import { db } from '$lib/data/database'; -import type { LocalTimeBlock } from '$lib/data/time-blocks/types'; import type { LocalHabit, LocalHabitLog, HabitSchedule } from '../types'; export const habitsStore = { diff --git a/apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts b/apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts index 21432e9c7..c6f62d363 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts @@ -47,7 +47,8 @@ export function useTaskForm() { title = task.title; description = task.description ?? ''; dueDate = task.dueDate ? task.dueDate.split('T')[0] : ''; - // Load scheduled time from TimeBlock if scheduled + // Load scheduled time + recurrence from TimeBlock if scheduled + recurrenceRule = ''; if (task.scheduledBlockId) { const block = await getBlock(task.scheduledBlockId); if (block) { @@ -55,6 +56,7 @@ export function useTaskForm() { dueTime = block.startDate.includes('T') ? block.startDate.split('T')[1]?.substring(0, 5) : ''; + recurrenceRule = block.recurrenceRule ?? ''; } } else { dueTime = ''; @@ -63,7 +65,6 @@ export function useTaskForm() { priority = task.priority; status = task.status; subtasks = task.subtasks ? [...task.subtasks] : []; - recurrenceRule = task.recurrenceRule ?? ''; effectiveDuration = task.estimatedDuration ?? null; showDeleteConfirm = false; isLoading = false; diff --git a/apps/mana/apps/web/src/lib/modules/todo/queries.ts b/apps/mana/apps/web/src/lib/modules/todo/queries.ts index f25107265..2ae04821d 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/queries.ts @@ -19,7 +19,7 @@ import type { export function toTask(local: LocalTask): Task { return { id: local.id, - projectId: (local as Record).projectId as string | null | undefined, + projectId: local.projectId, title: local.title, description: local.description, dueDate: local.dueDate, @@ -30,7 +30,6 @@ export function toTask(local: LocalTask): Task { isCompleted: local.isCompleted, completedAt: local.completedAt, order: local.order, - recurrenceRule: local.recurrenceRule, subtasks: local.subtasks ?? null, metadata: local.metadata ?? null, createdAt: local.createdAt ?? new Date().toISOString(), diff --git a/apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts b/apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts index 4fdb91e15..d52ca8f86 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts @@ -21,6 +21,7 @@ export const tasksStore = { subtasks?: Subtask[]; recurrenceRule?: string; estimatedDuration?: number; + labelIds?: string[]; // Optional: schedule on calendar scheduleStartDate?: string; // YYYY-MM-DD scheduleStartTime?: string; // HH:mm @@ -49,6 +50,7 @@ export const tasksStore = { sourceId: taskId, title: data.title, projectId: data.projectId ?? null, + recurrenceRule: data.recurrenceRule ?? null, }); } @@ -62,12 +64,12 @@ export const tasksStore = { scheduledBlockId, estimatedDuration: data.estimatedDuration ?? null, order: count, - recurrenceRule: data.recurrenceRule ?? null, subtasks: data.subtasks, + metadata: data.labelIds && data.labelIds.length > 0 ? { labelIds: data.labelIds } : undefined, }; if (data.projectId !== undefined) { - (newLocal as Record).projectId = data.projectId; + newLocal.projectId = data.projectId; } await taskTable.add(newLocal); @@ -82,8 +84,11 @@ export const tasksStore = { // Handle schedule changes via TimeBlock const schedStartDate = data._scheduleStartDate as string | null | undefined; const schedStartTime = data._scheduleStartTime as string | null | undefined; + const recurrenceRule = data.recurrenceRule as string | null | undefined; delete data._scheduleStartDate; delete data._scheduleStartTime; + // recurrenceRule lives on the TimeBlock — never on the task row + delete data.recurrenceRule; if (schedStartDate !== undefined) { if (schedStartDate) { @@ -103,6 +108,7 @@ export const tasksStore = { endDate: endISO, allDay: !schedStartTime, title: (data.title as string) ?? task.title, + ...(recurrenceRule !== undefined ? { recurrenceRule } : {}), }); } else { // Create new block @@ -116,6 +122,7 @@ export const tasksStore = { sourceId: id, title: (data.title as string) ?? task.title, projectId: (data.projectId as string) ?? task.projectId ?? null, + recurrenceRule: recurrenceRule ?? null, }); data.scheduledBlockId = blockId; } @@ -133,6 +140,11 @@ export const tasksStore = { await updateBlock(task.scheduledBlockId, { title: data.title as string }); } + // Update recurrence on existing TimeBlock when not also rescheduling + if (recurrenceRule !== undefined && schedStartDate === undefined && task.scheduledBlockId) { + await updateBlock(task.scheduledBlockId, { recurrenceRule }); + } + await taskTable.update(id, { ...data, updatedAt: new Date().toISOString(), diff --git a/packages/eslint-config/typescript.js b/packages/eslint-config/typescript.js index 20dd1a940..b44b27d61 100644 --- a/packages/eslint-config/typescript.js +++ b/packages/eslint-config/typescript.js @@ -22,6 +22,7 @@ export const typescriptConfig = [ ecmaVersion: 2022, sourceType: 'module', projectService: true, + tsconfigRootDir: import.meta.dirname, }, }, plugins: {