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
2 changes: 1 addition & 1 deletion Cotabby/Models/OnboardingTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ enum OnboardingTemplate: String, CaseIterable, Identifiable, Equatable, Sendable
var wordCountPreset: SuggestionWordCountPreset {
switch self {
case .quick:
return .threeToSeven
return .fourToSeven
case .everyday:
return .sevenToTwelve
case .powerful:
Expand Down
15 changes: 10 additions & 5 deletions Cotabby/Models/SuggestionModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import Foundation
/// User-facing presets that bound how long one inline suggestion may be.
/// Treating this as an enum keeps the UI and prompt policy in one source of truth.
enum SuggestionWordCountPreset: String, CaseIterable, Equatable, Hashable, Sendable, Identifiable {
case threeToSeven = "3-7"
case twoToFour = "2-4"
case fourToSeven = "4-7"
case sevenToTwelve = "7-12"
case twelveToTwenty = "12-20"

Expand All @@ -30,8 +31,10 @@ enum SuggestionWordCountPreset: String, CaseIterable, Equatable, Hashable, Senda

var promptInstruction: String {
switch self {
case .threeToSeven:
return "Return only the next 3 to 7 words."
case .twoToFour:
return "Return only the next 2 to 4 words."
case .fourToSeven:
return "Return only the next 4 to 7 words."
case .sevenToTwelve:
return "Return only the next 7 to 12 words."
case .twelveToTwenty:
Expand All @@ -43,10 +46,12 @@ enum SuggestionWordCountPreset: String, CaseIterable, Equatable, Hashable, Senda
/// word-range cue was removed), so it must track the upper word bound closely. Sized at
/// ~1.5x the upper word count to leave headroom for multi-token words (contractions, proper
/// nouns, punctuation) without overrunning the preset. The earlier 50% bump (17/27/45) let
/// completions blow past the settinge.g. ~12 words on the 3-7 preset (#271).
/// completions blow past the setting, e.g. ~12 words on the shortest preset (#271).
var suggestedPredictionTokenBudget: Int {
switch self {
case .threeToSeven:
case .twoToFour:
return 6
case .fourToSeven:
return 11
case .sevenToTwelve:
return 18
Expand Down
18 changes: 14 additions & 4 deletions Cotabby/Models/SuggestionSettingsModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ final class SuggestionSettingsModel: ObservableObject {
private static let ghostTextOpacityDefaultsKey = "cotabbyGhostTextOpacity"
private static let selectedEngineDefaultsKey = "cotabbySelectedEngine"
private static let selectedWordCountPresetDefaultsKey = "cotabbySelectedWordCountPreset"
/// Pre-#475 raw value for the shortest length tier. Kept here only so the read path can
/// rewrite it to `.fourToSeven` on launch; never re-emitted to UserDefaults.
private static let legacyShortPresetRawValue = "3-7"
private static let clipboardContextEnabledDefaultsKey = "cotabbyClipboardContextEnabled"
private static let fastModeEnabledDefaultsKey = "cotabbyFastModeEnabled"
private static let performanceTrackingEnabledDefaultsKey = "cotabbyPerformanceTrackingEnabled"
Expand Down Expand Up @@ -170,10 +173,17 @@ final class SuggestionSettingsModel: ObservableObject {
.string(forKey: Self.selectedEngineDefaultsKey)
.flatMap(SuggestionEngineKind.init(rawValue:))
?? .llamaOpenSource
let resolvedWordCountPreset = userDefaults
.string(forKey: Self.selectedWordCountPresetDefaultsKey)
.flatMap(SuggestionWordCountPreset.init(rawValue:))
?? configuration.defaultWordCountPreset
let resolvedWordCountPreset: SuggestionWordCountPreset = {
let storedRaw = userDefaults.string(forKey: Self.selectedWordCountPresetDefaultsKey)
// Migrate the retired "3-7" raw value to its replacement "4-7" so users who picked
// the short preset don't silently jump to the default after #475 split the short
// tier into 2-4 and 4-7.
if storedRaw == Self.legacyShortPresetRawValue {
return .fourToSeven
}
return storedRaw.flatMap(SuggestionWordCountPreset.init(rawValue:))
?? configuration.defaultWordCountPreset
}()
Comment on lines +176 to +186

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing test coverage for the "3-7" migration path

The migration of "3-7".fourToSeven is described in the PR description as the most important rollout concern (users silently jumping to the default preset), but there is no test that exercises this path in SuggestionSettingsModel. If someone accidentally removes or misplaces the if storedRaw == … guard in a future refactor, the test suite will still pass. The existing GhostTextOpacitySettingsTests already shows how to build an isolated UserDefaults suite and reload a SuggestionSettingsModel; the same pattern could seed "3-7" into selectedWordCountPresetDefaultsKey and assert the model reads back .fourToSeven.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

let resolvedClipboardContextEnabled =
userDefaults.object(forKey: Self.clipboardContextEnabledDefaultsKey) as? Bool ?? false
// Defaults to false so the visual-context pipeline keeps running for existing users; opting
Expand Down
7 changes: 5 additions & 2 deletions CotabbyTests/ModelAndPresentationValueTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ final class SuggestionTextColorCodecTests: XCTestCase {

final class SuggestionModelValueTests: XCTestCase {
func test_wordCountPresetsExposeMatchingPromptInstructionsAndTokenBudgets() {
XCTAssertEqual(SuggestionWordCountPreset.threeToSeven.promptInstruction, "Return only the next 3 to 7 words.")
XCTAssertEqual(SuggestionWordCountPreset.threeToSeven.suggestedPredictionTokenBudget, 11)
XCTAssertEqual(SuggestionWordCountPreset.twoToFour.promptInstruction, "Return only the next 2 to 4 words.")
XCTAssertEqual(SuggestionWordCountPreset.twoToFour.suggestedPredictionTokenBudget, 6)

XCTAssertEqual(SuggestionWordCountPreset.fourToSeven.promptInstruction, "Return only the next 4 to 7 words.")
XCTAssertEqual(SuggestionWordCountPreset.fourToSeven.suggestedPredictionTokenBudget, 11)

XCTAssertEqual(SuggestionWordCountPreset.sevenToTwelve.promptInstruction, "Return only the next 7 to 12 words.")
XCTAssertEqual(SuggestionWordCountPreset.sevenToTwelve.suggestedPredictionTokenBudget, 18)
Expand Down
2 changes: 1 addition & 1 deletion CotabbyTests/OnboardingTemplateRecommenderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class OnboardingTemplateRecommenderTests: XCTestCase {

func testAppleIntelligenceStillCarriesTierBehaviorFlags() {
let quick = OnboardingTemplateRecommender.resolvePlan(for: .quick, engine: .appleIntelligence)
XCTAssertEqual(quick.wordCountPreset, .threeToSeven)
XCTAssertEqual(quick.wordCountPreset, .fourToSeven)
XCTAssertTrue(quick.enablesFastMode)
XCTAssertFalse(quick.enablesMultiLine)
XCTAssertFalse(quick.enablesClipboardContext)
Expand Down