Skip to content

fix: restore menu bar icon on macOS 26.4 (#805)#849

Merged
steipete merged 5 commits intosteipete:mainfrom
willytop8:wr/805-menu-bar-icon-macos-26
May 6, 2026
Merged

fix: restore menu bar icon on macOS 26.4 (#805)#849
steipete merged 5 commits intosteipete:mainfrom
willytop8:wr/805-menu-bar-icon-macos-26

Conversation

@willytop8
Copy link
Copy Markdown
Contributor

@willytop8 willytop8 commented May 6, 2026

Summary

Fixes the invisible menu bar icon on macOS 26.4 reported in #805. Two changes, one PR since they address the same symptom.

Change 1 — UsageProgressBar.swift

PR #677 removed .drawingGroup() and .regularMaterial, but two more RenderBox-triggering SwiftUI compositing modifiers remained:

  • .compositingGroup() (two branches in body)
  • .blendMode(.destinationOut) applied as a view modifier on an inner Canvas

On macOS 26.4, these trigger Metal/RenderBox shader compilation. When that compilation fails with [com.apple.renderbox:error] precondition failure, the NSStatusItem window becomes invisible (isVisible = 0, windowWidth = 16). The menu is built at startup (before the user opens it), so the failure fires on every launch.

Fix: rewrite the entire view as a single Canvas. The punch-out effect (pace-tip stripe) is now done with context.blendMode = .destinationOut set on the GraphicsContext property inside the canvas closure — this stays in Core Graphics and never touches Metal/RenderBox. Visual output is identical.

Change 2 — CodexBarConfigMigrator.swift

migrateLegacySecrets() and migrateLegacyAccounts() ran unconditionally on every launch from SettingsStore.init() on the main thread, making ~28 SecItemCopyMatching calls that all return errSecItemNotFound after the first launch. macOS 26.4 Performance Diagnostics emits a fault per call.

Fix: guard the heavy migration behind a UserDefaults completion flag (codexbar.legacySecretsMigrationCompleted), set after clearLegacyStores() completes. Using a flag (rather than existing == nil) means a crash between configStore.save() and clearLegacyStores() can still finish cleanup on the next launch. applyLegacyCookieSources() (cheap UserDefaults reads) stays unconditional.

willytop8 and others added 3 commits May 5, 2026 20:55
Two changes addressing the invisible status item icon on macOS 26.4:

1. Replace compositingGroup + blendMode(.destinationOut) in UsageProgressBar
   with a single Canvas that draws track, fill, and pace-tip punch-out entirely
   inside Core Graphics. SwiftUI compositing modifiers (.compositingGroup,
   .blendMode as a view modifier) trigger Metal/RenderBox shader compilation;
   on macOS 26.4 that compilation can fail with a precondition error, making
   the NSStatusItem window invisible. Moving the blend to the GraphicsContext
   API inside the Canvas avoids the Metal path entirely.

2. Gate migrateLegacySecrets and migrateLegacyAccounts behind existing == nil
   in CodexBarConfigMigrator.loadOrMigrate(). These ran unconditionally on
   every launch, performing ~28 SecItemCopyMatching calls on the main thread.
   After the first launch the config is the source of truth and legacy stores
   are cleared, so subsequent calls all hit errSecItemNotFound — but macOS 26.4
   Performance Diagnostics still emits a fault per call, adding startup jank.
   applyLegacyCookieSources (cheap UserDefaults reads) remains unconditional.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the `existing == nil` gate added in the previous commit with a
UserDefaults boolean flag (`codexbar.legacySecretsMigrationCompleted`).

The `existing == nil` gate had a blind spot: if the app crashed after
configStore.save() but before clearLegacyStores(), the config would exist
on the next launch (`existing != nil`) so migration would be skipped and
orphaned Keychain items would never be cleaned up.

The flag approach is safe for all cases:
- Fresh install: flag absent → migration runs → items found → clear → set flag
- Normal re-launch: flag present → skip (0 Keychain calls)
- Crash-interrupted first migration: flag absent → migration re-runs → items
  still present → clear → set flag (data already in config via setIfEmpty)
- No legacy data ever existed: flag absent → migration runs → finds nothing →
  set flag → never scans again

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86e286d8b3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +70 to +71
self.clearLegacyStores(stores: stores, sawAccounts: state.sawLegacyAccounts, log: log)
userDefaults.set(true, forKey: Self.legacyMigrationCompletedKey)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Only mark migration complete after legacy cleanup succeeds

loadOrMigrate now sets codexbar.legacySecretsMigrationCompleted immediately after calling clearLegacyStores, but clearLegacyStores catches and logs failures instead of reporting success/failure. In environments where keychain writes fail transiently (for example, a storeToken(nil) call throws), cleanup can be partial or skipped, yet the completion flag is still persisted; subsequent launches then skip migration/cleanup entirely. This creates a one-shot failure mode where legacy secrets/accounts can remain indefinitely because retries are permanently disabled.

Useful? React with 👍 / 👎.

willytop8 and others added 2 commits May 5, 2026 21:41
clearLegacyStores catches keychain write failures internally and returns
normally, so the completion flag was being set even on partial cleanup.
A transient keychain error would permanently suppress retry on next launch.

- Make clearLegacyStores return Bool (true = all deletes succeeded)
- Gate legacyMigrationCompletedKey on that return value
- Remove comment block above the key constant (SwiftFormat docComments lint)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1160719b05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +70 to +71
} else if !migrationCompleted {
userDefaults.set(true, forKey: Self.legacyMigrationCompletedKey)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid finalizing migration after transient keychain read errors

Setting legacySecretsMigrationCompleted in this else branch permanently disables future migration attempts even when the current run failed to read legacy data. migrateLegacySecrets/migrateLegacyAccounts use try?, so transient keychain/file errors (for example, locked keychain returning errSecInteractionNotAllowed) are treated the same as “no legacy data,” leaving sawLegacy* false and triggering this path. In that case, users can lose one-time migration of existing credentials/accounts because subsequent launches skip migration entirely.

Useful? React with 👍 / 👎.

@steipete steipete merged commit ac24106 into steipete:main May 6, 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.

2 participants