Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cotabby/Models/CustomRulesCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions Cotabby/Support/SuggestionRequestFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 13 additions & 6 deletions Cotabby/UI/Settings/Panes/WritingPaneView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
}
}
}
Expand Down
15 changes: 10 additions & 5 deletions Cotabby/UI/WelcomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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:
Expand All @@ -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.
Expand All @@ -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
}
}

Expand Down