Skip to content

Add temporary pause controls#765

Open
akramj13 wants to merge 5 commits into
FuJacob:mainfrom
akramj13:codex/add-pause-menu-controls
Open

Add temporary pause controls#765
akramj13 wants to merge 5 commits into
FuJacob:mainfrom
akramj13:codex/add-pause-menu-controls

Conversation

@akramj13

@akramj13 akramj13 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Add temporary pause controls so users can quiet Cotabby for a bounded period without changing their normal global setting or adding a permanent per-app rule.

This branch adds durable pause state, menu-bar pause actions and status, and pipeline gating so Cotabby does not generate, show, or accept suggestions while paused.

Validation

  • xcodebuild -project Cotabby.xcodeproj -scheme "Cotabby Dev" -configuration Debug -destination 'platform=macOS' -derivedDataPath build/DerivedData CODE_SIGNING_ALLOWED=NO build
    • ** BUILD SUCCEEDED **

Linked issues

Fixes #8

Risk / rollout notes

  • This adds a new persisted pause-state setting and updates the project file to include the new model and tests.
  • The branch currently includes pause options for 15 minutes, 30 minutes, 1 hour, until tomorrow, and indefinitely. The issue only explicitly called out 15 minutes, 1 hour, tomorrow, and manual resume.

Greptile Summary

This PR adds a temporary pause feature to Cotabby, allowing users to silence suggestions for bounded durations (15 min, 30 min, 1 hr, until tomorrow, or indefinitely) without changing their permanent global-enable setting. The implementation introduces durable SuggestionPauseState persisted via UserDefaults, a SuggestionSettingsModel-owned timer for auto-expiry, and full pipeline gating (input, visual context capture, acceptance, indicator) via the SuggestionSettingsSnapshot.

  • New model layer (SuggestionPauseModels.swift): SuggestionPauseDuration encapsulates all calendar math (including DST-safe "until tomorrow" via Calendar.startOfDay), and SuggestionPauseState is a codable enum that is persisted across launches with expiry filtering on load.
  • Settings model changes: pauseState is a @Published property integrated into the Combine snapshot publisher alongside $isGloballyEnabled; a Timer on RunLoop.main(.common) auto-calls clearPause() on expiry; toggleGloballyEnabled() is updated to call enableCotabby() (which clears both mechanisms) instead of just flipping the global flag.
  • Pipeline & UI gating: All six call sites (input, visual context ×3, prediction, acceptance) now pass isTemporarilyPaused to SuggestionAvailabilityEvaluator; the menu bar replaces the global toggle with a pause sub-menu and a single recovery action.

Confidence Score: 5/5

The change is safe to merge. All six pipeline gate sites are updated consistently, persistence round-trips correctly, and the timer auto-expiry is wired to RunLoop.main(.common) so it fires through menu tracking and other UI modes.

The new pause state is well-isolated from the existing global-enable flag, the Combine publisher integration is correct, expired pauses are filtered on load and cleaned from UserDefaults, and the acceptance gate prevents Tab from accepting a stale pre-pause suggestion. Test coverage is thorough: calendar math, expiry boundary, store round-trips, coordinator gating, and snapshot publisher emissions are all exercised. No functional defects were identified in the changed paths.

No files require special attention.

Important Files Changed

Filename Overview
Cotabby/Models/SuggestionPauseModels.swift New file defining SuggestionPauseDuration (menu choices + calendar math) and SuggestionPauseState (codable enum with expiry logic). DST-safe "until tomorrow" uses Calendar.startOfDay; statusText equality check for tomorrow relies correctly on the same calendar method used at pause creation time.
Cotabby/Models/SuggestionSettingsModel.swift Adds @published pauseState, pauseExpirationTimer, pauseSuggestions/clearPause/enableCotabby methods, and integrates $pauseState into the CombineLatest snapshot publisher. Timer uses Timer(fire:interval:repeats:) + RunLoop.main(.common) correctly. toggleGloballyEnabled updated to call enableCotabby() when paused or disabled.
Cotabby/Support/SuggestionSettingsStore.swift Adds savePauseState (nil → removeObject, non-nil → encode+set) and loads with activeState() filtering to discard expired pauses on launch. The unconditional write-back correctly persists the filtered state (removing stale expired keys). pauseStateDefaultsKey is included in allPreferenceDefaultsKeys for reset coverage.
Cotabby/Support/SuggestionAvailabilityEvaluator.swift Adds temporarilyPaused parameter (default false) to disabledReason, shouldSchedulePrediction, and shouldCaptureVisualContext. Pause is checked after the global-disable guard, which is the correct precedence order.
Cotabby/UI/MenuBarView.swift Replaces the global enable toggle with a pause sub-menu (active state) or a single "Enable Cotabby" recovery button (paused/disabled state). Removes globallyEnabledBinding. The mutual-exclusion logic prevents stacking contradictory disable states from the UI.
Cotabby/App/Core/AppDelegate.swift Adds a snapshotPublisher subscription so the field-edge indicator is hidden when a menu action pauses Cotabby without a focus change. updateActivationIndicator now takes an optional snapshot parameter and falls back to suggestionSettings.snapshot, also checking isTemporarilyPaused.
Cotabby/App/Coordinators/SuggestionCoordinator+Acceptance.swift Gates acceptance on currentDisabledReason (which now includes pause) before entering the state-machine path. Correctly blocks Tab acceptance of a suggestion that was shown before the pause was activated.
CotabbyTests/SuggestionPauseModelsTests.swift New test file covering minute/hour intervals, DST-safe untilTomorrow (America/Toronto), and isActive boundary at exact expiration. Good coverage of the deterministic calendar math.
CotabbyTests/SuggestionSettingsStoreTests.swift Adds tests for indefinite-pause round-trip, expired-pause discard + UserDefaults cleanup, and inclusion in resetToDefaults coverage. All new paths are exercised.
Cotabby/App/Core/CotabbyAppEnvironment.swift Adds isTemporarilyPaused checks to the isCaptureSuppressedForBundle and shouldProcessEventsProvider closures, following the same pattern as the existing isGloballyEnabled checks.
Cotabby/UI/MenuBarStatusLabelView.swift Adds a pause.fill badge to the menu-bar icon when paused or globally disabled, with VoiceOver label correctly distinguishing "Cotabby paused" from "Cotabby disabled" via inactiveAccessibilityLabel.

Reviews (2): Last reviewed commit: "fix: preserve pause state on encode fail..." | Re-trigger Greptile

@akramj13

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread Cotabby/UI/MenuBarStatusLabelView.swift
Comment thread Cotabby/Models/SuggestionSettingsModel.swift Outdated
Comment thread Cotabby/Support/SuggestionSettingsStore.swift
@akramj13 akramj13 marked this pull request as ready for review June 29, 2026 03:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add temporary pause controls

1 participant