From 91a1db44f61d0a558b312caa064a6df73a1669cc Mon Sep 17 00:00:00 2001 From: Alex Kroman Date: Thu, 29 Oct 2026 12:50:18 -0700 Subject: [PATCH] =?UTF-8?q?feat(settings):=20move=20Key=20Styles=20from=20?= =?UTF-8?q?Advanced=20=E2=80=BA=20Experimental=20to=20the=20Styles=20pane?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Key Styles gets its own section on the Styles pane, under Custom Styles — the styles it picks from — rather than a row of Advanced's Experimental section. Its own section, not a row of Custom Styles, because that one is disabled while enhanced transcripts are off and turning a key on or off still works then. The footer says what the feature is for; the sheet's header now says how it works instead of repeating it. The row's icon is the keyboard, like the sheet's per-key rows, not the style glyph. ExperimentalSection no longer takes the coordinator it only needed for the row. The Key Styles UI tests open the Styles pane. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 11 ++++---- App/Blurt/Blurt/Wizard/DeveloperSection.swift | 7 ++--- App/Blurt/Blurt/Wizard/KeyStylesRow.swift | 27 ++++++++++++++----- .../Blurt/Wizard/SettingsWindowRoot.swift | 18 ++++++++----- .../Blurt/Wizard/Steps/HotkeyStepView.swift | 2 +- App/Blurt/BlurtUITests/SettingsUITests.swift | 14 +++++----- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b8883385..ab44c52d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -945,13 +945,14 @@ the `@AppStorage(TriggerKeyStore.defaultsKey)` + `TriggerKey.fromPersisted` pair restating that pairing per view. The unset default belongs to `fromPersisted` (an absent keycode maps to right ⌘), so views must not re-declare `TriggerKey.rightCommand.rawValue` themselves. -**Per-key styles.** A Key Styles row in the Advanced pane's Experimental section (`KeyStylesRow`) — a status naming the +**Per-key styles.** A Key Styles row in its own section on the Styles pane (`KeyStylesSection`, `KeyStylesRow`) — a status naming the keys set up, and an Edit… button — opens a sheet with one pop-up per `TriggerKey`: the style a press of that key dictates with — Same as main window, Default, or a profile (disabled while enhanced -transcripts are off) — or Off for keys other than the bound one. On Advanced rather than under the -key picker because it's a power-user extra, so General stays short enough for a small display; a -row in an existing section and a sheet, not a section or a row per key, because Advanced is already -near that limit — a section of its own pushed Reset… off the bottom of CI's screen. `TriggerKeyStyleStore` (`BlurtTriggerKeyStyles`, keycode → style id) holds +transcripts are off) — or Off for keys other than the bound one. On Styles, under the profiles it +picks from, rather than under the key picker, so General stays short enough for a small display; +its own section rather than a row of Custom Styles, because that section is disabled while +enhanced transcripts are off and turning a key on or off still works then; one row and a sheet, +not a row per key, to keep the pane short. `TriggerKeyStyleStore` (`BlurtTriggerKeyStyles`, keycode → style id) holds it; an absent value is today's single key with the current style. Rebinding swaps each moved key's entry with a stash (`BlurtTriggerKeyStyleStash`): the key moved away from goes back to what it was as an extra before it was bound (off, unless the user had set it up as one), and gets its bound diff --git a/App/Blurt/Blurt/Wizard/DeveloperSection.swift b/App/Blurt/Blurt/Wizard/DeveloperSection.swift index e677176f..34c3283b 100644 --- a/App/Blurt/Blurt/Wizard/DeveloperSection.swift +++ b/App/Blurt/Blurt/Wizard/DeveloperSection.swift @@ -50,14 +50,12 @@ struct UpdatesSection: View { } } -/// The Advanced pane's experimental features — per-key styles (`KeyStylesRow`), -/// read-aloud (see `SelectionSpeechStore`), asking about the selection +/// The Advanced pane's experimental features — read-aloud (see +/// `SelectionSpeechStore`), asking about the selection /// (`SelectionAskStore`) and how a read sounds (`ReadAloudStyleStore`). The /// router reads the same defaults these controls write at every press, so a /// change applies to the next one. struct ExperimentalSection: View { - var coordinator: AppCoordinator - @AppStorage(SelectionSpeechStore.defaultsKey) private var selectionSpeech = false @AppStorage(SelectionAskStore.defaultsKey) private var selectionAsk = SelectionAskStore.defaultValue @AppStorage(ReadAloudStyleStore.speedDefaultsKey) private var speed = ReadAloudStyleStore.defaultSpeed @@ -90,7 +88,6 @@ struct ExperimentalSection: View { var body: some View { Section { - KeyStylesRow(coordinator: coordinator) Toggle(isOn: $selectionSpeech) { SettingLabel(title: "Read selected text aloud", systemImage: "speaker.wave.2") } diff --git a/App/Blurt/Blurt/Wizard/KeyStylesRow.swift b/App/Blurt/Blurt/Wizard/KeyStylesRow.swift index 7a5086af..ad6d2733 100644 --- a/App/Blurt/Blurt/Wizard/KeyStylesRow.swift +++ b/App/Blurt/Blurt/Wizard/KeyStylesRow.swift @@ -1,12 +1,27 @@ import BlurtEngine import SwiftUI +/// The Key Styles row in a section of its own, under the styles it picks from. +/// Not a row of Custom Styles: that section is disabled while enhanced +/// transcripts are off, and turning a key on or off still works then (the +/// sheet disables only the styles). +struct KeyStylesSection: View { + let coordinator: AppCoordinator + + var body: some View { + Section { + KeyStylesRow(coordinator: coordinator) + } footer: { + Text("Use a different style with each key, without switching styles in the main window.") + } + } +} + /// The Key Styles row: a status and an "Edit…" button, like the API key row, /// whose sheet gives each trigger key its own style (or Off, for keys other -/// than the bound one). The first row of the Advanced pane's Experimental -/// section rather than beside the dictation key on General — it's a -/// power-user extra, and a section of its own made Advanced too tall for a -/// small display. Settings-only, so it is not a wizard step. +/// than the bound one). On the Styles pane, under the styles it picks from, +/// rather than beside the dictation key on General, which has to stay short +/// enough for a small display. Settings-only, so it is not a wizard step. struct KeyStylesRow: View { var coordinator: AppCoordinator @@ -40,7 +55,7 @@ struct KeyStylesRow: View { bound: bound, styles: TriggerKeyStyleStore.styles(decoding: keyStylesRaw), profiles: StyleProfileStore().profiles(decoding: profilesRaw)) let changed = entries.filter(\.isChanged).map(\.text) - SettingRow(title: "Key Styles", systemImage: "textformat") { + SettingRow(title: "Key Styles", systemImage: "keyboard") { HStack(spacing: 8) { // The keys set up beyond the default, or the main window's style. Text(changed.isEmpty ? KeyStylesSheet.mainWindowName : changed.joined(separator: ", ")) @@ -145,7 +160,7 @@ private struct KeyStylesSheet: View { VStack(alignment: .leading, spacing: 6) { Text("Key Styles") .font(.headline) - Text("Each key can dictate with its own style, without changing the style selected in the main window.") + Text("Pick a style for each key. Turn another key on to dictate with it too.") .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } diff --git a/App/Blurt/Blurt/Wizard/SettingsWindowRoot.swift b/App/Blurt/Blurt/Wizard/SettingsWindowRoot.swift index 367e9ef5..714e631b 100644 --- a/App/Blurt/Blurt/Wizard/SettingsWindowRoot.swift +++ b/App/Blurt/Blurt/Wizard/SettingsWindowRoot.swift @@ -20,7 +20,7 @@ struct SettingsWindowRoot: View { TabView { GeneralSettingsTab(coordinator: coordinator) .tabItem { Label(UITestIdentifiers.generalSettingsTab, systemImage: "gearshape") } - StylesSettingsTab() + StylesSettingsTab(coordinator: coordinator) .tabItem { Label(UITestIdentifiers.stylesSettingsTab, systemImage: "textformat") } VocabularySettingsTab() .tabItem { @@ -66,21 +66,25 @@ private struct GeneralSettingsTab: View { } } -/// The enhanced-transcripts switch and the user's style profiles — their own -/// pane rather than an Advanced section. The switch leads because styles only -/// shape the enhanced rewrite: with it off they're disabled, and the switch that -/// re-enables them sits directly above rather than on another pane. +/// The enhanced-transcripts switch, the user's style profiles, and which style +/// each trigger key dictates with — their own pane rather than an Advanced +/// section. The switch leads because styles only shape the enhanced rewrite: +/// with it off they're disabled, and the switch that re-enables them sits +/// directly above rather than on another pane. private struct StylesSettingsTab: View { + let coordinator: AppCoordinator + var body: some View { SettingsPane { TranscriptionSection() StyleProfilesSection() + KeyStylesSection(coordinator: coordinator) } } } /// The occasional stuff: checking for an update, experimental features -/// (per-key styles among them), the developer-mode log toggle, and the +/// (read-aloud), the developer-mode log toggle, and the /// start-over button. Kept out of General so /// the common pane stays short. private struct AdvancedSettingsTab: View { @@ -90,7 +94,7 @@ private struct AdvancedSettingsTab: View { var body: some View { SettingsPane { UpdatesSection(model: updaterModel) - ExperimentalSection(coordinator: coordinator) + ExperimentalSection() MaintenanceSection() ResetSection(coordinator: coordinator) } diff --git a/App/Blurt/Blurt/Wizard/Steps/HotkeyStepView.swift b/App/Blurt/Blurt/Wizard/Steps/HotkeyStepView.swift index 1d31e3bb..f1dcc85f 100644 --- a/App/Blurt/Blurt/Wizard/Steps/HotkeyStepView.swift +++ b/App/Blurt/Blurt/Wizard/Steps/HotkeyStepView.swift @@ -4,7 +4,7 @@ import SwiftUI /// The dictation-key section of the setup/settings screen. Two menu pickers: which /// lone modifier triggers dictation, and whether it activates by tap (toggle), /// hold (push-to-talk), or both; changes are persisted and pushed to the event tap -/// immediately. Giving the other trigger keys their own styles is the Advanced +/// immediately. Giving the other trigger keys their own styles is the Styles /// pane's `KeyStylesRow`. struct HotkeyStepView: View { var coordinator: AppCoordinator diff --git a/App/Blurt/BlurtUITests/SettingsUITests.swift b/App/Blurt/BlurtUITests/SettingsUITests.swift index 1f666cc7..78b819b6 100644 --- a/App/Blurt/BlurtUITests/SettingsUITests.swift +++ b/App/Blurt/BlurtUITests/SettingsUITests.swift @@ -247,7 +247,7 @@ final class SettingsUITests: BlurtUITestCase { /// right ⌘ is the bound key: it dictates with the main window's style and, since /// the bound key always dictates, offers no Off; the other keys start Off. func testKeyStylesSheetListsEveryKey() { - let sheet = openKeyStyles(openAdvancedSettings()) + let sheet = openKeyStyles(openStylesSettings()) for keyCode in [rightCommand, rightOption, fnKey] { XCTAssertTrue( @@ -268,7 +268,7 @@ final class SettingsUITests: BlurtUITestCase { /// Turning right ⌥ on persists: the button's spoken summary names it, and the /// sheet shows the choice again when reopened. func testKeyStyleChoicePersists() { - let settings = openAdvancedSettings() + let settings = openStylesSettings() let sheet = openKeyStyles(settings) sheet.popUpButtons[UITestIdentifiers.keyStylePicker(rightOption)].click() @@ -290,7 +290,7 @@ final class SettingsUITests: BlurtUITestCase { /// Escape closes the sheet (it has no Cancel button to carry the shortcut) and /// leaves the Settings window open. func testEscapeClosesKeyStylesSheet() { - let settings = openAdvancedSettings() + let settings = openStylesSettings() let sheet = openKeyStyles(settings) app.typeKey(.escape, modifierFlags: []) @@ -299,12 +299,12 @@ final class SettingsUITests: BlurtUITestCase { XCTAssertTrue(settings.exists, "Escape should close only the sheet, not Settings") } - /// Returns the Advanced pane, where the Key Styles row lives. - private func openAdvancedSettings() -> XCUIElement { - selectSettingsTab(openSettingsWindow(), named: UITestIdentifiers.advancedSettingsTab) + /// Returns the Styles pane, where the Key Styles row lives. + private func openStylesSettings() -> XCUIElement { + selectSettingsTab(openSettingsWindow(), named: UITestIdentifiers.stylesSettingsTab) } - /// Opens the Key Styles sheet from its row's Edit… button on the Advanced + /// Opens the Key Styles sheet from its row's Edit… button on the Styles /// pane and returns it. private func openKeyStyles(_ settings: XCUIElement) -> XCUIElement { let button = settings.buttons[UITestIdentifiers.keyStylesButton]