Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions Cotabby.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@
A147C5EC3F2214A670F7556E /* FocusPollBackoffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 273B4DC844F79B4BE2C8910F /* FocusPollBackoffTests.swift */; };
A2B3F4D38BCB0FED452B2A3F /* FocusTrackingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D42CD456B4B3C988B148A6 /* FocusTrackingModel.swift */; };
A36481222BB5B2A67349D389 /* ApplicationBundleMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A168A7B6A7AD11559B60C56B /* ApplicationBundleMetadataTests.swift */; };
A440C596EFD9CD1E44F2579B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F6AFF229D20B73CF14C6C /* SettingsView.swift */; };
A5A6CE0EF01CA6A9AFA7A400 /* RequestID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC693E00430F46E41CB56E6 /* RequestID.swift */; };
A87978083EBE1AC294377F7C /* HuggingFaceSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F426127917FCB1096134732 /* HuggingFaceSearchService.swift */; };
A93A741C8C3973687D28F0B6 /* SuggestionEngineModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADBE3E6CC585C1683787C877 /* SuggestionEngineModels.swift */; };
Expand Down Expand Up @@ -217,7 +216,6 @@
0A3D1125B962CBE0269EEDDB /* SuggestionInserter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionInserter.swift; sourceTree = "<group>"; };
0C383AE85B971A9605787358 /* FocusModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusModels.swift; sourceTree = "<group>"; };
0D80CC2CCAAFE3F23FB8C37A /* PromptContextSanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptContextSanitizerTests.swift; sourceTree = "<group>"; };
0E0F6AFF229D20B73CF14C6C /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
0F5E263AB69029D5E13D5EE8 /* FocusDebugOverlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusDebugOverlayController.swift; sourceTree = "<group>"; };
110CB0B53016644EF7840301 /* HuggingFaceAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFaceAPIClient.swift; sourceTree = "<group>"; };
12DD19BCE610808F1E38702D /* PermissionOverlayTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionOverlayTrackerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -644,7 +642,6 @@
9AA0117B322C625F6D4BBEAB /* MenuBarView.swift */,
656F58E56FE9BC087B6F1D33 /* PermissionReminderView.swift */,
5484C8A04B9C00CF79D589EB /* ScreenFrameReader.swift */,
0E0F6AFF229D20B73CF14C6C /* SettingsView.swift */,
FB317C82CE2CBC69056BA4B8 /* TagChip.swift */,
58C0F017699EE44C81C095CA /* TagsInputView.swift */,
67586807ACE8EB13C9014535 /* TickMarkSlider.swift */,
Expand Down Expand Up @@ -961,7 +958,6 @@
644EEF959D07D54CC779BBF6 /* SettingsCoordinator.swift in Sources */,
4B93D26BACEEA932E92B1A19 /* SettingsPaneScaffold.swift in Sources */,
27D4F5CACADE171F142178B4 /* SettingsSidebarView.swift in Sources */,
A440C596EFD9CD1E44F2579B /* SettingsView.swift in Sources */,
12995E5DDB11E3395E6AF82F /* ShortcutsPaneView.swift in Sources */,
4F369F5284DDCEABF082E59B /* SuggestionAvailabilityEvaluator.swift in Sources */,
A0657CE0488F69F0BD559CBC /* SuggestionCoordinator+Acceptance.swift in Sources */,
Expand Down
88 changes: 22 additions & 66 deletions Cotabby/App/Coordinators/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,6 @@ final class SettingsCoordinator: NSObject, NSWindowDelegate {
self.onShowWelcome = onShowWelcome
}

/// UserDefaults key that toggles between the legacy single-form Settings and the redesigned
/// sidebar Settings. Defaults to `true` now that the redesigned panes cover every legacy
/// control; the legacy view is retained as a one-`defaults write` rollback for one release
/// cycle before it is deleted. The key is intentionally narrow so a regression can be
/// reverted with `defaults write com.jacobfu.tabby cotabbySettingsRedesignEnabled -bool NO`
/// without redeploying the app.
static let redesignEnabledDefaultsKey = "cotabbySettingsRedesignEnabled"

private var isRedesignEnabled: Bool {
// `bool(forKey:)` cannot distinguish "user explicitly set false" from "key absent". Read
// the raw object so the absent case can default to true without overwriting an explicit
// opt-out from someone who set the key to false.
guard let stored = UserDefaults.standard.object(forKey: Self.redesignEnabledDefaultsKey) as? Bool else {
return true
}
return stored
}

/// Shows the settings window, reusing the existing instance if it is already open.
/// Reusing one window avoids subtle state duplication and matches standard macOS settings
/// behavior where there is a single shared preferences surface for the app.
Expand All @@ -73,56 +55,30 @@ final class SettingsCoordinator: NSObject, NSWindowDelegate {
return
}

let hostingController: NSHostingController<AnyView>
let initialFrame: CGRect
let minSize: NSSize
let autosaveName: String

if isRedesignEnabled {
hostingController = NSHostingController(
rootView: AnyView(
SettingsContainerView(
appUpdateManager: appUpdateManager,
launchAtLoginService: launchAtLoginService,
permissionManager: permissionManager,
suggestionSettings: suggestionSettings,
foundationModelAvailabilityService: foundationModelAvailabilityService,
runtimeModel: runtimeModel,
modelDownloadManager: modelDownloadManager,
huggingFaceSearchService: huggingFaceSearchService,
onShowWelcome: onShowWelcome
)
let hostingController = NSHostingController(
rootView: AnyView(
SettingsContainerView(
appUpdateManager: appUpdateManager,
launchAtLoginService: launchAtLoginService,
permissionManager: permissionManager,
suggestionSettings: suggestionSettings,
foundationModelAvailabilityService: foundationModelAvailabilityService,
runtimeModel: runtimeModel,
modelDownloadManager: modelDownloadManager,
huggingFaceSearchService: huggingFaceSearchService,
onShowWelcome: onShowWelcome
)
)
// Sized to fit the actual content: a fixed 260pt sidebar (see `SettingsSidebarView`)
// plus a ~600pt detail column for the grouped form. The previous 1320x820 default with
// a 1180 minimum was far wider than any pane's content, which is exactly what left the
// detail area looking stretched and the window feeling empty.
initialFrame = CGRect(x: 0, y: 0, width: 860, height: 700)
minSize = NSSize(width: 820, height: 560)
// Bump the autosave name so anyone holding a saved 1320-wide V3 frame gets the new
// right-sized default once, instead of restoring the oversized window.
autosaveName = "CotabbySettingsWindowV4"
} else {
hostingController = NSHostingController(
rootView: AnyView(
SettingsView(
appUpdateManager: appUpdateManager,
launchAtLoginService: launchAtLoginService,
permissionManager: permissionManager,
suggestionSettings: suggestionSettings,
foundationModelAvailabilityService: foundationModelAvailabilityService,
runtimeModel: runtimeModel,
modelDownloadManager: modelDownloadManager,
huggingFaceSearchService: huggingFaceSearchService,
onShowWelcome: onShowWelcome
)
)
)
initialFrame = CGRect(x: 0, y: 0, width: 700, height: 620)
minSize = NSSize(width: 640, height: 520)
autosaveName = "CotabbySettingsWindow"
}
)
// Sized to fit the actual content: a fixed 260pt sidebar (see `SettingsSidebarView`)
// plus a ~600pt detail column for the grouped form. The previous 1320x820 default with
// a 1180 minimum was far wider than any pane's content, which is exactly what left the
// detail area looking stretched and the window feeling empty.
let initialFrame = CGRect(x: 0, y: 0, width: 860, height: 700)
let minSize = NSSize(width: 820, height: 560)
// Bump the autosave name so anyone holding a saved 1320-wide V3 frame gets the new
// right-sized default once, instead of restoring the oversized window.
let autosaveName = "CotabbySettingsWindowV4"

let window = NSWindow(
contentRect: initialFrame,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import SwiftUI

/// File overview:
/// Shared "Acceptance Mode" picker for the primary accept key. Used by both the legacy
/// `SettingsView.shortcutsSection` and the redesigned `ShortcutsPaneView`, so the granularity
/// labels and cases live in one place — adding or renaming a mode can't drift between the two
/// settings shells.
/// Shared "Acceptance Mode" picker for the primary accept key, used by `ShortcutsPaneView`.
struct AcceptanceModePickerView: View {
@ObservedObject var suggestionSettings: SuggestionSettingsModel

Expand Down
3 changes: 1 addition & 2 deletions Cotabby/UI/Settings/Panes/AppsPaneView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import UniformTypeIdentifiers
/// "Apps" detail pane of the redesigned Settings window. Lists every app where Cotabby is
/// disabled, lets the user remove individual rules, and offers a file-picker entry point for apps
/// that can't be reached from the menu-bar toggle (launchers like Raycast or Spotlight that
/// dismiss themselves the moment the menu bar is clicked). Lifted from the legacy
/// `SettingsView.appsSection` so behavior is preserved.
/// dismiss themselves the moment the menu bar is clicked).
struct AppsPaneView: View {
@ObservedObject var suggestionSettings: SuggestionSettingsModel

Expand Down
4 changes: 1 addition & 3 deletions Cotabby/UI/Settings/Panes/ShortcutsPaneView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import SwiftUI

/// File overview:
/// "Shortcuts" detail pane of the redesigned Settings window. Surfaces the two keybindings that
/// drive suggestion acceptance: word-by-word and full-suggestion. Lifted from the legacy
/// `SettingsView.shortcutsSection` so binding capture, conflict resolution, and reset / clear
/// semantics are preserved exactly.
/// drive suggestion acceptance: word-by-word and full-suggestion.
struct ShortcutsPaneView: View {
@ObservedObject var suggestionSettings: SuggestionSettingsModel

Expand Down
3 changes: 1 addition & 2 deletions Cotabby/UI/Settings/Panes/WritingPaneView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import SwiftUI
/// File overview:
/// "Writing" detail pane of the redesigned Settings window. Owns how the completion reads:
/// preferred length, profile (display name), preferred response languages, and the user's custom
/// style rules. Lifted from the legacy `SettingsView.writingSection` so the controls inside the
/// pane behave identically; only the wrapping form scaffold is new.
/// style rules.
struct WritingPaneView: View {
@ObservedObject var suggestionSettings: SuggestionSettingsModel

Expand Down
3 changes: 0 additions & 3 deletions Cotabby/UI/Settings/SettingsContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import SwiftUI
/// Selection is persisted via `@AppStorage` so reopening Settings lands on the last-used pane.
/// `.id(selection)` on the detail body is the documented workaround for the macOS 14 split-view
/// selection bug where the first sidebar pick doesn't always re-render the detail column.
///
/// The view takes the same observable graph as the legacy `SettingsView` so the coordinator can
/// swap hosting controllers behind a feature flag without rewiring dependencies.
struct SettingsContainerView: View {
let appUpdateManager: AppUpdateManager

Expand Down
Loading