From 6b013a9a9087ee9be3506180477aedb874821ef0 Mon Sep 17 00:00:00 2001 From: Jacob Fu <141651335+FuJacob@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:08:17 -0700 Subject: [PATCH] Remove dead code: unused views, methods, getters, and a Phase-2 hook Whole-app audit (single module) for declared-but-unreferenced symbols. Each was verified to have zero references across the app and tests before removal; clean build-for-testing and SwiftLint both pass. - Views: StatusPill (MenuBarSections) and the whole TagsInputView.swift. - Methods: SuggestionInteractionState.clearContext (resetAll already clears the context), ModelDownloadManager.cancelAllDownloads, SuggestionSettingsModel.setDebounceMilliseconds / setFocusPollIntervalMilliseconds (no caller; the published values are still read and persisted elsewhere), FileLogWriter.wipeForTesting (a test hook no test calls), ExtendedContextPlaygroundModel.cancelGeneration. - Getters: MirrorPreference.helpDescription, FocusTrackingModel .menuBarStatusText / .menuBarSymbolName, FocusInspectionSnapshot's three role/capability summaries, FocusedInputSnapshot.textPreview, and GeneralPaneView.emojiSkinToneBinding (the skin-tone UI writes the setting directly). - OverlayController's never-called setCurrentBundleIdentifier and the always-nil currentBundleIdentifier it fed; the render policy now receives nil directly (behavior unchanged) until per-app overrides ship. --- Cotabby.xcodeproj/project.pbxproj | 4 - Cotabby/Models/FocusModels.swift | 26 ---- Cotabby/Models/FocusTrackingModel.swift | 16 -- Cotabby/Models/SuggestionSettingsModel.swift | 20 --- .../SuggestionInteractionState.swift | 4 - Cotabby/Services/UI/OverlayController.swift | 16 +- .../Utilities/ModelDownloadManager.swift | 9 -- .../Support/CompletionRenderModePolicy.swift | 13 -- Cotabby/Support/FileLogHandler.swift | 7 - Cotabby/UI/MenuBarSections.swift | 21 --- .../UI/Settings/Panes/AdvancedPaneView.swift | 11 -- .../UI/Settings/Panes/GeneralPaneView.swift | 7 - Cotabby/UI/TagsInputView.swift | 141 ------------------ 13 files changed, 3 insertions(+), 292 deletions(-) delete mode 100644 Cotabby/UI/TagsInputView.swift diff --git a/Cotabby.xcodeproj/project.pbxproj b/Cotabby.xcodeproj/project.pbxproj index 2b5550d8..654f718c 100644 --- a/Cotabby.xcodeproj/project.pbxproj +++ b/Cotabby.xcodeproj/project.pbxproj @@ -185,7 +185,6 @@ AB9C9C001F97F9D14F8B192A /* TerminalAppDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F4C4A7EAF886E0CC945BFEF /* TerminalAppDetector.swift */; }; AECC7289DA796B071B4FE3C0 /* MenuBarStatusLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD42C7E2852F59BEF7972663 /* MenuBarStatusLabelView.swift */; }; B00FDD3DEE0B73FF5136C91C /* FocusTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FDF029F7828CAF3FE8850 /* FocusTracker.swift */; }; - B0828FF0D7EE110C0B23DB94 /* TagsInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C0F017699EE44C81C095CA /* TagsInputView.swift */; }; B0B115C6EBAC37FF6115B4BE /* SuggestionCoordinator+Lifecycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E280F4F39A9D86840800D2 /* SuggestionCoordinator+Lifecycle.swift */; }; B2F7589B8D32ACF97BB642AB /* HuggingFaceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A520809E71697E3BB9A8139C /* HuggingFaceModels.swift */; }; B6652D81162C64248AA4CF0B /* EmojiPickerPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764659D09C3F0E8FBD267102 /* EmojiPickerPanelController.swift */; }; @@ -344,7 +343,6 @@ 54EF3C7F5D9D6F3FA50FD51C /* ContextBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextBuffer.swift; sourceTree = ""; }; 5664E34B23FBDF69292FEF43 /* FoundationModelSuggestionEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationModelSuggestionEngine.swift; sourceTree = ""; }; 5807E8508D9355D0271A00C5 /* LaunchAtLoginStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLoginStateTests.swift; sourceTree = ""; }; - 58C0F017699EE44C81C095CA /* TagsInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsInputView.swift; sourceTree = ""; }; 5976600F428C1265121D4C0C /* SystemSettingsWindowLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemSettingsWindowLocator.swift; sourceTree = ""; }; 59E299BE2E9D42A33D5D2F5D /* ScreenTextExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTextExtractor.swift; sourceTree = ""; }; 5A03E565A11581FD2150B142 /* CompletionRenderMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionRenderMode.swift; sourceTree = ""; }; @@ -830,7 +828,6 @@ 656F58E56FE9BC087B6F1D33 /* PermissionReminderView.swift */, 5484C8A04B9C00CF79D589EB /* ScreenFrameReader.swift */, FB317C82CE2CBC69056BA4B8 /* TagChip.swift */, - 58C0F017699EE44C81C095CA /* TagsInputView.swift */, 67586807ACE8EB13C9014535 /* TickMarkSlider.swift */, D5D6C2318E405AA717D1C256 /* WelcomePermissionStepView.swift */, A9199B9CEAB320982CA333B8 /* WelcomeTemplateStepView.swift */, @@ -1224,7 +1221,6 @@ E313639E71AE1374D2B9A956 /* SuggestionWorkController.swift in Sources */, 258EAFB0292290C88520E915 /* SystemSettingsWindowLocator.swift in Sources */, 90CD3F7238E223DEBA2B4D92 /* TagChip.swift in Sources */, - B0828FF0D7EE110C0B23DB94 /* TagsInputView.swift in Sources */, AB9C9C001F97F9D14F8B192A /* TerminalAppDetector.swift in Sources */, 96782E57CA26A16409368B69 /* TextDirectionDetector.swift in Sources */, 6014B31E2570EFFE45557E33 /* TickMarkSlider.swift in Sources */, diff --git a/Cotabby/Models/FocusModels.swift b/Cotabby/Models/FocusModels.swift index e4697ac6..905dceda 100644 --- a/Cotabby/Models/FocusModels.swift +++ b/Cotabby/Models/FocusModels.swift @@ -103,26 +103,6 @@ struct FocusInspectionSnapshot: Equatable { let resolvedRole: String? let resolvedSubrole: String? let missingCapabilities: [FocusCapabilityRequirement] - - var focusedRoleSummary: String { - "\(focusedRole) / \(focusedSubrole ?? "n/a")" - } - - var resolvedRoleSummary: String { - guard let resolvedRole else { - return "Unavailable" - } - - return "\(resolvedRole) / \(resolvedSubrole ?? "n/a")" - } - - var missingCapabilitySummary: String { - guard !missingCapabilities.isEmpty else { - return "None" - } - - return missingCapabilities.map(\.summary).joined(separator: ", ") - } } /// This snapshot is the future handoff point into suggestion generation. @@ -221,12 +201,6 @@ struct FocusedInputSnapshot: Equatable { isSecure ? "secure" : "plain" ].joined(separator: "::") } - - var textPreview: String { - let prefix = String(precedingText.suffix(32)) - let suffix = String(trailingText.prefix(32)) - return "\(prefix)|\(suffix)" - } } /// Top-level focus state that the menu can render directly. diff --git a/Cotabby/Models/FocusTrackingModel.swift b/Cotabby/Models/FocusTrackingModel.swift index e15cebb8..684d1825 100644 --- a/Cotabby/Models/FocusTrackingModel.swift +++ b/Cotabby/Models/FocusTrackingModel.swift @@ -74,22 +74,6 @@ final class FocusTrackingModel: ObservableObject { tracker.updatePollInterval(TimeInterval(milliseconds) / 1000.0) } - /// The menu bar needs a compact status string, not the full diagnostic reason. - var menuBarStatusText: String { - snapshot.capability.shortLabel - } - - var menuBarSymbolName: String { - switch snapshot.capability { - case .supported: - return "checkmark.circle" - case .blocked: - return "hand.raised.circle" - case .unsupported: - return "xmark.circle" - } - } - private func updateLatestExternalApplication(from snapshot: FocusSnapshot) { guard let application = snapshot.externalApplicationIdentity( ignoredBundleIdentifier: ignoredBundleIdentifier diff --git a/Cotabby/Models/SuggestionSettingsModel.swift b/Cotabby/Models/SuggestionSettingsModel.swift index c773e947..20c10097 100644 --- a/Cotabby/Models/SuggestionSettingsModel.swift +++ b/Cotabby/Models/SuggestionSettingsModel.swift @@ -530,26 +530,6 @@ final class SuggestionSettingsModel: ObservableObject { userDefaults.set(granularity.rawValue, forKey: Self.acceptanceGranularityDefaultsKey) } - func setDebounceMilliseconds(_ value: Int) { - let clamped = max(10, min(500, value)) - guard debounceMilliseconds != clamped else { - return - } - - debounceMilliseconds = clamped - userDefaults.set(clamped, forKey: Self.debounceMillisecondsDefaultsKey) - } - - func setFocusPollIntervalMilliseconds(_ value: Int) { - let clamped = max(10, min(500, value)) - guard focusPollIntervalMilliseconds != clamped else { - return - } - - focusPollIntervalMilliseconds = clamped - userDefaults.set(clamped, forKey: Self.focusPollIntervalMillisecondsDefaultsKey) - } - func setGloballyEnabled(_ enabled: Bool) { guard isGloballyEnabled != enabled else { return diff --git a/Cotabby/Services/Suggestion/SuggestionInteractionState.swift b/Cotabby/Services/Suggestion/SuggestionInteractionState.swift index aef6871f..60ddbe56 100644 --- a/Cotabby/Services/Suggestion/SuggestionInteractionState.swift +++ b/Cotabby/Services/Suggestion/SuggestionInteractionState.swift @@ -36,10 +36,6 @@ final class SuggestionInteractionState { contextBuffer.materialize(from: snapshot) } - func clearContext() { - contextBuffer.clear() - } - func clearSuggestion() { activeSession = nil pendingInsertionConsumedCount = nil diff --git a/Cotabby/Services/UI/OverlayController.swift b/Cotabby/Services/UI/OverlayController.swift index 74057755..d8ed617b 100644 --- a/Cotabby/Services/UI/OverlayController.swift +++ b/Cotabby/Services/UI/OverlayController.swift @@ -25,11 +25,6 @@ final class OverlayController: SuggestionOverlayControlling { /// instead of building one from live settings. Production code leaves this nil. private let renderModePolicyOverride: CompletionRenderModePolicy? - /// Bundle identifier of the currently focused host app, supplied by the coordinator each time - /// a suggestion is presented. The policy uses this to look up per-app overrides. Nil in tests - /// or when the focus pipeline could not identify the host. - private var currentBundleIdentifier: String? - /// Built from the live `mirrorPreference` setting at call time rather than cached. The struct /// is tiny (one enum + an empty dict in Phase 2) so per-show allocation cost is negligible, /// and the read-through model means the user's Settings/menu-bar toggle takes effect on the @@ -72,13 +67,6 @@ final class OverlayController: SuggestionOverlayControlling { self.renderModePolicyOverride = renderModePolicyOverride } - /// Coordinator hook that updates the bundle identifier used by per-app overrides. Phase 1 - /// callers do not need this (policy is `.auto` with no overrides); Phase 2 will wire it through - /// the presenter so per-app settings take effect immediately when the focused app changes. - func setCurrentBundleIdentifier(_ bundleIdentifier: String?) { - currentBundleIdentifier = bundleIdentifier - } - private lazy var panel: OverlayPanel = { let panel = OverlayPanel( contentRect: CGRect(x: 0, y: 0, width: 10, height: 10), @@ -111,9 +99,11 @@ final class OverlayController: SuggestionOverlayControlling { return } + // Per-app render-mode overrides are not wired yet, so the policy always resolves without a + // host bundle identifier; thread the focused app's id here when per-app overrides ship. let mode = currentRenderModePolicy.mode( for: geometry, - bundleIdentifier: currentBundleIdentifier + bundleIdentifier: nil ) switch mode { diff --git a/Cotabby/Services/Utilities/ModelDownloadManager.swift b/Cotabby/Services/Utilities/ModelDownloadManager.swift index e5f1ecbc..9ced7a26 100644 --- a/Cotabby/Services/Utilities/ModelDownloadManager.swift +++ b/Cotabby/Services/Utilities/ModelDownloadManager.swift @@ -191,15 +191,6 @@ final class ModelDownloadManager: ObservableObject { task.cancel() } - /// Cancels every in-flight model download before destructive app cleanup. - /// Uninstall should not race an active download that may still be writing into Cotabby's model - /// directory while the folder is being removed. - func cancelAllDownloads() { - for task in downloadTasks.values { - task.cancel() - } - } - func openModelsDirectory() { do { try ensureRuntimeDirectoryExists() diff --git a/Cotabby/Support/CompletionRenderModePolicy.swift b/Cotabby/Support/CompletionRenderModePolicy.swift index 6cc77f05..4b65793f 100644 --- a/Cotabby/Support/CompletionRenderModePolicy.swift +++ b/Cotabby/Support/CompletionRenderModePolicy.swift @@ -27,19 +27,6 @@ enum MirrorPreference: String, Codable, CaseIterable, Identifiable, Equatable, S return "Popup" } } - - /// One-sentence explanation suitable for `.help()` tooltips next to the picker. Read as a group - /// the three help strings teach the user when each option is the right pick. - var helpDescription: String { - switch self { - case .auto: - return "Inline ghost text when caret position is reliable; popup card when it isn't (some Electron and web editors)." - case .alwaysInline: - return "Always draw ghost text next to the caret, even when caret position may drift." - case .alwaysMirror: - return "Always show suggestions in a popup card anchored below the focused field." - } - } } /// Pure rule that translates "what kind of geometry do we have, and what does the user want?" into diff --git a/Cotabby/Support/FileLogHandler.swift b/Cotabby/Support/FileLogHandler.swift index 29f5a629..90a48820 100644 --- a/Cotabby/Support/FileLogHandler.swift +++ b/Cotabby/Support/FileLogHandler.swift @@ -67,13 +67,6 @@ final class FileLogWriter: @unchecked Sendable { } } - /// Test-only hook to force a rotation. - func wipeForTesting() { - lock.lock() - defer { lock.unlock() } - rotateLocked() - } - private func rotateLocked() { guard let logFileURL else { return } do { diff --git a/Cotabby/UI/MenuBarSections.swift b/Cotabby/UI/MenuBarSections.swift index b51494e1..0de21fb5 100644 --- a/Cotabby/UI/MenuBarSections.swift +++ b/Cotabby/UI/MenuBarSections.swift @@ -5,27 +5,6 @@ import SwiftUI /// Small, focused components used by the menu-bar panel. /// These stay purely presentational — all state derivation lives in `MenuBarView`. -/// Colored pill that communicates Cotabby's overall readiness at a glance. -/// Green = ready, orange = degraded/needs attention, red = broken, gray = transitional. -struct StatusPill: View { - let text: String - let color: Color - - var body: some View { - HStack(spacing: 5) { - Circle() - .fill(color) - .frame(width: 7, height: 7) - - Text(text) - .font(.subheadline.weight(.medium)) - } - .padding(.horizontal, 10) - .padding(.vertical, 4) - .background(color.opacity(0.12), in: Capsule()) - } -} - /// Compact labeled row for menu-bar pickers. Keeps label width consistent across /// Engine / Model / Length rows without a heavy generic layout container. struct MenuBarPickerRow: View { diff --git a/Cotabby/UI/Settings/Panes/AdvancedPaneView.swift b/Cotabby/UI/Settings/Panes/AdvancedPaneView.swift index 7bb23fb3..f2fb2021 100644 --- a/Cotabby/UI/Settings/Panes/AdvancedPaneView.swift +++ b/Cotabby/UI/Settings/Panes/AdvancedPaneView.swift @@ -355,17 +355,6 @@ final class ExtendedContextPlaygroundModel: ObservableObject { } } - /// Cancels any in-flight generation and clears the spinner. Currently unused by the UI but - /// kept on the model so a cancel button (or window-dismiss handler) can call into it without - /// re-deriving the cancellation protocol. The generation-ID flip is the load-bearing piece — - /// it tells the in-flight task to silently exit instead of clearing state itself. - func cancelGeneration() { - generationTask?.cancel() - generationTask = nil - currentGenerationID = nil - isGenerating = false - } - /// Builds a `FocusedInputContext` from the user's test text. The values are intentionally /// generic — the playground is a prompt-shape demo, not an attempt to mimic a specific host /// app's accessibility surface. diff --git a/Cotabby/UI/Settings/Panes/GeneralPaneView.swift b/Cotabby/UI/Settings/Panes/GeneralPaneView.swift index a4c31b13..c07c76de 100644 --- a/Cotabby/UI/Settings/Panes/GeneralPaneView.swift +++ b/Cotabby/UI/Settings/Panes/GeneralPaneView.swift @@ -278,13 +278,6 @@ struct GeneralPaneView: View { ) } - private var emojiSkinToneBinding: Binding { - Binding( - get: { suggestionSettings.preferredEmojiSkinTone }, - set: { suggestionSettings.setPreferredEmojiSkinTone($0) } - ) - } - private var autoAcceptTrailingPunctuationBinding: Binding { Binding( get: { suggestionSettings.autoAcceptTrailingPunctuation }, diff --git a/Cotabby/UI/TagsInputView.swift b/Cotabby/UI/TagsInputView.swift deleted file mode 100644 index e4a00cd4..00000000 --- a/Cotabby/UI/TagsInputView.swift +++ /dev/null @@ -1,141 +0,0 @@ -import SwiftUI - -/// File overview: -/// A custom input view that allows users to type text and convert it into bubble "tags" when pressing Enter. -/// -/// This provides a structured way to collect "things you type often" instead of relying on a freeform -/// text area, keeping the prompt input clean and predictable. -struct TagsInputView: View { - @Binding var tags: [String] - let placeholder: String - - @State private var inputText: String = "" - @FocusState private var isFocused: Bool - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - TextField(placeholder, text: $inputText) - .textFieldStyle(.roundedBorder) - .focused($isFocused) - .onSubmit { - addTag() - } - .onChange(of: inputText) { _, newValue in - // If the user types a comma, automatically treat it as a tag submission - if newValue.hasSuffix(",") { - addTag() - } - } - - if !tags.isEmpty { - FlowLayout(spacing: 8) { - ForEach(tags, id: \.self) { tag in - TagBubble(text: tag) { - removeTag(tag) - } - } - } - .animation(.spring(response: 0.3, dampingFraction: 0.7), value: tags) - } - } - } - - private func addTag() { - let trimmed = inputText.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: ",", with: "") - guard !trimmed.isEmpty, !tags.contains(trimmed) else { - inputText = "" - return - } - - tags.append(trimmed) - inputText = "" - } - - private func removeTag(_ tag: String) { - tags.removeAll { $0 == tag } - } -} - -/// A single removable bubble representing a tag. -private struct TagBubble: View { - let text: String - let onRemove: () -> Void - - @State private var isHovering = false - - var body: some View { - HStack(spacing: 4) { - Text(text) - .font(.system(size: 13, weight: .medium)) - - Button(action: onRemove) { - Image(systemName: "xmark.circle.fill") - .font(.system(size: 12)) - .foregroundStyle(.secondary) - } - .buttonStyle(.plain) - .opacity(isHovering ? 1.0 : 0.6) - } - .padding(.horizontal, 10) - .padding(.vertical, 4) - .background( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(.tertiary.opacity(0.3)) - ) - .overlay( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(.secondary.opacity(0.2), lineWidth: 1) - ) - .onHover { hovering in - withAnimation(.easeOut(duration: 0.15)) { - isHovering = hovering - } - } - } -} - -/// A simple flow layout that wraps its children. -private struct FlowLayout: Layout { - var spacing: CGFloat - - func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { - let result = FlowResult(in: proposal.width ?? 0, subviews: subviews, spacing: spacing) - return result.size - } - - func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { - let result = FlowResult(in: bounds.width, subviews: subviews, spacing: spacing) - for (index, subview) in subviews.enumerated() { - let point = result.frames[index].origin - subview.place(at: CGPoint(x: bounds.minX + point.x, y: bounds.minY + point.y), proposal: .unspecified) - } - } - - struct FlowResult { - var size: CGSize = .zero - var frames: [CGRect] = [] - - init(in maxWidth: CGFloat, subviews: Subviews, spacing: CGFloat) { - var currentX: CGFloat = 0 - var currentY: CGFloat = 0 - var lineHeight: CGFloat = 0 - - for subview in subviews { - let size = subview.sizeThatFits(.unspecified) - - if currentX + size.width > maxWidth, currentX > 0 { - currentX = 0 - currentY += lineHeight + spacing - lineHeight = 0 - } - - frames.append(CGRect(x: currentX, y: currentY, width: size.width, height: size.height)) - - lineHeight = max(lineHeight, size.height) - currentX += size.width + spacing - } - - self.size = CGSize(width: maxWidth, height: currentY + lineHeight) - } - } -}