From 33fc51c0dcef648bd5bbe51324a3ce4430a59b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Knoderer?= Date: Fri, 25 Sep 2026 09:26:42 +0200 Subject: [PATCH] Move feature enable switches to the top of Settings pages (#1144) * Promote feature enable controls in Settings * Reuse the shared feature toggle label * Keep Search Scopes available when Applications is off * Restore search highlight on launcher settings lists --- Scripts/check-settings-search.js | 1 + .../DesignSystem/SettingsComponents.swift | 66 ++++++++++++++++++- .../Features/AI/Settings/AISettingsView.swift | 5 +- .../Settings/AppleShortcutsSettingsView.swift | 9 +-- .../Settings/CalendarSettingsView.swift | 4 +- .../Settings/ClipboardSettingsView.swift | 7 +- .../Settings/CommandsSettingsView.swift | 2 + .../Settings/ExtensionsSettingsView.swift | 3 +- .../Settings/FileSearchSettingsView.swift | 8 +-- .../Settings/ApplicationsSettingsView.swift | 5 +- .../Settings/LauncherItemsSection.swift | 43 +++++++----- .../Settings/SystemSettingsSettingsView.swift | 3 + .../Notes/Settings/NotesSettingsView.swift | 15 +++-- .../Settings/QuickActionsSettingsView.swift | 5 +- .../Settings/QuicklinksSettingsView.swift | 5 +- .../Panes/NavigationSettingsView.swift | 8 +-- .../Features/Settings/SettingsAnchor.swift | 1 + .../Settings/SettingsSearchCatalog.swift | 10 +-- .../Settings/SettingsSidebarView.swift | 18 ----- .../Settings/SnippetsSettingsView.swift | 4 +- .../Settings/SystemActionsSettingsView.swift | 3 + .../WindowManagementSettingsView.swift | 4 +- docs/features/launcher.md | 7 +- docs/ui.md | 2 + 24 files changed, 162 insertions(+), 76 deletions(-) diff --git a/Scripts/check-settings-search.js b/Scripts/check-settings-search.js index abac4297..894c8bf2 100755 --- a/Scripts/check-settings-search.js +++ b/Scripts/check-settings-search.js @@ -55,6 +55,7 @@ for (const m of catalog.matchAll(/\.init\(\s*\.(\w+),\s*"((?:[^"\\]|\\.)*)"/g)) const quoted = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const marked = source.includes(`SettingsRowTitle(.${anchor}, "${title}")`) || + new RegExp(`SettingsFeatureToggleLabel\\(\\s*anchor: \\.${anchor}, title: "${quoted}"`).test(source) || // A `SettingsRow` renders the pill from its own title. new RegExp(`SettingsRow\\(\\s*title: "${quoted}",[\\s\\S]*?anchor: \\.${anchor}`).test(source) || // A feature pane's master switch, rendered by `FeatureSwitchSection`. diff --git a/Tinycast/DesignSystem/SettingsComponents.swift b/Tinycast/DesignSystem/SettingsComponents.swift index 91c0be34..7e33f2c7 100644 --- a/Tinycast/DesignSystem/SettingsComponents.swift +++ b/Tinycast/DesignSystem/SettingsComponents.swift @@ -3,6 +3,51 @@ import SwiftUI // The few pieces more than one Settings pane or editor needs; everything else stays feature-owned. +/// The Settings sidebar tile, shared with the matching feature switches. +struct SettingsTabIcon: View { + let systemImage: String + let tint: Color + var size = Theme.Size.settingsSidebarGlyph + Theme.Spacing.xs * 2 + + var body: some View { + let scale = size / (Theme.Size.settingsSidebarGlyph + Theme.Spacing.xs * 2) + Image(systemName: systemImage) + .resizable() + .scaledToFit() + .frame( + width: Theme.Size.settingsSidebarGlyph * scale, + height: Theme.Size.settingsSidebarGlyph * scale + ) + .foregroundStyle(tint) + .padding(Theme.Spacing.xs * scale) + .background( + tint.opacity(0.1), + in: RoundedRectangle( + cornerRadius: Theme.Radius.thumbnail * scale, style: .continuous)) + } +} + +struct SettingsFeatureToggleLabel: View { + let anchor: SettingsAnchor + let title: String + let subtitle: String + + var body: some View { + HStack(spacing: Theme.Spacing.lg) { + SettingsTabIcon( + systemImage: anchor.tab.systemImage, tint: .accentColor, + size: Theme.Size.settingsRowIcon * 1.5) + VStack(alignment: .leading, spacing: Theme.Spacing.xxs) { + SettingsRowTitle(anchor, title) + .fontWeight(.semibold) + Text(subtitle) + .font(.subheadline) + .foregroundStyle(.secondary) + } + } + } +} + /// Not `LabeledContent`: its selectable text field eats the taps a `ShortcutRecorder` needs. struct SettingsRow: View { let title: String @@ -166,18 +211,33 @@ struct FeatureSwitchSection: View { var enableSubtitle: String? @Binding var isEnabled: Bool @Binding var showsInLauncher: Bool + var showsIcon = false + var showsHeader = true var body: some View { + if showsHeader { + section + } else { + section.settingsAnchor(anchor) + } + } + + private var section: some View { Section { Toggle(isOn: $isEnabled) { - SettingsRowTitle(anchor, enableTitle) - if let enableSubtitle { Text(enableSubtitle) } + if showsIcon, let enableSubtitle { + SettingsFeatureToggleLabel( + anchor: anchor, title: enableTitle, subtitle: enableSubtitle) + } else { + SettingsRowTitle(anchor, enableTitle) + if let enableSubtitle { Text(enableSubtitle) } + } } Toggle("Show in launcher", isOn: $showsInLauncher) // The switch above stays live so the feature can always be turned back on. .settingsEnabled(isEnabled) } header: { - SettingsSectionHeader(anchor) + if showsHeader { SettingsSectionHeader(anchor) } } } } diff --git a/Tinycast/Features/AI/Settings/AISettingsView.swift b/Tinycast/Features/AI/Settings/AISettingsView.swift index fbb4317b..f18b95c1 100644 --- a/Tinycast/Features/AI/Settings/AISettingsView.swift +++ b/Tinycast/Features/AI/Settings/AISettingsView.swift @@ -22,8 +22,9 @@ struct AISettingsView: View { return Form { Section { Toggle(isOn: $appSettings.aiEnabled) { - SettingsRowTitle(.aiAI, "Enable AI") - Text("Nothing is loaded or sent while it is off.") + SettingsFeatureToggleLabel( + anchor: .aiAI, title: "Enable AI", + subtitle: "Nothing is loaded or sent while it is off.") } SettingsRow( title: "Providers", subtitle: providerSummary, anchor: .aiProviders diff --git a/Tinycast/Features/AppleShortcuts/Settings/AppleShortcutsSettingsView.swift b/Tinycast/Features/AppleShortcuts/Settings/AppleShortcutsSettingsView.swift index 9a84921d..f9bb927a 100644 --- a/Tinycast/Features/AppleShortcuts/Settings/AppleShortcutsSettingsView.swift +++ b/Tinycast/Features/AppleShortcuts/Settings/AppleShortcutsSettingsView.swift @@ -20,12 +20,13 @@ struct AppleShortcutsSettingsView: View { return Form { Section { Toggle(isOn: $settings.appleShortcutsEnabled) { - SettingsRowTitle(.appleShortcutsAppleShortcuts, "Enable Apple Shortcuts") - Text("Run your shortcuts from the launcher.") + SettingsFeatureToggleLabel( + anchor: .appleShortcutsAppleShortcuts, + title: "Enable Apple Shortcuts", + subtitle: "Run your shortcuts from the launcher.") } - } header: { - SettingsSectionHeader(.appleShortcutsAppleShortcuts) } + .settingsAnchor(.appleShortcutsAppleShortcuts) if settings.appleShortcutsEnabled { library diff --git a/Tinycast/Features/Calendar/Settings/CalendarSettingsView.swift b/Tinycast/Features/Calendar/Settings/CalendarSettingsView.swift index c325c967..8c5af310 100644 --- a/Tinycast/Features/Calendar/Settings/CalendarSettingsView.swift +++ b/Tinycast/Features/Calendar/Settings/CalendarSettingsView.swift @@ -15,7 +15,9 @@ struct CalendarSettingsView: View { "Reads \(core.calendarCoordinator.span.possessivePhrase) events for join links. " + "Nothing leaves this Mac.", isEnabled: enabledBinding, - showsInLauncher: $settings.calendarShowInLauncher) + showsInLauncher: $settings.calendarShowInLauncher, + showsIcon: true, + showsHeader: false) Section { Picker(selection: $settings.calendarLauncherLimit) { diff --git a/Tinycast/Features/Clipboard/Settings/ClipboardSettingsView.swift b/Tinycast/Features/Clipboard/Settings/ClipboardSettingsView.swift index a88ff026..a6440f8f 100644 --- a/Tinycast/Features/Clipboard/Settings/ClipboardSettingsView.swift +++ b/Tinycast/Features/Clipboard/Settings/ClipboardSettingsView.swift @@ -11,11 +11,12 @@ struct ClipboardSettingsView: View { return Form { Section { Toggle(isOn: $settings.clipboardEnabled) { - SettingsRowTitle(.clipboardClipboard, "Enable Clipboard History") + SettingsFeatureToggleLabel( + anchor: .clipboardClipboard, title: "Enable Clipboard History", + subtitle: "Keep copied items ready to reuse.") } - } header: { - SettingsSectionHeader(.clipboardClipboard) } + .settingsAnchor(.clipboardClipboard) FeatureCommandsSection(owner: .clipboard, anchor: .clipboardCommands) .settingsEnabled(settings.clipboardEnabled) diff --git a/Tinycast/Features/CustomCommands/Settings/CommandsSettingsView.swift b/Tinycast/Features/CustomCommands/Settings/CommandsSettingsView.swift index bdea187b..1ba2d70c 100644 --- a/Tinycast/Features/CustomCommands/Settings/CommandsSettingsView.swift +++ b/Tinycast/Features/CustomCommands/Settings/CommandsSettingsView.swift @@ -11,6 +11,8 @@ struct CommandsSettingsView: View { var body: some View { @Bindable var settings = settings return Form { + LauncherCategorySwitchSection(kind: .command, anchor: .commandsCommands) + LauncherItemsSection( kind: .command, anchor: .commandsCommands, diff --git a/Tinycast/Features/Extensions/Settings/ExtensionsSettingsView.swift b/Tinycast/Features/Extensions/Settings/ExtensionsSettingsView.swift index ccabe6d3..b6741861 100644 --- a/Tinycast/Features/Extensions/Settings/ExtensionsSettingsView.swift +++ b/Tinycast/Features/Extensions/Settings/ExtensionsSettingsView.swift @@ -28,7 +28,8 @@ struct ExtensionsSettingsView: View { isEnabled: Binding( get: { settings.extensionsEnabled }, set: { core.extensionCoordinator.setExtensionsEnabled($0) }), - showsInLauncher: $settings.extensionsShowInLauncher) + showsInLauncher: $settings.extensionsShowInLauncher, + showsIcon: true) Group { install diff --git a/Tinycast/Features/FileSearch/Settings/FileSearchSettingsView.swift b/Tinycast/Features/FileSearch/Settings/FileSearchSettingsView.swift index 02189c27..7ee4cb11 100644 --- a/Tinycast/Features/FileSearch/Settings/FileSearchSettingsView.swift +++ b/Tinycast/Features/FileSearch/Settings/FileSearchSettingsView.swift @@ -8,12 +8,12 @@ struct FileSearchSettingsView: View { return Form { Section { Toggle(isOn: $settings.fileSearchEnabled) { - SettingsRowTitle(.fileSearchFileSearch, "Enable File Search") - Text("Uses the Spotlight index, only when you search.") + SettingsFeatureToggleLabel( + anchor: .fileSearchFileSearch, title: "Enable File Search", + subtitle: "Uses the Spotlight index, only when you search.") } - } header: { - SettingsSectionHeader(.fileSearchFileSearch) } + .settingsAnchor(.fileSearchFileSearch) FeatureCommandsSection(owner: .fileSearch, anchor: .fileSearchCommands) .settingsEnabled(settings.fileSearchEnabled) diff --git a/Tinycast/Features/Launcher/Settings/ApplicationsSettingsView.swift b/Tinycast/Features/Launcher/Settings/ApplicationsSettingsView.swift index f8b81634..7fabade9 100644 --- a/Tinycast/Features/Launcher/Settings/ApplicationsSettingsView.swift +++ b/Tinycast/Features/Launcher/Settings/ApplicationsSettingsView.swift @@ -3,7 +3,9 @@ import SwiftUI struct ApplicationsSettingsView: View { var body: some View { Form { - // Scopes first: they decide what gets indexed, so they read before the results. + LauncherCategorySwitchSection( + kind: .application, anchor: .applicationsApplications) + SearchScopesSection() LauncherItemsSection( @@ -15,4 +17,5 @@ struct ApplicationsSettingsView: View { .settingsScrollTarget(.applications) .releasesFocusOnOutsideClick() } + } diff --git a/Tinycast/Features/Launcher/Settings/LauncherItemsSection.swift b/Tinycast/Features/Launcher/Settings/LauncherItemsSection.swift index 1d4541d7..91e8e24f 100644 --- a/Tinycast/Features/Launcher/Settings/LauncherItemsSection.swift +++ b/Tinycast/Features/Launcher/Settings/LauncherItemsSection.swift @@ -1,6 +1,29 @@ import SwiftUI -/// One category's Settings sections; never filters by visibility, so hidden rows stay listed. +/// A category's master switch stays available while its list is disabled. +struct LauncherCategorySwitchSection: View { + let kind: AppEntry.Kind + let anchor: SettingsAnchor + + @Environment(VisibilityStore.self) private var visibility + + var body: some View { + Section { + Toggle( + isOn: Binding( + get: { visibility.isKindEnabled(kind) }, + set: { visibility.setKindEnabled($0, for: kind) } + ) + ) { + SettingsFeatureToggleLabel( + anchor: anchor, title: "Enable \(anchor.title)", + subtitle: "Off hides all of them and stops their shortcuts.") + } + } + } +} + +/// One category's Settings list; never filters by visibility, so hidden rows stay listed. struct LauncherItemsSection: View { let kind: AppEntry.Kind let anchor: SettingsAnchor @@ -19,29 +42,15 @@ struct LauncherItemsSection: View { } var body: some View { - Section { - Toggle(isOn: enabledBinding) { - SettingsRowTitle(anchor, "Enable \(anchor.title)") - Text("Off hides all of them and stops their shortcuts.") - } - } header: { - SettingsSectionHeader(anchor) - } - Section { SettingsFilterField(prompt: searchPrompt, query: $query) LauncherItemsList( entries: entries, query: query, isEnabled: visibility.isKindEnabled(kind)) + } header: { + SettingsSectionHeader(anchor) } .settingsEnabled(visibility.isKindEnabled(kind)) } - - private var enabledBinding: Binding { - Binding( - get: { visibility.isKindEnabled(kind) }, - set: { visibility.setKindEnabled($0, for: kind) } - ) - } } /// The rows under a filter field, or what to say when there are none; shared by item panes. diff --git a/Tinycast/Features/Launcher/Settings/SystemSettingsSettingsView.swift b/Tinycast/Features/Launcher/Settings/SystemSettingsSettingsView.swift index 0673f8b0..d01db5b7 100644 --- a/Tinycast/Features/Launcher/Settings/SystemSettingsSettingsView.swift +++ b/Tinycast/Features/Launcher/Settings/SystemSettingsSettingsView.swift @@ -4,6 +4,9 @@ import SwiftUI struct SystemSettingsSettingsView: View { var body: some View { Form { + LauncherCategorySwitchSection( + kind: .systemSettings, anchor: .systemSettingsSystemSettings) + LauncherItemsSection( kind: .systemSettings, anchor: .systemSettingsSystemSettings, diff --git a/Tinycast/Features/Notes/Settings/NotesSettingsView.swift b/Tinycast/Features/Notes/Settings/NotesSettingsView.swift index 8cbd1fe0..eea7147f 100644 --- a/Tinycast/Features/Notes/Settings/NotesSettingsView.swift +++ b/Tinycast/Features/Notes/Settings/NotesSettingsView.swift @@ -8,20 +8,25 @@ struct NotesSettingsView: View { return Form { Section { Toggle(isOn: $settings.notesEnabled) { - SettingsRowTitle(.notesNotes, "Enable Notes") - Text("Plain Markdown in a floating editor.") + SettingsFeatureToggleLabel( + anchor: .notesNotes, title: "Enable Notes", + subtitle: "Plain Markdown in a floating editor.") } + } + .settingsAnchor(.notesNotes) + + Section { Toggle(isOn: $settings.notesRendersMarkdown) { - SettingsRowTitle(.notesNotes, "Render Markdown") + SettingsRowTitle(.notesOptions, "Render Markdown") Text("Formats as you type.") } .settingsEnabled(settings.notesEnabled) Toggle(isOn: $settings.notesShowsFormattingBar) { - SettingsRowTitle(.notesNotes, "Show Formatting Bar") + SettingsRowTitle(.notesOptions, "Show Formatting Bar") } .settingsEnabled(settings.notesEnabled && settings.notesRendersMarkdown) } header: { - SettingsSectionHeader(.notesNotes) + SettingsSectionHeader(.notesOptions) } FeatureCommandsSection(owner: .notes, anchor: .notesCommands) diff --git a/Tinycast/Features/QuickActions/Settings/QuickActionsSettingsView.swift b/Tinycast/Features/QuickActions/Settings/QuickActionsSettingsView.swift index 2eb95064..8f77c24b 100644 --- a/Tinycast/Features/QuickActions/Settings/QuickActionsSettingsView.swift +++ b/Tinycast/Features/QuickActions/Settings/QuickActionsSettingsView.swift @@ -20,8 +20,9 @@ struct QuickActionsSettingsView: View { Form { Section { Toggle(isOn: enabledBinding) { - SettingsRowTitle(.quickActionsQuickActions, "Enable Quick Actions") - Text("Act on selected text. Nothing is read until you press a shortcut.") + SettingsFeatureToggleLabel( + anchor: .quickActionsQuickActions, title: "Enable Quick Actions", + subtitle: "Act on selected text. Nothing is read until you press a shortcut.") } if appSettings.quickActionsEnabled, !isTrusted { // Every shortcut fails without it; better said here than found one press later. diff --git a/Tinycast/Features/Quicklinks/Settings/QuicklinksSettingsView.swift b/Tinycast/Features/Quicklinks/Settings/QuicklinksSettingsView.swift index 4321a709..9d889e66 100644 --- a/Tinycast/Features/Quicklinks/Settings/QuicklinksSettingsView.swift +++ b/Tinycast/Features/Quicklinks/Settings/QuicklinksSettingsView.swift @@ -15,8 +15,11 @@ struct QuicklinksSettingsView: View { FeatureSwitchSection( anchor: .quicklinksQuicklinks, enableTitle: "Enable quicklinks", + enableSubtitle: "Open saved links and searches from the launcher.", isEnabled: $settings.quicklinksEnabled, - showsInLauncher: $settings.quicklinksShowInLauncher) + showsInLauncher: $settings.quicklinksShowInLauncher, + showsIcon: true, + showsHeader: false) Group { if !store.isAvailable { storageNotice } diff --git a/Tinycast/Features/Settings/Panes/NavigationSettingsView.swift b/Tinycast/Features/Settings/Panes/NavigationSettingsView.swift index 82a20e91..abbaa6e3 100644 --- a/Tinycast/Features/Settings/Panes/NavigationSettingsView.swift +++ b/Tinycast/Features/Settings/Panes/NavigationSettingsView.swift @@ -10,12 +10,12 @@ struct NavigationSettingsView: View { return Form { Section { Toggle(isOn: $settings.navigationEnabled) { - SettingsRowTitle(.navigationNavigation, "Enable navigation") - Text("Switch windows and search menu bar items.") + SettingsFeatureToggleLabel( + anchor: .navigationNavigation, title: "Enable navigation", + subtitle: "Switch windows and search menu bar items.") } - } header: { - SettingsSectionHeader(.navigationNavigation) } + .settingsAnchor(.navigationNavigation) // No "show in launcher" switch: the per-command checkboxes below already are one. FeatureCommandsSection( diff --git a/Tinycast/Features/Settings/SettingsAnchor.swift b/Tinycast/Features/Settings/SettingsAnchor.swift index dd9712b1..ebcdd39c 100644 --- a/Tinycast/Features/Settings/SettingsAnchor.swift +++ b/Tinycast/Features/Settings/SettingsAnchor.swift @@ -57,6 +57,7 @@ extension SettingsAnchor { static let fileSearchIgnorePatterns = Self(tab: .fileSearch, title: "Ignore Patterns") static let notesNotes = Self(tab: .notes, title: "Notes") + static let notesOptions = Self(tab: .notes, title: "Options") static let notesCommands = Self(tab: .notes, title: "Commands") static let snippetsSnippets = Self(tab: .snippets, title: "Snippets") diff --git a/Tinycast/Features/Settings/SettingsSearchCatalog.swift b/Tinycast/Features/Settings/SettingsSearchCatalog.swift index c1ec0412..25f265a6 100644 --- a/Tinycast/Features/Settings/SettingsSearchCatalog.swift +++ b/Tinycast/Features/Settings/SettingsSearchCatalog.swift @@ -176,12 +176,12 @@ enum SettingsSearchCatalog { private static let applications: [SettingsSearchEntry] = [ .init(pane: .applications, keywords: ["apps", "index", "launcher"]), - .init( - group: .applicationsSearchScopes, "Search Scopes", - keywords: ["folders", "indexed", "locations", "add folder"]), .init( .applicationsApplications, "Enable Applications", keywords: ["hide apps", "visibility"]), + .init( + group: .applicationsSearchScopes, "Search Scopes", + keywords: ["folders", "indexed", "locations", "add folder"]), .init( group: .applicationsApplications, "Aliases and shortcuts", keywords: ["alias", "hotkey", "per app", "hide"]) @@ -349,10 +349,10 @@ enum SettingsSearchCatalog { .notesNotes, "Enable Notes", keywords: ["markdown", "scratchpad"]), .init( - .notesNotes, "Render Markdown", + .notesOptions, "Render Markdown", keywords: ["markdown", "formatting", "preview", "raw", "source"]), .init( - .notesNotes, "Show Formatting Bar", + .notesOptions, "Show Formatting Bar", keywords: ["toolbar", "format bar", "buttons", "bold", "heading", "markdown"]), .init( group: .notesCommands, "Notes commands", diff --git a/Tinycast/Features/Settings/SettingsSidebarView.swift b/Tinycast/Features/Settings/SettingsSidebarView.swift index b1b0e499..b3c3f37f 100644 --- a/Tinycast/Features/Settings/SettingsSidebarView.swift +++ b/Tinycast/Features/Settings/SettingsSidebarView.swift @@ -117,21 +117,3 @@ private struct CenteredLabelStyle: LabelStyle { } } } - -/// The glyph on a tinted tile, so every row's icon reads at one weight whatever its symbol's shape. -private struct SettingsTabIcon: View { - let systemImage: String - let tint: Color - - var body: some View { - Image(systemName: systemImage) - .resizable() - .scaledToFit() - .frame(width: Theme.Size.settingsSidebarGlyph, height: Theme.Size.settingsSidebarGlyph) - .foregroundStyle(tint) - .padding(Theme.Spacing.xs) - .background( - tint.opacity(0.1), - in: RoundedRectangle(cornerRadius: Theme.Radius.thumbnail, style: .continuous)) - } -} diff --git a/Tinycast/Features/Snippets/Settings/SnippetsSettingsView.swift b/Tinycast/Features/Snippets/Settings/SnippetsSettingsView.swift index 26da15a9..c6dd101f 100644 --- a/Tinycast/Features/Snippets/Settings/SnippetsSettingsView.swift +++ b/Tinycast/Features/Snippets/Settings/SnippetsSettingsView.swift @@ -19,7 +19,9 @@ struct SnippetsSettingsView: View { isEnabled: Binding( get: { settings.snippetsEnabled }, set: { core.snippetCoordinator.setSnippetsEnabled($0) }), - showsInLauncher: $settings.snippetsShowInLauncher) + showsInLauncher: $settings.snippetsShowInLauncher, + showsIcon: true, + showsHeader: false) if settings.snippetsEnabled, core.snippetListener.status == .needsAccessibility { Section { diff --git a/Tinycast/Features/SystemActions/Settings/SystemActionsSettingsView.swift b/Tinycast/Features/SystemActions/Settings/SystemActionsSettingsView.swift index af920b87..4b46595b 100644 --- a/Tinycast/Features/SystemActions/Settings/SystemActionsSettingsView.swift +++ b/Tinycast/Features/SystemActions/Settings/SystemActionsSettingsView.swift @@ -3,6 +3,9 @@ import SwiftUI struct SystemActionsSettingsView: View { var body: some View { Form { + LauncherCategorySwitchSection( + kind: .systemAction, anchor: .systemActionsSystemActions) + LauncherItemsSection( kind: .systemAction, anchor: .systemActionsSystemActions, diff --git a/Tinycast/Features/WindowManagement/Settings/WindowManagementSettingsView.swift b/Tinycast/Features/WindowManagement/Settings/WindowManagementSettingsView.swift index 432cf1a4..93bdf38a 100644 --- a/Tinycast/Features/WindowManagement/Settings/WindowManagementSettingsView.swift +++ b/Tinycast/Features/WindowManagement/Settings/WindowManagementSettingsView.swift @@ -15,7 +15,9 @@ struct WindowManagementSettingsView: View { enableTitle: "Enable window management", enableSubtitle: "Moves the last window you used. Needs Accessibility.", isEnabled: $settings.windowManagementEnabled, - showsInLauncher: $settings.windowManagementShowInLauncher) + showsInLauncher: $settings.windowManagementShowInLauncher, + showsIcon: true, + showsHeader: false) Group { options diff --git a/docs/features/launcher.md b/docs/features/launcher.md index e2bd9eb1..52c7f10d 100644 --- a/docs/features/launcher.md +++ b/docs/features/launcher.md @@ -11,9 +11,10 @@ earliest scope wins). `LauncherList.rows`, in that order. - **A category's switch is a master switch, not a list filter.** `VisibilityStore.isKindEnabled` gates `orderedResults` *and* `HotKeyManager.perform`, so `Enable Applications` off stops the per-app chords - as well as the rows — the guard sits in the one dispatch funnel, the way each feature switch already - guards its own. The per-item checkbox beside it is the narrow tool: it hides one row and leaves that - row's shortcut firing, and **Hide from Search** in the ⌘K menu ticks that same checkbox off for the + as well as the rows. Its Settings switch stays available while the application + list disables beneath it — the guard sits in the one dispatch funnel, the way each feature switch + already guards its own. The per-item checkbox beside it is the narrow tool: it hides one row and + leaves that row's shortcut firing, and **Hide from Search** in the ⌘K menu ticks that checkbox off for the kinds whose pane can tick it back on. A new category must be wired into `VisibilityStore.allowsHotKey`, or its chords keep running while its pane reads off. - **One command, one pane, one switch.** `SettingsTab.ownedCommands` is the whole table of which pane diff --git a/docs/ui.md b/docs/ui.md index a479155f..0582952c 100644 --- a/docs/ui.md +++ b/docs/ui.md @@ -660,6 +660,8 @@ system-drawn and a pane reads exactly as macOS System Settings does. titlebar they were tuned for. Never hand-draw a header band; a main surface takes the system's material, not `glassEffect`. - `SettingsComponents.swift` holds only what more than one pane or editor needs: **`SettingsRow`**, + **`SettingsTabIcon`** (the sidebar tile reused by feature switches), + **`SettingsFeatureToggleLabel`** (the icon, title and subtitle of a feature's master switch), **`FeatureSwitchSection`** (a feature's master switch plus its launcher-visibility companion), **`SettingsFilterField`** (the filter row above a long list), **`launcherVisibilityHelp()`**, and the Settings editor header, fields and surface. `ModalActionButtonStyle.swift` keeps every borderless surface's actions on one