feat(ui): collapsible folders for the list sidebar

Folders render above the top-level lists, each with its own collapsible
children; tapping a folder header folds it instead of selecting it.
Lists move in by dragging onto a folder header (or onto a row inside it)
and out by dropping between top-level rows or via the context menu.

Creating a folder is a toggle in the New-list modal, not a third sidebar
button: it reduces the modal to name + icon and writes a folder row
instead of the list row the create flow pre-created.
This commit is contained in:
mika kuns
2026-09-10 08:29:56 +02:00
parent 13e860f06b
commit 25c9a619dc
9 changed files with 522 additions and 95 deletions
+7 -1
View File
@@ -260,6 +260,8 @@
"contextOpenFindings": "Findings-Ordner öffnen",
"contextImportTickets": "Aus Ticketsystem aktualisieren",
"contextSettings": "Einstellungen…",
"contextFolderSettings": "Ordner-Einstellungen…",
"contextRemoveFromFolder": "Aus Ordner herausnehmen",
"newList": "Neue Liste",
"addReposTip": "Repos als Listen hinzufügen",
"noRepoBannerText": "Noch kein Repo verknüpft. ClaudeDo führt Tasks in einem Git-Repo aus.",
@@ -403,7 +405,11 @@
},
"listSettings": {
"title": "LISTENEINSTELLUNGEN",
"folderTitle": "ORDNER",
"deleteList": "Liste löschen",
"deleteFolder": "Ordner löschen",
"createAsFolder": "Stattdessen einen Ordner anlegen",
"createAsFolderHint": "Ein Ordner gruppiert nur Listen in der Seitenleiste — er enthält keine Aufgaben und führt nichts aus. Listen per Drag-and-Drop hineinziehen.",
"sectionGeneral": "ALLGEMEIN",
"name": "Name",
"workingDirectory": "Arbeitsverzeichnis",
@@ -793,7 +799,7 @@
"sessionSkillsTab": { "installed": "Installiert: {0}", "installFailed": "Installation fehlgeschlagen: {0}", "updated": "Aktualisiert.", "updateFailed": "Aktualisierung fehlgeschlagen: {0}", "removed": "Entfernt.", "removeFailed": "Entfernen fehlgeschlagen: {0}" },
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "{0} Worktree(s) entfernt.", "blocked": "Zwangsentfernung nicht möglich: {0} Aufgabe(n) laufen noch. Brich sie zuerst ab.", "removedFrom": "{0} Worktree(s) von {1} Aufgabe(n) entfernt.", "cleanupFailed": "Aufräumen fehlgeschlagen: {0}", "resetFailed": "Zurücksetzen fehlgeschlagen: {0}" },
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "Liste", "cleanupFailed": "Aufräumen fehlgeschlagen.", "cleanupFailedDetailed": "Aufräumen fehlgeschlagen: {0}", "removed": "{0} Worktree(s) entfernt.", "discardConfirm": "Worktree für \"{0}\" verwerfen? Nicht committete Arbeit geht verloren.", "discardFailed": "Worktree konnte nicht verworfen werden.", "keepFailed": "Worktree konnte nicht behalten werden.", "cannotForceRunning": "Eine laufende Aufgabe kann nicht zwangsweise entfernt werden.", "forceRemoveFailed": "Zwangsentfernung fehlgeschlagen.", "forceRemoveFailedDetailed": "Zwangsentfernung fehlgeschlagen: {0}", "batchDone": "{0} gemergt, {1} zu lösen.", "openInExplorerFailed": "Pfad konnte nicht geöffnet werden: {0}" },
"listSettings": { "untitled": "Unbenannt", "ticketProjectNone": "— keins —", "loadFailed": "Listeneinstellungen konnten nicht geladen werden: {0}", "ticketProjectsLoadFailed": "Ticket-Projekte konnten nicht geladen werden: {0}" },
"listSettings": { "untitled": "Unbenannt", "ticketProjectNone": "— keins —", "loadFailed": "Listeneinstellungen konnten nicht geladen werden: {0}", "ticketProjectsLoadFailed": "Ticket-Projekte konnten nicht geladen werden: {0}", "confirmDeleteFolder": "Ordner \"{0}\" löschen? Die Listen darin bleiben erhalten und wandern auf die oberste Ebene." },
"detailsIsland": { "verifyFailed": "Merge ist erfolgt, aber das Verifikationskommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf 'Erledigt' gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "stopOffline": "Worker offline — Task kann nicht gestoppt werden.", "stopFailed": "Stoppen fehlgeschlagen: {0}", "enqueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "dequeueFailed": "Aus der Warteschlange entfernen fehlgeschlagen: {0}", "resetAndRetryFailed": "Zurücksetzen & erneut versuchen fehlgeschlagen: {0}", "toggleDoneFailed": "Erledigt-Status konnte nicht aktualisiert werden: {0}" },
"mergeSection": { "openWorktreeFailed": "Worktree konnte nicht geöffnet werden: {0}" },
"about": { "openPathFailed": "Pfad konnte nicht geöffnet werden: {0}" },
+7 -1
View File
@@ -260,6 +260,8 @@
"contextOpenFindings": "Open findings folder",
"contextImportTickets": "Update from ticket system",
"contextSettings": "Settings…",
"contextFolderSettings": "Folder settings…",
"contextRemoveFromFolder": "Move out of folder",
"newList": "New list",
"addReposTip": "Add repos as lists",
"noRepoBannerText": "No repo linked yet. ClaudeDo runs tasks in a git repo.",
@@ -403,7 +405,11 @@
},
"listSettings": {
"title": "LIST SETTINGS",
"folderTitle": "FOLDER",
"deleteList": "Delete list",
"deleteFolder": "Delete folder",
"createAsFolder": "Create a folder instead",
"createAsFolderHint": "A folder just groups lists in the sidebar — it holds no tasks and runs nothing. Drag lists onto it to fill it.",
"sectionGeneral": "GENERAL",
"name": "Name",
"workingDirectory": "Working directory",
@@ -793,7 +799,7 @@
"sessionSkillsTab": { "installed": "Installed: {0}", "installFailed": "Install failed: {0}", "updated": "Updated.", "updateFailed": "Update failed: {0}", "removed": "Removed.", "removeFailed": "Remove failed: {0}" },
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "Removed {0} worktree(s).", "blocked": "Cannot force-remove: {0} task(s) still running. Cancel them first.", "removedFrom": "Removed {0} worktree(s) from {1} task(s).", "cleanupFailed": "Cleanup failed: {0}", "resetFailed": "Reset failed: {0}" },
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "list", "cleanupFailed": "Cleanup failed.", "cleanupFailedDetailed": "Cleanup failed: {0}", "removed": "Removed {0} worktree(s).", "discardConfirm": "Discard the worktree for \"{0}\"? Uncommitted work will be lost.", "discardFailed": "Failed to discard worktree.", "keepFailed": "Failed to keep worktree.", "cannotForceRunning": "Cannot force-remove a running task.", "forceRemoveFailed": "Force remove failed.", "forceRemoveFailedDetailed": "Force remove failed: {0}", "batchDone": "Merged {0}, {1} need resolution.", "openInExplorerFailed": "Couldn't open path: {0}" },
"listSettings": { "untitled": "Untitled", "ticketProjectNone": "— none —", "loadFailed": "Couldn't load list settings: {0}", "ticketProjectsLoadFailed": "Couldn't load ticket projects: {0}" },
"listSettings": { "untitled": "Untitled", "ticketProjectNone": "— none —", "loadFailed": "Couldn't load list settings: {0}", "ticketProjectsLoadFailed": "Couldn't load ticket projects: {0}", "confirmDeleteFolder": "Delete folder \"{0}\"? The lists inside it are kept and move back to the top level." },
"detailsIsland": { "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "stopOffline": "Worker offline — can't stop the task.", "stopFailed": "Stop failed: {0}", "enqueueFailed": "Queue failed: {0}", "dequeueFailed": "Remove from queue failed: {0}", "resetAndRetryFailed": "Reset & retry failed: {0}", "toggleDoneFailed": "Couldn't update done state: {0}" },
"mergeSection": { "openWorktreeFailed": "Couldn't open worktree: {0}" },
"about": { "openPathFailed": "Couldn't open path: {0}" },