2.5.1 #11

Merged
rune merged 10 commits from 2.5.1 into main 2026-08-04 14:12:17 +02:00
10 Commits
Author SHA1 Message Date
rune 87eab6fd75 Run git subprocess off the main thread with a timeout
runGit() previously called Process.waitUntilExit() synchronously on
the MainActor with no timeout. If a git network operation (push/pull/
fetch) was in flight when the Mac went to sleep, the dead connection
could hang indefinitely on wake with no OS-level timeout of its own,
freezing the entire app UI with nothing logged since the command never
actually finished. Now runs on a background queue with a 30s timeout
(120s for clone), matching the pattern already used by bash_execute.
2026-08-04 12:59:09 +02:00
rune e2284aba2b Show manual sync-conflict fix instructions in-app instead of deep-linking to Help
NSWorkspace.shared.open() silently drops #fragment anchors on file://
URLs, so "Fix It Myself" always landed on the Help Book index instead
of the relevant section. Replaced with GitSyncManualFixSheet, an
in-app sheet showing the real conflicting filenames and sync path.

Also indent conversation rows one level deeper than their containing
folder in the sidebar and conversation list, so nesting is visible on
the conversations themselves and not just the folder headers.
2026-08-04 12:21:15 +02:00
rune 125e1698f7 Fix Git Sync race between startup pull and auto-sync export
syncOnStartup() (pull+import, fired at launch) and autoSync() (export+push,
debounced off chat activity) ran as fully independent, uncoordinated Tasks
with no mutual exclusion. A user launching the app and chatting right away
could hit autoSync's export mid-pull, leaving a freshly-written untracked
file that the pull then refuses to merge over — the same failure class as
the earlier folders.json bug, now much more likely to surface widely since
folders.json/notes.json are brand new for every existing sync repo.

Adds a shared isSyncing guard across all three entry points (syncOnStartup
skips if busy, autoSync waits for a clear slot, syncNow throws
.syncInProgress) and moves Sync Now's pull/import/export/push orchestration
out of SettingsView into GitSyncService.syncNow(), where the guard can
actually protect it.
2026-08-04 08:52:48 +02:00
rune f4086c2563 Bump version to 2.5.1 2026-08-04 08:40:33 +02:00
rune d93c233453 Sync conversation notes via Git Sync, add discard shortcut to crash-recovery prompt
Notes files now export to notes/ + notes.json alongside conversations.json,
matching folders.json's manifest pattern: matched by conversation ID, never
overwrites notes a machine already has locally, same empty-state orphan-
cleanup safety guard as the existing conversation/folder sync code. Also
adds Cmd+D to the "Discard" button on the crash-recovery restore prompt.

Fixes two Swift 6 actor-isolation build warnings surfaced along the way:
ConversationNotesService and the String filename-sanitizing extension are
pure, state-free helpers called from nonisolated contexts (DatabaseService,
GitSyncService) but defaulted to @MainActor — marked nonisolated.
2026-08-04 08:31:16 +02:00
rune 3414e37e24 Add per-conversation notes.md
Gives each conversation an opt-in, persistent memory file the model reads
automatically every turn and writes to on its own initiative via a fenced
```update-notes``` block in its reply — no per-write approval, matching the
Confab-as-CLAUDE.md-for-itself concept Rune wanted. /notes on|off|show,
files live in ~/Library/Application Support/oAI/notes/, embedded ID header
for future Git Sync compatibility. Adds DB migration v12.
2026-08-04 07:58:47 +02:00
rune 32e6ce3c37 Show release notes in-app instead of opening the web releases page
New "Read Release Notes" entries in the Help menu (current installed
version) and the "Check for Updates" alert (the new, not-yet-installed
version) render a release's markdown notes in a Confab modal.

- UpdateCheckService.fetchReleaseNotes(forTag:) fetches a release's
  title + body from Gitea's public releases-by-tag API, caching the
  result by version tag in the settings table (a published release's
  notes don't change, so no need to refetch on every view).
- ReleaseNotesView reuses the existing MarkdownContentView renderer;
  shows a friendly "not available yet" state for versions with no
  published Gitea release (e.g. a dev build ahead of the last release).
- ReleaseNotesRequest carries which version to show atomically via
  .sheet(item:), per this project's established sheet-timing pattern.
- Removed the redundant "Release Page" button from the update alert
  now that notes show in-app; added an explicit .keyboardShortcut
  (.cancelAction) to its cancel button so Escape actually closes it —
  role: .cancel alone didn't do it, since NSAlert only auto-binds
  Escape to a button literally titled "Cancel".
2026-08-03 13:13:36 +02:00
rune 6480a50eee Sync folder structure via Git Sync (folders.json), plus bugs found testing it
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.
2026-08-03 11:48:23 +02:00
rune c3abc5a748 Update remaining oai.pm references to confab.no
Covers the per-file license-header comment (~80 Swift files) plus
the contact/website links in README.md, PRIVACY.md, and SECURITY.md.
2026-08-03 08:44:35 +02:00
rune c207c86a1e Update LICENSE contact domain to confab.no
The oai.pm links were leftover from before the oAI→Confab rename.
This file is copied into every DMG as LICENSE.txt.
2026-08-03 08:42:21 +02:00