/* Shared record writes for forms and the Life OS terminal. */ (() => { const board = () => { const current = sys().tasksBoard; return current && Array.isArray(current.goals) && Array.isArray(current.tasks) ? current : { goals: [], tasks: [] }; }; const validDate = value => !value || (/^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(`${value}T12:00:00`)) && new Date(`${value}T12:00:00`).getFullYear() === Number(value.slice(0, 4)) && new Date(`${value}T12:00:00`).getMonth() + 1 === Number(value.slice(5, 7)) && new Date(`${value}T12:00:00`).getDate() === Number(value.slice(8))); const required = (value, max) => { const result = String(value || '').trim(); if (!result || result.length > max) throw new Error('invalidTitle'); return result; }; const persistBoard = data => { const state = sys(); state.tasksBoard = data; save('lifeos_system', state); renderAll(); }; window.LifeOSActions = { board, createGoal({ title, description = '', targetDate = '', showOnDesktop = false }) { title = required(title, 100); if (!validDate(targetDate)) throw new Error('invalidDate'); const goal = { id: `goal_${crypto.randomUUID()}`, title, description: String(description).trim().slice(0, 240), targetDate, showOnDesktop: Boolean(targetDate && showOnDesktop), done: false, createdAt: new Date().toISOString() }; const data = board(); data.goals.unshift(goal); persistBoard(data); return goal; }, updateGoal(id, { title, description = '', targetDate = '', showOnDesktop = false }) { title = required(title, 100); if (!validDate(targetDate)) throw new Error('invalidDate'); const data = board(), goal = data.goals.find(item => item.id === id); if (!goal) throw new Error('notFound'); Object.assign(goal, { title, description: String(description).trim().slice(0, 240), targetDate, showOnDesktop: Boolean(targetDate && showOnDesktop), updatedAt: new Date().toISOString() }); persistBoard(data); return goal; }, createTask({ title, dueDate = '', goalId = '', priority = 'normal' }) { title = required(title, 140); if (!validDate(dueDate)) throw new Error('invalidDate'); if (!['normal', 'high', 'low'].includes(priority)) throw new Error('invalidPriority'); const data = board(); if (goalId && !data.goals.some(goal => goal.id === goalId)) throw new Error('notFound'); const task = { id: `task_${crypto.randomUUID()}`, title, dueDate, goalId, priority, done: false, createdAt: new Date().toISOString() }; data.tasks.push(task); persistBoard(data); return task; }, updateTask(id, { title, dueDate = '', goalId = '', priority = 'normal' }) { title = required(title, 140); if (!validDate(dueDate)) throw new Error('invalidDate'); if (!['normal', 'high', 'low'].includes(priority)) throw new Error('invalidPriority'); const data = board(), task = data.tasks.find(item => item.id === id); if (!task || (goalId && !data.goals.some(goal => goal.id === goalId))) throw new Error('notFound'); Object.assign(task, { title, dueDate, goalId, priority, updatedAt: new Date().toISOString() }); persistBoard(data); return task; }, setTaskDone(id, done) { const data = board(), task = data.tasks.find(item => item.id === id); if (!task) throw new Error('notFound'); task.done = done; task.completedAt = done ? new Date().toISOString() : null; persistBoard(data); return task; }, setGoalDone(id, done) { const data = board(), goal = data.goals.find(item => item.id === id); if (!goal) throw new Error('notFound'); goal.done = done; goal.completedAt = done ? new Date().toISOString() : null; persistBoard(data); return goal; }, deleteTask(id) { const data = board(), task = data.tasks.find(item => item.id === id); if (!task) throw new Error('notFound'); data.tasks = data.tasks.filter(item => item.id !== id); const all = mems(); if (all.some(item => item.taskId === id)) save('lifeos_memories', all.map(item => item.taskId === id ? { ...item, taskId: '', updatedAt: new Date().toISOString() } : item)); persistBoard(data); return task; }, deleteGoal(id) { const data = board(), goal = data.goals.find(item => item.id === id); if (!goal) throw new Error('notFound'); data.goals = data.goals.filter(item => item.id !== id); data.tasks.forEach(task => { if (task.goalId === id) task.goalId = ''; }); persistBoard(data); return goal; }, createMemory({ date = day(), title, text = '', mood = 'cloudy', energy = 3, tags = [], location = '', taskId = '', replace = false }) { title = String(title || '').trim(); if (title.length > 120) throw new Error('invalidTitle'); if (!validDate(date) || !date) throw new Error('invalidDate'); if (!['clear', 'light', 'cloudy', 'rain', 'storm'].includes(mood) || !Number.isInteger(Number(energy)) || Number(energy) < 1 || Number(energy) > 5) throw new Error('invalidValue'); if (taskId && !board().tasks.some(task => task.id === taskId)) throw new Error('notFound'); const all = mems(), old = all.find(item => item.date === date); if (old && !replace) throw new Error('memoryExists'); const film = filmFor(mood, Number(energy)), now = new Date().toISOString(); const memory = { ...old, id: old?.id || `memory_${crypto.randomUUID()}`, date, title, text: String(text).trim(), mood, energy: Number(energy), tags: [...new Set(tags.map(tag => String(tag).trim()).filter(Boolean))].slice(0, 12), location: String(location).trim().slice(0, 100), taskId, filmTitle: film.filmTitle, filmSubtitle: film.filmSubtitle, filmLine: film.filmLine, createdAt: old?.createdAt || now, updatedAt: now }; save('lifeos_memories', [...all.filter(item => item.date !== date), memory].sort((a, b) => b.date.localeCompare(a.date))); renderAll(); return memory; }, deleteMemory(id) { const all = mems(), memory = all.find(item => item.id === id); if (!memory) throw new Error('notFound'); save('lifeos_memories', all.filter(item => item.id !== id)); save('lifeos_museum', museum().filter(item => item.memoryId !== id)); renderAll(); return memory; } }; })();