Skip to content

Add draggable New Items badge to Menu Bar Layout settings#349

Open
kylewhirl wants to merge 6 commits intostonerl:developmentfrom
kylewhirl:development
Open

Add draggable New Items badge to Menu Bar Layout settings#349
kylewhirl wants to merge 6 commits intostonerl:developmentfrom
kylewhirl:development

Conversation

@kylewhirl
Copy link
Copy Markdown

@kylewhirl kylewhirl commented Mar 31, 2026

What does this PR do?

Adds a draggable New ​Items badge to the Menu Bar Layout settings so users can choose where newly detected menu bar items should land. The selected destination is persisted and used by the menu bar item manager when routing new items into the Visible, Hidden, or Always​-​Hidden sections.

PR Type

• [] Bugfix
• [ ] CI/CD related changes
• [ ] Code style update (formatting, renaming)
• [ ] Documentation
• [x] Feature
• [ ] i18n/l10n (Translation/Localization)
• [x] Refactor
• [ ] Performance improvement
• [ ] Test addition or update
• [ ] Other (please describe):

Does this PR introduce a breaking change?

• [ ] Yes
• [x] No

If yes, please describe the impact and migration path:

What is the current behavior?

New menu bar items are routed by hardcoded behavior instead of a user-controlled layout setting. They effectively default into always​-hidden when that section is enabled, or hidden when it is not, and there is no layout UI affordance for changing that destination.

Issue Number: N/A

What is the new behavior?

The Menu Bar Layout tab now shows a draggable New ​Items badge inside the layout UI, similar to Bartender behavior. Users can move that badge between Visible, Hidden, and Always​-​Hidden when the always-hidden section is enabled. The chosen location is persisted and used for future newly detected menu bar items.

PR Checklist

• [ x] I’ve built and run the app locally
• [ x] I’ve checked for console errors or crashes
• [x ] I've run relevant tests and they pass
• [ ] I've added or updated tests (if applicable)
• [ ] I've updated documentation as needed
• [ n/a] I've verified localized strings work correctly (if i18n/l10n changes)

Notes for reviewers

Please focus on:
• Drag/drop behavior for the New ​Items badge across sections
• Persistence and fallback behavior when Always​-​Hidden is disabled
• New-item routing in Menu​Bar​Item​Manager to confirm it matches the badge location

Summary by CodeRabbit

  • New Features

    • Draggable "New Items" badge in the menu bar layout editor with improved drag/reorder behavior and visual feedback.
  • Settings & Preferences

    • Persistent "new items" placement preference; settings pane now shows guidance for moving the badge.
    • Advanced settings reset also clears the new-items placement.
  • Localization

    • Added localized strings for the badge label and explanatory guidance.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76b29b2e-dbd5-4bf3-92ab-e24432f238a8

📥 Commits

Reviewing files that changed from the base of the PR and between 97c2d34 and 2db0e45.

📒 Files selected for processing (1)
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift

📝 Walkthrough

Walkthrough

Adds a repositionable "New Items" badge, a shared draggable view base, persisted badge placement preferences, and updates layout bar components and defaults to support badge-driven placement of newly detected menu bar items.

Changes

Cohort / File(s) Summary
Build Configuration
Thaw.xcodeproj/project.pbxproj
Removed DEVELOPMENT_TEAM = 456BPWQ6U5 entries from Debug/Release configurations for Thaw and MenuBarItemService.
Draggable View Base
Thaw/MenuBar/LayoutBar/LayoutBarArrangedView.swift
Added LayoutBarArrangedView base class with Kind enum, drag-state fields (oldContainerInfo, hasContainer, isDraggingPlaceholder, isEnabled), draggingImage() stub, NSDraggingSource lifecycle handling, and accessibility conformance.
New Items Badge
Thaw/MenuBar/LayoutBar/LayoutBarNewItemsBadgeView.swift
New LayoutBarNewItemsBadgeView subclass implementing .newItemsBadge, custom drawing, cached dragging image, and drag initiation on mouse-drag.
Item View Refactor
Thaw/MenuBar/LayoutBar/LayoutBarItemView.swift
LayoutBarItemView now subclasses LayoutBarArrangedView; removed duplicated drag-state and NSDraggingSource logic; retained draggingImage() usage.
Container & Layout Updates
Thaw/MenuBar/LayoutBar/LayoutBarContainer.swift, Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift, Thaw/MenuBar/LayoutBar/LayoutBarScrollView.swift
Switched arranged view element type to [LayoutBarArrangedView]; container inserts badge at computed index; Combine reactive chain includes newItemsPlacement and enableAlwaysHiddenSection; drag & placement resolution updated (nearest-item search, badge-specific branch).
Placement Persistence & Logic
Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
Added persisted newItemsPlacement, load/save helpers, effectiveNewItemsSection, APIs newItemsBadgeIndex(in:itemIdentifiers:) and updateNewItemsPlacement(section:arrangedViews:), and helpers to compute anchor/index and move destinations for new items.
Defaults & Persistence Keys
Thaw/Utilities/Defaults.swift
Added Defaults keys and values for newItemsSection ("hidden") and newItemsPlacementData (nil) with corresponding UserDefaults keys.
Settings UI & Reset
Thaw/Settings/SettingsPanes/MenuBarLayoutSettingsPane.swift, Thaw/Settings/Models/SettingsResetter.swift
Added explanatory text about moving the "New Items" badge in settings UI; reset now clears new-items placement by setting it to hidden.
Localization
Thaw/Resources/Localizable.xcstrings
Added localized strings: "New Items" and "Move the New Items badge to choose where newly detected items will appear.".

Sequence Diagram

sequenceDiagram
    participant User
    participant Badge as LayoutBarNewItemsBadgeView
    participant Container as LayoutBarContainer
    participant Manager as MenuBarItemManager
    participant Defaults as UserDefaults

    User->>Badge: mouseDragged -> beginDraggingSession
    Badge->>Container: NSDraggingSource willBegin (disable updates)
    User->>Container: drop badge at destination
    Container->>Manager: updateNewItemsPlacement(section, arrangedViews)
    Manager->>Manager: compute anchor/index & persist placement
    Manager->>Defaults: persistNewItemsPlacementData
    Container->>Container: re-enable canSetArrangedViews, refresh arrangedViews
    Container->>Badge: badge repositioned (layout updated)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nudged the badge with a careful hop,

moved the marker where new things will pop.
Saved its spot in a comfy place,
layouts shuffle with gentle grace.
A rabbit's hop keeps order top.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a draggable New Items badge to Menu Bar Layout settings.
Description check ✅ Passed The description covers the feature objective, current vs. new behavior, PR type, breaking changes, and a detailed checklist with review focus areas, but uses an informal template rather than following the repository's required template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
Thaw/Resources/Localizable.xcstrings (1)

15057-15059: Tighten translator comment for accuracy.

At Line 15058, the comment says this badge “displays the number of new items.” If the UI shows a static “New Items” label (not a numeric count), consider revising the comment to avoid translator confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Thaw/Resources/Localizable.xcstrings` around lines 15057 - 15059, The
translator comment for the "New Items" localization key is misleading about
showing a numeric count; update the comment for the "New Items" string (key "New
Items") to accurately describe what the UI shows (e.g., "Static label shown on a
badge" or "Label for a badge; not a numeric count") so translators know whether
to translate as a static label vs. a count; edit the comment text for the "New
Items" entry accordingly in Localizable.xcstrings.
Thaw/MenuBar/LayoutBar/LayoutBarNewItemsBadgeView.swift (2)

14-19: Consider dynamic sizing for localized text.

The badge uses a fixed size (84 x 24) which may not accommodate longer translations of "New Items" in other languages.

💡 Alternative: Calculate size from text

You could compute the badge width dynamically based on the localized string:

private lazy var titleSize: CGSize = {
    let attributes: [NSAttributedString.Key: Any] = [
        .font: NSFont.systemFont(ofSize: 11, weight: .semibold)
    ]
    let title = String(localized: "New Items")
    return (title as NSString).size(withAttributes: attributes)
}()

private var badgeSize: CGSize {
    CGSize(
        width: titleSize.width + (Metrics.horizontalInset * 2) + 4,
        height: max(titleSize.height + 8, 24)
    )
}

This would ensure the badge fits the text regardless of language. However, if all supported locales have been verified to fit within 84pt, the current approach is acceptable.

Also applies to: 56-63

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Thaw/MenuBar/LayoutBar/LayoutBarNewItemsBadgeView.swift` around lines 14 -
19, The badge uses a fixed Metrics.size (CGSize(width: 84, height: 24)) in
LayoutBarNewItemsBadgeView which can break when the localized "New Items" string
is longer; change sizing to compute the title size (e.g., add a lazy titleSize
computed with the same font used in the view) and replace Metrics.size usage
with a computed badgeSize that adds horizontalInset and a small padding and
ensures minimum height (matching Metrics.cornerRadius or 24), then use badgeSize
for layout and hit testing so the badge always fits localized text.

35-37: Potential issue: draggingImage() captures placeholder state.

bitmapImage() uses cacheDisplay(in:bounds, to:rep) which renders the current view state. If isDraggingPlaceholder is true when draggingImage() is called, the badge's draw(_:) returns early and produces a blank image.

Looking at the call site in mouseDragged(with:) (line 73), isDraggingPlaceholder should still be false at that point since the drag session hasn't begun yet. However, if the image is ever requested after the drag starts (e.g., for a drag update), it would be blank.

This appears safe given the current flow, but consider caching the image before the drag begins or ensuring bitmapImage() temporarily overrides isDraggingPlaceholder.

Also applies to: 78-86

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Thaw/MenuBar/LayoutBar/LayoutBarNewItemsBadgeView.swift` around lines 35 -
37, The dragging image can be blank if bitmapImage() is called while
isDraggingPlaceholder is true; update draggingImage() to ensure it returns a
non-empty snapshot by either (a) caching the bitmap before the drag begins in
mouseDragged(with:) and returning that cached image from draggingImage(), or (b)
temporarily forcing isDraggingPlaceholder = false (or otherwise bypassing the
early-return in draw(_:)) just for the duration of bitmapImage() so the rendered
image is complete; reference the methods draggingImage(), bitmapImage(),
mouseDragged(with:), and the isDraggingPlaceholder flag when making the change.
Thaw/MenuBar/LayoutBar/LayoutBarArrangedView.swift (1)

74-88: Pre-existing pattern: canSetArrangedViews not re-enabled in drag end.

The draggingSession(_:endedAt:operation:) method does not re-enable canSetArrangedViews on the container. This is consistent with the original LayoutBarItemView implementation (per the relevant code snippets), where re-enabling happens in LayoutBarPaddingView.performDragOperation.

However, if a drag session ends without calling performDragOperation (e.g., the drag is cancelled or drops outside all padding views), canSetArrangedViews could remain false, preventing subsequent cache-driven view updates.

The reinsertion logic at lines 81-87 handles putting the view back, but consider also re-enabling canSetArrangedViews in the !hasContainer branch to ensure the container can update in future cycles.

♻️ Proposed fix to re-enable canSetArrangedViews on failed drop
     func draggingSession(_: NSDraggingSession, endedAt _: NSPoint, operation _: NSDragOperation) {
         defer {
             oldContainerInfo = nil
         }

         isDraggingPlaceholder = false

         if !hasContainer {
             guard let (container, index) = oldContainerInfo else {
                 return
             }
             container.shouldAnimateNextLayoutPass = false
             container.arrangedViews.insert(self, at: index)
+            container.canSetArrangedViews = true
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Thaw/MenuBar/LayoutBar/LayoutBarArrangedView.swift` around lines 74 - 88, The
drag-end handler draggingSession(_:endedAt:operation:) can leave the original
container with canSetArrangedViews == false when a drop is cancelled or falls
outside padding views; update the !hasContainer branch that uses
oldContainerInfo to re-enable the container by setting
container.canSetArrangedViews = true (alongside
container.shouldAnimateNextLayoutPass = false and
container.arrangedViews.insert(self, at: index)) so the container can accept
future cache-driven updates (this mirrors the behavior performed in
LayoutBarPaddingView.performDragOperation).
Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift (1)

144-155: Consider the edge case when all helpers return nil in the visible section.

When arrangedViews.count > 1 but all nearestItem lookups and fallbackDestinationForDraggedItem return nil, willMove remains false and canSetArrangedViews is re-enabled at line 161. This is safe, but it means the item visually moves in the UI but doesn't actually persist its new position.

This can happen in the visible section since fallbackDestinationForDraggedItem returns nil for .visible. If all other arranged views are non-item views (e.g., only badges), the item would appear to drop but not move.

Given the current feature scope (only one badge exists and items always exist in visible), this is unlikely to be hit in practice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift` around lines 144 - 155,
The current drag-drop branch can leave willMove false when arrangedViews.count >
1 but both nearestItem(toRightOf:), nearestItem(toLeftOf:) and
fallbackDestinationForDraggedItem() return nil, causing the UI to show a moved
item that isn't persisted; update the logic in the block handling case
.item(item) (the function using arrangedViews, nearestItem(...),
fallbackDestinationForDraggedItem(), willMove and move(item:to:)) to handle this
edge: detect when all helper lookups return nil and in that case either compute
a sensible default destination (e.g., append/prepend within visible section) or
explicitly cancel the visual move and reset state before re-enabling
canSetArrangedViews so the UI and model remain consistent. Ensure willMove and
any state toggles (canSetArrangedViews) are updated accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Thaw` 2026-03-30 20-54-59/DistributionSummary.plist:
- Around line 1-224: Delete the committed Xcode export artifact directory
containing DistributionSummary.plist (which includes keys like Thaw, certificate
-> SHA1, team -> name "Kyle Worrall", versionNumber, embeddedBinaries, etc.),
remove the entire directory from the repo history or at least from the current
commit, and add the directory pattern to .gitignore (e.g., the Xcode
export/artifact folder) so these signing metadata files are not committed again.

In `@Thaw` 2026-03-30 20-54-59/ExportOptions.plist:
- Around line 1-14: The committed Xcode export artifact directory (contains
ExportOptions.plist with keys like teamID, destination, method) should be
removed and ignored: delete the Thaw 2026-03-30 20-54-59/ directory from the
repo (and optionally purge the file from history if sensitive), then add a
gitignore entry matching the pattern "Thaw 20*/" to .gitignore so future
timestamped Thaw export directories are not committed; commit the .gitignore
change and the removal together.

In `@Thaw.xcodeproj/project.pbxproj`:
- Line 439: The project file was accidentally updated with a personal
DEVELOPMENT_TEAM value (DEVELOPMENT_TEAM = LMMY392DLM) which will break signing
for other contributors; revert the committed changes in
Thaw.xcodeproj/project.pbxproj (remove or restore the original DEVELOPMENT_TEAM
entries) OR move the team override into a local .xcconfig (e.g., add
DEVELOPMENT_TEAM = LMMY392DLM to a developer-only xcconfig) and add that
xcconfig to .gitignore; apply this fix for all occurrences referenced (the lines
with DEVELOPMENT_TEAM at the indicated locations).

---

Nitpick comments:
In `@Thaw/MenuBar/LayoutBar/LayoutBarArrangedView.swift`:
- Around line 74-88: The drag-end handler draggingSession(_:endedAt:operation:)
can leave the original container with canSetArrangedViews == false when a drop
is cancelled or falls outside padding views; update the !hasContainer branch
that uses oldContainerInfo to re-enable the container by setting
container.canSetArrangedViews = true (alongside
container.shouldAnimateNextLayoutPass = false and
container.arrangedViews.insert(self, at: index)) so the container can accept
future cache-driven updates (this mirrors the behavior performed in
LayoutBarPaddingView.performDragOperation).

In `@Thaw/MenuBar/LayoutBar/LayoutBarNewItemsBadgeView.swift`:
- Around line 14-19: The badge uses a fixed Metrics.size (CGSize(width: 84,
height: 24)) in LayoutBarNewItemsBadgeView which can break when the localized
"New Items" string is longer; change sizing to compute the title size (e.g., add
a lazy titleSize computed with the same font used in the view) and replace
Metrics.size usage with a computed badgeSize that adds horizontalInset and a
small padding and ensures minimum height (matching Metrics.cornerRadius or 24),
then use badgeSize for layout and hit testing so the badge always fits localized
text.
- Around line 35-37: The dragging image can be blank if bitmapImage() is called
while isDraggingPlaceholder is true; update draggingImage() to ensure it returns
a non-empty snapshot by either (a) caching the bitmap before the drag begins in
mouseDragged(with:) and returning that cached image from draggingImage(), or (b)
temporarily forcing isDraggingPlaceholder = false (or otherwise bypassing the
early-return in draw(_:)) just for the duration of bitmapImage() so the rendered
image is complete; reference the methods draggingImage(), bitmapImage(),
mouseDragged(with:), and the isDraggingPlaceholder flag when making the change.

In `@Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift`:
- Around line 144-155: The current drag-drop branch can leave willMove false
when arrangedViews.count > 1 but both nearestItem(toRightOf:),
nearestItem(toLeftOf:) and fallbackDestinationForDraggedItem() return nil,
causing the UI to show a moved item that isn't persisted; update the logic in
the block handling case .item(item) (the function using arrangedViews,
nearestItem(...), fallbackDestinationForDraggedItem(), willMove and
move(item:to:)) to handle this edge: detect when all helper lookups return nil
and in that case either compute a sensible default destination (e.g.,
append/prepend within visible section) or explicitly cancel the visual move and
reset state before re-enabling canSetArrangedViews so the UI and model remain
consistent. Ensure willMove and any state toggles (canSetArrangedViews) are
updated accordingly.

In `@Thaw/Resources/Localizable.xcstrings`:
- Around line 15057-15059: The translator comment for the "New Items"
localization key is misleading about showing a numeric count; update the comment
for the "New Items" string (key "New Items") to accurately describe what the UI
shows (e.g., "Static label shown on a badge" or "Label for a badge; not a
numeric count") so translators know whether to translate as a static label vs. a
count; edit the comment text for the "New Items" entry accordingly in
Localizable.xcstrings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4aabae44-4edf-40d2-8388-19d4185883d2

📥 Commits

Reviewing files that changed from the base of the PR and between 926d548 and 38fc165.

⛔ Files ignored due to path filters (1)
  • Thaw 2026-03-30 20-54-59/Packaging.log is excluded by !**/*.log
📒 Files selected for processing (14)
  • Thaw 2026-03-30 20-54-59/DistributionSummary.plist
  • Thaw 2026-03-30 20-54-59/ExportOptions.plist
  • Thaw.xcodeproj/project.pbxproj
  • Thaw/MenuBar/LayoutBar/LayoutBarArrangedView.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarContainer.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarItemView.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarNewItemsBadgeView.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarScrollView.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
  • Thaw/Resources/Localizable.xcstrings
  • Thaw/Settings/Models/SettingsResetter.swift
  • Thaw/Settings/SettingsPanes/MenuBarLayoutSettingsPane.swift
  • Thaw/Utilities/Defaults.swift

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift`:
- Around line 156-166: The drop path for .item in the draggingSource.kind branch
can miss a valid fallback when a section only has the New Items badge because
nearestItem(toRightOf:) and nearestItem(toLeftOf:) return nil and
fallbackDestinationForDraggedItem() reads itemCache.managedItems (which omits
badge-only controls); update this branch in the block handling .item (and the
analogous block at lines 264-278) to reuse the same live control-item lookup
used in the single-item branch (the arrangedViews-based lookup) instead of
consulting itemCache.managedItems—i.e., if both nearestItem(...) calls return
nil, check arrangedViews.count and use the live arrangedViews/control-items
logic from the single-item path to resolve a destination and then call
move(item:to:).

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 481-483: The current anchor lookup uses itemCache (see
sectionName(for:), newItemsPlacement.sectionKey, anchorIdentifier and the
anchorItem search) which can return logically cached items that are temporarily
shown; change the lookup to resolve anchors against the live/visible items
produced during the current cache pass (the in-memory list used to compute
layout) instead of itemCache, or skip using an anchor when that anchor item has
an active temporary-show context; ensure the code that calls move() receives an
anchor derived from the live items (or nil) so move() uses physical bounds for
placement rather than cached logical positions.
- Around line 394-396: The badge anchor persistence is using the changing
uniqueIdentifier (checked in the if with sectionName(for:
newItemsPlacement.sectionKey), newItemsPlacement.anchorIdentifier, and
itemIdentifiers.firstIndex(of: anchorIdentifier)), so anchors placed next to
dynamic-title items are lost when the title (and identifier) changes; update the
lookup to use the same stable-key fallback used by the section-order restore
logic (i.e., detect dynamic-title items and fall back from uniqueIdentifier to
the stable key used for section ordering) so that when resolving
anchorIdentifier to an index you first try the exact match on
newItemsPlacement.anchorIdentifier and, if not found and the item is a
dynamic-title entry, try the stable fallback key before defaulting to
.sectionDefault; apply the same change to the analogous block around lines
441–451 that handles anchor persistence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 977383bc-0c71-4006-822a-4018684739a6

📥 Commits

Reviewing files that changed from the base of the PR and between 38fc165 and 5b821a7.

📒 Files selected for processing (7)
  • Thaw.xcodeproj/project.pbxproj
  • Thaw/MenuBar/LayoutBar/LayoutBarArrangedView.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarContainer.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
  • Thaw/Settings/Models/SettingsResetter.swift
  • Thaw/Utilities/Defaults.swift
💤 Files with no reviewable changes (1)
  • Thaw.xcodeproj/project.pbxproj
🚧 Files skipped from review as they are similar to previous changes (3)
  • Thaw/Settings/Models/SettingsResetter.swift
  • Thaw/Utilities/Defaults.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarArrangedView.swift

@kylewhirl kylewhirl changed the title Added option for new items Add draggable New Items badge to Menu Bar Layout settings Mar 31, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift (1)

536-577: ⚠️ Potential issue | 🟠 Major

Dynamic-title anchors still resolve to nil.

Line 539 stores a namespace-only anchor for dynamic items, but Lines 553-555 and 571-573 immediately return when that same stable key comes back in. As a result, resolvedNewItemsAnchorItem(...) never finds the live anchor again, so new-item routing next to dynamic-title entries still falls back to .sectionDefault.

💡 Minimal fix
 private func resolvedNewItemsAnchorIndex(
     for anchorIdentifier: String,
     in itemIdentifiers: [String]
 ) -> Int? {
     if let exactMatch = itemIdentifiers.firstIndex(of: anchorIdentifier) {
         return exactMatch
     }

     let stableIdentifier = stableNewItemsAnchorIdentifier(from: anchorIdentifier)
-    guard stableIdentifier != anchorIdentifier else {
-        return nil
-    }
-
     return itemIdentifiers.firstIndex { identifier in
         stableNewItemsAnchorIdentifier(from: identifier) == stableIdentifier
     }
 }

 private func resolvedNewItemsAnchorItem(
     for anchorIdentifier: String,
     in items: [MenuBarItem]
 ) -> MenuBarItem? {
     if let exactMatch = items.first(where: { $0.uniqueIdentifier == anchorIdentifier }) {
         return exactMatch
     }

     let stableIdentifier = stableNewItemsAnchorIdentifier(from: anchorIdentifier)
-    guard stableIdentifier != anchorIdentifier else {
-        return nil
-    }
-
     return items.first { item in
         persistedNewItemsAnchorIdentifier(for: item) == stableIdentifier
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 536 - 577,
The functions resolvedNewItemsAnchorIndex(...) and
resolvedNewItemsAnchorItem(...) bail out early when
stableNewItemsAnchorIdentifier(from: anchorIdentifier) == anchorIdentifier,
preventing namespace-only dynamic anchors from matching their persisted
counterparts; remove the guard/early-return so the subsequent search
(itemIdentifiers.firstIndex { stableNewItemsAnchorIdentifier(from: identifier)
== stableIdentifier } and items.first { persistedNewItemsAnchorIdentifier(for:
item) == stableIdentifier }) still runs even when stableIdentifier equals the
original anchorIdentifier, leaving the rest of the matching logic (using
stableNewItemsAnchorIdentifier, persistedNewItemsAnchorIdentifier, and
DynamicItemOverrides.isDynamic) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 183-185: The persisted newItemsPlacement preference (backed by
NewItemsPlacement / newItemsPlacement) is not cleared by
resetLayoutToFreshState; update resetLayoutToFreshState to remove or reset the
Defaults entry used to persist newItemsPlacement (and any other new Defaults
keys introduced in the same change) so a layout reset truly returns to fresh
state. Locate the persistence key(s) in Defaults (the key used by
NewItemsPlacement/newItemsPlacement) and call Defaults.removeObject(forKey:
<thatKey>) or set the stored value back to NewItemsPlacement.defaultValue inside
resetLayoutToFreshState (also ensure you do the same for the additional keys
noted around lines 249–293).

---

Duplicate comments:
In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 536-577: The functions resolvedNewItemsAnchorIndex(...) and
resolvedNewItemsAnchorItem(...) bail out early when
stableNewItemsAnchorIdentifier(from: anchorIdentifier) == anchorIdentifier,
preventing namespace-only dynamic anchors from matching their persisted
counterparts; remove the guard/early-return so the subsequent search
(itemIdentifiers.firstIndex { stableNewItemsAnchorIdentifier(from: identifier)
== stableIdentifier } and items.first { persistedNewItemsAnchorIdentifier(for:
item) == stableIdentifier }) still runs even when stableIdentifier equals the
original anchorIdentifier, leaving the rest of the matching logic (using
stableNewItemsAnchorIdentifier, persistedNewItemsAnchorIdentifier, and
DynamicItemOverrides.isDynamic) intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3fec5e70-5782-4e38-9c6a-268907933964

📥 Commits

Reviewing files that changed from the base of the PR and between 5b821a7 and 97c2d34.

📒 Files selected for processing (2)
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift

Comment on lines +183 to 185
/// Placement preference for newly detected menu bar items.
@Published private(set) var newItemsPlacement = NewItemsPlacement.defaultValue

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.

⚠️ Potential issue | 🟡 Minor

Reset Layout won't clear this new persisted preference.

This adds another layout preference under Defaults, but the later resetLayoutToFreshState() cleanup still only clears the older layout keys. After a “fresh-install state” reset, newly detected items will keep routing to the old badge location until the user moves the badge again.

Also applies to: 249-293

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 183 - 185,
The persisted newItemsPlacement preference (backed by NewItemsPlacement /
newItemsPlacement) is not cleared by resetLayoutToFreshState; update
resetLayoutToFreshState to remove or reset the Defaults entry used to persist
newItemsPlacement (and any other new Defaults keys introduced in the same
change) so a layout reset truly returns to fresh state. Locate the persistence
key(s) in Defaults (the key used by NewItemsPlacement/newItemsPlacement) and
call Defaults.removeObject(forKey: <thatKey>) or set the stored value back to
NewItemsPlacement.defaultValue inside resetLayoutToFreshState (also ensure you
do the same for the additional keys noted around lines 249–293).

@diazdesandi
Copy link
Copy Markdown
Collaborator

The linter check is failing which is blocking the build from running. Could you fix the linter errors before we take a look at this?

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 4, 2026

@kylewhirl
Copy link
Copy Markdown
Author

The linter check is failing which is blocking the build from running. Could you fix the linter errors before we take a look at this?

Should be fixed now, there’s still a minor visual accessibility issue between light and dark mode I need to fix but general function works

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.

2 participants