Folders and conversation→folder assignments now sync across machines:
- Folder gains updatedAt (v11 migration) to resolve renames/reparents
last-write-wins across machines.
- New folders.json manifest at the sync repo root: folder tree +
conversationId→folderId assignments, imported before conversation
files so new conversations land in the right folder immediately.
- Local folders missing from the manifest are pruned (reparent-safe),
guarded the same way conversation-orphan cleanup already is against
an empty/stale manifest wiping everything.
Three real bugs found and fixed during live multi-machine testing:
- Sidebar never refreshed after Git Sync imported conversations/folders
directly into the database — only reloaded on launch or when the
advanced conversation list closed, with no equivalent hook for the
Settings sheet.
- "Sync Now" exported before pulling, so it could write folders.json
as an untracked file that then collided with the remote's tracked
copy on the next pull ("untracked working tree files would be
overwritten by merge"). Reordered to pull → import → export → push.
- Folder assignment only applied to brand-new conversations during
import, so any conversation already synced to a machine before this
feature existed never got filed — which in practice is every
conversation on a second machine, not an edge case. Now backfills
a folder assignment for existing conversations that aren't filed
anywhere locally yet, without clobbering an already-set folderId.
Also renamed the "Initialize Repository" button to "Clone Repository"
(it's always been a git clone, not new-repo creation) across the UI,
localization catalog, and Help Book.
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.
"oAI" reads as easily confused with OpenAI, both visually and in
casual conversation. Renamed to "Confab" throughout: Xcode
target/scheme/bundle ID (com.oai.Confab), Info.plist and Help Book
identity, all user-facing UI text, internal Log subsystem and color
identifiers, localization catalogs (6 languages, including a proper
reworded/retranslated Intel-deprecation notice), Help Book HTML
content, and docs (README/DEVELOPMENT/PRIVACY/SECURITY).
Deliberately cosmetic-only: the on-disk data folder
(~/Library/Application Support/oAI/), database/backup filenames,
Keychain service identifiers, and EncryptionService's key-derivation
inputs are all left untouched so existing conversations, settings,
and stored API keys survive the update with zero migration and no
re-entering credentials. Verified live: a real signed build
successfully decrypted a stored API key and loaded an existing
conversation database after the bundle ID change.
Also includes a small already-completed, previously uncommitted
model-release-date feature (ModelInfo/OpenRouterModels/
OpenRouterProvider/ModelInfoView) that happened to share several
files with this rename.
Gitignored on this branch and updated on disk but not part of this
commit: CLAUDE.md, RELEASE_NOTES.md, and the build*.sh scripts.
Conversations can now be filed into flat (non-nested) folders, shown
as collapsible sections in both the sidebar and the advanced
conversation list. New folders migration (v9) adds a folders table
and conversations.folderId with ON DELETE SET NULL, so deleting a
folder unfiles its conversations rather than losing them. Move/rename/
delete via context menu; conversation lists with no folders fall back
to the existing flat view unchanged.