diff --git a/Cotabby/Models/CustomRulesCatalog.swift b/Cotabby/Models/CustomRulesCatalog.swift index 217d87d..c18c604 100644 --- a/Cotabby/Models/CustomRulesCatalog.swift +++ b/Cotabby/Models/CustomRulesCatalog.swift @@ -11,6 +11,16 @@ import Foundation /// is the single chokepoint that keeps stored rules bounded and de-duplicated regardless of whether /// they came from onboarding, settings, the palette, or a future import path. enum CustomRulesCatalog { + /// Master switch for the user-facing custom-rules feature. Temporarily `false`: the Open Source + /// path now runs base models that cannot obey free-text instructions, and on every engine the + /// rule text tends to leak verbatim into the suggestion (issues #340 / #292) while no eval + /// measures adherence, so surfacing the feature is a net-negative promise right now. Hiding is + /// fully reversible: stored rules in `cotabbyCustomRules` are never deleted, the editor and + /// renderer code is kept, so flipping this back to `true` restores the feature once rules are + /// measured to actually influence output. Honored at three sites: `SuggestionRequestFactory` + /// (prompt injection), `WritingPaneView` (Settings surface), and `WelcomeView` (onboarding step). + static let isUserFacingEnabled = false + /// Caps protect the local model's limited context budget and guard against pasted essays. static let maxRules = 10 static let maxRuleLength = 60 diff --git a/Cotabby/Support/SuggestionRequestFactory.swift b/Cotabby/Support/SuggestionRequestFactory.swift index 0ec7dd3..9ab4601 100644 --- a/Cotabby/Support/SuggestionRequestFactory.swift +++ b/Cotabby/Support/SuggestionRequestFactory.swift @@ -43,8 +43,12 @@ enum SuggestionRequestFactory { ) let completionLengthInstruction = settings.selectedWordCountPreset.promptInstruction let userName = activeUserName(settings: settings) - // Already normalized (trimmed/deduped/capped) by SuggestionSettingsModel.setRules. - let customRules = settings.customRules + // Custom rules are hidden from users (CustomRulesCatalog.isUserFacingEnabled == false): the + // base-model OSS path cannot obey free-text instructions and the rule text leaks into output, + // so injection is suppressed on every engine. Stored rules survive untouched, so flipping the + // flag restores this. When enabled, the value is already normalized (trimmed/deduped/capped) + // by SuggestionSettingsModel.setRules. + let customRules = CustomRulesCatalog.isUserFacingEnabled ? settings.customRules : [] // The settings model length-caps but does NOT trim whitespace (trimming on every keystroke // would prevent the user from typing a space at the end of a word in the editor). Do the // trim here, once per request, and collapse a whitespace-only body back to nil so renderers diff --git a/Cotabby/UI/Settings/Panes/WritingPaneView.swift b/Cotabby/UI/Settings/Panes/WritingPaneView.swift index deb6880..92131d3 100644 --- a/Cotabby/UI/Settings/Panes/WritingPaneView.swift +++ b/Cotabby/UI/Settings/Panes/WritingPaneView.swift @@ -25,9 +25,12 @@ struct WritingPaneView: View { Section("Profile") { VStack(alignment: .leading, spacing: 16) { - // The caption introduces all three personalization inputs (name, languages, - // rules) since each is passed to the AI, even though they live in separate cards. - Text("Your name, languages, and rules are passed to the AI to help personalize your completions.") + // Introduces the personalization inputs passed to the AI. The custom-rules input + // is gated (CustomRulesCatalog.isUserFacingEnabled), so this copy and the Rules + // section below are dropped together while the feature is hidden. + Text(CustomRulesCatalog.isUserFacingEnabled + ? "Your name, languages, and rules are passed to the AI to help personalize your completions." + : "Your name and languages are passed to the AI to help personalize your completions.") .font(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -53,9 +56,13 @@ struct WritingPaneView: View { .padding(.vertical, 6) } - Section("Rules") { - CustomRulesEditor(suggestionSettings: suggestionSettings, showsTitleHeader: false) - .padding(.vertical, 6) + // Hidden while custom rules are gated off (CustomRulesCatalog.isUserFacingEnabled). The + // editor and its storage are intentionally kept so re-enabling is a one-line flip. + if CustomRulesCatalog.isUserFacingEnabled { + Section("Rules") { + CustomRulesEditor(suggestionSettings: suggestionSettings, showsTitleHeader: false) + .padding(.vertical, 6) + } } } } diff --git a/Cotabby/UI/WelcomeView.swift b/Cotabby/UI/WelcomeView.swift index b994320..75aa77f 100644 --- a/Cotabby/UI/WelcomeView.swift +++ b/Cotabby/UI/WelcomeView.swift @@ -204,7 +204,8 @@ extension WelcomeView { canGoBack: true, canContinue: true, onBack: { step = .template }, - onContinue: { step = .writingStyle } + // Skip the writing-style (custom rules) step when that feature is gated off. + onContinue: { step = CustomRulesCatalog.isUserFacingEnabled ? .writingStyle : .keybind } ) case .writingStyle: WelcomeNavigation( @@ -217,7 +218,7 @@ extension WelcomeView { WelcomeNavigation( canGoBack: true, canContinue: true, - onBack: { step = .writingStyle }, + onBack: { step = CustomRulesCatalog.isUserFacingEnabled ? .writingStyle : .aboutYou }, onContinue: { step = .done } ) case .welcome, .done: @@ -241,8 +242,11 @@ private enum WelcomeStep: Int, Comparable { lhs.rawValue < rhs.rawValue } - /// Number of steps shown in the progress indicator (the middle, non-terminal steps). - static let totalProgressSteps = 5 + /// Number of steps shown in the progress indicator (the middle, non-terminal steps). Drops to 4 + /// when the custom-rules writing-style step is gated off (CustomRulesCatalog.isUserFacingEnabled). + static var totalProgressSteps: Int { + CustomRulesCatalog.isUserFacingEnabled ? 5 : 4 + } /// 1-based position within the progress indicator, or `nil` for the intro/outro steps that /// intentionally sit outside the counted flow. @@ -259,7 +263,8 @@ private enum WelcomeStep: Int, Comparable { case .writingStyle: return 4 case .keybind: - return 5 + // The 4th step when the writing-style step is gated off, otherwise the 5th. + return CustomRulesCatalog.isUserFacingEnabled ? 5 : 4 } }