Folders can now contain other folders, arbitrarily deep — e.g. "Work"
containing "Project A"/"Project B". v10 migration adds a
self-referencing parentId column; tree ordering, depth, and cycle
detection are pure Swift (Folder.orderedTree/isDescendant/
visibleFolderIds), not SQL, so listFolders() stays a simple flat
query.
- Create nested folders via "New Subfolder…" (context menu, both
list views) or by dragging a folder onto another to reparent it.
Dragging onto an existing descendant is rejected (cycle guard).
- Deleting a folder reparents its children and any conversations
filed directly in it up one level to the deleted folder's own
parent — conversations are never deleted. This also fixes a real
bug: the previous deleteFolder never persisted unfiling to the
database, only patched in-memory state, so a conversation whose
folder was deleted kept a dangling folderId and silently vanished
from view after the next relaunch.
- All "Move to Folder" pickers (sidebar, advanced list, per-row
context menus, the Save dialog's folder popup) show an indented
flat list reflecting the tree.
- New DraggedItem enum disambiguates a dragged folder from dragged
conversation(s) in the shared string-based drag payload, and
unifies both list views on the same bundled-multi-selection format
— closes a gap where dragging a multi-selection in the advanced
list (⌘L) only moved the one row grabbed, unlike the sidebar.
Confirmed working live, including relaunch-survival of the
delete/reparent fix.