diff --git a/CHANGELOG.md b/CHANGELOG.md index 9251c66..07a72ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ## [Unreleased] ### Changed +- „Als Notiz vertiefen“ bei einer Aufgabe: Die Aufgabe bleibt in ihrer Liste und wird zum ersten Häkchen der neuen Notiz, verknüpft wie eine Aufgabe aus einer Notiz („In Notiz“); Abhaken wirkt an beiden Stellen. Ein Gedanke aus der Triage wird wie bisher zur Notiz. - Quick Dump: Die Listenzeile unter den Zielen richtet sich nach dem Ziel. Bei „Direkt als Aufgabe“ wählst du die Aufgabenliste (oder „Alle Aufgaben“), bei „Direkt auf Liste“ die Einkaufs- oder Checkliste; bei Quick Dump und Notiz gibt es keine Zeile mehr. Die zuletzt gewählte Liste bleibt je Ziel für die Sitzung gemerkt. ## [0.32.0] - 2026-11-01 diff --git a/Doku/modules/tasks.md b/Doku/modules/tasks.md index 9a1e2f0..511a21d 100644 --- a/Doku/modules/tasks.md +++ b/Doku/modules/tasks.md @@ -11,7 +11,7 @@ Eine Aufgabe ist ein `thought` ([database.md](../database.md)) mit `kanbanStatus - **Liste** mit Filtern (offen, erledigt, Papierkorb), Gruppierung (z. B. nach Status oder Fälligkeit), Sortierung und Hashtag-Filter. - **Kanban:** Spalten pro Liste frei konfigurierbar (Name, Farbe, „Erledigt“-Spalte). Farben in „Spalten anpassen“ über den Punkt vor dem Namen: Grau, Anthrazit (Textfarbe des Themes), Grün, Gelb, Blau, Rot, Lila, Akzentfarbe (`columns[].color` = `gray`/`slate`/`green`/`yellow`/`blue`/`red`/`purple`/`accent`; ältere Werte `emerald`/`amber` gelten als Grün/Gelb, Spalten ohne Farbe behalten die ihrer Rolle). Der Punkt erscheint im Spaltenkopf, vor dem Status in der Listenansicht und im Detail-Fenster ([ManageColumnsModal.tsx](../../frontend/src/features/triage/ManageColumnsModal.tsx), `columnDotClass`). Blau und Lila kommen aus `--color-info` / `--color-purple`; Themes können sie setzen, sonst gelten Standardwerte aus `index.css`. Standard: *Zu erledigen*, *In Bearbeitung*, *Wartet auf andere*, *Erledigt*. Eine Karte in eine „Erledigt“-Spalte zu ziehen setzt `isCompleted`. - Drag & Drop sortiert in der Liste und zwischen Spalten; ein Klick öffnet das Detail-Fenster (Titel, Status, Priorität, Fälligkeit mit optionaler Uhrzeit und Schnellwahl, Liste, Hashtags, Unteraufgaben mit Fortschrittsbalken, Notizen). -- Aufgaben lassen sich in eine Notiz umwandeln („Als Notiz vertiefen“). Aufgaben aus Notiz-Checkboxen (`enhanced-task`) bleiben verknüpft: Abhaken wirkt in beide Richtungen (`SyncDocBlockAndTask`), „In Notiz öffnen“ springt zum Block. +- „Als Notiz vertiefen“ legt aus einer Aufgabe eine Notiz an (Titel, Notizen, Unteraufgaben, Fälligkeit, Priorität als Inhalt). Die Aufgabe bleibt in ihrer Liste und wird mit dem ersten Häkchen der Notiz verknüpft (`converted_type = document_block`); ein Gedanke aus der Triage wird dagegen ganz zur Notiz. Aufgaben aus Notiz-Checkboxen (`enhanced-task`) bleiben verknüpft: Abhaken wirkt in beide Richtungen (`SyncDocBlockAndTask`), „In Notiz öffnen“ springt zum Block. ## Aufgabenlisten und Freigabe diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx index 39d93cf..6f78071 100644 --- a/frontend/src/features/triage/TasksView.tsx +++ b/frontend/src/features/triage/TasksView.tsx @@ -666,8 +666,11 @@ export const TasksView: React.FC = ({ triggerHaptic(20); const updated = await api.triageAction(task.id, 'keep', { convertTo: 'document' }); onRefreshStats(); - if (updated.convertedRefId) { - onOpenDocument(updated.convertedRefId); + // The task stays in its list, linked to the note's first checkbox (docId); open that note + setTasks((prev) => prev.map((t) => (t.id === updated.id ? updated : t))); + const docId = updated.docId || (updated.convertedType === 'document' ? updated.convertedRefId : ''); + if (docId) { + onOpenDocument(docId); } else { loadData(); } diff --git a/internal/storage/deepen_test.go b/internal/storage/deepen_test.go new file mode 100644 index 0000000..ab7f2f7 --- /dev/null +++ b/internal/storage/deepen_test.go @@ -0,0 +1,51 @@ +package storage + +import ( + "path/filepath" + "testing" + + "github.com/sandrodach/mindsift/internal/domain" +) + +// "Als Notiz vertiefen": a task stays in its list, linked to the note's first checkbox; a thought from +// the inbox still turns into the note. +func TestDeepenTaskIntoNoteKeepsTheTask(t *testing.T) { + store, err := NewStore(filepath.Join(t.TempDir(), "deepen.db")) + if err != nil { + t.Fatalf("NewStore: %v", err) + } + defer store.Close() + anna, _ := store.CreateUser("anna@example.com", "hash", "Anna", "member") + tl, _ := store.CreateTaskList(anna.ID, "", "", "Homelab", "", "") + + th, _ := store.CreateThoughtWithDetails(anna.ID, "Backup-Konzept", "high", "manual", "", tl.ID, "todo", "Restic oder Borg?") + if _, err := store.UpdateThoughtTriage(anna.ID, th.ID, "keep", "task", "", "", tl.ID); err != nil { + t.Fatal(err) + } + deepened, err := store.UpdateThoughtTriage(anna.ID, th.ID, "keep", "document", "", "", "") + if err != nil { + t.Fatal(err) + } + if deepened.Status != domain.StatusKept || deepened.ConvertedType != "document_block" || deepened.DocID == "" || deepened.TaskListID != tl.ID { + t.Fatalf("task should stay in its list, linked to the note: %+v", deepened) + } + tasks, _ := store.GetTasks(anna.ID, tl.ID) + if len(tasks) != 1 || tasks[0].ID != th.ID { + t.Fatalf("task missing from its list: %+v", tasks) + } + doc, err := store.GetDocumentByID(anna.ID, deepened.DocID) + if err != nil || doc == nil || len(doc.Blocks) == 0 || doc.Blocks[0].ID != deepened.BlockID || doc.Blocks[0].Type != "todo" { + t.Fatalf("first block should be the linked checkbox: %+v %v", doc, err) + } + // Completing the task ticks the checkbox in the note + if store.SyncDocBlockAndTask(anna.ID, th.ID, true) != deepened.DocID { + t.Fatal("completing the task should tick its block") + } + + // A thought from the inbox becomes the note as before + inbox, _ := store.CreateThought(anna.ID, "Idee für Blogartikel", "normal", "manual", "") + asNote, err := store.UpdateThoughtTriage(anna.ID, inbox.ID, "keep", "document", "", "", "") + if err != nil || asNote.Status != domain.StatusConverted || asNote.ConvertedType != "document" { + t.Fatalf("inbox thought should turn into a note: %+v %v", asNote, err) + } +} diff --git a/internal/storage/thoughts.go b/internal/storage/thoughts.go index ed5b18a..8a6e344 100644 --- a/internal/storage/thoughts.go +++ b/internal/storage/thoughts.go @@ -310,6 +310,7 @@ func (s *Store) UpdateThoughtTriage(userID, thoughtID, action, convertTo, target newStatus = domain.StatusConverted convertedType = "task" } else if convertTo == "document" { + isTask := thought.Status == domain.StatusKept || (thought.Status == domain.StatusConverted && thought.ConvertedType == "task") newStatus = domain.StatusConverted convertedType = "document" title := thought.Content @@ -373,6 +374,13 @@ func (s *Store) UpdateThoughtTriage(userID, thoughtID, action, convertTo, target doc, err := s.CreateDocument(userID, "", title, "📄", "", blocks, thought.Tags, false) if err == nil && doc != nil { convertedRefID = doc.ID + // A task stays in its list and becomes the note's first checkbox (same link as a task made + // from a note block); a thought from the inbox turns into the note + if isTask { + newStatus = domain.StatusKept + convertedType = "document_block" + convertedRefID = doc.ID + ":" + blocks[0].ID + } } } else if convertTo == "list_item" && targetListID != "" { // Same rule as adding an item in the list itself: owner or editor