From 45888744afaebdcf0d6ad2759d8cd9c0d910a449 Mon Sep 17 00:00:00 2001 From: Jacob Fu <141651335+FuJacob@users.noreply.github.com> Date: Thu, 28 May 2026 03:13:57 -0700 Subject: [PATCH] Add Writing / Shortcuts / Apps panes to redesigned Settings --- Cotabby.xcodeproj/project.pbxproj | 12 ++ Cotabby/UI/Settings/Panes/AppsPaneView.swift | 109 +++++++++++++++ .../UI/Settings/Panes/ShortcutsPaneView.swift | 126 ++++++++++++++++++ .../UI/Settings/Panes/WritingPaneView.swift | 53 ++++++++ .../UI/Settings/SettingsContainerView.swift | 8 +- 5 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 Cotabby/UI/Settings/Panes/AppsPaneView.swift create mode 100644 Cotabby/UI/Settings/Panes/ShortcutsPaneView.swift create mode 100644 Cotabby/UI/Settings/Panes/WritingPaneView.swift diff --git a/Cotabby.xcodeproj/project.pbxproj b/Cotabby.xcodeproj/project.pbxproj index 3a313f7..1057385 100644 --- a/Cotabby.xcodeproj/project.pbxproj +++ b/Cotabby.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 0DDC0CFF5558A8F4355836B2 /* OverlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F308F6E274CC645E27CB651F /* OverlayController.swift */; }; 0F3267956257401F39386773 /* SuggestionOverlayStabilityGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2F95847D76893C8A5B504B4 /* SuggestionOverlayStabilityGate.swift */; }; 1003373E13779882503C0E9D /* DisplayCoordinateConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BD1D4DB27D5D96D1E06096 /* DisplayCoordinateConverter.swift */; }; + 12995E5DDB11E3395E6AF82F /* ShortcutsPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB630F9814388203DD1CA2EC /* ShortcutsPaneView.swift */; }; 14D77F0B8A195AC2FA8D24A9 /* MirrorOverlayLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC83D14A7557BC0196E59007 /* MirrorOverlayLayoutTests.swift */; }; 156E6AB3D24134EEC29FDB93 /* FocusSnapshotResolverSelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA705EDFE1C41294F0E381F1 /* FocusSnapshotResolverSelectionTests.swift */; }; 157A55EB796BEB7819B90D5D /* ClipboardRelevanceFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A2AC525DC664DB540D4F19 /* ClipboardRelevanceFilter.swift */; }; @@ -161,6 +162,7 @@ E17CAA453B1F534D284F0D89 /* PermissionHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ACCB12E4DB32D2F2BEA567 /* PermissionHostApp.swift */; }; E313639E71AE1374D2B9A956 /* SuggestionWorkController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B2D97BAA3618A7D0357AC44 /* SuggestionWorkController.swift */; }; E442A19096A4B5BDA944BDEA /* OpenSourcePaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E7064AC8B6EFEA971F4CA2 /* OpenSourcePaneView.swift */; }; + E51FA12B690428CA431328FC /* WritingPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48B95B6665109B6C6A63B42 /* WritingPaneView.swift */; }; E6EE3C13FA31F261CD734C69 /* DownloadOutcomeClassifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE1975F3B5F4A70478DBF41 /* DownloadOutcomeClassifier.swift */; }; E912D4617AE1376061DF1F00 /* LanguageSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4793D4EA5D36D7E5CC216C27 /* LanguageSupportTests.swift */; }; E994FE418A961FB234D9057A /* DownloadFileRescuerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F46767D9D1F0D44E239CA8 /* DownloadFileRescuerTests.swift */; }; @@ -168,6 +170,7 @@ ED9C51B0D7056F0753AADF2D /* GhostSuggestionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043E8AA850F930222DD112C0 /* GhostSuggestionLayout.swift */; }; EDA8E8250FC2F70B206B4894 /* LlamaVisualContextSummarizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D2782B6C7BE3F56BCB22DE /* LlamaVisualContextSummarizer.swift */; }; EE87886AC1BFC8BB3DE09762 /* HuggingFaceModelBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E49BDA7F3A42455C4C5350 /* HuggingFaceModelBrowserView.swift */; }; + EF0DE5E045F328F1E912A02A /* AppsPaneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C1C921A1CDA2ADFC39EA01 /* AppsPaneView.swift */; }; F08C139B246C1EC7BB435455 /* MenuBarPresentationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00824BDD8D0E9B3063827C78 /* MenuBarPresentationObserver.swift */; }; F0DEEE8A866ABB560E7A7E6A /* LaunchAtLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220CD4AFA1E96A37BC4514AD /* LaunchAtLoginService.swift */; }; F4EEE6291095B0BF2D3FBA21 /* GhostTextColorPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E44E393DD58B978B1EAB6CF /* GhostTextColorPreset.swift */; }; @@ -328,12 +331,14 @@ CE8C2569A8217EE9BD3B197F /* FileLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogHandler.swift; sourceTree = ""; }; D2F46767D9D1F0D44E239CA8 /* DownloadFileRescuerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadFileRescuerTests.swift; sourceTree = ""; }; D3A2AC525DC664DB540D4F19 /* ClipboardRelevanceFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardRelevanceFilter.swift; sourceTree = ""; }; + D48B95B6665109B6C6A63B42 /* WritingPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WritingPaneView.swift; sourceTree = ""; }; D49F3B597374208594861B9B /* FoundationModelDriftEvalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationModelDriftEvalTests.swift; sourceTree = ""; }; D4F6D5F94B238F7B4BE7C247 /* FocusCapabilityResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusCapabilityResolverTests.swift; sourceTree = ""; }; D504BEB224E0C176F5FCFF6E /* CompletionRenderModePolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionRenderModePolicyTests.swift; sourceTree = ""; }; D5916CF13B85EFCE6A049296 /* AppleIntelligencePaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIntelligencePaneView.swift; sourceTree = ""; }; D5D6C2318E405AA717D1C256 /* WelcomePermissionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePermissionStepView.swift; sourceTree = ""; }; D84D4528EEC9EFEB8AE8E318 /* ActivationIndicatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivationIndicatorController.swift; sourceTree = ""; }; + D9C1C921A1CDA2ADFC39EA01 /* AppsPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsPaneView.swift; sourceTree = ""; }; DB0CE9AB1286367BA2E82392 /* SettingsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsContainerView.swift; sourceTree = ""; }; DDE858CB1E687E3CEB8FDD5B /* SuggestionRequestFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionRequestFactory.swift; sourceTree = ""; }; DEB16474A67CE1D210B944C9 /* SuggestionSubsystemContracts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionSubsystemContracts.swift; sourceTree = ""; }; @@ -344,6 +349,7 @@ E6423D6CC8CC371D2DA899DE /* PermissionOverlayTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionOverlayTracker.swift; sourceTree = ""; }; E7F42112F14026E6253BB865 /* PermissionAndContextModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionAndContextModelTests.swift; sourceTree = ""; }; EAAE6B395FAB604DF059280A /* KeyCodeLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCodeLabels.swift; sourceTree = ""; }; + EB630F9814388203DD1CA2EC /* ShortcutsPaneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsPaneView.swift; sourceTree = ""; }; ED8672B87CEC72BE3978C6BB /* CotabbyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CotabbyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; EE94342B888A5A2CCF66BC93 /* SuggestionRequestFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionRequestFactoryTests.swift; sourceTree = ""; }; EFD89799BB82AF7A92559AEB /* ClipboardContentDistillerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardContentDistillerTests.swift; sourceTree = ""; }; @@ -433,11 +439,14 @@ A3FA53BBC3D81503C1D17477 /* AboutPaneView.swift */, 2B7A28471B8526C2693FFF65 /* AcknowledgementsView.swift */, D5916CF13B85EFCE6A049296 /* AppleIntelligencePaneView.swift */, + D9C1C921A1CDA2ADFC39EA01 /* AppsPaneView.swift */, FC9ECD5408B0F5708149B5C0 /* EngineAndModelPaneView.swift */, 07480CE96ED0EBD94817C6B1 /* GeneralPaneView.swift */, 65E7064AC8B6EFEA971F4CA2 /* OpenSourcePaneView.swift */, 7113D3373525113CA69E7597 /* PermissionsPaneView.swift */, 93028F328388432E72C58D09 /* PlaceholderPaneView.swift */, + EB630F9814388203DD1CA2EC /* ShortcutsPaneView.swift */, + D48B95B6665109B6C6A63B42 /* WritingPaneView.swift */, ); path = Panes; sourceTree = ""; @@ -824,6 +833,7 @@ C4C6734678797669055988E0 /* AppUpdateManager.swift in Sources */, 8557DF86F088D19D2DAC70BC /* AppleIntelligencePaneView.swift in Sources */, 66C23A7C2FCDE0266FF425F8 /* ApplicationBundleMetadata.swift in Sources */, + EF0DE5E045F328F1E912A02A /* AppsPaneView.swift in Sources */, 3CBBC3BFAC0DC8952EE24EF7 /* BundledRuntimeLocator.swift in Sources */, 76FD91607794883F8E121450 /* CaretGeometrySelector.swift in Sources */, 6E01052209B73D7361C12CEF /* ClipboardContentDistiller.swift in Sources */, @@ -906,6 +916,7 @@ 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 */, D2F1DD215989BF32675308C2 /* SuggestionCoordinator+Input.swift in Sources */, @@ -942,6 +953,7 @@ 9ABF75CDA78B27453C3F5B34 /* WelcomeView.swift in Sources */, 1F8CC88AFFE67C08944CF506 /* WindowScreenshotService.swift in Sources */, 22DF59FD6F3B83B092A6F5ED /* WordCountFormatter.swift in Sources */, + E51FA12B690428CA431328FC /* WritingPaneView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Cotabby/UI/Settings/Panes/AppsPaneView.swift b/Cotabby/UI/Settings/Panes/AppsPaneView.swift new file mode 100644 index 0000000..7926735 --- /dev/null +++ b/Cotabby/UI/Settings/Panes/AppsPaneView.swift @@ -0,0 +1,109 @@ +import AppKit +import SwiftUI +import UniformTypeIdentifiers + +/// File overview: +/// "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. +struct AppsPaneView: View { + @ObservedObject var suggestionSettings: SuggestionSettingsModel + + var body: some View { + SettingsPaneScaffold { + Section("Apps") { + Text("Cotabby won't autocomplete in these apps. Add an app you can't disable from the " + + "menu bar, like a launcher that closes the moment it loses focus.") + .font(.caption) + .foregroundStyle(.secondary) + + if suggestionSettings.disabledAppRules.isEmpty { + Text("No apps are disabled. Cotabby is active in every supported field.") + .font(.callout) + .foregroundStyle(.secondary) + } else { + ForEach(suggestionSettings.disabledAppRules) { rule in + disabledAppRuleRow(rule) + } + } + + Button("Add App…") { + presentDisabledAppPicker() + } + } + } + } + + @ViewBuilder + private func disabledAppRuleRow(_ rule: DisabledApplicationRule) -> some View { + HStack(spacing: 12) { + Image(nsImage: icon(for: rule)) + .resizable() + .frame(width: 28, height: 28) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: 2) { + Text(rule.displayName) + + Text(rule.bundleIdentifier) + .font(.caption) + .foregroundStyle(.secondary) + .textSelection(.enabled) + } + + Spacer(minLength: 0) + + Button { + suggestionSettings.removeDisabledApplication( + bundleIdentifier: rule.bundleIdentifier + ) + } label: { + Image(systemName: "xmark.circle.fill") + .foregroundStyle(.secondary) + } + .buttonStyle(.borderless) + } + } + + /// Bundle IDs are durable; app paths are not. Resolve the current app URL at render time so + /// Settings naturally picks up app updates, moves, or reinstalls without persisting UI cache. + private func icon(for rule: DisabledApplicationRule) -> NSImage { + guard let appURL = NSWorkspace.shared.urlForApplication( + withBundleIdentifier: rule.bundleIdentifier + ) else { + return NSWorkspace.shared.icon(for: .applicationBundle) + } + return NSWorkspace.shared.icon(forFile: appURL.path) + } + + /// Lets the user disable Cotabby in an app they can't reach from the menu bar. The menu-bar + /// "Enable in " switch only targets the frontmost app, so a launcher like Raycast or + /// Spotlight (which dismisses itself the instant the menu bar is clicked) can never be turned + /// off that way. An open panel names any installed app whether or not it is running. + private func presentDisabledAppPicker() { + let panel = NSOpenPanel() + panel.allowedContentTypes = [.application] + panel.allowsMultipleSelection = true + panel.canChooseDirectories = false + panel.canChooseFiles = true + panel.directoryURL = URL(fileURLWithPath: "/Applications", isDirectory: true) + panel.prompt = "Disable" + panel.message = "Choose apps where Cotabby should not autocomplete." + + guard panel.runModal() == .OK else { + return + } + + for url in panel.urls { + guard let metadata = ApplicationBundleMetadata(appURL: url) else { + continue + } + suggestionSettings.disableApplication( + bundleIdentifier: metadata.bundleIdentifier, + displayName: metadata.displayName + ) + } + } +} diff --git a/Cotabby/UI/Settings/Panes/ShortcutsPaneView.swift b/Cotabby/UI/Settings/Panes/ShortcutsPaneView.swift new file mode 100644 index 0000000..495ec26 --- /dev/null +++ b/Cotabby/UI/Settings/Panes/ShortcutsPaneView.swift @@ -0,0 +1,126 @@ +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. +struct ShortcutsPaneView: View { + @ObservedObject var suggestionSettings: SuggestionSettingsModel + + @State private var isRecordingKeybind = false + @State private var isRecordingFullAcceptKeybind = false + + var body: some View { + SettingsPaneScaffold { + Section("Shortcuts") { + LabeledContent("Accept Word") { + KeybindRow( + label: suggestionSettings.acceptanceKeyLabel, + keyCode: suggestionSettings.acceptanceKeyCode, + modifiers: suggestionSettings.acceptanceKeyModifiers, + defaultKeyCode: SuggestionSettingsModel.defaultAcceptanceKeyCode, + isRecording: $isRecordingKeybind, + onRecord: { keyCode, modifiers, label in + suggestionSettings.setAcceptanceKey( + keyCode: keyCode, + modifiers: modifiers, + label: label + ) + }, + onReset: { + suggestionSettings.setAcceptanceKey( + keyCode: SuggestionSettingsModel.defaultAcceptanceKeyCode, + modifiers: [], + label: SuggestionSettingsModel.defaultAcceptanceKeyLabel + ) + }, + onClear: { suggestionSettings.clearAcceptanceKey() }, + clearHelp: "Unbind this shortcut. No key will accept word-by-word." + ) + } + + LabeledContent("Accept Entire Suggestion") { + KeybindRow( + label: suggestionSettings.fullAcceptanceKeyLabel, + keyCode: suggestionSettings.fullAcceptanceKeyCode, + modifiers: suggestionSettings.fullAcceptanceKeyModifiers, + defaultKeyCode: SuggestionSettingsModel.defaultFullAcceptanceKeyCode, + isRecording: $isRecordingFullAcceptKeybind, + onRecord: { keyCode, modifiers, label in + suggestionSettings.setFullAcceptanceKey( + keyCode: keyCode, + modifiers: modifiers, + label: label + ) + }, + onReset: { + suggestionSettings.setFullAcceptanceKey( + keyCode: SuggestionSettingsModel.defaultFullAcceptanceKeyCode, + modifiers: [], + label: SuggestionSettingsModel.defaultFullAcceptanceKeyLabel + ) + }, + onClear: { suggestionSettings.clearFullAcceptanceKey() }, + clearHelp: "Unbind this shortcut. No key will accept the whole suggestion at once." + ) + } + } + } + } +} + +/// Shared row chrome for one keybinding. Owns the badge / Change / Reset / Clear layout and the +/// `KeyRecorderView` recording state hand-off so the surrounding pane stays focused on what each +/// binding does rather than how it is rendered. +private struct KeybindRow: View { + let label: String + let keyCode: CGKeyCode + let modifiers: ShortcutModifierMask + let defaultKeyCode: CGKeyCode + @Binding var isRecording: Bool + let onRecord: (CGKeyCode, ShortcutModifierMask, String) -> Void + let onReset: () -> Void + let onClear: () -> Void + let clearHelp: String + + var body: some View { + HStack(spacing: 8) { + Text(label) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(.quaternary) + ) + + if isRecording { + KeyRecorderView( + onKeyRecorded: { keyCode, modifiers, label in + onRecord(keyCode, modifiers, label) + isRecording = false + }, + onCancelled: { isRecording = false } + ) + } else { + Button("Change") { + isRecording = true + } + } + + if keyCode != defaultKeyCode || !modifiers.isEmpty { + Button("Reset") { + onReset() + isRecording = false + } + } + + if keyCode != SuggestionSettingsModel.disabledKeyCode { + Button("Clear") { + onClear() + isRecording = false + } + } + } + } +} diff --git a/Cotabby/UI/Settings/Panes/WritingPaneView.swift b/Cotabby/UI/Settings/Panes/WritingPaneView.swift new file mode 100644 index 0000000..4388297 --- /dev/null +++ b/Cotabby/UI/Settings/Panes/WritingPaneView.swift @@ -0,0 +1,53 @@ +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. +struct WritingPaneView: View { + @ObservedObject var suggestionSettings: SuggestionSettingsModel + + var body: some View { + SettingsPaneScaffold { + Section("Writing") { + Picker("Length", selection: selectedWordCountPresetBinding) { + ForEach(SuggestionWordCountPreset.allCases) { preset in + Text(preset.displayLabel).tag(preset) + } + } + } + + Section("Profile") { + VStack(alignment: .leading, spacing: 24) { + Text("This information is passed to the AI to help personalize your completions.") + .font(.caption) + .foregroundStyle(.secondary) + + VStack(alignment: .leading, spacing: 8) { + Text("Name") + .font(.system(size: 13, weight: .medium)) + + TextField("What should Cotabby call you?", text: Binding( + get: { suggestionSettings.userName }, + set: { suggestionSettings.setUserName($0) } + )) + .textFieldStyle(.roundedBorder) + } + + LanguageTagsEditor(suggestionSettings: suggestionSettings) + + CustomRulesEditor(suggestionSettings: suggestionSettings) + } + .padding(.vertical, 10) + } + } + } + + private var selectedWordCountPresetBinding: Binding { + Binding( + get: { suggestionSettings.selectedWordCountPreset }, + set: { suggestionSettings.selectWordCountPreset($0) } + ) + } +} diff --git a/Cotabby/UI/Settings/SettingsContainerView.swift b/Cotabby/UI/Settings/SettingsContainerView.swift index 62dd208..c27005d 100644 --- a/Cotabby/UI/Settings/SettingsContainerView.swift +++ b/Cotabby/UI/Settings/SettingsContainerView.swift @@ -81,12 +81,16 @@ struct SettingsContainerView: View { modelDownloadManager: modelDownloadManager, huggingFaceSearchService: huggingFaceSearchService ) + case .writing: + WritingPaneView(suggestionSettings: suggestionSettings) + case .shortcuts: + ShortcutsPaneView(suggestionSettings: suggestionSettings) + case .apps: + AppsPaneView(suggestionSettings: suggestionSettings) case .permissions: PermissionsPaneView(permissionManager: permissionManager) case .about: AboutPaneView(appUpdateManager: appUpdateManager) - case .writing, .shortcuts, .apps: - PlaceholderPaneView(category: selection) } }