Skip to content

feat: implemented initial version of hiding feature#744

Merged
FuJacob merged 9 commits into
FuJacob:mainfrom
akramj13:issue-742
Jun 28, 2026
Merged

feat: implemented initial version of hiding feature#744
FuJacob merged 9 commits into
FuJacob:mainfrom
akramj13:issue-742

Conversation

@akramj13

@akramj13 akramj13 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

This pull request adds a new user preference to allow hiding Cotabby's menu bar icon while keeping the app running in the background. It introduces a toggle in Settings for controlling the visibility of the menu bar status item, ensures the app remains accessible by showing Settings when relaunched with the icon hidden, and persists this preference across launches. The implementation spans UI, model, persistence, and test updates.

Menu Bar Icon Visibility Preference:

  • Added a new isMenuBarIconVisible property to SuggestionSettingsData and SuggestionSettingsModel, with corresponding getter, setter, and persistence logic in SuggestionSettingsStore.
  • Updated CotabbyApp to conditionally insert the MenuBarExtra scene based on the new preference, using a SwiftUI binding that syncs with the settings model and persists changes.
  • Updated SettingsItem to support the new toggle, including title, icon, description, search terms, and categorization under the Appearance section.

App Behavior and Recovery:

  • Implemented applicationShouldHandleReopen in AppDelegate to show Settings if the app is reopened while the menu bar icon is hidden, ensuring users can always access the app's UI.

Testing and Persistence:

  • Added and updated tests to verify the new preference is persisted and restored correctly, and that the default is visible for new/existing users.

Solves #742

Greptile Summary

This PR introduces a user preference to hide Cotabby's menu bar status item while keeping the app running, with Settings as the recovery surface when the icon is hidden. The implementation is cleanly layered across model, store, UI, and AppDelegate, and includes a dedicated MenuBarRecoveryPolicy pure-policy type with a thorough test matrix.

  • New isMenuBarIconVisible preference propagates from SuggestionSettingsDataSuggestionSettingsModelSuggestionSettingsStore and drives a MenuBarExtra(isInserted:) binding in CotabbyApp via @AppStorage, so SwiftUI re-evaluates scene insertion exactly once per UserDefaults write without double-persisting the value.
  • Recovery paths cover both cold manual launches (Settings is opened automatically if the icon is hidden and the app wasn't launched at login) and reopen events (applicationShouldHandleReopen), with each decision delegated to the testable MenuBarRecoveryPolicy enum.
  • Sidebar Quit button is added unconditionally to the Settings sidebar (not only when the icon is hidden), ensuring users can always exit the app from Settings; the button registers ⌘Q as a keyboard shortcut.

Confidence Score: 5/5

Safe to merge — the changes are well-scoped, the recovery paths are fully tested, and no existing behaviour is altered for users who keep the icon visible.

All three layers of the feature (model/store, scene binding, and AppKit lifecycle) are internally consistent. The @AppStorage / settings-model split is the most unusual pattern in the PR, but the comment explains the intent and the data flow is correct: a single UserDefaults write drives both the @published model property and the SwiftUI scene re-evaluation without risk of divergence. The MenuBarRecoveryPolicy decision matrix is fully covered by unit tests, including the miniaturised-window and non-Settings-window edge cases.

No files require special attention.

Important Files Changed

Filename Overview
Cotabby/Support/MenuBarRecoveryPolicy.swift New pure-policy enum with two static helpers covering cold-launch and reopen decisions; logic is correct and fully covered by unit tests in MenuBarRecoveryPolicyTests.
Cotabby/App/Core/AppDelegate.swift Adds cold-launch recovery path (opens Settings when icon is hidden and not a login launch) and implements applicationShouldHandleReopen delegating to MenuBarRecoveryPolicy; logic is well-documented and correctly handles the miniaturized-window edge case via isSettingsWindowOpen.
Cotabby/App/Core/CotabbyApp.swift Uses @AppStorage as the getter and the settings model as the setter in a custom Binding to drive MenuBarExtra(isInserted:), ensuring a single UserDefaults write triggers scene re-evaluation without double-persisting the value; pattern is sound.
Cotabby/Models/SuggestionSettingsModel.swift Adds isMenuBarIconVisible @published property and setMenuBarIconVisible setter with guard against no-op writes; also fixes a pre-existing omission of fadeInSuggestions/fadeInDurationSeconds in one apply path.
Cotabby/UI/Settings/SettingsSidebarView.swift Adds an always-visible Quit button footer to the sidebar with a ⌘Q keyboard shortcut; the footer is unconditionally rendered regardless of icon visibility, ensuring quit is always reachable from Settings.
Cotabby/UI/Settings/Panes/AppearancePaneView.swift Adds Show Cotabby in Menu Bar toggle and correctly disables the word-count toggle when the icon is hidden; both bindings route through the settings model.
Cotabby/App/Coordinators/SettingsCoordinator.swift Adds isSettingsWindowOpen computed property (window != nil, not isVisible, to handle miniaturized windows correctly) and wires onQuit closure to the settings container.
Cotabby/Support/SuggestionSettingsStore.swift Adds menuBarIconVisibleDefaultsKey as a static (not private) constant so CotabbyApp's @AppStorage can reference the same key, plus saveMenuBarIconVisible and load-time resolution with default true for existing installs.
CotabbyTests/MenuBarRecoveryPolicyTests.swift New test class with 7 cases covering the full decision matrix for both cold-launch and reopen policy functions; all edge cases (login launch, explicit request, miniaturized window, non-Settings visible window) are represented.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant AppearancePaneView
    participant SuggestionSettingsModel
    participant UserDefaults
    participant CotabbyApp
    participant MenuBarExtra
    participant AppDelegate
    participant MenuBarRecoveryPolicy
    participant SettingsCoordinator

    User->>AppearancePaneView: Toggle Show Cotabby in Menu Bar OFF
    AppearancePaneView->>SuggestionSettingsModel: setMenuBarIconVisible(false)
    SuggestionSettingsModel->>UserDefaults: saveMenuBarIconVisible(false)
    UserDefaults-->>CotabbyApp: "@AppStorage update isMenuBarIconVisible = false"
    CotabbyApp-->>MenuBarExtra: "isInserted binding = false"
    MenuBarExtra-->>User: Status item removed

    User->>AppDelegate: Re-opens app (Finder/Dock)
    AppDelegate->>MenuBarRecoveryPolicy: shouldLetAppKitHandleReopen(...)
    MenuBarRecoveryPolicy-->>AppDelegate: false
    AppDelegate->>SettingsCoordinator: showSettings()
    SettingsCoordinator-->>User: Settings window shown as recovery surface
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant AppearancePaneView
    participant SuggestionSettingsModel
    participant UserDefaults
    participant CotabbyApp
    participant MenuBarExtra
    participant AppDelegate
    participant MenuBarRecoveryPolicy
    participant SettingsCoordinator

    User->>AppearancePaneView: Toggle Show Cotabby in Menu Bar OFF
    AppearancePaneView->>SuggestionSettingsModel: setMenuBarIconVisible(false)
    SuggestionSettingsModel->>UserDefaults: saveMenuBarIconVisible(false)
    UserDefaults-->>CotabbyApp: "@AppStorage update isMenuBarIconVisible = false"
    CotabbyApp-->>MenuBarExtra: "isInserted binding = false"
    MenuBarExtra-->>User: Status item removed

    User->>AppDelegate: Re-opens app (Finder/Dock)
    AppDelegate->>MenuBarRecoveryPolicy: shouldLetAppKitHandleReopen(...)
    MenuBarRecoveryPolicy-->>AppDelegate: false
    AppDelegate->>SettingsCoordinator: showSettings()
    SettingsCoordinator-->>User: Settings window shown as recovery surface
Loading

Comments Outside Diff (1)

  1. Cotabby/UI/Settings/Panes/AppearancePaneView.swift, line 50-57 (link)

    P2 "Show Word Count in Menu Bar" toggle remains active when the icon is hidden

    When isMenuBarIconVisible is false the word-count badge has nowhere to render, yet the toggle stays enabled and its value is preserved. A user who hides the icon, notices the toggle is still "on," and tries to turn it off will have no visible effect — which reads as broken. Disabling the toggle (.disabled(!suggestionSettings.isMenuBarIconVisible)) when the icon is hidden would make the dependency explicit and avoid the confusion.

    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

Reviews (10): Last reviewed commit: "fix: resolve menu bar settings conflicts..." | Re-trigger Greptile

Comment thread Cotabby/App/Core/AppDelegate.swift

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new persisted preference that lets users hide Cotabby’s menu bar status item while keeping the app running, with a reopen “recovery” path that surfaces Settings when the user relaunches Cotabby without the icon visible.

Changes:

  • Introduces isMenuBarIconVisible across settings data/model/store and persists it to UserDefaults.
  • Conditionally inserts/removes the MenuBarExtra scene via a scene-level @AppStorage projection.
  • Implements reopen handling to open Settings when the app is reopened while the menu bar icon is hidden, plus updates/expands test coverage.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Cotabby/App/Core/CotabbyApp.swift Makes the menu bar scene optional via MenuBarExtra(isInserted:) driven by @AppStorage.
Cotabby/App/Core/AppDelegate.swift Adds applicationShouldHandleReopen recovery behavior when the icon is hidden.
Cotabby/Models/SuggestionSettingsData.swift Extends the durable settings payload with isMenuBarIconVisible.
Cotabby/Models/SuggestionSettingsModel.swift Adds published state + setter that persists isMenuBarIconVisible.
Cotabby/Support/SuggestionSettingsStore.swift Adds the new defaults key, load defaulting behavior, and save plumbing.
Cotabby/UI/Settings/Panes/AppearancePaneView.swift Adds the Settings toggle and binding for menu bar icon visibility.
Cotabby/UI/Settings/SettingsIndex.swift Adds a new searchable SettingsItem entry for the toggle.
CotabbyTests/SuggestionSettingsModelTests.swift Extends model round-trip tests to include the new preference.
CotabbyTests/SuggestionSettingsStoreTests.swift Adds default + round-trip persistence tests for the new preference.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Cotabby/App/Core/CotabbyApp.swift
@akramj13 akramj13 requested a review from FuJacob June 19, 2026 06:28
@FuJacob

FuJacob commented Jun 20, 2026

Copy link
Copy Markdown
Owner

@akramj13 fix copilot feedback.
Also questions:

  1. How does user exit out of cotabby? Do we provide a new button in settings for them?

FuJacob and others added 2 commits June 19, 2026 19:05
… only

Addresses Copilot review on FuJacob#744: the MenuBarExtra binding setter no longer writes the visibility preference twice. It now calls setMenuBarIconVisible only; the @AppStorage projection observes the same UserDefaults key, so the getter and scene re-evaluate from that single write and can't drift if model-side validation is added. The hasVisibleWindows reopen guard flagged by Greptile is already consulted in the current head, so no change was needed there.
@akramj13

Copy link
Copy Markdown
Contributor Author

This change also solves #705

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread Cotabby/App/Core/AppDelegate.swift Outdated
Comment on lines +159 to +165
guard !suggestionSettings.isMenuBarIconVisible else {
return true
}

guard !flag else {
return false
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread Cotabby/App/Core/AppDelegate.swift
@FuJacob

FuJacob commented Jun 20, 2026

Copy link
Copy Markdown
Owner

@greptileai

- Open Settings on manual cold launch when menu bar icon is hidden
- Keep login-item launches silent
- Verify Settings window specifically during reopen recovery
- Add 8 recovery-policy test cases
- Validate cold-launch recovery flow end-to-end

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Comment thread Cotabby/UI/Settings/Panes/AppearancePaneView.swift
Comment thread Cotabby/Support/MenuBarRecoveryPolicy.swift Outdated
akramj13 and others added 2 commits June 20, 2026 01:26
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@akramj13

Copy link
Copy Markdown
Contributor Author

@greptileai

@akramj13 akramj13 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

LGTM

@FuJacob FuJacob merged commit 4416749 into FuJacob:main Jun 28, 2026
4 checks passed
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.

3 participants