From 0ec1a8b7f7f444fea127e0099cede7620434a366 Mon Sep 17 00:00:00 2001
From: Sandro
Date: Sun, 25 Oct 2026 01:43:14 +0200
Subject: [PATCH] feat(tasks): support subtasks with inline creation, checklist
and title progress bar
---
frontend/src/features/triage/TasksView.tsx | 276 ++++++++++++++++--
.../src/features/workspace/WorkspaceView.tsx | 98 +++++--
frontend/src/lib/api.ts | 2 +
frontend/src/lib/types.ts | 8 +
internal/api/server.go | 17 +-
internal/domain/models.go | 8 +
internal/storage/sqlite.go | 37 ++-
7 files changed, 388 insertions(+), 58 deletions(-)
diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx
index 13948c0..b7ea35f 100644
--- a/frontend/src/features/triage/TasksView.tsx
+++ b/frontend/src/features/triage/TasksView.tsx
@@ -15,8 +15,9 @@ import {
Layers,
Share2,
Users,
+ CheckSquare,
} from 'lucide-react';
-import { TaskList, Thought, Space } from '../../lib/types';
+import { TaskList, Thought, Space, SubTask } from '../../lib/types';
import { api, triggerHaptic } from '../../lib/api';
const getInitials = (name?: string): string => {
@@ -67,6 +68,10 @@ export const TasksView: React.FC = ({
const [activeNotesTaskId, setActiveNotesTaskId] = useState(null);
const [notesDraft, setNotesDraft] = useState('');
+ // Subtask creation state
+ const [addingSubtaskTaskId, setAddingSubtaskTaskId] = useState(null);
+ const [subtaskDraft, setSubtaskDraft] = useState('');
+
const loadData = async () => {
setLoading(true);
try {
@@ -125,6 +130,168 @@ export const TasksView: React.FC = ({
setActiveNotesTaskId(null);
};
+ const handleAddSubtask = async (task: Thought, title: string) => {
+ const trimmed = title.trim();
+ if (!trimmed) return;
+ triggerHaptic(10);
+ const newSub: SubTask = {
+ id: `sub_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
+ title: trimmed,
+ isCompleted: false,
+ createdAt: new Date().toISOString(),
+ };
+ const updatedSubtasks = [...(task.subtasks || []), newSub];
+ setTasks((prev) =>
+ prev.map((t) => (t.id === task.id ? { ...t, subtasks: updatedSubtasks } : t))
+ );
+ setSubtaskDraft('');
+ await api.updateThought(task.id, { subtasks: updatedSubtasks });
+ };
+
+ const handleToggleSubtask = async (task: Thought, subtaskId: string) => {
+ triggerHaptic(10);
+ const updatedSubtasks = (task.subtasks || []).map((s) =>
+ s.id === subtaskId ? { ...s, isCompleted: !s.isCompleted } : s
+ );
+ setTasks((prev) =>
+ prev.map((t) => (t.id === task.id ? { ...t, subtasks: updatedSubtasks } : t))
+ );
+ await api.updateThought(task.id, { subtasks: updatedSubtasks });
+ };
+
+ const handleDeleteSubtask = async (task: Thought, subtaskId: string) => {
+ triggerHaptic(10);
+ const updatedSubtasks = (task.subtasks || []).filter((s) => s.id !== subtaskId);
+ setTasks((prev) =>
+ prev.map((t) => (t.id === task.id ? { ...t, subtasks: updatedSubtasks } : t))
+ );
+ await api.updateThought(task.id, { subtasks: updatedSubtasks });
+ };
+
+ const renderTaskProgressBar = (task: Thought) => {
+ if (!task.subtasks || task.subtasks.length === 0) return null;
+ const total = task.subtasks.length;
+ const done = task.subtasks.filter((s) => s.isCompleted).length;
+ const percent = Math.round((done / total) * 100);
+
+ return (
+
+
+
+
+
+ {done}/{total} Unteraufgaben
+
+
+
+ {percent}%
+
+
+
+
+ );
+ };
+
+ const renderSubtaskList = (task: Thought) => {
+ const hasSubtasks = task.subtasks && task.subtasks.length > 0;
+ const isAdding = addingSubtaskTaskId === task.id;
+
+ if (!hasSubtasks && !isAdding) return null;
+
+ return (
+
+ {hasSubtasks && (
+
+ {task.subtasks!.map((sub) => (
+
+
+
+
+ ))}
+
+ )}
+
+ {isAdding && (
+
+ )}
+
+ );
+ };
+
const handleConvertToDoc = async (task: Thought) => {
triggerHaptic(20);
const updated = await api.triageAction(task.id, 'keep', { convertTo: 'document' });
@@ -657,6 +824,9 @@ export const TasksView: React.FC = ({
+ {/* Subtasks Progress Bar on Title */}
+ {renderTaskProgressBar(task)}
+
{/* Notes Preview / Inline Editor */}
{activeNotesTaskId === task.id ? (
@@ -695,6 +865,9 @@ export const TasksView: React.FC = ({
) : null}
+ {/* Subtasks Checklist & Inline Creator */}
+ {renderSubtaskList(task)}
+
{/* Footer & Movement Controls */}
@@ -714,6 +887,18 @@ export const TasksView: React.FC
= ({
+ Notiz
)}
+
@@ -764,6 +949,9 @@ export const TasksView: React.FC = ({
+ {/* Subtasks Progress Bar on Title */}
+ {renderTaskProgressBar(task)}
+
{/* Notes Preview / Inline Editor */}
{activeNotesTaskId === task.id ? (
@@ -801,25 +989,56 @@ export const TasksView: React.FC = ({
) : null}
+ {/* Subtasks Checklist & Inline Creator */}
+ {renderSubtaskList(task)}
+
{/* Movement Controls */}
-
+
+ {!task.notes && (
+
+ )}
+
+
-
+
+
+
+
+
))}
@@ -849,6 +1068,10 @@ export const TasksView: React.FC = ({
{task.content}
+ {/* Subtasks Progress Bar & Checklist */}
+ {renderTaskProgressBar(task)}
+ {renderSubtaskList(task)}
+