diff --git a/Cotabby/UI/Settings/Panes/WritingPaneView.swift b/Cotabby/UI/Settings/Panes/WritingPaneView.swift index 7767178b..c7762021 100644 --- a/Cotabby/UI/Settings/Panes/WritingPaneView.swift +++ b/Cotabby/UI/Settings/Panes/WritingPaneView.swift @@ -70,40 +70,57 @@ struct WritingPaneView: View { .settingsItem(.addSpaceAfterAccept) } - Section("Corrections") { + // Typo handling is a dependency chain, so the UI discloses it progressively rather than + // showing every control at once. The master gate ("Hide Suggestions on Typo") detects + // misspellings; the correction actions only function while it is on; the dictionaries + // only matter once an action can consume them. Revealing each level when it becomes + // relevant — instead of dimming dependents in place — makes the hierarchy unmistakable: + // turning the gate off removes the controls that depend on it. This is the established + // master/dependent idiom in this app (see `EmojiPaneView`). The earlier flat layout put + // all three toggles at the same visual level, so it was not obvious the gate governed the + // other two. + Section("Typos") { Toggle(isOn: suppressCompletionsOnTypoBinding) { SettingsRowLabel( title: "Hide Suggestions on Typo", - description: "Stops normal completions while the current word appears misspelled.", + description: "Pauses normal completions while the current word looks misspelled. " + + "Turn this on to use the correction options.", systemImage: "eye.slash" ) } .settingsItem(.hideSuggestionsOnTypo) + } - Toggle(isOn: offerTypoCorrectionsBinding) { - SettingsRowLabel( - title: "Offer Corrections on Typo", - description: "Shows a green replacement you can apply with your accept key.", - systemImage: "checkmark.bubble" - ) - } - .disabled(!suggestionSettings.suppressCompletionsOnTypo) - .settingsItem(.offerTypoCorrections) + if suggestionSettings.suppressCompletionsOnTypo { + Section("Corrections") { + Toggle(isOn: offerTypoCorrectionsBinding) { + SettingsRowLabel( + title: "Offer Corrections on Typo", + description: "Shows a green replacement you can apply with your accept key.", + systemImage: "checkmark.bubble" + ) + } + .settingsItem(.offerTypoCorrections) - Toggle(isOn: automaticallyFixTyposBinding) { - SettingsRowLabel( - title: "Automatically Fix Typos", - description: "After you press Space, replaces a misspelled word without requiring your accept key.", - systemImage: "checkmark.circle" - ) + Toggle(isOn: automaticallyFixTyposBinding) { + SettingsRowLabel( + title: "Automatically Fix Typos", + description: "After you press Space, replaces a misspelled word without requiring your accept key.", + systemImage: "checkmark.circle" + ) + } + .settingsItem(.automaticallyFixTypos) } - .disabled(!suggestionSettings.suppressCompletionsOnTypo) - .settingsItem(.automaticallyFixTypos) - - Divider() - SpellingDictionaryPicker(suggestionSettings: suggestionSettings) - .settingsItem(.spellingDictionaries) + // Dictionaries rank candidates for the two correction actions above, so they only + // appear once at least one action is on. With neither active, choosing a dictionary + // would have no observable effect. + if suggestionSettings.offerTypoCorrections || suggestionSettings.automaticallyFixTypos { + Section("Spelling Dictionaries") { + SpellingDictionaryPicker(suggestionSettings: suggestionSettings) + .settingsItem(.spellingDictionaries) + } + } } Section("Profile") { diff --git a/Cotabby/UI/SpellingDictionaryPicker.swift b/Cotabby/UI/SpellingDictionaryPicker.swift index 491f821f..e9045682 100644 --- a/Cotabby/UI/SpellingDictionaryPicker.swift +++ b/Cotabby/UI/SpellingDictionaryPicker.swift @@ -5,25 +5,16 @@ import SwiftUI /// This view owns presentation only. `SuggestionSettingsModel` normalizes and persists each toggle, /// while `SpellingLanguageResolver` and `SymSpellCorrector` own runtime selection and loading. Keeping /// those responsibilities separate prevents a Settings view from constructing heavyweight indexes. +/// +/// Relevance gating lives in the parent (`WritingPaneView`): the picker is only rendered once the +/// typo gate and at least one correction action are on, so the checkboxes here are always live and +/// carry no self-disabling logic. The enclosing `Section("Spelling Dictionaries")` supplies the +/// header, so this view starts straight at its explanatory caption. struct SpellingDictionaryPicker: View { @ObservedObject var suggestionSettings: SuggestionSettingsModel - /// The dictionary choices only change behavior when a correction can actually be produced and - /// surfaced. The typo gate must be armed (`suppressCompletionsOnTypo`) and at least one - /// correction path active: both "Offer Corrections on Typo" and "Automatically Fix Typos" rank - /// candidates through the enabled SymSpell dictionaries (see `TypoGate`/`bestCorrection`). When - /// none of those is on, ticking a dictionary has no observable effect, so we disable the - /// checkboxes, mirroring how the correction toggles disable themselves when the gate is off. - private var dictionariesAffectCorrections: Bool { - suggestionSettings.suppressCompletionsOnTypo - && (suggestionSettings.offerTypoCorrections || suggestionSettings.automaticallyFixTypos) - } - var body: some View { VStack(alignment: .leading, spacing: 10) { - Text("Spelling Dictionaries") - .font(.system(size: 13, weight: .medium)) - Text( "Choose which bundled dictionaries Cotabby may use for frequency-ranked corrections. " + "With several enabled, Cotabby selects one from the surrounding text." @@ -51,7 +42,6 @@ struct SpellingDictionaryPicker: View { .toggleStyle(.checkbox) } } - .disabled(!dictionariesAffectCorrections) Text( "Indexes load on demand and Cotabby keeps at most two in memory. If no bundled "