UI redesign Phase 1: NavigationSplitView with collapsible sidebar

- Replace root VStack with NavigationSplitView (2-column, collapsible sidebar)
- Add SidebarView: new chat button, conversation search, list with swipe actions
- Slim HeaderView to text-only (provider + model + star); remove all icon rows
- Move status pills (Online, MCP, Synced) to footer right side
- Remove version number and shortcut hints from footer
- Add resizable InputBar with drag handle (persisted height) and globe/network.slash online toggle
- Fix Norwegian menu appearing on English systems (CFBundleLocalizations in Info.plist)
- Add View menu (Model Info, History, Stats, Credits, Online Mode toggle ⌘⇧O)
- Add ⌘L as alias for Search Conversations (muscle memory for /load users)
- Add Check for Updates to Help menu with download URL from Gitea API
- Add one-time Intel/Rosetta deprecation warning on first launch
- Swift 6: fix self.Self.isoString() call sites in DatabaseService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 11:18:48 +02:00
parent cd0ceeab41
commit 8451db1142
19 changed files with 702 additions and 363 deletions
+20 -18
View File
@@ -114,41 +114,43 @@ final class FileLogger: @unchecked Sendable {
// MARK: - App Logger (wraps os.Logger + file)
struct AppLogger {
let osLogger: Logger
// os.Logger methods are @MainActor in macOS 27. AppLogger is Sendable and all methods are
// nonisolated FileLogger runs on its own serial queue, os.Logger dispatches to main actor.
struct AppLogger: Sendable {
let subsystem: String
let category: String
func debug(_ message: String) {
nonisolated func debug(_ message: String) {
FileLogger.shared.write(.debug, category: category, message: message)
osLogger.debug("\(message, privacy: .public)")
Task { @MainActor [self] in Logger(subsystem: subsystem, category: category).debug("\(message, privacy: .public)") }
}
func info(_ message: String) {
nonisolated func info(_ message: String) {
FileLogger.shared.write(.info, category: category, message: message)
osLogger.info("\(message, privacy: .public)")
Task { @MainActor [self] in Logger(subsystem: subsystem, category: category).info("\(message, privacy: .public)") }
}
func warning(_ message: String) {
nonisolated func warning(_ message: String) {
FileLogger.shared.write(.warning, category: category, message: message)
osLogger.warning("\(message, privacy: .public)")
Task { @MainActor [self] in Logger(subsystem: subsystem, category: category).warning("\(message, privacy: .public)") }
}
func error(_ message: String) {
nonisolated func error(_ message: String) {
FileLogger.shared.write(.error, category: category, message: message)
osLogger.error("\(message, privacy: .public)")
Task { @MainActor [self] in Logger(subsystem: subsystem, category: category).error("\(message, privacy: .public)") }
}
}
// MARK: - Log Namespace
enum Log {
private static let subsystem = "com.oai.oAI"
private nonisolated static let subsystem = "com.oai.oAI"
static let api = AppLogger(osLogger: Logger(subsystem: subsystem, category: "api"), category: "api")
static let db = AppLogger(osLogger: Logger(subsystem: subsystem, category: "database"), category: "database")
static let mcp = AppLogger(osLogger: Logger(subsystem: subsystem, category: "mcp"), category: "mcp")
static let settings = AppLogger(osLogger: Logger(subsystem: subsystem, category: "settings"), category: "settings")
static let search = AppLogger(osLogger: Logger(subsystem: subsystem, category: "search"), category: "search")
static let ui = AppLogger(osLogger: Logger(subsystem: subsystem, category: "ui"), category: "ui")
static let general = AppLogger(osLogger: Logger(subsystem: subsystem, category: "general"), category: "general")
nonisolated static let api = AppLogger(subsystem: subsystem, category: "api")
nonisolated static let db = AppLogger(subsystem: subsystem, category: "database")
nonisolated static let mcp = AppLogger(subsystem: subsystem, category: "mcp")
nonisolated static let settings = AppLogger(subsystem: subsystem, category: "settings")
nonisolated static let search = AppLogger(subsystem: subsystem, category: "search")
nonisolated static let ui = AppLogger(subsystem: subsystem, category: "ui")
nonisolated static let general = AppLogger(subsystem: subsystem, category: "general")
}