From b37df6facf0fc3954671274bfcd6f632f76e1de4 Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 31 Mar 2026 17:13:59 +0200 Subject: [PATCH] feat(todo): redesign task detail modal + add inline subtasks - Redesign TaskEditModal as a multi-column page-like layout: - Title (large, borderless textarea with autoGrow) - Content grid: description (left) | subtasks + links (right) - Props strip: all metadata as horizontal flex-wrap cells (status, priority, due date, recurrence, tags, assignee, story points, etc.) - No sidebar, no scrollable property list - Remove redundant `notes` field from TaskMetadata (keep only `description`) - Remove all legacy migration code from useTaskForm composable - Add inline subtasks display to TaskItem on the homepage: - Shown indented under parent task when task has subtasks and is incomplete - Each subtask is directly checkable (toggleSubtask via onSave callback) - Vertical connecting line via CSS pseudo-element Co-Authored-By: Claude Sonnet 4.6 --- .../src/lib/components/TaskEditModal.svelte | 904 ++++++++++-------- .../web/src/lib/components/TaskItem.svelte | 154 ++- .../components/kanban/KanbanTaskCard.svelte | 2 +- .../src/lib/composables/useTaskForm.svelte.ts | 9 - apps/todo/packages/shared/src/types/task.ts | 1 - 5 files changed, 637 insertions(+), 433 deletions(-) diff --git a/apps/todo/apps/web/src/lib/components/TaskEditModal.svelte b/apps/todo/apps/web/src/lib/components/TaskEditModal.svelte index 066efe877..7b0163112 100644 --- a/apps/todo/apps/web/src/lib/components/TaskEditModal.svelte +++ b/apps/todo/apps/web/src/lib/components/TaskEditModal.svelte @@ -14,7 +14,7 @@ import { ContactSelector, focusTrap } from '@manacore/shared-ui'; import { ManaLinkList, ManaLinkPicker } from '@manacore/shared-links/ui'; import { searchCrossApp } from '$lib/data/cross-app-search'; - import { X } from '@manacore/shared-icons'; + import { X, Trash } from '@manacore/shared-icons'; interface Props { task: Task; @@ -27,35 +27,26 @@ let { task, open, onClose, onSave, onDelete }: Props = $props(); const form = useTaskForm(); - - // Link picker state let showLinkPicker = $state(false); - // Initialize form when task changes or modal opens $effect(() => { - if (open && task) { - form.initFromTask(task); - } + if (open && task) form.initFromTask(task); }); function handleKeydown(e: KeyboardEvent) { - if (e.key === 'Escape') { - onClose(); - } else if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + if (e.key === 'Escape') onClose(); + else if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); handleSave(); } } function handleBackdropClick(e: MouseEvent) { - if (e.target === e.currentTarget) { - onClose(); - } + if (e.target === e.currentTarget) onClose(); } async function handleSave() { if (!form.title.trim()) return; - form.isLoading = true; try { onSave(form.buildUpdateInput(task)); @@ -65,203 +56,199 @@ } function handleDelete() { - if (form.showDeleteConfirm) { - onDelete(task.id); - } else { - form.showDeleteConfirm = true; - } + if (form.showDeleteConfirm) onDelete(task.id); + else form.showDeleteConfirm = true; } function handleSubtasksChange(newSubtasks: Subtask[]) { form.subtasks = newSubtasks; } + + function autoGrow(node: HTMLTextAreaElement) { + function resize() { + node.style.height = 'auto'; + node.style.height = node.scrollHeight + 'px'; + } + node.addEventListener('input', resize); + resize(); + return { destroy: () => node.removeEventListener('input', resize) }; + } {#if open} -