Skip to content

test: migrate suite to Swift Testing and raise coverage - #861

Merged
diazdesandi merged 29 commits into
developmentfrom
test/swift-testing-migration-and-coverage
Aug 1, 2026
Merged

test: migrate suite to Swift Testing and raise coverage#861
diazdesandi merged 29 commits into
developmentfrom
test/swift-testing-migration-and-coverage

Conversation

@diazdesandi

@diazdesandi diazdesandi commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Migrate the remaining XCTest suite to Swift Testing, raise unit coverage, and land the small product fixes/refactors that the new test seams unlocked.

Scope: Large by design — the XCTest → Swift Testing migration touches the whole ThawTests/ tree (reorg + ~79 file migration) and cannot be usefully split from the coverage sweep or the Defaults / MenuBar / profiles seams those tests require. Product fixes in this PR are minimal and gated on those seams.

We are on the process of migrating from XCTest to Swift Test. If you are adding new tests, please use Swift Test.
External contributors: before opening a PR for a bug fix or new feature, please make sure there's a corresponding issue in the issue tracker. PRs that fix or change things that haven't been reported/agreed on may be closed without review.

Closes: N/A

PR Type

Describe what this change does (not the linked issue’s request kind). Bug reports use bug on issues; bug fixes use fix on PRs.

If you tick Feature or Refactor and touch more than ~20 files, please mention why this can’t be split.

  • Bug fix
  • CI/CD
  • Documentation
  • Feature
  • Enhancement
  • Performance improvement
  • Refactor
  • Test addition or update
  • Other (please describe)

Why not split: XCTest migration + folder reorg is one coherent suite change (~225 files). Coverage expansions depend on the Defaults / MenuBarItem / MenuBarSection seams introduced here; splitting would leave half-migrated tests or untested seams.

Area

Product surfaces (optional when the change is not about the app UI). Path-based labeling also applies.

  • Use PR Type for what changed (CI/CD, Documentation, Other / chore, etc.).

  • Use ops for where when it is repo operations: CI, release, GitHub hygiene, scripts, lint/sonar config — not a product surface.

  • menubar

  • icebar

  • layout

  • appearance

  • settings

  • onboarding

  • permissions

  • profiles

  • hotkeys

  • updates

  • ops

Does this PR introduce a breaking change?

  • Yes - if yes, please describe the impact and migration path
  • No

What is the new behavior?

  • Swift Testing migration: remaining XCTest files under ThawTests/ moved to Swift Testing (import Testing); 0 XCTest imports left. Suites reorganized into folders mirroring the source tree.
  • Coverage: new/expanded suites for Defaults seams, menu bar values/snapshots, URI handler tail, Extensions, WindowInfo, Hotkey, profiles, and related utilities. Local reporter scripts/coverage-report.py uses Sonar’s denominator.
  • Testability seams: injectable Defaults store; MenuBarSection / MenuBarItem split along testable boundaries; SettingsResetter tests no longer wipe the real defaults domain.
  • Product fixes unlocked by seams: named-display URI toggle/set validation consistency; two silent profile data-loss bugs; profiles Combine → AsyncAlgorithms; Algorithms.uniqued() for order-preserving dedups.
  • Docs / ops: OpenSSF Silver coverage claim in docs/ASSURANCE_CASE.md; .gitignore for .swiftpm-overrides; sonar property tweaks; swiftformat hygiene (including not stripping stored raw values).

PR Checklist

  • I've built and run the app locally and verified that it works as expected.
  • I've run swiftformat . to keep the code style consistent.
  • I've run the smallest relevant test commands (list 1–2 below), e.g. xcodebuild test … or swift test --package-path MenuBarModel.
  • I've added tests for new behavior (if applicable).
  • I've documented new public APIs / non-obvious helpers.
  • I've updated documentation as needed.
  • This PR targets the development branch.

Test commands run:

  • (CI on this PR)

Known limitations / follow-ups

  • Branch is a few commits behind current development — may need a rebase before merge.
  • Coverage sweep targets reachable unit seams; UI / AX / WindowServer paths remain integration-heavy and are not fully covered here.
  • OpenSSF Silver coverage claim assumes CI/Sonar numbers stay at the documented thresholds.

Other information

  • Head: test/swift-testing-migration-and-coverage (~21 commits ahead of development)
  • Diff scale: ~225 files, +20k / −7k lines (mostly tests)

Summary by CodeRabbit

  • New Features

    • Improved menu bar layout around notches, overflow, spacing, and presentation modes.
    • Added more reliable menu bar item identification, ordering, and window matching.
    • Expanded profile and settings persistence for safer configuration handling.
    • Added broader coverage for settings, profiles, menu bar behavior, hotkeys, and onboarding.
  • Bug Fixes

    • Cancelled operations now exit cleanly and reset temporary state.
    • Invalid display-specific settings requests are rejected safely.
    • Profile and layout updates handle cancellation more reliably.
  • Documentation

    • Updated assurance documentation with current test coverage and quality metrics.

@github-actions github-actions Bot added appearance Tint, shapes, menu bar appearance editor hotkeys Hotkey recording and bindings layout Saved layouts, LayoutBar, reorder, spacing menubar Hide/show, sections, control items, backends, capacity onboarding First-run / tour ops CI, release, GitHub hygiene, scripts — repo operations, not a product surface profiles Profiles and layout snapshots updates Sparkle / release channels / appcast labels Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Labels: docs, enhancement, test, refactor, fix, menubar, layout, appearance, settings, onboarding, profiles, hotkeys, updates, ops

Warning

This PR looks large (243 files, 34862 lines changed). Consider splitting if possible (see CONTRIBUTING: aim ≤20 files / ≤500 LOC).

cc @diazdesandi

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 52f64055-2400-434d-9728-a5e999b8941a

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea2d88 and f3d078b.

📒 Files selected for processing (243)
  • .github/workflows/ci.yml
  • .gitignore
  • .swiftformat
  • MenuBarItemService/Listener.swift
  • MenuBarItemService/SourcePIDCache.swift
  • MenuBarItemService/main.swift
  • Shared/Bridging/Shims.swift
  • Shared/Utilities/DiagnosticLogger.swift
  • Shared/Utilities/MarkerPairResolver.swift
  • Thaw/Events/EventTap.swift
  • Thaw/Events/HIDEventManager.swift
  • Thaw/Events/RunLoopLocalEventMonitor.swift
  • Thaw/Hotkeys/Hotkey.swift
  • Thaw/Hotkeys/HotkeyRegistry.swift
  • Thaw/Main/AppState.swift
  • Thaw/Main/Updates.swift
  • Thaw/MenuBar/Appearance/Configurations/MenuBarAppearanceConfigurationV2.swift
  • Thaw/MenuBar/Appearance/Configurations/MenuBarShapes.swift
  • Thaw/MenuBar/Appearance/MissionControlDetector.swift
  • Thaw/MenuBar/ControlItem/ControlItem.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift
  • Thaw/MenuBar/MenuBarItems/AXIdentityCatalog.swift
  • Thaw/MenuBar/MenuBarItems/AXItemActivator.swift
  • Thaw/MenuBar/MenuBarItems/ClickReactionVerifier.swift
  • Thaw/MenuBar/MenuBarItems/LayoutSolver.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemFailureLedger.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemImageCache.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemServiceConnection.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemTag.swift
  • Thaw/MenuBar/MenuBarManager.swift
  • Thaw/MenuBar/MenuBarSection.swift
  • Thaw/MenuBar/MenuBarSectionLayout.swift
  • Thaw/MenuBar/Search/MenuBarSearchPanel.swift
  • Thaw/MenuBar/Spacing/MenuBarItemSpacingManager.swift
  • Thaw/Onboarding/Tour/OnboardingTour.swift
  • Thaw/Onboarding/Tour/TourMockups.swift
  • Thaw/Onboarding/Tour/WelcomeMockup.swift
  • Thaw/Settings/Models/AdvancedSettings.swift
  • Thaw/Settings/Models/DisplaySettingsManager.swift
  • Thaw/Settings/Models/FocusFilterIntent.swift
  • Thaw/Settings/Models/Profile.swift
  • Thaw/Settings/Models/ProfileManager.swift
  • Thaw/UI/Views/CustomTooltip.swift
  • Thaw/Utilities/ConcurrencyHelpers.swift
  • Thaw/Utilities/Defaults.swift
  • Thaw/Utilities/Extensions.swift
  • Thaw/Utilities/ScreenCapture.swift
  • Thaw/Utilities/SettingsURIHandler.swift
  • Thaw/Utilities/SettingsURIParser.swift
  • ThawTests/AdvancedSettingsTests.swift
  • ThawTests/AutomationSettingsTests.swift
  • ThawTests/CaptureBoundsValidationTests.swift
  • ThawTests/CrossCutting/CoverageSweep1Tests.swift
  • ThawTests/CrossCutting/CoverageSweep2Tests.swift
  • ThawTests/CrossCutting/CoverageSweep3Tests.swift
  • ThawTests/CrossCutting/CoverageSweep4Tests.swift
  • ThawTests/CrossCutting/CoverageSweep5Tests.swift
  • ThawTests/DefaultsKeyTests.swift
  • ThawTests/DiagnosticLoggerTests.swift
  • ThawTests/DisplayIceBarConfigurationTests.swift
  • ThawTests/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/DisplaySettingsManagerMutationTests.swift
  • ThawTests/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/ExtensionsTests.swift
  • ThawTests/GeneralSettingsSnapshotTests.swift
  • ThawTests/HotkeyActionTests.swift
  • ThawTests/Hotkeys/HotkeyActionTests.swift
  • ThawTests/Hotkeys/HotkeyLifecycleTests.swift
  • ThawTests/Hotkeys/KeyCodeTests.swift
  • ThawTests/Hotkeys/KeyCombinationTests.swift
  • ThawTests/Hotkeys/ModifiersTests.swift
  • ThawTests/HotkeysSettingsPersistenceTests.swift
  • ThawTests/IceBarLocationTests.swift
  • ThawTests/IceSettingsImporterTests.swift
  • ThawTests/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarShapesTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarTintKindTests.swift
  • ThawTests/MenuBar/Appearance/MissionControlDetectorTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemImageTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemOcclusionTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemRecoveryTests.swift
  • ThawTests/MenuBar/IceBar/IceBarLocationTests.swift
  • ThawTests/MenuBar/Items/AXIdentityCatalogTests.swift
  • ThawTests/MenuBar/Items/AXItemActivatorTests.swift
  • ThawTests/MenuBar/Items/AnchorDestinationTests.swift
  • ThawTests/MenuBar/Items/BadgeIndexTests.swift
  • ThawTests/MenuBar/Items/CaptureBoundsValidationTests.swift
  • ThawTests/MenuBar/Items/CapturedImageScaleTests.swift
  • ThawTests/MenuBar/Items/ClickReactionVerifierTests.swift
  • ThawTests/MenuBar/Items/ControlCenterHostedMatchLogReplayTests.swift
  • ThawTests/MenuBar/Items/DisplaySpreadGateTests.swift
  • ThawTests/MenuBar/Items/DivergencePersistenceGateTests.swift
  • ThawTests/MenuBar/Items/FlattenCurrentSectionsTests.swift
  • ThawTests/MenuBar/Items/GhostControlItemWindowTests.swift
  • ThawTests/MenuBar/Items/HiddenDragFailureClassificationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheDisplayResolutionTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheGatingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheObservationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemManagerRearmTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemOrderingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemServiceTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagCodingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemValueTests.swift
  • ThawTests/MenuBar/Items/MenuBarTestFixturesTests.swift
  • ThawTests/MenuBar/Items/MidSectionTransitionTests.swift
  • ThawTests/MenuBar/Items/MoveEventCoordinatesTests.swift
  • ThawTests/MenuBar/Items/MoveFailureBackoffTests.swift
  • ThawTests/MenuBar/Items/MoveMenuGuardTests.swift
  • ThawTests/MenuBar/Items/NewItemsPlacementTests.swift
  • ThawTests/MenuBar/Items/PartitionUnmanagedUIDsTests.swift
  • ThawTests/MenuBar/Items/PendingRehideTagIdentifiersTests.swift
  • ThawTests/MenuBar/Items/SavedLayoutSectionLookupTests.swift
  • ThawTests/MenuBar/Items/SavedPositionLookupTests.swift
  • ThawTests/MenuBar/Items/SectionGeometryGateTests.swift
  • ThawTests/MenuBar/Items/SelectWindowForBatchScanTests.swift
  • ThawTests/MenuBar/Items/ShouldPersistSavedOrderTests.swift
  • ThawTests/MenuBar/Items/SourcePIDResolutionGateTests.swift
  • ThawTests/MenuBar/Items/WindowIDsChangedGateTests.swift
  • ThawTests/MenuBar/Layout/LayoutReconcilerTests.swift
  • ThawTests/MenuBar/Layout/LayoutReconcilerUnmanagedPlacementTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetReentrancyTests.swift
  • ThawTests/MenuBar/Layout/NotchOverflowRevealTests.swift
  • ThawTests/MenuBar/Layout/PlanFullSortSequenceTests.swift
  • ThawTests/MenuBar/Layout/PlanLCSMoveSequenceTests.swift
  • ThawTests/MenuBar/Layout/PlanLeftmostMoveTests.swift
  • ThawTests/MenuBar/Layout/PlanNotchOverflowTests.swift
  • ThawTests/MenuBar/Layout/PlanPendingMoveTests.swift
  • ThawTests/MenuBar/Layout/PlanSectionOrderTests.swift
  • ThawTests/MenuBar/Layout/PlanUnmanagedPlacementTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionGeometryTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/MenuBarItemServiceTests.swift
  • ThawTests/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBarItemTagTests.swift
  • ThawTests/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarShapesTests.swift
  • ThawTests/MenuBarTestFixturesTests.swift
  • ThawTests/ModifiersTests.swift
  • ThawTests/MoveFailureBackoffTests.swift
  • ThawTests/Onboarding/OnboardingMockupsTests.swift
  • ThawTests/Onboarding/OnboardingSlideTests.swift
  • ThawTests/OnboardingMockupsTests.swift
  • ThawTests/OnboardingSlideTests.swift
  • ThawTests/PermissionTests.swift
  • ThawTests/Permissions/PermissionTests.swift
  • ThawTests/ProfileEntityTests.swift
  • ThawTests/ProfileExportBundleTests.swift
  • ThawTests/ProfileManagerDeleteTests.swift
  • ThawTests/ProfileManagerRearmGateTests.swift
  • ThawTests/ProfileTests.swift
  • ThawTests/RehideStrategyTests.swift
  • ThawTests/SectionDividerStyleTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsTests.swift
  • ThawTests/Settings/Models/AutomationSettingsTests.swift
  • ThawTests/Settings/Models/DisplayIceBarConfigurationTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerLookupTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerURINotificationTests.swift
  • ThawTests/Settings/Models/ExternalSettingsChangeTests.swift
  • ThawTests/Settings/Models/FocusFilterIntentTests.swift
  • ThawTests/Settings/Models/GeneralSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/GeneralSettingsTests.swift
  • ThawTests/Settings/Models/HookScriptTests.swift
  • ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileApplyInFlightFlagTests.swift
  • ThawTests/Settings/Models/ProfileDecodingDefaultsTests.swift
  • ThawTests/Settings/Models/ProfileEntityTests.swift
  • ThawTests/Settings/Models/ProfileExportBundleTests.swift
  • ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeepTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeleteTests.swift
  • ThawTests/Settings/Models/ProfileManagerObservationTasksTests.swift
  • ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileManagerRearmGateTests.swift
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift
  • ThawTests/Settings/Models/ProfileTests.swift
  • ThawTests/Settings/Models/RehideStrategyTests.swift
  • ThawTests/Settings/Models/SectionDividerStyleTests.swift
  • ThawTests/Settings/Models/SettingsResetterTests.swift
  • ThawTests/Settings/Search/SearchIndexTests.swift
  • ThawTests/Settings/Search/SearchModelTests.swift
  • ThawTests/Settings/Search/SettingsSearchNavigationTests.swift
  • ThawTests/Settings/URI/IntegrationURIContractTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerGetTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTailTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTests.swift
  • ThawTests/Settings/URI/SettingsURIParserTests.swift
  • ThawTests/SettingsResetterTests.swift
  • ThawTests/SettingsURIHandlerApplyTests.swift
  • ThawTests/SettingsURIHandlerGetTests.swift
  • ThawTests/SettingsURIHandlerTests.swift
  • ThawTests/Shared/CodeSigningInfoTests.swift
  • ThawTests/Shared/DiagnosticLoggerFileTests.swift
  • ThawTests/Shared/DiagnosticLoggerTests.swift
  • ThawTests/Shared/MarkerPairResolverTests.swift
  • ThawTests/Shared/SharedExtensionsTests.swift
  • ThawTests/Shared/WindowInfoDecodingTests.swift
  • ThawTests/Shared/WindowInfoDerivedTests.swift
  • ThawTests/Shared/WindowInfoTests.swift
  • ThawTests/SharedExtensionsTests.swift
  • ThawTests/Support/GraphicsTestFixtures.swift
  • ThawTests/Support/MenuBarTestFixtures.swift
  • ThawTests/Support/ProfileTestFixtures.swift
  • ThawTests/Support/ScratchDefaults.swift
  • ThawTests/Utilities/CGImageAnalysisTests.swift
  • ThawTests/Utilities/CGImageDetachedCopyTests.swift
  • ThawTests/Utilities/ConcurrencyHelpersTests.swift
  • ThawTests/Utilities/CustomTooltipPanelTests.swift
  • ThawTests/Utilities/DefaultsKeyTests.swift
  • ThawTests/Utilities/ExtensionsCoverageTests.swift
  • ThawTests/Utilities/ExtensionsGraphicsTests.swift
  • ThawTests/Utilities/ExtensionsRemainderTests.swift
  • ThawTests/Utilities/ExtensionsTests.swift
  • ThawTests/Utilities/HookRunnerTests.swift
  • ThawTests/Utilities/IceSettingsImporterTailTests.swift
  • ThawTests/Utilities/IceSettingsImporterTests.swift
  • ThawTests/Utilities/LocalizedErrorWrapperTests.swift
  • ThawTests/Utilities/MigrationTests.swift
  • ThawTests/Utilities/MouseMovedThrottleTests.swift
  • ThawTests/Utilities/ObjectStorageTests.swift
  • ThawTests/Utilities/PredicatesTests.swift
  • ThawTests/Utilities/ShareableContentCacheTests.swift
  • ThawTests/Utilities/SimpleSemaphoreTests.swift
  • ThawTests/WindowInfoTests.swift
  • docs/ASSURANCE_CASE.md
  • scripts/coverage-report.py
  • scripts/swiftlint-inputs.xcfilelist
  • sonar-project.properties

📝 Walkthrough

Walkthrough

The PR centralizes persistence through Defaults.store, extracts menu-bar enumeration and layout logic, adds cancellation-safe profile application, migrates tests to Swift Testing, expands behavioral coverage, and adds coverage-reporting tooling and CI normalization.

Changes

Thaw modernization and coverage

Layer / File(s) Summary
Persistence, profiles, and concurrency
Thaw/Utilities/Defaults.swift, Thaw/Settings/Models/*, Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
Persistence uses configurable Defaults.store. Profile observers use task-owned debounced notification tasks. Profile application records tokens and rolls back owned state after cancellation.
Menu-bar enumeration and layout extraction
Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift, Thaw/MenuBar/MenuBarItems/MenuBarItem.swift, Thaw/MenuBar/MenuBarSection.swift, Thaw/MenuBar/MenuBarSectionLayout.swift
Menu-bar enumeration, source-PID resolution, namespace caching, and section geometry move into dedicated implementations.
Settings URI and runtime syntax updates
Thaw/Utilities/SettingsURIHandler.swift, Thaw/Utilities/SettingsURIParser.swift, Thaw/Events/*, Thaw/MenuBar/*
Specific-display URI operations validate UUIDs and display existence. Route parsing uses switch expressions. Several declarations and cancellation-aware sleeps use updated Swift syntax.
Swift Testing migration and coverage
ThawTests/*
Existing tests migrate from XCTest to Swift Testing. New suites cover menu-bar items, layout, settings, profiles, URI handling, utilities, concurrency, graphics, and shared models.
Coverage tooling and assurance configuration
scripts/coverage-report.py, .github/workflows/ci.yml, sonar-project.properties, docs/ASSURANCE_CASE.md
Coverage reporting, path normalization, exclusions, SwiftLint inputs, and assurance documentation are updated for Swift Testing coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • thaw-app/Thaw#811: Overlaps in menu-bar enumeration, persistence, concurrency, and testing changes.
  • thaw-app/Thaw#857: Shares changes to listeners, source-PID handling, diagnostics, and event managers.
  • thaw-app/Thaw#858: Overlaps in menu-bar, defaults, concurrency, and Swift Testing changes.

Suggested labels: ci, chore

Suggested reviewers: stonerl

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: migrating tests to Swift Testing and increasing coverage.
Description check ✅ Passed The description follows the template and clearly documents the migration, coverage work, product changes, scope, limitations, and test status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/swift-testing-migration-and-coverage

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.

diazdesandi and others added 20 commits July 31, 2026 10:50
Raw `xccov` reports ~22% for this project because it counts every SwiftUI
view and WindowServer wrapper. SonarCloud reports 65.3% because
sonar-project.properties excludes them. Without applying those exclusions
locally there is no way to tell whether a change moved the number that the
quality gate actually reports.

This reads an .xcresult with xccov, applies sonar.sources and
sonar.coverage.exclusions, and prints total coverage plus a per-file
breakdown sorted by uncovered lines.

Files under Shared/ compile into both the app and the XPC service, so xccov
reports them once per target; the best result per path is kept so they are
not counted twice.

Baseline on this commit: 63.24% (11279 measurable, 4146 uncovered),
1354 tests passing (948 XCTest + 406 Swift Testing).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
ThawTests was split across two frameworks: 79 files on XCTest and 32 already
on Swift Testing, with no plan to finish. This converts the remainder so the
suite speaks one dialect.

Pure translation. No test was added, removed, merged, or reordered, and no
assertion was strengthened. The mapping preserves semantics rather than
collapsing everything into #expect:

  XCTUnwrap            -> try #require   (keeps stop-on-failure)
  XCTFail              -> Issue.record
  XCTAssertThrowsError -> #expect(throws:)
  XCTAssertNoThrow     -> bare `try` in a throws test
  XCTAssertEqual(accuracy:) -> #expect(abs(a - b) < e)
  XCTestExpectation    -> await confirmation

Multi-class files became one outer @suite with nested @suites. setUp/tearDown
became scoped `withX { }` helpers, or init/deinit where state spans the suite.

Two things needed care. XCTest re-exports Foundation and Swift Testing does
not, so files using UUID/Date/Data/JSONEncoder needed an explicit import.
And Swift Testing runs tests in parallel within the process while this suite
has only ever run serially (parallelizable = "NO" in Thaw.xcscheme), so every
suite touching process-wide state carries .serialized for now. That is
deliberately conservative and gets unwound once Defaults is injectable.

PermissionTests was the one real trap: `onChange` fires on every poll tick,
so a naive confirmation translation fails on the second call and
continuation.resume() traps. It latches the first grant.

Verified:
  - 1354 tests pass, 0 failures -- identical to the pre-migration count
    (948 XCTest methods + 406 Swift Testing tests)
  - coverage byte-for-byte unchanged at 63.24% (7133 covered, 4146 uncovered),
    which is what a pure translation should produce
  - swiftlint --strict clean
  - swiftformat violations 22 -> 21; none introduced

Verified on Xcode 27.0 / Swift 6.4 locally. CI pins Xcode 26.5, which is not
installed here, so this needs a CI run to confirm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
…tore

Every accessor on `Defaults` hardwired `UserDefaults.standard`, so any code
path that persists a setting was untestable without writing into the real
com.stonerl.Thaw domain of whoever ran the suite. That is why ProfileManager
sits at 39% and DisplaySettingsManager at 26%: the reachable-but-destructive
paths simply were not exercised.

Adds `Defaults.store`, defaulting to `.standard` and never reassigned in
production, plus a `withScratchDefaults { }` test helper that points the
facade at a throwaway suite and deletes it afterward.

This supersedes the per-key snapshot/restore in GeneralSettingsTests and
AdvancedSettingsTests. That approach could only protect keys it knew about,
and its own doc comment flags it as unsound once suites run concurrently.

`Defaults.store` is process-wide mutable state, so suites using the helper
must be `.serialized`. That is documented on the helper.

1354 tests pass, unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
…ries

Follow-on to 6142798, which only covered the `Defaults` facade itself.

Twenty-five call sites bypassed the facade and used `UserDefaults.standard`
directly, so the seam had holes exactly where it mattered. The worst is
MenuBarItemManager persisting `savedSectionOrder`: a test touching it rewrites
the real menu bar arrangement of whoever runs the suite. All 25 now route
through `Defaults.store`, which is `.standard` in production, so behaviour is
unchanged outside tests.

Also widens two boundaries in DisplaySettingsManager:

  - `handleExternalPerDisplaySettingsChange` private -> internal, so the URI
    notification path can be driven with a hand-built Notification instead of
    `performSetup(with:)`, which needs a live AppState and installs a
    one-second debounced observer. That path is ~250 uncovered lines.
  - `parseScope(from:)` private -> internal static. It reads nothing but its
    argument.

`withScratchDefaults` now takes a process-wide lock. `Defaults.store` is a
single global and `.serialized` only orders tests within a suite, so without
the lock two suites could install scratch stores concurrently and read each
other's. The async overload deliberately does not lock -- holding an unfair
lock across a suspension point risks deadlock -- and says so.

1354 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
Adds 168 tests across ten new suites, taking measured coverage from 62.89%
to 66.67% (7093 -> 7520 covered lines).

Largest movement is DisplaySettingsManager, 25.4% -> 50.8%. Its Settings-URI
notification path is now driven with a hand-built Notification and a synthetic
display UUID seeded into `configurations`, which reaches every per-display
setter without a second monitor attached and without `performSetup(with:)`.

Also covers ProfileManager's persistence surface (manifest decode failure,
directory-creation failure, captureCurrentLayout's Defaults reads, import
reconciliation, export skip-on-unloadable, and the reachable throw sites),
plus ObjectStorage, Predicates, LocalizedErrorWrapper, MenuBarTintKind,
ControlItemImage and Migration, which were at or near zero.

Every suite touching Defaults runs through `withScratchDefaults`, so the run
no longer writes into the developer's real domain.

Four compile fixes were needed on top of the generated tests: the app target
sets SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor and ThawTests does not, so
ObjectStorage, Predicates and MenuBarTintKind's members are main-actor
isolated and their suites needed @mainactor. LocalizedStringKey is Equatable
but not Hashable, so the distinctness check compares pairwise.

1522 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
Adds 103 tests across five suites, taking measured coverage from 66.67% to
68.93% (7520 -> 7775 covered lines).

  Extensions.swift        45.8% -> 71.2%  (482 -> 256 uncovered)
  SettingsURIHandler       85.5% -> 86.7%
  WindowInfo               33.1% -> 35.1%
  Hotkey                   21.8% -> 31.9%

Extensions is split across two suites: the non-graphical half (bundle
metadata, MenuBarItem collection helpers, the unfair-lock claim, the Combine
operators) and the CoreGraphics/AppKit half. Every NSScreen member is left
alone -- those answers depend on the number and arrangement of attached
displays, so a test would assert the developer's monitor layout rather than
the code.

Hotkey's most useful addition is that `keyCombinationDidChange` fires exactly
once per assignment and not during init. ProfileManager.rebuildProfileHotkeys
depends on that invariant and nothing verified it.

Two compile fixes were needed on top of the generated tests:

  - WindowInfoDecodingTests held its fixture payload in a `static let` of
    [String: Any], which strict concurrency rejects as non-Sendable. It is now
    computed, which also stops a mutating test from leaking into the next.
  - #expect/#require capture their argument into a closure that binds it
    immutably, so `removeFirst(matching:)` -- a mutating method -- cannot be
    called inside the macro. Those three calls are hoisted to their own line.

1625 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
The suites added in the previous two commits carried formatting drift that
swiftformat flags: brace placement on multi-line dictionary literals and
numeric-literal grouping. Running the project config over the seven offending
new files brings ThawTests back to 21/130, the same count the branch started
from, so this work adds no new drift.

swiftformat is not CI-enforced (only swiftlint --strict is), which is why the
pre-existing 21 are left alone -- fixing those is unrelated churn.

1625 tests pass, swiftlint --strict clean, coverage unchanged at 68.93%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
swift:S2961 on MenuBarShapes.swift -- `static let \`default\`` becomes
`defaultKind`. Backtick-escaping a keyword makes the declaration read as a
language construct at the point of use. One call site.

swift:S107 on Profile.swift -- ProfileContent's ten-parameter initializer is
deleted in favour of the synthesized memberwise one. The four defaults move
onto the properties, and the synthesized initializer takes its parameter order
from property order, so all ten call sites are unchanged. ProfileContent is
not Codable, so nothing here touches a stored format.

The other two findings are deliberately left and should be marked Accepted in
SonarCloud rather than fixed:

  swift:S107 on Profile.swift:188 (AdvancedSettingsSnapshot, 20 parameters).
  That type has explicit CodingKeys and a hand-written init(from:) but no
  custom encode(to:), so encoding is synthesized from the stored properties.
  Grouping them into sub-structs changes the on-disk JSON and breaks every
  saved profile plus ProfileExportBundle v1.

  swift:S1075 on HookRunner.swift:29. The path already is customizable:
  defaultOSAScriptPath is the default for the osascriptPath parameter on
  run/runIfEnabled, and tests already override it. Sonar is matching the
  string literal and cannot see the seam.

1625 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
Adds 268 tests across seven suites, taking measured coverage from 68.93% to
72.30% (7775 -> 8148 covered lines).

  MenuBarItem            15.3% -> 30.5%
  DisplaySettingsManager 50.8% -> 64.7%
  SettingsURIHandler     86.7% -> 88.0%
  Profile                79.0% -> ~100%

MenuBarItem's whole value surface is reachable through its internal memberwise
init: capability flags, both uniqueIdentifier shapes, per-field equality and
hashing, autoDetectedName's namespace switch and title-casing, and customName
through a scratch defaults store. Tests needing a resolvable sourceApplication
pass the test runner's own pid rather than guessing one, so nothing depends on
which apps happen to be running.

MenuBarSection contributes only its two pure statics, usableInlineWidth and
presentationMode, table-driven over notch and no-notch screens including the
clamping and the <= boundary. The instance half needs a live NSStatusItem.

Two of the generated tests asserted behaviour the code does not have and were
corrected rather than committed red:

  - "the converted V1 is written last" claimed V1 overwrites V2 when Ice holds
    both appearance keys. importAppearanceSettings is an if/else if, so V2 wins
    and V1 is only a fallback. The test now pins that precedence, which is the
    behaviour worth protecting: preferring the older key would silently undo an
    appearance change made in a later Ice build.
  - "a mistyped UseIceBar generates no per-display configuration" was removed.
    UserDefaults.bool(forKey:) coerces a stored 1 to true, so the per-display
    conversion runs, and that walks NSScreen.screens -- the result depends on
    how many displays the machine has attached.

Three test methods in MenuBarSectionGeometryTests take file-private case types
and had to be declared fileprivate to compile.

1791 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
Adds 87 tests across six suites, taking measured coverage from 72.30% to
73.59% (8148 -> 8294 covered lines).

ProfileManagerDeepTests reaches two catch blocks nothing else could. The
existing "occupied directory" test cannot enter ensureDirectoryExists's catch,
because a file at the profiles directory path makes fileExists return true and
createDirectory is never called; putting a regular file at a *parent*
component makes creation genuinely fail. saveManifest's catch is reached by
parking a non-empty directory on profiles.json, since an atomic write cannot
rename its scratch file over a directory. Each of those tests reloads through
a second ProfileManager to prove the swallowed write really failed rather than
quietly succeeding.

One generated test asserted that deleting from an unreachable directory
succeeds. It does not: deleteProfile swallows only CocoaError.fileNoSuchFile,
and an ENOTDIR parent surfaces as NSFileWriteUnknownError instead, so the
error propagates. The test now pins that, which is the behaviour worth having
-- it is what stops a broken profiles directory being reported to the caller
as a successful delete.

ProfileManager is now effectively exhausted at 35.1%. Roughly 700 of its 742
remaining lines sit behind `private(set) weak var appState`, assigned only in
performSetup(with:). Reaching them needs a production seam, not more tests.

1878 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
`MenuBarBackgroundKind.defaultKind == .none` is ambiguous now that the member
is no longer backtick-escaped -- `.none` resolves to `Optional.none` as
readily as to the case. Spelled out in full.

The two averageColor colour-space assertions compared `CGColorSpace.Name?`
against `CFString` through a cast chain that inferred an unintended optional.
The name is required first, then compared as String.

1889 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
…eams

Takes measured coverage from 74.10% to 81.09%, clearing the 80% statement
coverage criterion for the OpenSSF Best Practices silver badge.

Both files buried pure logic behind system-bound code, so excluding either
whole would have contradicted the rule stated at the top of
sonar-project.properties -- pure value types and algorithms stay measured even
under a UI-sounding path. Instead they are split the way MenuBarItemManager
already was when LayoutSolver, PendingLedger, AXIdentityCatalog and
ClickReactionVerifier were extracted out of it.

  MenuBarSectionLayout.swift (measured) takes Name, PresentationMode, notchGap
  and the three rules that decide how a section is shown. MenuBarSection.swift
  keeps show/hide/toggle, updateControlItemState and the rehide task and
  monitor, which need a live ControlItem/NSStatusItem, an AppState and a real
  NSScreen.

  MenuBarItem.swift (measured) keeps the value type. MenuBarItem+Enumeration
  .swift takes the window enumeration, which reaches the window server through
  Bridging/CGS and resolves owning processes over XPC.

No access level was widened. The unchecked initializers, the MenuBarItemTag and
Namespace helpers and ResolvedPID moved into the excluded file with the code
that calls them, so they stay private. `nonisolated` had to be restored on the
two nested enums -- an extension's nonisolated does not reach into nested
types. MenuBarItem's explicit memberwise init is now synthesized: it was only
written out because the unchecked initializers sat in the struct body and
suppressed synthesis.

Also excludes three files whose substance genuinely cannot run in CI:
AXItemActivator (Accessibility permissions; its two pure helpers were already
extracted and stay covered), SettingsDetailStyle (a PrimitiveButtonStyle whose
makeBody needs a ButtonStyleConfiguration, which has no public initializer),
and OnboardingTour (a SwiftUI View whose navigation lives in @State). The last
of those was contributing unstable coverage -- its lines were being counted
only when the test host happened to render the onboarding sheet at launch.

1889 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
The suite snapshotted and restored the app's whole persistent domain in
init/deinit. Swift Testing builds a fresh suite instance per test, so that wipe
ran once per test -- eight times -- and each one raced every other suite
reading Defaults concurrently. Its own doc comment said it did this "because
Defaults is hardcoded to .standard", which stopped being true when
Defaults.store landed.

Each test now builds its AppSettings inside withScratchDefaults, which takes a
process-wide lock and never touches the real domain. The suite no longer needs
.serialized and becomes a struct.

Honest note on the flake this was expected to fix: a subagent reported
AutomationSettingsTests failing roughly one run in nine from this interaction.
I could not reproduce it -- six consecutive pre-fix runs came back clean. At
one-in-nine, six runs only had about a even chance of catching it, so this is
not evidence the flake was absent, and the change stands on its own merits
regardless: wiping a shared domain eight times per run while other suites read
it is a hazard whether or not it had been observed failing.

Verified:
  - 1889 tests pass, four consecutive runs, zero issues
  - `defaults read com.stonerl.Thaw` is byte-identical before and after a full
    run, which is the acceptance criterion this work was aiming at
  - swiftlint --strict clean

The remaining 37 .serialized suites are deliberately left alone. Defaults.store
is a single process-wide global, so suites that touch it are genuinely mutually
exclusive; removing the trait would reintroduce the class of race this commit
removes. The trait is correct there, not vestigial.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
…s that fit

swift-algorithms is already a dependency. Two hand-rolled dedup loops were
doing exactly what `uniqued()` does:

  AdvancedSettings.sanitizedSearchSectionOrder -- parse, drop duplicates
  keeping first-seen order, then append the missing cases.

  MenuBarItemTag.canonicalPersistentIdentifiers -- canonicalize, then drop
  duplicates that differ only in their volatile portion.

Both are covered by existing suites (AdvancedSettingsTests,
MenuBarItemTagCanonicalizationTests), which is what makes the swap safe.

Two further candidates were examined and deliberately left alone, because
`uniqued()` would change behaviour rather than express it:

  HotkeysSettingsPane.makeRows -- `guard let hotkey = ..., seen.insert(id)
  .inserted` short-circuits left to right, so an item with no hotkey never
  marks its identifier seen. Hoisting the dedup ahead of the lookup would
  start consuming identifiers for items that produce no row.

  MenuBarItemManager.uncheckedCacheItems -- the duplicate branch emits a
  diagnostic naming the skipped item. `uniqued()` would drop it silently, and
  that log is what makes a duplicate-window report explicable after the fact.

Both files also gained an explicit `import Algorithms`. They compiled without
one only because member import visibility is off on the Thaw target, so
`uniqued()` was resolving transitively through MenuBarItemManager.swift's
import. That upcoming feature is already enabled on MenuBarItemService, so
relying on the transitive path here would break the day it is turned on for
the app target.

1889 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
MenuBarManager and DisplaySettingsManager already moved their debounced
observers to swift-async-algorithms, each with a comment explaining why.
ProfileManager was the straggler, still running three Combine
`.debounce(for:scheduler: DispatchQueue.main)` chains -- and it was the last
thing in the file needing Combine at all, so `import Combine` and the
`cancellables` set both go with them.

Each observer now follows the shape DisplaySettingsManager established: a
NotificationCenter observer feeds an AsyncStream that `.debounce(for:)`
coalesces, with the observer owned by its task and removed by a defer when the
task ends. That keeps the non-Sendable observer token off the class, so the
new deinit only has to cancel three tasks. Each assignment cancels the previous
task first, so a repeated performSetup cannot leave an orphaned observer
running -- the Combine version had the same protection via `cancellables`
being replaced wholesale.

Debounce intervals are unchanged: 1.5s for screen parameters, 0.5s for both
Focus Filter notifications.

Also flips six `var filter` to `let` in FocusFilterIntentTests. ThawFocusFilter's
@parameter wrapper is a final class, so its setter is nonmutating and the
compiler was warning that the variables are never mutated.

Coverage moves 81.09% -> 80.76%. The migration adds lines to performSetup,
which needs a live AppState and so stays uncovered; the OpenSSF silver
threshold still holds but with less margin than before.

1889 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
Both were surfaced while writing coverage tests, and both were verified in
source before being changed.

1. An empty `itemOrder` shadowed `savedSectionOrder`.

`captureCurrentLayout` derives `itemOrder` from the item manager's cache, which
is empty while the menu bar is still settling, so a capture taken at the wrong
moment writes `[:]` rather than `nil`. `resolvedItemOrder` was a plain
`itemOrder ?? savedSectionOrder`, and an empty dictionary is still non-nil, so
that empty value shadowed a perfectly good `savedSectionOrder` and the next
apply saw no layout at all. The fallback now also triggers on empty, which
repairs profiles already written that way rather than only preventing new ones.
`resolvedItemSectionMap` is built from the same property and is fixed with it.

2. `apply(to:)` discarded the snapshot's own `lastCustomIceIcon`.

It assigned `lastCustomIceIcon` before `iceIcon`, and `iceIcon`'s `didSet`
mirrors a `.custom` icon into `lastCustomIceIcon`. Every profile carrying a
custom icon therefore came back with the two fields equal, losing whichever
icon the profile had remembered separately. The assignment now happens after.

An existing test pinned that second behaviour as expected -- it was written
from observation during the coverage work, before the ordering was recognised
as the cause. It now asserts the corrected behaviour, and its doc comment says
what changed. Three new tests cover the empty/populated/both-empty cases of the
first fix.

1893 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
`handlePerDisplayToggle` accepted any identifier containing a hyphen, while
`handlePerDisplaySetForSpecificDisplay` required it to parse as a UUID and to
name a display that is connected or has a persisted configuration. So
`thaw://toggle?key=useIceBar&display=a-b` posted a notification for a display
that does not exist and returned true, while the equivalent `set` refused it.
DisplaySettingsManager discards such a notification, so the only effect was
telling the caller a toggle had happened when none had.

Toggle now uses the same two guards. That also removes a redundant
`!uuid.isEmpty`, which the enclosing `if let uuid, !uuid.isEmpty` already
guaranteed.

Three tests had pinned the loose behaviour, one of them explicitly ("checks the
identifier's shape, not the display's existence"). Two now seed a persisted
configuration for the display they toggle; the third asserts the refusal and
says in its doc comment what changed and why.

1893 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
ASSURANCE_CASE.md still recorded ~44% coverage with
`test_statement_coverage80` deferred, dated before this cycle's work. It also
named the SonarCloud project `stonerl_Thaw`, which is no longer the key.

The criterion it set for itself was "raise coverage (or document an accepted
exclusion set with measured coverage of the remainder) before marking Silver
coverage Met". Both now hold: 80.8% over 9,960 measured lines from 1,893
tests, with the exclusion set and the rule governing it written down in
sonar-project.properties. §6.4 says which code is excluded and why, notes
there is still no enforced gate, and records that the residual gap is
concentrated in ProfileManager behind a live AppState -- a testability limit
rather than untested logic.

sonar.projectVersion moves to 2.0.0-rc.2 to match MARKETING_VERSION, which the
properties file asks to keep in sync. The marketing version itself is left
alone; this work ships in rc-2.

Separately, `useIceBar` is removed from `supportedBooleanKeys`. It is a
per-display setting resolved through DisplaySettingsManager, not a
Defaults-backed global, and it had no entry in `keyMapping`. That only worked
because all three handlers test `perDisplayKeys` first and the one loop over
the table filters per-display keys out; a new call site reading this table
first would have found no mapping and failed silently. `alwaysShowHiddenItems`,
the other per-display Boolean, was already absent, which is the shape both
should have. `isValidSettingsKey` consults both tables, so the key stays valid.

Two tests asserted the old table and now assert the corrected split, including
that `useIceBar` remains a valid settings key.

1893 tests pass, swiftlint --strict clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
…raw values

`swiftformat` reported 56 files needing formatting across Thaw, Shared,
MenuBarItemService and ThawTests, all of it pre-existing drift. It is not
CI-enforced -- only swiftlint --strict is -- so the tree had accumulated
inconsistency. All four directories are now clean.

The bulk is cosmetic: wrapIfStatementBodies, indent, modifierOrder, and the
file header on 43 files that were missing the Copyright (Ice) line. The
semantic-adjacent rules that did fire are equivalence-preserving --
hoistAwait, hoistTry, conditionalAssignment, preferKeyPath, redundantVariable.

One rule had to be disabled rather than applied. `redundantRawValues` wanted to
strip the raw values from four cases in Defaults.swift, and those raw values
are the literal UserDefaults key names on disk -- the same values
DefaultsKeyTests pins precisely because they are a stored format. Dropping them
would make each stored key follow its Swift case name, so a later rename would
silently orphan every user's setting with no compile-time complaint. It is now
disabled in .swiftformat with that reasoning recorded, so a future run cannot
reintroduce the hazard.

`redundantSwiftUIGroup` did fire, all of it in TourMockups.swift, and was
allowed: each case is a `Group { if ... }` at the top of a `body`, which the
result builder handles directly.

1893 tests pass, swiftlint --strict clean, coverage unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
ThawTests had grown to 143 files in one flat directory, which made it hard to
tell what a given area was covered by, or where a new test belongs. The layout
now mirrors Thaw/ and Shared/:

  MenuBar/Items      39   MenuBar/Layout     12   MenuBar/Appearance  4
  MenuBar/ControlItem 3   MenuBar/Section     2   MenuBar/IceBar      1
  Settings/Models    30   Settings/URI        7   Settings/Search     3
  Utilities          19   Shared              8   Hotkeys             5
  Onboarding          2   Permissions         1
  Support             2   Fixtures            2   CrossCutting        5

Support holds the two shared helpers that are not themselves tests
(MenuBarTestFixtures, ScratchDefaults). Fixtures is unchanged -- it already
held the recorded log data.

CrossCutting holds the five CoverageSweep files. Each spans several unrelated
types, so no single folder fits, and their names describe how they were
produced rather than what they cover. They are worth renaming and probably
splitting; grouping them makes that visible rather than hiding it across the
tree.

Pure moves: 143 renames, zero content changes, and git records every one as a
rename so blame survives. ThawTests is a PBXFileSystemSynchronizedRootGroup,
so the folders are picked up automatically and project.pbxproj is untouched.
sonar.tests = ThawTests is directory-based and follows without change.

1893 tests pass -- the same count as before the move -- swiftlint --strict and
swiftformat clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
@diazdesandi
diazdesandi force-pushed the test/swift-testing-migration-and-coverage branch from 201303e to a7d0213 Compare July 31, 2026 17:51
@github-actions github-actions Bot added docs Improvements or additions to documentation enhancement Iteration, improvement, or optimization of an existing feature fix PR that fixes a defect (issue reports use bug) refactor Code restructuring without behavior change settings Settings UI not covered by a more specific area test Test additions or updates labels Jul 31, 2026
Include MenuBarItem+Enumeration.swift and MenuBarSectionLayout.swift
added by the MenuBar testability split.

Signed-off-by: diazdesandi <diazdesandi@proton.me>
…rage

Signed-off-by: diazdesandi <diazdesandi@proton.me>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 46

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/coverage-report.py`:
- Around line 75-90: Replace the hand-rolled option parsing in main with a
parse_args helper using argparse. Define positional xcresult_path as an optional
Path, --top as an integer defaulting to 30, and --json as a boolean stored in
as_json; have main consume the returned Namespace so missing or invalid --top
values produce argparse usage errors and the existing behavior remains intact.
- Around line 46-58: Update is_excluded so patterns ending in "/*.swift" are
handled before the general fnmatch call, enforcing that the matched path has
exactly one filename component beneath the prefix and rejecting nested paths.
Retain fnmatch for other patterns, including recursive Sonar-style patterns,
while preserving the existing boolean exclusion behavior.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItem`+Enumeration.swift:
- Around line 76-85: Move the `diagLog` declaration out of the documentation
block so the comment directly precedes `getMenuBarItemWindows`. Remove or
relocate the duplicated display/option documentation currently before
`assignStableInstanceIndices(to:using:)`, leaving that method without inaccurate
documentation and preserving the correct comment before `getMenuBarItems`.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 7015-7017: Update the profile-apply flow surrounding
armProfileState to snapshot the prior savedSectionOrder, pinning sets, and
activeProfileLayout and associate the operation with an apply token; when
cancellation is detected in Phase 6 or Phase 7, restore the snapshot only if
that token still owns the state, prevent late-arrival re-sorting from applying
the cancelled profile, and skip scheduling the deferred cache refresh.

In `@Thaw/Settings/Models/FocusFilterIntent.swift`:
- Line 88: Replace the duplicated "FocusFilterRequestedProfileID" literals in
the FocusFilterIntent write path and ProfileManager read path with one shared
key constant, preferably a shared Defaults.Key or static property. Update both
access sites to reference that constant while preserving the existing storage
behavior.

In `@Thaw/Utilities/SettingsURIHandler.swift`:
- Around line 696-713: Extract the UUID-format and display-existence checks into
a private helper returning the validated DisplayIceBarConfiguration or nil after
logging. Update both handlePerDisplayToggle and
handlePerDisplaySetForSpecificDisplay to call this helper and use its returned
configuration, preserving the existing validation behavior and diagnostic
messages.

In `@ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift`:
- Around line 29-44: Update the MenuBarItemFailureLedgerTests setup and teardown
to also snapshot, clear, and restore Defaults key .unresponsiveMenuBarItemsBuild
alongside .unresponsiveMenuBarItems. Preserve the existing restoration behavior
for both keys, including removing each key when its original value was absent.

In `@ThawTests/MenuBar/Items/MenuBarItemValueTests.swift`:
- Around line 473-476: Update the assertions in the menu-agent auto-detection
test to verify that autoDetectedName equals the resolved source application name
and is unaffected by the menu title, rather than asserting it lacks “Menu”.
Preserve the non-empty validation and use the existing source-application-name
value established by the test setup.

In `@ThawTests/MenuBar/Items/ShouldPersistSavedOrderTests.swift`:
- Around line 110-118: The multipleBlockingFlagsAllBlock test does not cover the
alwaysHiddenSectionResolved and hiddenSectionHasRoom gates. Add separate
assertions or test cases that set each of these fields to false while leaving
the other persistence conditions satisfied, and verify
LayoutSolver.shouldPersistSavedOrder returns false for both.

In `@ThawTests/MenuBar/Layout/LayoutResetErrorTests.swift`:
- Around line 17-33: Remove the three case-existence tests—missingAppStateError,
missingControlItemsError, and alreadyInProgressError—from
LayoutResetErrorTests.swift. Keep the description and recovery-suggestion tests
below unchanged, as they already exercise all enum cases.

In `@ThawTests/MenuBar/Section/MenuBarSectionNameTests.swift`:
- Around line 90-107: Replace the tautological nil checks in localizedVisible,
localizedHidden, and localizedAlwaysHidden with equality assertions comparing
each localized value directly against its expected LocalizedStringKey. Keep the
existing test coverage for all three MenuBarSection.Name cases while validating
the actual key returned by localized.

In `@ThawTests/Permissions/PermissionTests.swift`:
- Around line 19-63: Add the existing .timeLimit(.minutes(1)) trait to
performRequestRestartsPollingAfterChecksStop, matching the async test pattern
used elsewhere. Keep the continuation and polling assertions unchanged so a
missing onChange event fails after the bounded timeout instead of hanging.

In `@ThawTests/Settings/Models/AutomationSettingsTests.swift`:
- Around line 13-45: Replace real UserDefaults snapshot/restore isolation with
withScratchDefaults in ThawTests/Settings/Models/AutomationSettingsTests.swift
(lines 13-45), removing savedDefaults, init wiping, and deinit restoration and
wrapping each test case body. Apply the same change in
ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift (lines
28-55), moving makeManager and all assertions, including reload cases, inside
withScratchDefaults. In
ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift (lines
17-46), remove snapshot/restore logic, wrap each case in withScratchDefaults,
and update the doc comment to reflect the injectable store.

In `@ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift`:
- Around line 66-100: The fallback tests that depend on display identity should
skip when UUID lookup is unavailable instead of failing via `#require`. Update
resolvedDisplayWithoutOverrideFallsBackToGlobalConfiguration and
activeDisplayUsesExplicitConfiguration to guard their respective Bridging
lookups and return early (or apply the project’s existing skip trait), matching
the early-return pattern in DisplaySettingsManagerMutationTests; leave tests
with valid UUIDs unchanged.

In `@ThawTests/Settings/Models/ExternalSettingsChangeTests.swift`:
- Around line 49-72: Update observerFiltersAndDelivers so its notification
handler cancels subscription before resuming the continuation, preventing
duplicate delivery from resuming it twice or over-counting confirmation. Also
add the .serialized trait to the enclosing External settings changes `@Suite`
declaration to isolate this shared NotificationCenter test suite.

In `@ThawTests/Settings/Models/GeneralSettingsSnapshotTests.swift`:
- Around line 49-52: Update makeCustomSnapshot to require, rather than default,
the .ellipsis and .chevron icon lookups, making the helper throwable so missing
fixtures fail immediately. Propagate this change to both call sites by using try
makeCustomSnapshot() and marking the corresponding tests as throws.

In `@ThawTests/Settings/Models/GeneralSettingsTests.swift`:
- Around line 329-336: Update the three unknown-key external-change tests,
including externalChangeToUnknownKeyIsIgnored and the cases around it, to
compare settings.showIceIcon against Defaults.DefaultValue.showIceIcon instead
of asserting a hard-coded truth value. Preserve the existing ignored-change
verification and test structure.

In `@ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift`:
- Around line 16-27: The hotkey and section-order test seeds currently mutate
global defaults instead of using the scratch store. Update
HotkeysSettingsPersistenceTests.bindingsRoundTrip() and the two
savedSectionOrder setup sites in ProfileManagerUpdateRearmIntegrationTests to
run through withScratchDefaults, moving all seed, read, update, and cleanup
operations to that store and removing direct global snapshot/restore or
UserDefaults.standard writes.

In `@ThawTests/Settings/Models/ProfileApplyInFlightFlagTests.swift`:
- Around line 74-86: Add a `.timeLimit` trait to
`emptyProfileApplyClearsInFlightFlag` so the test fails promptly if
`applyProfileLayout` remains blocked by startup settling. Keep the test’s
existing assertions and empty-layout setup unchanged.

In `@ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift`:
- Around line 458-548: Add the documented `// Format contract: parsed by
ProfileLayoutLogReplayTests.parse(_:)` marker to every production `diagLog` call
site emitting messages matched by `ProfileLayoutLogReplayTests.parse(_:)`,
including the `LittleSnitchOrphanLog` path. Alternatively, remove the parser’s
marker expectation, but preserve an explicit mechanism that prevents rewording
these production log formats from silently breaking fixture parity.

In `@ThawTests/Settings/Models/ProfileManagerCRUDTests.swift`:
- Around line 437-488: Extract the shared makeProfile(named:) fixture and
seedManifest(with:into:) helper into
ThawTests/Support/MenuBarTestFixtures.swift, parameterizing the factory with
name, savedSectionOrder, and displayConfigurations. In
ThawTests/Settings/Models/ProfileManagerCRUDTests.swift ranges 437-488 and
411-435, move these helpers while retaining withManager as the local seam. In
ProfileManagerDeleteTests.swift range 112-192, remove both local copies and use
the shared helpers; in ProfileManagerUpdateRearmIntegrationTests.swift range
136-198, replace makeProfile(savedSectionOrder:) with the shared factory and
retain writeProfile only if needed for manifest-free seeding; in
ProfileExportBundleTests.swift range 29-80, remove makeTestProfile(name:) and
use the shared factory.

In `@ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift`:
- Around line 546-630: The profile-manager test fixtures are duplicated across
both suites. In ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift
lines 546-630, move withTemporaryDirectory (both overloads), seedManifest,
makeEncoder, makeDecoder, and makeProfile into a shared helper under
ThawTests/Support/, retaining only withManager and writeBundle locally. In
ThawTests/Settings/Models/ProfileManagerDeepTests.swift lines 243-341, remove
the duplicate withTemporaryDirectory, seedManifest, makeEncoder, profileURL, and
makeProfile implementations and use the shared helpers, retaining
makeUncreatableDirectoryURL, blockManifestPath, and isDirectory.

In `@ThawTests/Settings/Models/ProfileTests.swift`:
- Around line 548-664: Extract shared file-scope fixture factories for
GeneralSettingsSnapshot and AdvancedSettingsSnapshot, then reuse them in
profileContentInitialization, profileContentWithEmptyCollections, and
ProfileFullTests.makeTestContent(). Preserve each test’s required overrides
while eliminating duplicated initializer argument lists, including the empty
collection overrides for the collection-focused test.

In `@ThawTests/Settings/Search/SearchModelTests.swift`:
- Around line 56-65: Update entryTitlesAreLocalized so it does not assert the
host-dependent entry.titleText value or hard-code the German translation.
Compare titles resolved through the explicitly loaded English and German
bundles, asserting they differ and that the running localization result matches
the appropriate bundle only when the test can determine the active locale.

In `@ThawTests/Settings/URI/IntegrationURIContractTests.swift`:
- Around line 53-60: Add the combined requestId and broadcast URL shape to the
droppyURIs fixture, then update the test around the existing parsing/no-set
assertions to read that URL from the fixture instead of repeating the literal.
Keep droppyURIs as the single source of truth for all Droppy-emitted URL shapes.

In `@ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift`:
- Around line 29-46: Replace the real UserDefaults snapshot/restore lifecycle in
SettingsURIHandlerApplyTests.swift (lines 29-46) with withScratchDefaults
wrapping each test body. In AdvancedSettingsTests.swift (lines 69-105), remove
touchedKeys, savedDefaults, deinit, and clearStoredSettings, and use
withScratchDefaults so each test starts with an empty isolated store; update
both suites while preserving their existing test behavior.
- Around line 443-452: Rename whitelistChangesAreAnnounced to reflect that it
tests adding an entry, then add a separate removal test using
SettingsURIHandler.removeFromWhitelist after establishing a whitelisted bundle
ID. Assert that the removal operation posts settingsURIWhitelistDidChange,
preserving the existing add notification coverage.

In `@ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift`:
- Around line 96-98: Remove the no-op globalBooleanKeys filtering and simplify
everyBooleanKeyIsSettable to use the global .settingsDidChangeViaURI channel
directly, or explicitly include per-display Boolean keys if the per-display
branch and .perDisplaySettingsDidChangeViaURI behavior must remain covered. Keep
the existing perDisplayKeys assertions consistent with the chosen test scope and
update the misleading documentation accordingly.

In `@ThawTests/Settings/URI/SettingsURIHandlerGetTests.swift`:
- Around line 47-64: Remove the whole-domain snapshot and restoration from the
test suite’s init/deinit. Route all suite operations that write through
injectable Defaults storage via withScratchDefaults, and track and restore only
the individual UserDefaults.standard keys written directly by the handler,
preserving values changed by other suites.

In `@ThawTests/Settings/URI/SettingsURIHandlerTailTests.swift`:
- Around line 86-89: Ensure the nested suites EmptyDisplayIdentifier,
UnsupportedPerDisplayToggles, and AnnouncementPayloads execute every
SettingsURIHandler.handleSet and handleToggle call inside withScratchDefaults,
preserving each test’s existing assertions. Alternatively, narrow the suite
documentation to explicitly identify these exemptions, but prefer wrapping the
test bodies so they cannot access or mutate the shared real store.

In `@ThawTests/Settings/URI/SettingsURIParserTests.swift`:
- Around line 21-22: Rename the nested test suite struct from Set to SetRoute
within SettingsURIParserTests, while preserving the `@Suite`("set") display name
and updating any references to the nested type if present.
- Around line 35-46: Update emptyKeyIsNotMalformed in the SettingsURIParserTests
route assertions to pair each URI with its exact expected SettingsURIRoute,
including a present empty key, and compare the parsed route directly rather than
using negative malformed comparisons. Preserve coverage for both set and toggle
URIs.

In `@ThawTests/Shared/DiagnosticLoggerTests.swift`:
- Around line 98-115: Add CaseIterable conformance to DiagnosticLogger.Level in
DiagnosticLogger.swift, then update allLevelsHaveUppercaseRawValues and
allLevelsAreDistinct to use DiagnosticLogger.Level.allCases instead of hardcoded
level arrays.

In `@ThawTests/Shared/WindowInfoTests.swift`:
- Around line 18-43: Update createWindowInfo to be throwing, construct the JSON
payload with Data(json.utf8), and replace the forced JSONDecoder decode with
`#require`. Propagate try to every createWindowInfo call site and mark
non-throwing test methods that invoke it as throws, following the sibling suite
pattern.

In `@ThawTests/Support/ScratchDefaults.swift`:
- Around line 45-65: Document in the withScratchDefaults doc comment that
scratchDefaultsLock is non-recursive and nested calls on the same thread are
unsupported and will deadlock. Do not alter the locking behavior or add
ownership tracking.
- Around line 67-93: In ThawTests/Support/ScratchDefaults.swift lines 67-93,
update withScratchDefaults so saving, installing, and restoring the process-wide
Defaults.store are serialized through an async-safe mutex such as an actor,
while preserving suite cleanup and async body execution. In
ThawTests/Settings/Models/FocusFilterIntentTests.swift lines 57-66, remove the
suite-documentation claim that .serialized makes the async helper safe,
retaining .serialized only for its remaining applicable reasons.

In `@ThawTests/Utilities/CGImageAnalysisTests.swift`:
- Around line 109-119: Strengthen the assertion in explicitColorSpaceIsUsed by
comparing the returned average.colorSpace name directly with CGColorSpace.sRGB
instead of checking only its model. Keep the test’s explicit space argument and
existing setup unchanged so it verifies that averageColor(using:) honors the
requested color space.

In `@ThawTests/Utilities/CGImageDetachedCopyTests.swift`:
- Around line 19-59: Replace the force unwraps in makeSolidImage and pixelData
with try `#require`, and mark both helpers as throws. Update every call site in
the affected tests to use try, including the five references around lines 65,
75, 80, 134, and 140, and mark their enclosing test methods as throws.

In `@ThawTests/Utilities/ConcurrencyHelpersTests.swift`:
- Around line 77-80: Replace the fixed Task.sleep in the cancellation test with
polling of observed until it becomes true or a reasonable deadline expires. Use
the existing observed lock access, include a short interval between polls, and
retain the final expectation so the test tolerates scheduler latency without
waiting indefinitely.

In `@ThawTests/Utilities/ExtensionsGraphicsTests.swift`:
- Around line 17-33: Extract the duplicated premultiplied-first, 32-bit
little-endian bitmap-context builder into a shared fixture under
ThawTests/Support/ and reuse it from
ThawTests/Utilities/ExtensionsGraphicsTests.swift#L17-L33, retaining only the
local fill:background: overload or moving both helpers. Remove
makeImage(width:height:draw:) from
ThawTests/Utilities/CGImageAnalysisTests.swift#L222-L241 and update its callers
to use the shared fixture; also fold makeOpaqueImage in
ThawTests/Utilities/ExtensionsRemainderTests.swift (line range not provided)
into the same fixture, preserving the exact bitmapInfo parameters.
- Around line 482-499: Update visiblePanelWakesTheWaiter to use a Swift Testing
condition trait based on panel.isVisible, so the test is skipped when no window
server is available instead of failing via `#require`. Remove the in-body `#require`
while preserving the existing panel setup, waiter behavior, and visibility
assertion.

In `@ThawTests/Utilities/IceSettingsImporterTests.swift`:
- Around line 13-15: Update IceSettingsImporterTests to match the isolation used
by IceSettingsImporterTailTests: mark the suite as serialized and wrap every
test case’s Defaults-backed work in withScratchDefaults. Ensure all
IceSettingsImporter fixture imports execute against scratch defaults rather than
the process-wide host defaults.

In `@ThawTests/Utilities/MigrationTests.swift`:
- Around line 109-119: Add a non-empty assertion immediately after each
`storedConfigurations()` result is obtained in the migration tests at the
referenced sections, before iterating over `configurations.values`. Keep the
existing per-configuration expectations unchanged, and ensure the tests fail
when the decoded configuration map contains no display entries.

In `@ThawTests/Utilities/ObjectStorageTests.swift`:
- Around line 144-201: Make deallocation explicit in
weakSetDoesNotRetainItsValue and strongWriteCanBeReplacedByAWeakOne instead of
relying on the do-scope ending; use an explicit lifetime-ending action for
payload after the storage write and before asserting observed == nil, while
preserving the existing weak-reference assertions.

In `@ThawTests/Utilities/ShareableContentCacheTests.swift`:
- Around line 78-84: Replace the 20 ms sleeps in both concurrency cases with a
causal synchronization gate: have the first fetch signal when it begins, then
await that signal before starting the subsequent callers. Apply the same gate
before cancelledTask.cancel() in the second case, preserving the existing
fetch-count and cancellation assertions without relying on elapsed time.

In `@ThawTests/Utilities/SimpleSemaphoreTests.swift`:
- Around line 66-105: Correct the reconciliation logic in the semaphore race
loop: in the acquired branch after awaiting waitTask, do not call
semaphore.signal(), while in the timeout branch consume the restored permit with
semaphore.wait(timeout:) so each iteration returns to the held state. Update the
surrounding comments to match this behavior and preserve the final sanity check.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 830cdd91-4f2b-425b-a599-e5d5e90f793c

📥 Commits

Reviewing files that changed from the base of the PR and between ca62e84 and 1b67f9e.

📒 Files selected for processing (226)
  • .gitignore
  • .swiftformat
  • MenuBarItemService/Listener.swift
  • MenuBarItemService/SourcePIDCache.swift
  • MenuBarItemService/main.swift
  • Shared/Bridging/Shims.swift
  • Shared/Utilities/DiagnosticLogger.swift
  • Shared/Utilities/MarkerPairResolver.swift
  • Thaw/Events/EventTap.swift
  • Thaw/Events/HIDEventManager.swift
  • Thaw/Events/RunLoopLocalEventMonitor.swift
  • Thaw/Hotkeys/Hotkey.swift
  • Thaw/Hotkeys/HotkeyRegistry.swift
  • Thaw/Main/AppState.swift
  • Thaw/Main/Updates.swift
  • Thaw/MenuBar/Appearance/Configurations/MenuBarAppearanceConfigurationV2.swift
  • Thaw/MenuBar/Appearance/Configurations/MenuBarShapes.swift
  • Thaw/MenuBar/Appearance/MissionControlDetector.swift
  • Thaw/MenuBar/ControlItem/ControlItem.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift
  • Thaw/MenuBar/MenuBarItems/AXIdentityCatalog.swift
  • Thaw/MenuBar/MenuBarItems/AXItemActivator.swift
  • Thaw/MenuBar/MenuBarItems/ClickReactionVerifier.swift
  • Thaw/MenuBar/MenuBarItems/LayoutSolver.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemFailureLedger.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemImageCache.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemServiceConnection.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemTag.swift
  • Thaw/MenuBar/MenuBarManager.swift
  • Thaw/MenuBar/MenuBarSection.swift
  • Thaw/MenuBar/MenuBarSectionLayout.swift
  • Thaw/MenuBar/Search/MenuBarSearchPanel.swift
  • Thaw/MenuBar/Spacing/MenuBarItemSpacingManager.swift
  • Thaw/Onboarding/Tour/OnboardingTour.swift
  • Thaw/Onboarding/Tour/TourMockups.swift
  • Thaw/Onboarding/Tour/WelcomeMockup.swift
  • Thaw/Settings/Models/AdvancedSettings.swift
  • Thaw/Settings/Models/DisplaySettingsManager.swift
  • Thaw/Settings/Models/FocusFilterIntent.swift
  • Thaw/Settings/Models/Profile.swift
  • Thaw/Settings/Models/ProfileManager.swift
  • Thaw/UI/Views/CustomTooltip.swift
  • Thaw/Utilities/Defaults.swift
  • Thaw/Utilities/Extensions.swift
  • Thaw/Utilities/ScreenCapture.swift
  • Thaw/Utilities/SettingsURIHandler.swift
  • Thaw/Utilities/SettingsURIParser.swift
  • ThawTests/CaptureBoundsValidationTests.swift
  • ThawTests/CrossCutting/CoverageSweep1Tests.swift
  • ThawTests/CrossCutting/CoverageSweep2Tests.swift
  • ThawTests/CrossCutting/CoverageSweep3Tests.swift
  • ThawTests/CrossCutting/CoverageSweep4Tests.swift
  • ThawTests/CrossCutting/CoverageSweep5Tests.swift
  • ThawTests/DefaultsKeyTests.swift
  • ThawTests/DiagnosticLoggerTests.swift
  • ThawTests/DisplayIceBarConfigurationTests.swift
  • ThawTests/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/ExtensionsTests.swift
  • ThawTests/GeneralSettingsSnapshotTests.swift
  • ThawTests/HotkeyActionTests.swift
  • ThawTests/Hotkeys/HotkeyActionTests.swift
  • ThawTests/Hotkeys/HotkeyLifecycleTests.swift
  • ThawTests/Hotkeys/KeyCodeTests.swift
  • ThawTests/Hotkeys/KeyCombinationTests.swift
  • ThawTests/Hotkeys/ModifiersTests.swift
  • ThawTests/IceBarLocationTests.swift
  • ThawTests/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarShapesTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarTintKindTests.swift
  • ThawTests/MenuBar/Appearance/MissionControlDetectorTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemImageTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemOcclusionTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemRecoveryTests.swift
  • ThawTests/MenuBar/IceBar/IceBarLocationTests.swift
  • ThawTests/MenuBar/Items/AXIdentityCatalogTests.swift
  • ThawTests/MenuBar/Items/AXItemActivatorTests.swift
  • ThawTests/MenuBar/Items/AnchorDestinationTests.swift
  • ThawTests/MenuBar/Items/BadgeIndexTests.swift
  • ThawTests/MenuBar/Items/CaptureBoundsValidationTests.swift
  • ThawTests/MenuBar/Items/CapturedImageScaleTests.swift
  • ThawTests/MenuBar/Items/ClickReactionVerifierTests.swift
  • ThawTests/MenuBar/Items/ControlCenterHostedMatchLogReplayTests.swift
  • ThawTests/MenuBar/Items/DisplaySpreadGateTests.swift
  • ThawTests/MenuBar/Items/DivergencePersistenceGateTests.swift
  • ThawTests/MenuBar/Items/FlattenCurrentSectionsTests.swift
  • ThawTests/MenuBar/Items/GhostControlItemWindowTests.swift
  • ThawTests/MenuBar/Items/HiddenDragFailureClassificationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheDisplayResolutionTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheGatingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheObservationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemManagerRearmTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemOrderingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemServiceTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagCodingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemValueTests.swift
  • ThawTests/MenuBar/Items/MenuBarTestFixturesTests.swift
  • ThawTests/MenuBar/Items/MidSectionTransitionTests.swift
  • ThawTests/MenuBar/Items/MoveEventCoordinatesTests.swift
  • ThawTests/MenuBar/Items/MoveFailureBackoffTests.swift
  • ThawTests/MenuBar/Items/MoveMenuGuardTests.swift
  • ThawTests/MenuBar/Items/NewItemsPlacementTests.swift
  • ThawTests/MenuBar/Items/PartitionUnmanagedUIDsTests.swift
  • ThawTests/MenuBar/Items/PendingRehideTagIdentifiersTests.swift
  • ThawTests/MenuBar/Items/SavedLayoutSectionLookupTests.swift
  • ThawTests/MenuBar/Items/SavedPositionLookupTests.swift
  • ThawTests/MenuBar/Items/SectionGeometryGateTests.swift
  • ThawTests/MenuBar/Items/SelectWindowForBatchScanTests.swift
  • ThawTests/MenuBar/Items/ShouldPersistSavedOrderTests.swift
  • ThawTests/MenuBar/Items/SourcePIDResolutionGateTests.swift
  • ThawTests/MenuBar/Items/WindowIDsChangedGateTests.swift
  • ThawTests/MenuBar/Layout/LayoutReconcilerTests.swift
  • ThawTests/MenuBar/Layout/LayoutReconcilerUnmanagedPlacementTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetReentrancyTests.swift
  • ThawTests/MenuBar/Layout/NotchOverflowRevealTests.swift
  • ThawTests/MenuBar/Layout/PlanFullSortSequenceTests.swift
  • ThawTests/MenuBar/Layout/PlanLCSMoveSequenceTests.swift
  • ThawTests/MenuBar/Layout/PlanLeftmostMoveTests.swift
  • ThawTests/MenuBar/Layout/PlanNotchOverflowTests.swift
  • ThawTests/MenuBar/Layout/PlanPendingMoveTests.swift
  • ThawTests/MenuBar/Layout/PlanSectionOrderTests.swift
  • ThawTests/MenuBar/Layout/PlanUnmanagedPlacementTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionGeometryTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/MenuBarItemServiceTests.swift
  • ThawTests/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBarItemTagTests.swift
  • ThawTests/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarShapesTests.swift
  • ThawTests/MenuBarTestFixturesTests.swift
  • ThawTests/ModifiersTests.swift
  • ThawTests/MoveFailureBackoffTests.swift
  • ThawTests/Onboarding/OnboardingMockupsTests.swift
  • ThawTests/Onboarding/OnboardingSlideTests.swift
  • ThawTests/OnboardingMockupsTests.swift
  • ThawTests/OnboardingSlideTests.swift
  • ThawTests/PermissionTests.swift
  • ThawTests/Permissions/PermissionTests.swift
  • ThawTests/ProfileEntityTests.swift
  • ThawTests/ProfileManagerRearmGateTests.swift
  • ThawTests/RehideStrategyTests.swift
  • ThawTests/SectionDividerStyleTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsTests.swift
  • ThawTests/Settings/Models/AutomationSettingsTests.swift
  • ThawTests/Settings/Models/DisplayIceBarConfigurationTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerLookupTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerURINotificationTests.swift
  • ThawTests/Settings/Models/ExternalSettingsChangeTests.swift
  • ThawTests/Settings/Models/FocusFilterIntentTests.swift
  • ThawTests/Settings/Models/GeneralSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/GeneralSettingsTests.swift
  • ThawTests/Settings/Models/HookScriptTests.swift
  • ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileApplyInFlightFlagTests.swift
  • ThawTests/Settings/Models/ProfileDecodingDefaultsTests.swift
  • ThawTests/Settings/Models/ProfileEntityTests.swift
  • ThawTests/Settings/Models/ProfileExportBundleTests.swift
  • ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeepTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeleteTests.swift
  • ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileManagerRearmGateTests.swift
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift
  • ThawTests/Settings/Models/ProfileTests.swift
  • ThawTests/Settings/Models/RehideStrategyTests.swift
  • ThawTests/Settings/Models/SectionDividerStyleTests.swift
  • ThawTests/Settings/Models/SettingsResetterTests.swift
  • ThawTests/Settings/Search/SearchIndexTests.swift
  • ThawTests/Settings/Search/SearchModelTests.swift
  • ThawTests/Settings/Search/SettingsSearchNavigationTests.swift
  • ThawTests/Settings/URI/IntegrationURIContractTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerGetTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTailTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTests.swift
  • ThawTests/Settings/URI/SettingsURIParserTests.swift
  • ThawTests/SettingsResetterTests.swift
  • ThawTests/SettingsURIHandlerTests.swift
  • ThawTests/Shared/CodeSigningInfoTests.swift
  • ThawTests/Shared/DiagnosticLoggerFileTests.swift
  • ThawTests/Shared/DiagnosticLoggerTests.swift
  • ThawTests/Shared/MarkerPairResolverTests.swift
  • ThawTests/Shared/SharedExtensionsTests.swift
  • ThawTests/Shared/WindowInfoDecodingTests.swift
  • ThawTests/Shared/WindowInfoDerivedTests.swift
  • ThawTests/Shared/WindowInfoTests.swift
  • ThawTests/SharedExtensionsTests.swift
  • ThawTests/Support/MenuBarTestFixtures.swift
  • ThawTests/Support/ScratchDefaults.swift
  • ThawTests/Utilities/CGImageAnalysisTests.swift
  • ThawTests/Utilities/CGImageDetachedCopyTests.swift
  • ThawTests/Utilities/ConcurrencyHelpersTests.swift
  • ThawTests/Utilities/CustomTooltipPanelTests.swift
  • ThawTests/Utilities/DefaultsKeyTests.swift
  • ThawTests/Utilities/ExtensionsCoverageTests.swift
  • ThawTests/Utilities/ExtensionsGraphicsTests.swift
  • ThawTests/Utilities/ExtensionsRemainderTests.swift
  • ThawTests/Utilities/ExtensionsTests.swift
  • ThawTests/Utilities/HookRunnerTests.swift
  • ThawTests/Utilities/IceSettingsImporterTailTests.swift
  • ThawTests/Utilities/IceSettingsImporterTests.swift
  • ThawTests/Utilities/LocalizedErrorWrapperTests.swift
  • ThawTests/Utilities/MigrationTests.swift
  • ThawTests/Utilities/MouseMovedThrottleTests.swift
  • ThawTests/Utilities/ObjectStorageTests.swift
  • ThawTests/Utilities/PredicatesTests.swift
  • ThawTests/Utilities/ShareableContentCacheTests.swift
  • ThawTests/Utilities/SimpleSemaphoreTests.swift
  • docs/ASSURANCE_CASE.md
  • scripts/coverage-report.py
  • scripts/swiftlint-inputs.xcfilelist
  • sonar-project.properties
💤 Files with no reviewable changes (29)
  • ThawTests/IceBarLocationTests.swift
  • ThawTests/DiagnosticLoggerTests.swift
  • ThawTests/HotkeyActionTests.swift
  • Thaw/MenuBar/Appearance/MissionControlDetector.swift
  • ThawTests/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarTestFixturesTests.swift
  • ThawTests/CaptureBoundsValidationTests.swift
  • ThawTests/MenuBarShapesTests.swift
  • ThawTests/MoveFailureBackoffTests.swift
  • ThawTests/MenuBarItemTagTests.swift
  • ThawTests/OnboardingMockupsTests.swift
  • ThawTests/ProfileManagerRearmGateTests.swift
  • ThawTests/OnboardingSlideTests.swift
  • ThawTests/ExtensionsTests.swift
  • ThawTests/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/DefaultsKeyTests.swift
  • ThawTests/LayoutResetErrorTests.swift
  • ThawTests/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBarItemServiceTests.swift
  • ThawTests/DisplayIceBarConfigurationTests.swift
  • ThawTests/RehideStrategyTests.swift
  • ThawTests/MenuBar/Items/GhostControlItemWindowTests.swift
  • ThawTests/PermissionTests.swift
  • ThawTests/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/ModifiersTests.swift
  • ThawTests/GeneralSettingsSnapshotTests.swift
  • ThawTests/SectionDividerStyleTests.swift
  • Thaw/MenuBar/MenuBarSection.swift
  • ThawTests/ProfileEntityTests.swift

Comment thread scripts/coverage-report.py
Comment thread scripts/coverage-report.py Outdated
Comment thread Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift
Comment thread Thaw/Settings/Models/FocusFilterIntent.swift Outdated
Comment thread Thaw/Utilities/SettingsURIHandler.swift
Comment thread ThawTests/Support/ScratchDefaults.swift
Comment thread ThawTests/Support/ScratchDefaults.swift
Comment thread ThawTests/Utilities/ExtensionsGraphicsTests.swift Outdated
Comment thread ThawTests/Utilities/ExtensionsGraphicsTests.swift Outdated
Comment thread ThawTests/Utilities/MigrationTests.swift

@coderabbitai coderabbitai Bot left a comment

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.

Review continued from previous batch...

Comment thread ThawTests/Settings/Models/GeneralSettingsSnapshotTests.swift Outdated
Comment thread ThawTests/Shared/DiagnosticLoggerTests.swift
Comment thread ThawTests/Utilities/ObjectStorageTests.swift

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (22)
Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift (1)

7015-7017: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Restore state after a cancelled profile apply.

This check only handles cancellation before armProfileState. A cancellation during Phase 6 reaches Phase 7. The code skips direct persistence, but it retains the new savedSectionOrder, pinning sets, and activeProfileLayout.

The deferred cache refresh can then save a partial layout. A later late-arrival re-sort can also apply the cancelled profile state.

Snapshot the prior state before arming. Use an apply token to restore it only when the cancelled operation still owns the state. Do not schedule the deferred refresh after cancellation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 7015 - 7017,
Update the profile-apply flow surrounding armProfileState to snapshot the prior
savedSectionOrder, pinning sets, and activeProfileLayout and associate the
operation with an apply token; when cancellation is detected in Phase 6 or Phase
7, restore the snapshot only if that token still owns the state, prevent
late-arrival re-sorting from applying the cancelled profile, and skip scheduling
the deferred cache refresh.
ThawTests/Settings/Models/ExternalSettingsChangeTests.swift (1)

49-72: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard the continuation against a second resume, and isolate the observer from other suites.

The handler resumes the continuation on every delivered change. Two conditions make a second delivery possible:

  • The subscription stays active until Line 71, after the confirmation block returns.
  • The observer listens on NotificationCenter.default, which every suite shares. This suite is not .serialized, and Swift Testing runs suites in parallel, so another suite that posts .settingsDidChangeViaURI reaches this handler.

A second delivery resumes the same CheckedContinuation twice, which traps at runtime, and it also over-counts the confirmation. Cancel the subscription inside the handler before resuming, and mark the suite .serialized.

🛡️ Proposed fix
 `@Suite`("External settings changes")
+@MainActor
+// Serialized: the observer listens on the shared `NotificationCenter.default`,
+// which other settings suites also post to.
 struct ExternalSettingsChangeTests {
     `@Test`("The observer ignores malformed notifications and delivers valid changes")
     func observerFiltersAndDelivers() async {
         var subscription: AnyCancellable?
 
         await confirmation("Delivered one valid settings change") { confirm in
             await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
                 subscription = NotificationCenter.observeSettingsChangesViaURI { change in
+                    guard change.key == "showOnHover" else { return }
+                    // Cancel first: a later delivery must not resume twice.
+                    subscription?.cancel()
+                    subscription = nil
                     `#expect`(change.key == "showOnHover")
                     `#expect`(change.boolValue == true)
                     confirm()
                     continuation.resume()
                 }

Apply .serialized on the @Suite attribute:

`@Suite`("External settings changes", .serialized)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/ExternalSettingsChangeTests.swift` around lines 49
- 72, Update observerFiltersAndDelivers so its notification handler cancels
subscription before resuming the continuation, preventing duplicate delivery
from resuming it twice or over-counting confirmation. Also add the .serialized
trait to the enclosing External settings changes `@Suite` declaration to isolate
this shared NotificationCenter test suite.
ThawTests/Settings/Models/GeneralSettingsTests.swift (1)

329-336: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert against Defaults.DefaultValue.showIceIcon.

These three tests verify that a change is ignored. They assert settings.showIceIcon as a bare truth value, which also encodes the current shipped default as a literal.

If Defaults.DefaultValue.showIceIcon becomes false, all three fail even though the ignore behaviour is still correct. The neighbouring tests at lines 348-349 already use the Defaults.DefaultValue form.

♻️ Proposed change
-        `#expect`(settings.showIceIcon, "\(key) must not reach any property")
+        `#expect`(
+            settings.showIceIcon == Defaults.DefaultValue.showIceIcon,
+            "\(key) must not reach any property"
+        )

Also applies to: 376-384, 397-405

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/GeneralSettingsTests.swift` around lines 329 - 336,
Update the three unknown-key external-change tests, including
externalChangeToUnknownKeyIsIgnored and the cases around it, to compare
settings.showIceIcon against Defaults.DefaultValue.showIceIcon instead of
asserting a hard-coded truth value. Preserve the existing ignored-change
verification and test structure.
ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift (1)

16-27: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -H 'ScratchDefaults.swift' --exec cat -n
rg -nP -C 3 'savedSectionOrder' Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
rg -nP 'UserDefaults\.standard' --type=swift -g '!ThawTests/**'

Repository: thaw-app/Thaw

Length of output: 19898


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Candidate files =="
fd -i 'Defaults.swift|HotkeysSettingsPersistenceTests.swift|ProfileManagerUpdateRearmIntegrationTests.swift|MenuBarItemManager.swift|ScratchDefaults.swift' .

echo
echo "== Defaults implementation/declarations =="
for f in $(fd -i 'Defaults.swift' .); do
  echo "-- $f --"
  wc -l "$f"
  sed -n '1,220p' "$f" | cat -n
done

echo
echo "== Hotkeys test section =="

f=$(fd -i 'HotkeysSettingsPersistenceTests.swift' . | head -n1)
echo "-- $f --"
wc -l "$f"
sed -n '1,120p' "$f" | cat -n

echo
echo "== Profile rearm test section =="
f=$(fd -i 'ProfileManagerUpdateRearmIntegrationTests.swift' . | head -n1)
echo "-- $f --"
wc -l "$f"
sed -n '1,160p' "$f" | cat -n
rg -n -C 4 'savedSectionOrder|Defaults|withScratchDefaults|`@Test`|func ' "$f"

echo
echo "== Hotkeys access through Defaults (no tests) =="
rg -n -C 3 'Defaults\.|`@objc` dynamic\.|\.hotkeys|HotkeyAction' --type=swift -g '!ThawTests/**' Thaw | head -n 200

Repository: thaw-app/Thaw

Length of output: 45576


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Remaining UserDefaults.standard references in non-test Swift files =="
rg -n -C 2 'UserDefaults\.standard' --type=swift -g '!ThawTests/**' || true

echo
echo "== ScratchDefaults import paths =="
rg -n 'ScratchDefaults|withScratchDefaults' --type=swift -g '!ThawTests/**' || true

Repository: thaw-app/Thaw

Length of output: 224


Route both test suites through withScratchDefaults. HotkeysSettingsPersistenceTests.bindingsRoundTrip() still snapshots and restores .hotkeys on the global store, and ProfileManagerRearmIntegrationTests seeds savedSectionOrder directly into UserDefaults.standard; move the hotkeys seed and both section-order seeds through a scratch store so the real defaults domain is not a crash/uninterruptible teardown hazard.

📍 Affects 2 files
  • ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift#L16-L27 (this comment)
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift#L27-L49
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift` around lines
16 - 27, The hotkey and section-order test seeds currently mutate global
defaults instead of using the scratch store. Update
HotkeysSettingsPersistenceTests.bindingsRoundTrip() and the two
savedSectionOrder setup sites in ProfileManagerUpdateRearmIntegrationTests to
run through withScratchDefaults, moving all seed, read, update, and cleanup
operations to that store and removing direct global snapshot/restore or
UserDefaults.standard writes.
ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift (1)

458-548: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the format-contract markers and the checked-in log fixture.
set -uo pipefail

echo "== format-contract markers =="
rg -n 'Format contract: parsed by ProfileLayoutLogReplayTests' -g '!ThawTests/**' || echo "NO MARKERS FOUND"

echo
echo "== production message texts the parser matches =="
for pat in \
  'Missing sourcePID for' \
  'applyProfileLayout: current ' \
  'Profile layout Phase 1: ahCtrlUID=' \
  'Profile layout Phase 1: desiredHidden=' \
  'Profile layout Phase 1: desiredAH=' \
  'Profile layout Phase 1: desiredVisible=' \
  'Profile layout: planUnmanagedPlacement' \
  'Notch overflow budget:' \
  'notch overflow; ' ; do
  printf '\n--- %s ---\n' "$pat"
  rg -n --fixed-strings "$pat" -g '!ThawTests/**' || echo "NOT FOUND IN PRODUCTION SOURCES"
done

echo
echo "== fixture =="
rg -n 'enum LittleSnitchOrphanLog|LittleSnitchOrphanLog' -g '*.swift' | head -20

Repository: thaw-app/Thaw

Length of output: 198


🏁 Script executed:

#!/bin/bash
# Description: Inspect the parser, surrounding test fixtures, and relevant production references.
set -uo pipefail

echo "== candidate file list =="
git ls-files | rg 'ProfileLayoutLogReplayTests|LittleSnitchOrphanLog|ProfileLayout|Layout' | head -200

echo
echo "== parser file outline/size =="
FILE="$(git ls-files | rg 'ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift$' | head -1)"
echo "FILE=$FILE"
if [ -n "$FILE" ]; then
  wc -l "$FILE"
  sed -n '420,570p' "$FILE" | cat -n
fi

echo
echo "== all references to parser and fixture =="
rg -n 'ProfileLayoutLogReplayTests\.parse|parse\(_:\)|LittleSnitchOrphanLog|Profile layout Phase 1:|Missing sourcePID|planUnmanagedPlacement|notch overflow|applyProfileLayout: current' -g '*.swift' | head -300

Repository: thaw-app/Thaw

Length of output: 9646


Add the missing format-contract markers before relying on this parser.

The LittleSnitchOrphanLog fixture is present, but none of the production call sites carry the documented // Format contract: parsed by ProfileLayoutLogReplayTests.parse(_:) marker that this parser depends on. Add the marker or remove this contract expectation to keep log-message rewording from producing silent parity errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift` around lines 458
- 548, Add the documented `// Format contract: parsed by
ProfileLayoutLogReplayTests.parse(_:)` marker to every production `diagLog` call
site emitting messages matched by `ProfileLayoutLogReplayTests.parse(_:)`,
including the `LittleSnitchOrphanLog` path. Alternatively, remove the parser’s
marker expectation, but preserve an explicit mechanism that prevents rewording
these production log formats from silently breaking fixture parity.
ThawTests/Settings/Models/ProfileManagerCRUDTests.swift (1)

437-488: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract the profile fixture and the manifest-seeding helper into ThawTests/Support. Four suites each carry a private copy of the same ~50-line ProfileContent fixture, and two carry a byte-identical seedManifest. ProfileContent, GeneralSettingsSnapshot, and AdvancedSettingsSnapshot are memberwise-initialized, so every field added to any of them breaks all four copies at once, and a copy that is updated differently from the others silently changes what its suite asserts. ThawTests/Support/MenuBarTestFixtures.swift already exists in this PR as the home for shared fixtures.

  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift#L437-L488: move makeProfile(named:) into ThawTests/Support as the single fixture factory, parameterized on name and on the fields the other suites vary (savedSectionOrder, displayConfigurations).
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift#L411-L435: move seedManifest(with:into:) into ThawTests/Support alongside it, and keep withManager here as the local seam over it.
  • ThawTests/Settings/Models/ProfileManagerDeleteTests.swift#L112-L192: delete both local helpers and call the shared factory and the shared seedManifest.
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift#L136-L198: delete makeProfile(savedSectionOrder:) and call the shared factory with the savedSectionOrder argument; keep writeProfile only if seeding without a manifest is required here.
  • ThawTests/Settings/Models/ProfileExportBundleTests.swift#L29-L80: delete makeTestProfile(name:) and call the shared factory.
📍 Affects 4 files
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift#L437-L488 (this comment)
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift#L411-L435
  • ThawTests/Settings/Models/ProfileManagerDeleteTests.swift#L112-L192
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift#L136-L198
  • ThawTests/Settings/Models/ProfileExportBundleTests.swift#L29-L80
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/ProfileManagerCRUDTests.swift` around lines 437 -
488, Extract the shared makeProfile(named:) fixture and seedManifest(with:into:)
helper into ThawTests/Support/MenuBarTestFixtures.swift, parameterizing the
factory with name, savedSectionOrder, and displayConfigurations. In
ThawTests/Settings/Models/ProfileManagerCRUDTests.swift ranges 437-488 and
411-435, move these helpers while retaining withManager as the local seam. In
ProfileManagerDeleteTests.swift range 112-192, remove both local copies and use
the shared helpers; in ProfileManagerUpdateRearmIntegrationTests.swift range
136-198, replace makeProfile(savedSectionOrder:) with the shared factory and
retain writeProfile only if needed for manifest-free seeding; in
ProfileExportBundleTests.swift range 29-80, remove makeTestProfile(name:) and
use the shared factory.
ThawTests/Settings/Search/SearchModelTests.swift (1)

56-65: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

This test depends on the host locale and on an exact translation string.

Line 62 asserts entry.titleText == "Launch at Login". titleText resolves against the running localization, so the test fails on a machine or CI runner whose preferred language is German. Line 64 also pins the exact German string, so any translation edit breaks the test. Compare the two bundle-resolved titles against each other, or derive the expected values from the same .strings resources instead of hard-coding them.

♻️ Suggested direction
-        `#expect`(entry.titleText == "Launch at Login")
         `#expect`(entry.localizedTitle(bundle: english) == "Launch at Login")
-        `#expect`(entry.localizedTitle(bundle: german) == "Beim Einloggen starten")
+        `#expect`(entry.localizedTitle(bundle: german) != entry.localizedTitle(bundle: english))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    `@Test`("Entry titles resolve to the running localization, not the English source")
    func entryTitlesAreLocalized() throws {
        let entry = try `#require`(SearchIndex.entries.first { $0.id == "general.launchAtLogin" })
        let german = try Self.localizationBundle("de")
        let english = try Self.localizationBundle("en")

        `#expect`(entry.localizedTitle(bundle: english) == "Launch at Login")
        `#expect`(entry.localizedTitle(bundle: german) != entry.localizedTitle(bundle: english))
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Search/SearchModelTests.swift` around lines 56 - 65,
Update entryTitlesAreLocalized so it does not assert the host-dependent
entry.titleText value or hard-code the German translation. Compare titles
resolved through the explicitly loaded English and German bundles, asserting
they differ and that the running localization result matches the appropriate
bundle only when the test can determine the active locale.
ThawTests/Settings/URI/IntegrationURIContractTests.swift (1)

53-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the combined requestId plus broadcast shape to droppyURIs.

Line 52 states the list holds every URL shape Droppy can emit. The test at line 148 parses thaw://get?key=all&callback=droppy://thaw-response&requestId=7&broadcast=true, which the list does not contain. That URL therefore never reaches the malformed guard at line 74 or the no-set guard at line 165.

Add the combined shape to the list and read it back in the test, so the fixture list stays the single source of truth.

♻️ Proposed change to keep the fixture list authoritative
 private let droppyURIs: [String] = [
     "thaw://get?key=all&callback=droppy://thaw-response",
     "thaw://get?key=all&callback=droppy%3A%2F%2Fthaw-response",
     "thaw://get?key=all&callback=droppy://thaw-response&requestId=7",
     "thaw://get?key=all&callback=droppy://thaw-response&broadcast=true",
+    "thaw://get?key=all&callback=droppy://thaw-response&requestId=7&broadcast=true",
     "thaw://toggle?key=autoRehide",
     "thaw://toggle?key=useIceBar",
 ]

Then reference that entry from the test instead of repeating the literal:

     `@Test`("Request correlation and broadcast are carried through")
     func requestIdAndBroadcast() throws {
-        let uri = "thaw://get?key=all&callback=droppy://thaw-response&requestId=7&broadcast=true"
+        let uri = try `#require`(droppyURIs.first { $0.contains("requestId=7&broadcast=true") })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

private let droppyURIs: [String] = [
    "thaw://get?key=all&callback=droppy://thaw-response",
    "thaw://get?key=all&callback=droppy%3A%2F%2Fthaw-response",
    "thaw://get?key=all&callback=droppy://thaw-response&requestId=7",
    "thaw://get?key=all&callback=droppy://thaw-response&broadcast=true",
    "thaw://get?key=all&callback=droppy://thaw-response&requestId=7&broadcast=true",
    "thaw://toggle?key=autoRehide",
    "thaw://toggle?key=useIceBar",
]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/URI/IntegrationURIContractTests.swift` around lines 53 -
60, Add the combined requestId and broadcast URL shape to the droppyURIs
fixture, then update the test around the existing parsing/no-set assertions to
read that URL from the fixture instead of repeating the literal. Keep droppyURIs
as the single source of truth for all Droppy-emitted URL shapes.
ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift (2)

29-46: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Two suites mutate the real defaults domain instead of the injectable scratch store. The shared root cause is that both suites drive production code against UserDefaults.standard and then try to undo the damage in deinit. The PR adds injectable Defaults storage, and SettingsURIHandlerCoverageTests already drives the same SettingsURIHandler entirely inside withScratchDefaults, so the seam is available for both. Snapshot/restore also races with parallel suites, which AdvancedSettingsTests documents at lines 32-36 and 358-361.

  • ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift#L29-L46: replace the persistentDomain/setPersistentDomain snapshot and deinit with withScratchDefaults around each test body. This suite performs the whole-domain snapshot that AdvancedSettingsTests warns can capture and then write back another suite's scratch values.
  • ThawTests/Settings/Models/AdvancedSettingsTests.swift#L69-L105: replace touchedKeys, savedDefaults, deinit, and clearStoredSettings with withScratchDefaults, which starts every test from an empty suite without touching the developer's settings.
📍 Affects 2 files
  • ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift#L29-L46 (this comment)
  • ThawTests/Settings/Models/AdvancedSettingsTests.swift#L69-L105
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift` around lines 29 -
46, Replace the real UserDefaults snapshot/restore lifecycle in
SettingsURIHandlerApplyTests.swift (lines 29-46) with withScratchDefaults
wrapping each test body. In AdvancedSettingsTests.swift (lines 69-105), remove
touchedKeys, savedDefaults, deinit, and clearStoredSettings, and use
withScratchDefaults so each test starts with an empty isolated store; update
both suites while preserving their existing test behavior.

443-452: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The test name says "Removing", but the body only adds.

whitelistChangesAreAnnounced asserts the notification posted by addToWhitelist. It never calls removeFromWhitelist, so the removal notification documented in Thaw/Utilities/SettingsURIHandler.swift (lines 339-351) stays untested. Rename the test to match the body, and add the removal case.

💚 Proposed fix
-    `@Test`("Removing an entry announces the change")
-    func whitelistChangesAreAnnounced() {
+    `@Test`("Adding an entry announces the change")
+    func whitelistAdditionIsAnnounced() {
         Defaults.removeObject(forKey: .settingsURIWhitelist)
 
         let posted = notifications(named: .settingsURIWhitelistDidChange) {
             SettingsURIHandler.addToWhitelist(bundleId: "com.example.Alpha")
         }
 
         `#expect`(!posted.isEmpty)
     }
+
+    `@Test`("Removing an entry announces the change")
+    func whitelistRemovalIsAnnounced() {
+        Defaults.removeObject(forKey: .settingsURIWhitelist)
+        SettingsURIHandler.addToWhitelist(bundleId: "com.example.Alpha")
+
+        let posted = notifications(named: .settingsURIWhitelistDidChange) {
+            SettingsURIHandler.removeFromWhitelist(bundleId: "com.example.Alpha")
+        }
+
+        `#expect`(!posted.isEmpty)
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    `@Test`("Adding an entry announces the change")
    func whitelistAdditionIsAnnounced() {
        Defaults.removeObject(forKey: .settingsURIWhitelist)

        let posted = notifications(named: .settingsURIWhitelistDidChange) {
            SettingsURIHandler.addToWhitelist(bundleId: "com.example.Alpha")
        }

        `#expect`(!posted.isEmpty)
    }

    `@Test`("Removing an entry announces the change")
    func whitelistRemovalIsAnnounced() {
        Defaults.removeObject(forKey: .settingsURIWhitelist)
        SettingsURIHandler.addToWhitelist(bundleId: "com.example.Alpha")

        let posted = notifications(named: .settingsURIWhitelistDidChange) {
            SettingsURIHandler.removeFromWhitelist(bundleId: "com.example.Alpha")
        }

        `#expect`(!posted.isEmpty)
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift` around lines 443 -
452, Rename whitelistChangesAreAnnounced to reflect that it tests adding an
entry, then add a separate removal test using
SettingsURIHandler.removeFromWhitelist after establishing a whitelisted bundle
ID. Assert that the removal operation posts settingsURIWhitelistDidChange,
preserving the existing add notification coverage.
ThawTests/Settings/URI/SettingsURIHandlerGetTests.swift (1)

47-64: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Do not snapshot and restore the whole persistent domain.

init captures the entire persistent domain, and deinit writes it back. .serialized orders cases inside this suite only; it does not serialize this suite against other suites.

ThawTests/Settings/Models/GeneralSettingsTests.swift:31-35 documents this exact hazard, and lines 308-311 of that file already weaken an assertion because of it: "a suite that restores a whole persistent domain can land during the suspension above and wipe the write this model just made." This suite is that suite.

Two consequences follow. Concurrently running suites lose scratch values that this restore overwrites, and values another suite wrote after this snapshot are silently reverted.

This PR adds injectable Defaults storage. Route the suite through withScratchDefaults for everything it writes through Defaults, and restore only the individual keys the handler writes straight to UserDefaults.standard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/URI/SettingsURIHandlerGetTests.swift` around lines 47 -
64, Remove the whole-domain snapshot and restoration from the test suite’s
init/deinit. Route all suite operations that write through injectable Defaults
storage via withScratchDefaults, and track and restore only the individual
UserDefaults.standard keys written directly by the handler, preserving values
changed by other suites.
ThawTests/Settings/URI/SettingsURIParserTests.swift (2)

21-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the nested Set suite.

struct Set shadows Swift.Set for every declaration nested inside SettingsURIParserTests. Nothing in the file uses Swift.Set today, so it compiles. A later edit that writes Set(...) in this file resolves to the test suite type and produces a confusing error. Name it SetRoute and keep the @Suite("set") display name.

♻️ Proposed refactor
     `@Suite`("set")
-    struct Set {
+    struct SetRoute {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    `@Suite`("set")
    struct SetRoute {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/URI/SettingsURIParserTests.swift` around lines 21 - 22,
Rename the nested test suite struct from Set to SetRoute within
SettingsURIParserTests, while preserving the `@Suite`("set") display name and
updating any references to the nested type if present.

35-46: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the exact route instead of two negative comparisons.

The stated intent is that key= yields a present but empty key. The current assertions only rule out two .malformed values. A parser that returned .unrecognized(host: "set"), or .set with the key dropped entirely, would still pass both checks.

Pair each URI with its expected route and compare directly.

♻️ Proposed refactor
         `@Test`("An empty key is present, not absent", arguments: [
-            "thaw://set?key=&value=true",
-            "thaw://toggle?key=",
+            ("thaw://set?key=&value=true", SettingsURIRoute.set(key: "", value: "true", displayUUID: nil)),
+            ("thaw://toggle?key=", SettingsURIRoute.toggle(key: "", displayUUID: nil)),
         ])
-        func emptyKeyIsNotMalformed(uri: String) throws {
-            let route = try parse(uri).route
-            `#expect`(route != .malformed(host: "set"))
-            `#expect`(route != .malformed(host: "toggle"))
+        func emptyKeyIsNotMalformed(uri: String, expected: SettingsURIRoute) throws {
+            `#expect`(try parse(uri).route == expected, "\(uri)")
         }

Adjust the route type name if it differs from SettingsURIRoute.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        /// `key=` supplies an empty value rather than omitting the parameter, so
        /// it parses and is rejected later by key validation. ThawCtl and Droppy
        /// both emit this shape, so it must not be treated as malformed.
        `@Test`("An empty key is present, not absent", arguments: [
            ("thaw://set?key=&value=true", SettingsURIRoute.set(key: "", value: "true", displayUUID: nil)),
            ("thaw://toggle?key=", SettingsURIRoute.toggle(key: "", displayUUID: nil)),
        ])
        func emptyKeyIsNotMalformed(uri: String, expected: SettingsURIRoute) throws {
            `#expect`(try parse(uri).route == expected, "\(uri)")
        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/URI/SettingsURIParserTests.swift` around lines 35 - 46,
Update emptyKeyIsNotMalformed in the SettingsURIParserTests route assertions to
pair each URI with its exact expected SettingsURIRoute, including a present
empty key, and compare the parsed route directly rather than using negative
malformed comparisons. Preserve coverage for both set and toggle URIs.
ThawTests/Shared/WindowInfoTests.swift (1)

18-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the fixture helper throw instead of trapping.

try! on line 42 traps if WindowInfo decoding ever fails. A trap aborts the whole test process, so a single coding-key change in WindowInfo would stop every other suite in the run instead of failing these tests.

Mark the helper throws and use #require, which is the pattern the sibling suites in this PR already use. Data(json.utf8) also removes the force unwrap on line 41.

♻️ Proposed change
-    ) -> WindowInfo {
+    ) throws -> WindowInfo {
@@
-        let data = json.data(using: .utf8)!
-        return try! JSONDecoder().decode(WindowInfo.self, from: data)
+        return try JSONDecoder().decode(WindowInfo.self, from: Data(json.utf8))
     }

Every call site then needs try, and the non-throwing tests need a throws annotation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Shared/WindowInfoTests.swift` around lines 18 - 43, Update
createWindowInfo to be throwing, construct the JSON payload with
Data(json.utf8), and replace the forced JSONDecoder decode with `#require`.
Propagate try to every createWindowInfo call site and mark non-throwing test
methods that invoke it as throws, following the sibling suite pattern.
ThawTests/Utilities/CGImageAnalysisTests.swift (1)

109-119: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the explicit color-space assertion.

Line 118 asserts only model == .rgb. The fallback chain in averageColor also produces an RGB space, through self.colorSpace, then displayP3, then device RGB. So this assertion passes even if the space argument is dropped, and the test does not verify what its name claims.

Compare the returned color space name to CGColorSpace.sRGB instead.

♻️ Proposed change to pin the explicit color space
         let average = try `#require`(image.averageColor(using: space))
-        `#expect`(average.colorSpace?.model == .rgb)
+        `#expect`(average.colorSpace?.name == CGColorSpace.sRGB)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    `@Test`("An explicit RGB color space is honored")
    func explicitColorSpaceIsUsed() throws {
        let image = try makeImage(width: 4, height: 4) { context in
            context.setFillColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1)
            context.fill(CGRect(x: 0, y: 0, width: 4, height: 4))
        }
        let space = try `#require`(CGColorSpace(name: CGColorSpace.sRGB))

        let average = try `#require`(image.averageColor(using: space))
        `#expect`(average.colorSpace?.name == CGColorSpace.sRGB)
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Utilities/CGImageAnalysisTests.swift` around lines 109 - 119,
Strengthen the assertion in explicitColorSpaceIsUsed by comparing the returned
average.colorSpace name directly with CGColorSpace.sRGB instead of checking only
its model. Keep the test’s explicit space argument and existing setup unchanged
so it verifies that averageColor(using:) honors the requested color space.
ThawTests/Utilities/CGImageDetachedCopyTests.swift (1)

19-59: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Replace the force unwraps in the helpers with try #require``.

Lines 30, 38, and 56 force-unwrap CGContext(...) and makeImage(). A nil result crashes the test process and takes down every other suite in the run, instead of failing one test with a message.

Every comparable helper added in this PR already uses try #require`` for these same calls. See ThawTests/Utilities/CGImageAnalysisTests.swift lines 227-240 and `ThawTests/Utilities/ExtensionsGraphicsTests.swift` lines 18-32.

Make both helpers throws and update the four call sites at lines 65, 75, 80, 134, and 140.

🛡️ Proposed fix for the helpers
-    private func makeSolidImage(width: Int, height: Int, color: (UInt8, UInt8, UInt8, UInt8)) -> CGImage {
+    private func makeSolidImage(width: Int, height: Int, color: (UInt8, UInt8, UInt8, UInt8)) throws -> CGImage {
         let colorSpace = CGColorSpaceCreateDeviceRGB()
         let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
-        let context = CGContext(
-            data: nil,
-            width: width,
-            height: height,
-            bitsPerComponent: 8,
-            bytesPerRow: 0,
-            space: colorSpace,
-            bitmapInfo: bitmapInfo
-        )!
+        let context = try `#require`(
+            CGContext(
+                data: nil,
+                width: width,
+                height: height,
+                bitsPerComponent: 8,
+                bytesPerRow: 0,
+                space: colorSpace,
+                bitmapInfo: bitmapInfo
+            ),
+            "Could not create a bitmap context"
+        )
         context.setFillColor(
             red: CGFloat(color.0) / 255,
             green: CGFloat(color.1) / 255,
             blue: CGFloat(color.2) / 255,
             alpha: CGFloat(color.3) / 255
         )
         context.fill(CGRect(x: 0, y: 0, width: width, height: height))
-        return context.makeImage()!
+        return try `#require`(context.makeImage(), "Could not snapshot the bitmap context")
     }
 
     /// Reads the raw RGBA bytes of `image` into an array.
-    private func pixelData(of image: CGImage) -> [UInt8] {
+    private func pixelData(of image: CGImage) throws -> [UInt8] {
         let width = image.width
         let height = image.height
         var data = [UInt8](repeating: 0, count: width * height * 4)
         let colorSpace = CGColorSpaceCreateDeviceRGB()
         let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
-        let context = CGContext(
-            data: &data,
-            width: width,
-            height: height,
-            bitsPerComponent: 8,
-            bytesPerRow: width * 4,
-            space: colorSpace,
-            bitmapInfo: bitmapInfo
-        )!
+        let context = try `#require`(
+            CGContext(
+                data: &data,
+                width: width,
+                height: height,
+                bitsPerComponent: 8,
+                bytesPerRow: width * 4,
+                space: colorSpace,
+                bitmapInfo: bitmapInfo
+            ),
+            "Could not create a bitmap context"
+        )
         context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
         return data
     }

Mark the three call-site tests throws and add try at lines 65, 75, 80, 134, and 140.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Utilities/CGImageDetachedCopyTests.swift` around lines 19 - 59,
Replace the force unwraps in makeSolidImage and pixelData with try `#require`, and
mark both helpers as throws. Update every call site in the affected tests to use
try, including the five references around lines 65, 75, 80, 134, and 140, and
mark their enclosing test methods as throws.
ThawTests/Utilities/ConcurrencyHelpersTests.swift (1)

77-80: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Poll for the cancellation instead of sleeping a fixed 200 ms.

The fixed sleep couples the assertion to scheduler latency. Under load on CI the losing arm can observe cancellation later than 200 ms, and the test fails intermittently. Poll until a deadline instead.

♻️ Proposed change
-        // The cancellation is delivered after withTimeout has already thrown,
-        // so give the losing arm a moment to observe it.
-        try await Task.sleep(for: .milliseconds(200))
-        `#expect`(observed.withLock { $0 })
+        // The cancellation is delivered after withTimeout has already thrown,
+        // so wait for the losing arm to observe it.
+        let deadline = ContinuousClock.now + .seconds(5)
+        while !observed.withLock({ $0 }), ContinuousClock.now < deadline {
+            try await Task.sleep(for: .milliseconds(20))
+        }
+        `#expect`(observed.withLock { $0 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        // The cancellation is delivered after withTimeout has already thrown,
        // so wait for the losing arm to observe it.
        let deadline = ContinuousClock.now + .seconds(5)
        while !observed.withLock({ $0 }), ContinuousClock.now < deadline {
            try await Task.sleep(for: .milliseconds(20))
        }
        `#expect`(observed.withLock { $0 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Utilities/ConcurrencyHelpersTests.swift` around lines 77 - 80,
Replace the fixed Task.sleep in the cancellation test with polling of observed
until it becomes true or a reasonable deadline expires. Use the existing
observed lock access, include a short interval between polls, and retain the
final expectation so the test tolerates scheduler latency without waiting
indefinitely.
ThawTests/Utilities/IceSettingsImporterTests.swift (1)

13-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Isolate this suite the same way IceSettingsImporterTailTests is isolated.

IceSettingsImporter writes through Defaults, and Defaults.store is process-wide (Thaw/Utilities/Defaults.swift:127-129). ThawTests/Utilities/IceSettingsImporterTailTests.swift:24-27 records this and therefore marks that suite .serialized and wraps every case in withScratchDefaults. This suite does neither.

The current fixtures happen to contain only an appearance key, so the import currently writes only through the injected closure. Any fixture that later adds a general, advanced, or hotkey key would write into the host application's real defaults domain and would race with other suites.

♻️ Proposed change
 `@Suite`("Ice settings importer")
 `@MainActor`
-struct IceSettingsImporterTests {
+@Suite("Ice settings importer", .serialized)
+struct IceSettingsImporterTests {
     `@Test`("A V1 appearance is converted and written as V2")
     func convertsV1Appearance() throws {
-        var oldConfiguration = MenuBarAppearanceConfigurationV1.defaultConfiguration
+        try withScratchDefaults { _ in
+            // existing body, indented one level
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Utilities/IceSettingsImporterTests.swift` around lines 13 - 15,
Update IceSettingsImporterTests to match the isolation used by
IceSettingsImporterTailTests: mark the suite as serialized and wrap every test
case’s Defaults-backed work in withScratchDefaults. Ensure all
IceSettingsImporter fixture imports execute against scratch defaults rather than
the process-wide host defaults.
ThawTests/Utilities/ShareableContentCacheTests.swift (1)

78-84: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace the sleep-based head starts with a causal signal.

Both concurrency cases establish caller ordering with a 20 ms sleep. .serialized removes contention from sibling cases in this suite. It does not remove contention from machine load on CI. If the second caller reaches content before the first registers its in-flight task, each caller starts its own fetch and the count == 1 assertions on Lines 89 and 120 fail without a product defect.

Make the ordering causal. Let the first fetch signal that it has begun, and let the test await that signal before starting the other callers. Then the head start no longer depends on elapsed time.

♻️ Sketch of a causal gate
+/// Signals that the shared fetch has started, so callers can join it
+/// without racing a wall-clock head start.
+private actor Gate {
+    private var continuations: [CheckedContinuation<Void, Never>] = []
+    private var isOpen = false
+
+    func open() {
+        isOpen = true
+        for continuation in continuations { continuation.resume() }
+        continuations.removeAll()
+    }
+
+    func wait() async {
+        if isOpen { return }
+        await withCheckedContinuation { continuations.append($0) }
+    }
+}
     `@Test`("Concurrent callers join the single in-flight fetch")
     func concurrentCallersJoinSingleInFlightFetch() async throws {
         let cache = ShareableContentCache<Int>()
         let invocationCount = Counter()
+        let started = Gate()
 
         `@Sendable` func slowFetch() async throws -> Int {
             await invocationCount.increment()
+            await started.open()
             try await Task.sleep(for: .milliseconds(150))
             return 42
         }
 
         async let first = cache.content(maxAge: .seconds(60), fetch: slowFetch)
-        // Give the first call a head start so it's the one that creates the
-        // in-flight task, then join it with two more concurrent callers.
-        try await Task.sleep(for: .milliseconds(20))
+        // The first call has created the in-flight task; now join it.
+        await started.wait()
         async let second = cache.content(maxAge: .seconds(60), fetch: slowFetch)
         async let third = cache.content(maxAge: .seconds(60), fetch: slowFetch)

Apply the same gate on Line 110 before cancelledTask.cancel().

Also applies to: 110-111

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Utilities/ShareableContentCacheTests.swift` around lines 78 - 84,
Replace the 20 ms sleeps in both concurrency cases with a causal synchronization
gate: have the first fetch signal when it begins, then await that signal before
starting the subsequent callers. Apply the same gate before
cancelledTask.cancel() in the second case, preserving the existing fetch-count
and cancellation assertions without relying on elapsed time.
ThawTests/Utilities/SimpleSemaphoreTests.swift (1)

66-105: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The race hammer stops racing after the first iteration and inflates the permit count.

SimpleSemaphore.signal() increments a permit counter. Trace the loop with that in mind, starting from value == 0 after Line 66:

  • Iteration 1: releaseTask signals (value == 1). The waiter acquires (value == 0) or times out (value == 1).
  • Acquired branch: Line 94 signals again, so value == 1 at the end of the iteration.
  • Timeout branch: no correction runs, so value == 1 at the end of the iteration.

Every later iteration therefore starts with a permit already available. wait(timeout: .milliseconds(1)) then succeeds immediately and never races the release. The net change per iteration is +1 (one signal from releaseTask, one wait, one extra signal), so value reaches roughly 500 by the end and the final check at Line 103 cannot fail.

The two branches are inverted. When the waiter acquires, it holds the permit that releaseTask restored, so nothing more is needed. When the waiter times out, the restored permit is unheld and must be consumed to return to the held state.

🐛 Proposed fix
             let acquired = await waitTask
             _ = await releaseTask
 
-            if acquired {
-                // The waiter won the race and now holds the permit that the
-                // release() call restored; give it back for the next
-                // iteration.
-                await semaphore.signal()
-            }
-            // If the waiter timed out, reconciliation inside wait(timeout:)
-            // already gave back any permit it might have raced into, and
-            // release() already restored the held permit, so the semaphore
-            // is back to "held by nobody, value == 1" either way.
+            // Restore the "held by exactly one caller" state the next
+            // iteration races against.
+            //
+            // If the waiter acquired, it already holds the permit that
+            // signal() restored, so there is nothing to do. If it timed out,
+            // reconciliation gave back any permit it raced into and the
+            // restored permit is now unheld, so take it back.
+            if !acquired {
+                try await semaphore.wait(timeout: .seconds(1))
+            }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        try await semaphore.wait(timeout: .milliseconds(50))

        for _ in 0 ..< 500 {
            async let releaseTask: Void = {
                let delay = Duration.nanoseconds(Int.random(in: 0 ... 2_000_000)) // 0-2 ms
                try? await Task.sleep(for: delay)
                await semaphore.signal()
            }()

            async let waitTask: Bool = {
                do {
                    try await semaphore.wait(timeout: .milliseconds(1))
                    return true
                } catch is SimpleSemaphore.TimeoutError {
                    return false
                } catch {
                    Issue.record("Unexpected error: \(error)")
                    return false
                }
            }()

            let acquired = await waitTask
            _ = await releaseTask

            // Restore the "held by exactly one caller" state the next
            // iteration races against.
            //
            // If the waiter acquired, it already holds the permit that
            // signal() restored, so there is nothing to do. If it timed out,
            // reconciliation gave back any permit it raced into and the
            // restored permit is now unheld, so take it back.
            if !acquired {
                try await semaphore.wait(timeout: .seconds(1))
            }
        }

        // Final sanity check: the semaphore must be fully available.
        try await semaphore.wait(timeout: .seconds(1))
        await semaphore.signal()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Utilities/SimpleSemaphoreTests.swift` around lines 66 - 105,
Correct the reconciliation logic in the semaphore race loop: in the acquired
branch after awaiting waitTask, do not call semaphore.signal(), while in the
timeout branch consume the restored permit with semaphore.wait(timeout:) so each
iteration returns to the held state. Update the surrounding comments to match
this behavior and preserve the final sanity check.
ThawTests/Settings/Models/AutomationSettingsTests.swift (1)

13-45: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Three suites still snapshot the real UserDefaults domain instead of using withScratchDefaults. All three predate the injectable Defaults.store this PR adds, and each documents that Defaults is hardcoded to .standard. Each therefore mutates the developer's real preferences and restores them from an init/deinit snapshot. .serialized orders only the cases inside one suite, so a parallel suite that touches the same keys can read the intermediate state or have its write discarded by the restore. ThawTests/Settings/Models/SettingsResetterTests.swift lines 25-30 record this pattern as the cause of an intermittent cross-suite failure.

  • ThawTests/Settings/Models/AutomationSettingsTests.swift#L13-L45: delete the savedDefaults snapshot, the init wipe, and the deinit restore; wrap each case body in withScratchDefaults.
  • ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift#L28-L55: delete the snapshot and restore; move makeManager and every assertion inside withScratchDefaults, including the reload cases at lines 253-274.
  • ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift#L17-L46: delete the snapshot and restore; wrap each case in withScratchDefaults and correct the doc comment that states the store is hardcoded.
📍 Affects 3 files
  • ThawTests/Settings/Models/AutomationSettingsTests.swift#L13-L45 (this comment)
  • ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift#L28-L55
  • ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift#L17-L46
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/AutomationSettingsTests.swift` around lines 13 -
45, Replace real UserDefaults snapshot/restore isolation with
withScratchDefaults in ThawTests/Settings/Models/AutomationSettingsTests.swift
(lines 13-45), removing savedDefaults, init wiping, and deinit restoration and
wrapping each test case body. Apply the same change in
ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift (lines
28-55), moving makeManager and all assertions, including reload cases, inside
withScratchDefaults. In
ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift (lines
17-46), remove snapshot/restore logic, wrap each case in withScratchDefaults,
and update the doc comment to reflect the injectable store.
ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift (1)

66-100: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect CI test invocation for the presence of a window server / display.
set -euo pipefail

fd --extension yml --extension yaml . .github/workflows --exec rg -n 'xcodebuild|test-without-building|runs-on|destination' {}

Repository: thaw-app/Thaw

Length of output: 1384


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'DisplaySettingsManagerGlobalFallbackTests.swift|DisplaySettingsManagerMutationTests.swift|Bridging.*' . | sed 's#^\./##'

echo
echo "== target workflow xcodebuild lines =="
fd --extension yml --extension yaml . .github/workflows -x sh -c 'echo "--- $1"; rg -n -C 2 "xcodebuild test|-destination '\''platform=macOS'\''" "$1"' sh {}

echo
echo "== reference test snippets =="
for f in $(fd 'DisplaySettingsManagerGlobalFallbackTests.swift|DisplaySettingsManagerMutationTests.swift' .); do
  echo "--- $f"
  rg -n -C 3 "resolvedDisplayUsesExplicitConfiguration|resolvedDisplayWithoutOverrideFallsBackToGlobalConfiguration|activeDisplayWithoutOverrideFallsBackToGlobalConfiguration|activeDisplayUsesExplicitConfiguration|getDisplayUUIDString|getActiveMenuBarDisplayUUID|return early|guard" "$f" || true
done

Repository: thaw-app/Thaw

Length of output: 1828


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== display bridge declarations/usages =="
rg -n "getDisplayUUIDString|getActiveMenuBarDisplayUUID|func getDisplayUUIDString|func getActiveMenuBarDisplayUUID" . --glob '!**/.build/**' --glob '!**/.git/**'

echo
echo "== Settings/Bridging related files =="
fd 'Bridging|Display.*Bridge|CG.*|Display.*' . --type f | sed 's#^\./##' | head -100

Repository: thaw-app/Thaw

Length of output: 6303


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Bridging.swift display helpers =="
sed -n '108,152p' Shared/Bridging/Bridging.swift

echo
echo "== Global fallback test relevant lines =="
sed -n '60,102p' ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift

echo
echo "== Mutation test relevant lines =="
sed -n '138,152p' ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift

echo
echo "== Test workflow snippets around xcodebuild test =="
sed -n '100,160p' .github/workflows/ci.yml

Repository: thaw-app/Thaw

Length of output: 6893


Skip fallback-display tests when the display UUID cannot be resolved.

Bridging.getDisplayUUIDString(for:) and Bridging.getActiveMenuBarDisplayUUID() return nil when no display or no menu-bar display is available, so try #require(...) turns an unavailable display into a test failure. DisplaySettingsManagerMutationTests.swift guards the same lookups with early returns. Gate these cases with a trait or return early so runtimes without an attached display do not fail coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift`
around lines 66 - 100, The fallback tests that depend on display identity should
skip when UUID lookup is unavailable instead of failing via `#require`. Update
resolvedDisplayWithoutOverrideFallsBackToGlobalConfiguration and
activeDisplayUsesExplicitConfiguration to guard their respective Bridging
lookups and return early (or apply the project’s existing skip trait), matching
the early-return pattern in DisplaySettingsManagerMutationTests; leave tests
with valid UUIDs unchanged.

diazdesandi added a commit that referenced this pull request Jul 31, 2026
Coverage came back 0% on PR #861 with every one of 5,606 lines reported
uncovered -- the analysis matched no coverage data at all.

xcresultparser emits absolute paths from the macOS runner
(/Users/runner/work/Thaw/Thaw/...), and the Sonar scan runs on a separate
Linux runner that matches coverage entries to sources by path. Every entry
therefore failed to resolve. scripts/normalize-coverage.py exists to rewrite
those to repo-relative and had simply stopped being called; the script was
still in the tree, just orphaned.

Verified against the actual artifact from run 30652941215 rather than in
principle: of 389 file entries, the script keeps 309 repo-relative and drops
80 build artifacts, leaving zero absolute paths.

This is the same regression sonar-project.properties documented once before,
when the report path moved into the workflow and the normalize step went with
it. The gated `-Dsonar.coverageReportPaths` arg is deliberate and stays as it
is -- docs-only scans would otherwise point at a file the build never
produced. Only the normalize step was missing.

The script exits non-zero if no entry resolves, so a future break fails the
job instead of silently publishing 0% again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift (1)

5169-5208: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Sort items before selecting return anchors.

items is in Window Server order, not physical menu-bar order. firstIndex, eligibleIndices, and LayoutSolver.returnAnchors therefore select sequence neighbors that can be unrelated to the item's actual left and right neighbors.

This can return an item beside an arbitrary same-section item. The resulting order can persist after relaunch. Sort by leading edge before deriving indices and destinations.

Proposed fix
 private func getReturnDestination(
     for item: MenuBarItem,
     in items: [MenuBarItem],
     section: MenuBarSection.Name
 ) -> (destination: MoveDestination, fallbackNeighbor: (tag: MenuBarItemTag, pid: pid_t)?)? {
-    guard let index = items.firstIndex(matching: item.tag) else {
+    let orderedItems = MenuBarItem.sortByLeadingEdgeThenIdentifier(items)
+    guard let index = orderedItems.firstIndex(matching: item.tag) else {
         return nil
     }

-    let eligibleIndices = Set(items.indices.filter { candidateIndex in
-        let candidate = items[candidateIndex]
+    let eligibleIndices = Set(orderedItems.indices.filter { candidateIndex in
+        let candidate = orderedItems[candidateIndex]
         guard candidate.canBeHidden else {
             return false
         }
         return itemCache.address(for: candidate.tag)?.section == section
     })

     let anchors = LayoutSolver.returnAnchors(
         forIndex: index,
-        itemCount: items.count,
+        itemCount: orderedItems.count,
         eligibleIndices: eligibleIndices
     )

     if let successor = anchors.successor {
         let fallback = anchors.predecessor.map { predecessor in
-            let neighbor = items[predecessor]
+            let neighbor = orderedItems[predecessor]
             return (neighbor.tag, neighbor.sourcePID ?? neighbor.ownerPID)
         }
-        return (.leftOfItem(items[successor]), fallback)
+        return (.leftOfItem(orderedItems[successor]), fallback)
     }
     if let predecessor = anchors.predecessor {
-        return (.rightOfItem(items[predecessor]), nil)
+        return (.rightOfItem(orderedItems[predecessor]), nil)
     }

-    return sectionDestination(for: section, in: items).map { ($0, nil) }
+    return sectionDestination(for: section, in: orderedItems).map { ($0, nil) }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 5169 - 5208,
Update getReturnDestination to derive anchor indices from items ordered by each
item’s physical leading edge rather than Window Server order. Sort the items
before firstIndex, eligibleIndices, LayoutSolver.returnAnchors, and destination
selection, while preserving the existing section filtering and fallback
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 5169-5208: Update getReturnDestination to derive anchor indices
from items ordered by each item’s physical leading edge rather than Window
Server order. Sort the items before firstIndex, eligibleIndices,
LayoutSolver.returnAnchors, and destination selection, while preserving the
existing section filtering and fallback behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2e90ee32-88ec-430d-a96d-c92d771b58af

📥 Commits

Reviewing files that changed from the base of the PR and between 1b67f9e and aaf5258.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • MenuBarItemService/SourcePIDCache.swift
  • Thaw/MenuBar/MenuBarItems/LayoutSolver.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
  • scripts/swiftlint-inputs.xcfilelist
  • sonar-project.properties

diazdesandi added a commit that referenced this pull request Aug 1, 2026
Coverage came back 0% on PR #861 with every one of 5,606 lines reported
uncovered -- the analysis matched no coverage data at all.

xcresultparser emits absolute paths from the macOS runner
(/Users/runner/work/Thaw/Thaw/...), and the Sonar scan runs on a separate
Linux runner that matches coverage entries to sources by path. Every entry
therefore failed to resolve. scripts/normalize-coverage.py exists to rewrite
those to repo-relative and had simply stopped being called; the script was
still in the tree, just orphaned.

Verified against the actual artifact from run 30652941215 rather than in
principle: of 389 file entries, the script keeps 309 repo-relative and drops
80 build artifacts, leaving zero absolute paths.

This is the same regression sonar-project.properties documented once before,
when the report path moved into the workflow and the normalize step went with
it. The gated `-Dsonar.coverageReportPaths` arg is deliberate and stays as it
is -- docs-only scans would otherwise point at a file the build never
produced. Only the normalize step was missing.

The script exits non-zero if no entry resolves, so a future break fails the
job instead of silently publishing 0% again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Signed-off-by: diazdesandi <diazdesandi@proton.me>
@diazdesandi
diazdesandi force-pushed the test/swift-testing-migration-and-coverage branch from 4155e4f to 652500f Compare August 1, 2026 00:17
diazdesandi and others added 6 commits July 31, 2026 17:22
Coverage came back 0% on PR #861 with every one of 5,606 lines reported
uncovered -- the analysis matched no coverage data at all.

xcresultparser emits absolute paths from the macOS runner
(/Users/runner/work/Thaw/Thaw/...), and the Sonar scan runs on a separate
Linux runner that matches coverage entries to sources by path. Every entry
therefore failed to resolve. scripts/normalize-coverage.py exists to rewrite
those to repo-relative and had simply stopped being called; the script was
still in the tree, just orphaned.

Verified against the actual artifact from run 30652941215 rather than in
principle: of 389 file entries, the script keeps 309 repo-relative and drops
80 build artifacts, leaving zero absolute paths.

This is the same regression sonar-project.properties documented once before,
when the report path moved into the workflow and the normalize step went with
it. The gated `-Dsonar.coverageReportPaths` arg is deliberate and stays as it
is -- docs-only scans would otherwise point at a file the build never
produced. Only the normalize step was missing.

The script exits non-zero if no entry resolves, so a future break fails the
job instead of silently publishing 0% again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Signed-off-by: diazdesandi <diazdesandi@proton.me>
- Roll back in-memory profile state when a profile apply is cancelled
  mid-flight, guarded by an apply token so a late-arriving cancellation
  cannot clobber the state armed by the newer apply that displaced it;
  the cancelled apply also skips the deferred cache refresh.
- Replace the duplicated "FocusFilterRequestedProfileID" literals with a
  shared Defaults.Key (raw value unchanged).
- Extract validatedDisplayConfiguration(forUUID:) in SettingsURIHandler
  and use it from both per-display set and toggle paths.
- Move the misplaced doc comment off diagLog so it precedes
  getMenuBarItemWindows, and drop the inaccurate duplicate before
  assignStableInstanceIndices(to:using:).
- Add CaseIterable to DiagnosticLogger.Level.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
- Serialize withScratchDefaults through a FIFO actor-backed mutex and
  document the non-recursive sync lock; migrate the Automation,
  DisplaySettingsManager, Hotkeys, AdvancedSettings, IceSettingsImporter,
  ProfileManagerUpdateRearm, and SettingsURIHandler Apply/Get/Tail
  suites off real-UserDefaults snapshot/restore onto the scratch store.
- Extract shared fixtures: ProfileTestFixtures (makeProfile,
  seedManifest, snapshot/content factories, temp-dir and codec helpers)
  and GraphicsTestFixtures (premultiplied-first 32-bit-LE makeCanvas,
  makeOpaqueImage), removing the per-suite duplicates.
- Replace tautological assertions with real ones: localized section
  names, parsed URI routes, registered-default comparisons, sRGB color
  space name, menu-agent auto-detected source name.
- Harden flaky-prone tests: .timeLimit traits on the permission-polling
  and empty-profile-apply tests, skip-instead-of-fail for display- and
  window-server-dependent tests, polling and causal gates in place of
  sleeps in the concurrency/cache tests, corrected semaphore permit
  accounting, try #require in place of force unwraps, non-empty guards
  before iterating stored configurations, and snapshot/restore of the
  unresponsive-items build key in the failure-ledger suite.
- Cover the alwaysHiddenSectionResolved and hiddenSectionHasRoom gates
  in shouldPersistSavedOrder; drop the three case-existence
  LayoutResetError tests; rename the parser's nested Set suite to
  SetRoute; keep droppyURIs the single source of truth for Droppy URL
  shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
…port

Replace the hand-rolled option loop with argparse (invalid --top now
produces a usage error), and make dir/*.swift exclusion patterns match
only direct children while recursive ** patterns still go through
fnmatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
The PR's new-code coverage sat just under the 80% gate, almost entirely
because performSetup(with:) — which needs a live AppState and is
therefore untestable by convention — contained three copies of the
observer -> AsyncStream -> debounce wiring.

Extract the wiring into a shared debouncedNotificationTask helper in
ConcurrencyHelpers (observer registered before the task starts, removed
by its defer) and move the three call sites into an internal
startObservationTasks() that unit tests can drive without an AppState.
New tests cover burst coalescing, repeated bursts, cancellation, and
the re-setup path cancelling the previous tasks.

Lifts new-code coverage of measured files from ~73% to ~92% locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
getReturnDestination fed LayoutSolver.returnAnchors indices from the
item list as enumerated, which is Window Server order — so the
"successor" anchor could be a physically distant item and returning a
temporarily shown item could scramble its position. Sort the items by
leading edge before deriving the index, eligible set, anchors, and
destination; section filtering and the section-boundary fallback are
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: diazdesandi <diazdesandi@proton.me>
@diazdesandi
diazdesandi force-pushed the test/swift-testing-migration-and-coverage branch from 652500f to f3d078b Compare August 1, 2026 00:23

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
ThawTests/Settings/Models/ExternalSettingsChangeTests.swift (1)

49-50: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Bound the asynchronous wait.

If the observer does not receive the valid notification, withCheckedContinuation does not resume. The test then hangs instead of failing. Add .timeLimit(.minutes(1)) to the @Test on Line 49.

Proposed fix
-    `@Test`("The observer ignores malformed notifications and delivers valid changes")
+    `@Test`(
+        "The observer ignores malformed notifications and delivers valid changes",
+        .timeLimit(.minutes(1))
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/ExternalSettingsChangeTests.swift` around lines 49
- 50, Add the .timeLimit(.minutes(1)) trait to the `@Test` annotation for
observerFiltersAndDelivers so the asynchronous test fails after a bounded wait
instead of hanging when the continuation is not resumed.
ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift (1)

29-53: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Route this suite through withScratchDefaults instead of save-and-restore.

The .unresponsiveMenuBarItemsBuild snapshot closes the gap raised earlier. The suite still writes to the real Defaults store and depends on init/deinit to put the user's values back.

Two problems remain with that approach:

  • A crash or a forced test-run stop leaves the developer's ledger keys in the state a test left them.
  • Another suite can install a scratch store through withScratchDefaults while this suite runs, because .serialized orders tests inside one suite only. The reads at Lines 94, 164, 176, and 232 then observe someone else's store.

ThawTests/Support/ScratchDefaults.swift documents the intended target state: every Defaults-touching suite routes through the helper. Wrapping each test body removes both the snapshot fields and the interference window.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift` around lines 29
- 53, Refactor MenuBarItemFailureLedgerTests to run every Defaults-touching test
body through withScratchDefaults, including the reads around the referenced test
sections. Remove the savedValue/savedBuildValue properties and init/deinit
save-and-restore logic, and ensure each test uses the isolated scratch store for
its entire execution.
ThawTests/Settings/Models/AdvancedSettingsTests.swift (1)

62-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated notification-posting helper into a shared test fixture. postExternalChanges/postExternalChange are implemented identically, with identical documentation, in both suites. The shared root cause is a missing shared test fixture for "post to NotificationCenter.default and wait for main-queue delivery."

  • ThawTests/Settings/Models/AdvancedSettingsTests.swift#L62-L80: remove the local postExternalChanges/postExternalChange pair and call a shared helper instead.
  • ThawTests/Settings/Models/GeneralSettingsTests.swift#L111-L129: remove the local postExternalChanges/postExternalChange pair and call the same shared helper.

Move the pair into a shared file under ThawTests/Support/ (for example alongside ThawTests/Support/ScratchDefaults.swift), the same treatment already applied to the duplicated bitmap-context builder in ThawTests/Support/GraphicsTestFixtures.swift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/AdvancedSettingsTests.swift` around lines 62 - 80,
The notification-posting helpers are duplicated across both settings test
suites. Create a shared test fixture under ThawTests/Support containing
postExternalChanges and postExternalChange, preserving
NotificationCenter.default posting and main-queue delivery waiting; remove both
local helper pairs from ThawTests/Settings/Models/AdvancedSettingsTests.swift
lines 62-80 and ThawTests/Settings/Models/GeneralSettingsTests.swift lines
111-129, and update each suite to call the shared helpers.
ThawTests/Shared/WindowInfoTests.swift (1)

18-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the fixture JSON with JSONSerialization instead of string interpolation.

Lines 28-29 interpolate title and ownerName into quoted JSON string literals without escaping. A caller that passes a title containing a double quote or a backslash produces invalid JSON. The test then fails with a DecodingError instead of a clear assertion failure, and the cause is not obvious from the message.

Current callers pass only simple identifiers, so nothing fails today. WindowInfo.title carries arbitrary window titles in production, so a test for a quoted title is a realistic addition. Constructing the object graph and serializing it removes the escaping concern and shortens the helper.

♻️ Proposed refactor
     ) throws -> WindowInfo {
-        // CGRect encodes as nested arrays: [[x,y],[width,height]]
-        let titleJSON = title.map { "\"\($0)\"" } ?? "null"
-        let ownerNameJSON = ownerName.map { "\"\($0)\"" } ?? "null"
-        let json = """
-        {
-            "windowID": \(windowID),
-            "ownerPID": \(ownerPID),
-            "bounds": [[\(bounds.origin.x), \(bounds.origin.y)], [\(bounds.size.width), \(bounds.size.height)]],
-            "layer": \(layer),
-            "title": \(titleJSON),
-            "ownerName": \(ownerNameJSON),
-            "isOnScreen": \(isOnScreen)
-        }
-        """
-        let data = Data(json.utf8)
+        // CGRect encodes as nested arrays: [[x,y],[width,height]]
+        let object: [String: Any] = [
+            "windowID": windowID,
+            "ownerPID": ownerPID,
+            "bounds": [
+                [bounds.origin.x, bounds.origin.y],
+                [bounds.size.width, bounds.size.height],
+            ],
+            "layer": layer,
+            "title": title ?? NSNull(),
+            "ownerName": ownerName ?? NSNull(),
+            "isOnScreen": isOnScreen,
+        ]
+        let data = try JSONSerialization.data(withJSONObject: object)
         return try JSONDecoder().decode(WindowInfo.self, from: data)
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Shared/WindowInfoTests.swift` around lines 18 - 43, Update
createWindowInfo to construct the fixture as a Swift object graph and serialize
it with JSONSerialization instead of interpolating title and ownerName into JSON
text. Preserve the existing field values, including optional nil handling and
nested bounds structure, so titles containing quotes or backslashes produce
valid JSON for WindowInfo decoding.
Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift (1)

132-148: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Collapse the duplicated initializer call and assert the parallel-array assumption.

Both branches at Lines 135-147 call the same initializer with the same arguments. In the else branch, nil is exactly items[itemIndex].sourcePID. The branch adds no behavior.

The function also depends on an unstated contract: items[i] must describe windows[i]. Both current callers satisfy it, because each builds items as an order-preserving 1:1 map of windows. A future caller that filters items would either rebuild an item from the wrong window or trap on windows[itemIndex]. Add a precondition so that contract fails loudly instead of silently mismatching.

♻️ Proposed refactor
+        precondition(
+            items.count == windows.count,
+            "assignStableInstanceIndices requires items to be a 1:1, order-preserving map of windows"
+        )
         var groups = [String: [Int]]()
         for i in 0 ..< items.count {
             let key = "\(items[i].tag.namespace):\(items[i].tag.title)"
             groups[key, default: []].append(i)
         }
         for (_, indices) in groups where indices.count > 1 {
             let sorted = indices.sorted { items[$0].windowID < items[$1].windowID }
             for (instanceIndex, itemIndex) in sorted.enumerated() where instanceIndex > 0 {
-                if let sourcePID = items[itemIndex].sourcePID {
-                    items[itemIndex] = MenuBarItem(
-                        uncheckedItemWindow: windows[itemIndex],
-                        sourcePID: sourcePID,
-                        instanceIndex: instanceIndex
-                    )
-                } else {
-                    items[itemIndex] = MenuBarItem(
-                        uncheckedItemWindow: windows[itemIndex],
-                        sourcePID: nil,
-                        instanceIndex: instanceIndex
-                    )
-                }
+                items[itemIndex] = MenuBarItem(
+                    uncheckedItemWindow: windows[itemIndex],
+                    sourcePID: items[itemIndex].sourcePID,
+                    instanceIndex: instanceIndex
+                )
             }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItem`+Enumeration.swift around lines 132 -
148, In the enumeration function containing the grouped-item loop, add a
precondition establishing that the parallel items and windows collections have
equal counts before indexing them together. Then simplify the instance-rebuild
loop to one MenuBarItem initializer using the existing
items[itemIndex].sourcePID value directly, removing the redundant if/else
branches while preserving source PID and instance index behavior.
Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift (1)

7024-7030: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Gate the no-move exits for profile applies before clearing the snapshot.

persistProfileStateOnSuccess clears priorProfileApplySnapshot for every .profile success path. Before its callers that can be reached by a cancelled task use it, check whether that task still owns the current profileApplyToken and roll back through restoreProfileStateAfterCancelledApply if a newer apply has already started.

Apply the same guard to the itemOrder.isEmpty, fullSequence.isEmpty, and plannedMoves.isEmpty exits.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 7024 - 7030,
Guard every profile-apply no-move exit for itemOrder.isEmpty,
fullSequence.isEmpty, and plannedMoves.isEmpty before calling
persistProfileStateOnSuccess. Verify the task still owns the current
profileApplyToken; if ownership is lost, call
restoreProfileStateAfterCancelledApply instead of clearing
priorProfileApplySnapshot, otherwise continue the existing success persistence
path.
♻️ Duplicate comments (1)
ThawTests/Support/ScratchDefaults.swift (1)

113-143: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

The async scratch-defaults lane still does not exclude the synchronous lane. ScratchDefaultsMutex orders async callers only, so a synchronous suite can install and delete a scratch suite between the async lane's read of Defaults.store and its restore. The async lane then republishes a deleted store as the process-wide Defaults.store. The suite documentation that depends on this guarantee is wrong for the same reason.

  • ThawTests/Support/ScratchDefaults.swift#L113-L143: take scratchDefaultsLock across the non-suspending store swap and across the restore in the defer, then narrow the warning to the non-recursive constraint.
  • ThawTests/Settings/Models/FocusFilterIntentTests.swift#L32-L34: state that the async overload serializes against other async callers only, or drop the serialization claim once the helper covers both lanes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Support/ScratchDefaults.swift` around lines 113 - 143, Update
ThawTests/Support/ScratchDefaults.swift: in withScratchDefaults, hold
scratchDefaultsLock across the synchronous Defaults.store swap and defer-based
restore, while retaining ScratchDefaultsMutex for the full async body; narrow
the warning to the mutex’s non-recursive constraint. Update
ThawTests/Settings/Models/FocusFilterIntentTests.swift:32-34 to state that the
async overload serializes only with other async callers, or remove that
serialization claim if the helper now covers both lanes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Thaw/Settings/Models/ProfileManager.swift`:
- Around line 170-188: Extract the Focus Filter notification names into shared
Notification.Name constants, alongside the existing shared Focus Filter defaults
key, so both targets use one definition. Update the consumer in the
focusFilterActivatedTask and focusFilterDeactivatedTask setup to reference those
constants instead of string literals, and update the ThawFocusFilter producer to
post the same shared constants while preserving the existing notification
values.

In `@ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift`:
- Around line 182-194: Update globalBroadcastWithNoDisplays to assert the
intended no-target contract under a controlled setup, ensuring connected
displays cannot contribute targets; alternatively rename the test to describe
the configuration-application invariant it currently verifies. Keep the
assertion meaningful and avoid relying on a potentially empty loop.

In `@ThawTests/Settings/Models/FocusFilterIntentTests.swift`:
- Around line 32-34: Update the suite documentation comment describing the async
withScratchDefaults overload to state that it serializes only against other
async callers, not the entire process or synchronous callers. Leave the
.serialized rationale about nonisolated static variables unchanged.

In `@ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift`:
- Around line 97-103: Update everyBooleanKeyIsSettable to always use
.settingsDidChangeViaURI instead of selecting a channel via
perDisplayKeys.contains(key). Remove the now-unreachable
.perDisplaySettingsDidChangeViaURI branch while preserving the existing
boolean-key validation.

---

Outside diff comments:
In `@Thaw/MenuBar/MenuBarItems/MenuBarItem`+Enumeration.swift:
- Around line 132-148: In the enumeration function containing the grouped-item
loop, add a precondition establishing that the parallel items and windows
collections have equal counts before indexing them together. Then simplify the
instance-rebuild loop to one MenuBarItem initializer using the existing
items[itemIndex].sourcePID value directly, removing the redundant if/else
branches while preserving source PID and instance index behavior.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 7024-7030: Guard every profile-apply no-move exit for
itemOrder.isEmpty, fullSequence.isEmpty, and plannedMoves.isEmpty before calling
persistProfileStateOnSuccess. Verify the task still owns the current
profileApplyToken; if ownership is lost, call
restoreProfileStateAfterCancelledApply instead of clearing
priorProfileApplySnapshot, otherwise continue the existing success persistence
path.

In `@ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift`:
- Around line 29-53: Refactor MenuBarItemFailureLedgerTests to run every
Defaults-touching test body through withScratchDefaults, including the reads
around the referenced test sections. Remove the savedValue/savedBuildValue
properties and init/deinit save-and-restore logic, and ensure each test uses the
isolated scratch store for its entire execution.

In `@ThawTests/Settings/Models/AdvancedSettingsTests.swift`:
- Around line 62-80: The notification-posting helpers are duplicated across both
settings test suites. Create a shared test fixture under ThawTests/Support
containing postExternalChanges and postExternalChange, preserving
NotificationCenter.default posting and main-queue delivery waiting; remove both
local helper pairs from ThawTests/Settings/Models/AdvancedSettingsTests.swift
lines 62-80 and ThawTests/Settings/Models/GeneralSettingsTests.swift lines
111-129, and update each suite to call the shared helpers.

In `@ThawTests/Settings/Models/ExternalSettingsChangeTests.swift`:
- Around line 49-50: Add the .timeLimit(.minutes(1)) trait to the `@Test`
annotation for observerFiltersAndDelivers so the asynchronous test fails after a
bounded wait instead of hanging when the continuation is not resumed.

In `@ThawTests/Shared/WindowInfoTests.swift`:
- Around line 18-43: Update createWindowInfo to construct the fixture as a Swift
object graph and serialize it with JSONSerialization instead of interpolating
title and ownerName into JSON text. Preserve the existing field values,
including optional nil handling and nested bounds structure, so titles
containing quotes or backslashes produce valid JSON for WindowInfo decoding.

---

Duplicate comments:
In `@ThawTests/Support/ScratchDefaults.swift`:
- Around line 113-143: Update ThawTests/Support/ScratchDefaults.swift: in
withScratchDefaults, hold scratchDefaultsLock across the synchronous
Defaults.store swap and defer-based restore, while retaining
ScratchDefaultsMutex for the full async body; narrow the warning to the mutex’s
non-recursive constraint. Update
ThawTests/Settings/Models/FocusFilterIntentTests.swift:32-34 to state that the
async overload serializes only with other async callers, or remove that
serialization claim if the helper now covers both lanes.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e439667e-f37a-4a46-a607-47fcebe6cb88

📥 Commits

Reviewing files that changed from the base of the PR and between aaf5258 and 4155e4f.

📒 Files selected for processing (55)
  • Shared/Utilities/DiagnosticLogger.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
  • Thaw/Settings/Models/FocusFilterIntent.swift
  • Thaw/Settings/Models/ProfileManager.swift
  • Thaw/Utilities/ConcurrencyHelpers.swift
  • Thaw/Utilities/Defaults.swift
  • Thaw/Utilities/SettingsURIHandler.swift
  • ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemValueTests.swift
  • ThawTests/MenuBar/Items/ShouldPersistSavedOrderTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionNameTests.swift
  • ThawTests/Permissions/PermissionTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsTests.swift
  • ThawTests/Settings/Models/AutomationSettingsTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift
  • ThawTests/Settings/Models/ExternalSettingsChangeTests.swift
  • ThawTests/Settings/Models/FocusFilterIntentTests.swift
  • ThawTests/Settings/Models/GeneralSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/GeneralSettingsTests.swift
  • ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileApplyInFlightFlagTests.swift
  • ThawTests/Settings/Models/ProfileExportBundleTests.swift
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeepTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeleteTests.swift
  • ThawTests/Settings/Models/ProfileManagerObservationTasksTests.swift
  • ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift
  • ThawTests/Settings/Models/ProfileTests.swift
  • ThawTests/Settings/Search/SearchModelTests.swift
  • ThawTests/Settings/URI/IntegrationURIContractTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerGetTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTailTests.swift
  • ThawTests/Settings/URI/SettingsURIParserTests.swift
  • ThawTests/Shared/DiagnosticLoggerTests.swift
  • ThawTests/Shared/WindowInfoTests.swift
  • ThawTests/Support/GraphicsTestFixtures.swift
  • ThawTests/Support/ProfileTestFixtures.swift
  • ThawTests/Support/ScratchDefaults.swift
  • ThawTests/Utilities/CGImageAnalysisTests.swift
  • ThawTests/Utilities/CGImageDetachedCopyTests.swift
  • ThawTests/Utilities/ConcurrencyHelpersTests.swift
  • ThawTests/Utilities/ExtensionsGraphicsTests.swift
  • ThawTests/Utilities/ExtensionsRemainderTests.swift
  • ThawTests/Utilities/IceSettingsImporterTests.swift
  • ThawTests/Utilities/MigrationTests.swift
  • ThawTests/Utilities/ObjectStorageTests.swift
  • ThawTests/Utilities/ShareableContentCacheTests.swift
  • ThawTests/Utilities/SimpleSemaphoreTests.swift
  • scripts/coverage-report.py
💤 Files with no reviewable changes (6)
  • ThawTests/Utilities/ExtensionsRemainderTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetErrorTests.swift
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeleteTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeepTests.swift
  • ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift

Comment thread Thaw/Settings/Models/ProfileManager.swift
Comment thread ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift
Comment thread ThawTests/Settings/Models/FocusFilterIntentTests.swift
Comment thread ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift
@sonarqubecloud

sonarqubecloud Bot commented Aug 1, 2026

Copy link
Copy Markdown

@diazdesandi
diazdesandi merged commit 3476cbf into development Aug 1, 2026
7 of 8 checks passed
@diazdesandi
diazdesandi deleted the test/swift-testing-migration-and-coverage branch August 1, 2026 00:29

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 28

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift (2)

202-207: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the enrichment gate read Defaults.store on each access.

static nonisolated let initializes once, on first access, and caches the value for the process lifetime. Defaults.store is now injectable, so the cached value depends on which store happens to be installed when some test first touches this property. A test that installs a scratch store afterwards reads the stale flag, and a test that touches the property while a scratch store is installed bakes that scratch value in for every later test in the same process.

The flag only gates a diagnostic pass, so re-reading it per access costs one UserDefaults lookup per cache cycle.

🛡️ Proposed fix
-    static nonisolated let isDegradedIdentityEnrichmentEnabled =
-        Defaults.store.bool(forKey: "EnableDegradedItemAXEnrichment")
+    static nonisolated var isDegradedIdentityEnrichmentEnabled: Bool {
+        Defaults.store.bool(forKey: "EnableDegradedItemAXEnrichment")
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 202 - 207,
Change isDegradedIdentityEnrichmentEnabled from a cached static let to a
computed static property that reads Defaults.store.bool(forKey:
"EnableDegradedItemAXEnrichment") on every access, preserving the existing key
and nonisolated behavior.

7051-7057: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Abort paths leave the never-committed profile spec armed in memory.

armProfileState overwrites pinnedHiddenBundleIDs, pinnedAlwaysHiddenBundleIDs, savedSectionOrder, activeProfileLayout, and activeProfileItemIdentifiers, and defers disk persistence to persistProfileStateOnSuccess. The new restoreProfileStateAfterCancelledApply undoes that overwrite, but only Phase 7 cancellation calls it (Line 8088).

Four post-arm aborts call clearProfileState instead, which clears isApplyingProfileLayout and nothing else:

  • Line 7222: unresolved-sourcePID majority.
  • Line 7231: a menu bar item menu is open.
  • Line 7243: missing control items.
  • Line 7955: control items lost after Phase 1.

No moves ran and nothing was persisted on any of these paths, so memory now describes a profile that disk does not hold. That is the exact condition the snapshot doc comment at Lines 495-500 names: the late-arrival re-sort path keeps sorting toward a profile that never committed. priorProfileApplySnapshot also stays retained until the next arm replaces it.

Roll back on these aborts as well, using the same token guard.

🐛 Proposed fix
     /// Profile-only exit cleanup: refresh the sorted snapshot and clear
     /// the in-flight profile flag. No-op for .savedOrder.
     private func clearProfileState(source: ApplySource, items: [MenuBarItem]) {
         updateProfileSortedSnapshot(source: source, items: items)
         guard case .profile = source else { return }
         isApplyingProfileLayout = false
     }
+
+    /// Exit cleanup for a profile apply that aborted after arming without
+    /// moving anything and without persisting. Reverts the armed spec so
+    /// memory matches what disk still holds, then runs the normal teardown.
+    private func abandonProfileApply(
+        source: ApplySource,
+        items: [MenuBarItem],
+        token: Int
+    ) {
+        if case .profile = source {
+            restoreProfileStateAfterCancelledApply(token: token)
+        }
+        clearProfileState(source: source, items: items)
+    }

Then replace the four abort calls, for example at Line 7231:

         if await isAnyMenuBarItemMenuOpen() {
             MenuBarItemManager.diagLog.info("applyProfileLayout: skipping, a menu bar item menu is open")
-            clearProfileState(source: source, items: items)
+            abandonProfileApply(source: source, items: items, token: applyToken)
             return
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 7051 - 7057,
Update the post-arm abort handling in the functions containing the
unresolved-sourcePID, open-menu, missing-control-item, and post-Phase-1
control-loss paths to call restoreProfileStateAfterCancelledApply with the same
apply token guard instead of clearProfileState. Preserve clearProfileState for
successful completion, and ensure each cancellation restores all armed profile
state and releases priorProfileApplySnapshot without persisting changes.
Thaw/Events/HIDEventManager.swift (1)

1085-1091: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use the documented try? await order for the sleep guard.

Swift requires try before await in expressions that use both operators. guard await (try? Task.sleep(...)) violates that order; use guard (try? await Task.sleep(...)) so the line is guaranteed to compile and matches the existing try await Task.sleep(...) usage pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/Events/HIDEventManager.swift` around lines 1085 - 1091, Update the sleep
guard inside the Task closure in HIDEventManager to use the Swift-required
operator order, placing try? before await: guard (try? await Task.sleep(...)) !=
nil. Preserve the existing cancellation behavior and 250-millisecond delay.
Thaw/Settings/Models/AdvancedSettings.swift (1)

270-279: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Correct the doc comment: this function never returns the default order.

The doc states "Returns the default order if the input is unusable." The implementation does not do that. When rawValues is empty or holds only unrecognized values, preferred is empty and the result is MenuBarSection.Name.allCases, which is the declaration order rather than Defaults.DefaultValue.searchSectionOrder.

SettingsResetterTests.resetAdvancedRestoresSearchSectionOrder derives its expectation by calling this same function, so it cannot detect the difference. Either update the doc to describe the allCases fallback, or make the empty-input case return the sanitized default order.

📝 Proposed doc correction
     /// Returns a search-section order that contains each `MenuBarSection.Name`
     /// case exactly once, using the supplied raw values as the preferred order
-    /// and filling any missing cases at the end. Returns the default order if
-    /// the input is unusable.
+    /// and filling any missing cases at the end in `allCases` order. Unusable
+    /// raw values are dropped, so an input with no recognized value yields
+    /// `MenuBarSection.Name.allCases`.
     static func sanitizedSearchSectionOrder(from rawValues: [String]) -> [MenuBarSection.Name] {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/Settings/Models/AdvancedSettings.swift` around lines 270 - 279, Correct
the documentation for sanitizedSearchSectionOrder(from:) to state that empty or
entirely unrecognized input falls back to MenuBarSection.Name.allCases in
declaration order, not Defaults.DefaultValue.searchSectionOrder. Keep the
implementation unchanged unless intentionally aligning its behavior with the
documented default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/coverage-report.py`:
- Around line 125-151: Update the explanatory comment above the `best` map and
per-file deduplication loop to state that the implementation approximates Sonar
by retaining the entry with the greatest `coveredLines`, rather than unioning
covered or executable line sets across targets. Preserve the existing
deduplication logic.

In `@sonar-project.properties`:
- Line 85: Remove the whole-file coverage exclusion for AXItemActivator.swift
unless it is genuinely entirely AX-driven; preserve coverage for the pure logic
exercised by AXItemActivatorTests. If the file cannot be split, document the
specific AX-only rationale in the exclusion comment block above, consistent with
the existing whole-file exclusions.

In `@Thaw/Main/Updates.swift`:
- Around line 58-62: Update the allowsBetaUpdates property getter and setter to
use the centralized typed Defaults interface: replace the raw
"AllowsBetaUpdates" accesses with Defaults.bool(forKey: .allowsBetaUpdates) and
Defaults.set(_:forKey: .allowsBetaUpdates), including the additional occurrence
near the related update settings code.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItem`+Enumeration.swift:
- Around line 132-149: In the instance-index rebuild loop within the enumeration
logic, replace the duplicated sourcePID branches with one MenuBarItem
initializer that passes items[itemIndex].sourcePID directly, while preserving
the existing uncheckedItemWindow and instanceIndex arguments.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 5207-5215: Update the orderedItems sort in the neighbor-resolution
flow to use windowID as a deterministic secondary key whenever bounds.minX
values are equal, producing a total reproducible order while preserving
leading-edge ordering as the primary key.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemTag.swift`:
- Line 9: Confirm that the package configuration declares the swift-algorithms
dependency required by the Algorithms import in MenuBarItemTag. Ensure the
target containing MenuBarItemTag links that product, distinguishing it from
swift-async-algorithms, and preserve the order-preserving deduplication
implementation.

In `@Thaw/MenuBar/Search/MenuBarSearchPanel.swift`:
- Around line 401-402: Remove the deprecated Defaults.store.synchronize() call
from the panel frame save path, while preserving the existing
Defaults.store.set(...) frame persistence.

In `@Thaw/Settings/Models/Profile.swift`:
- Around line 334-347: Update resolvedItemSectionMap to treat a non-nil empty
itemSectionMap as absent and fall back to the saved section map, matching
resolvedItemOrder. Also update captureCurrentLayout so empty derived itemOrder
and itemSectionMap collections are not persisted over existing valid saved
values.
- Around line 71-76: Update the snapshot-application logic around the
lastCustomIceIcon assignment in Profile so a nil snapshot value does not
overwrite the value populated by the .custom iceIcon didSet. Apply
lastCustomIceIcon only when the snapshot contains a value, while preserving the
reordered assignment for snapshots that provide one.

In `@Thaw/Settings/Models/ProfileManager.swift`:
- Around line 652-660: Update ProfileManager.captureCurrentLayout() to read
savedSectionOrder, pinnedHiddenBundleIDs, and pinnedAlwaysHiddenBundleIDs using
their typed Defaults.Key cases from MenuBarItemManager instead of raw string
literals. Ensure the corresponding MenuBarItemManager writers and these readers
share the same compile-time key definitions.

In `@ThawTests/MenuBar/Items/CapturedImageScaleTests.swift`:
- Around line 118-135: Update resolvedScaleRestoresPointSize() to declare
throws, then replace try? `#require`(scale) with direct try `#require`(scale) so a
nil resolved scale fails with the framework’s clear requirement error instead of
falling through to the fallback arithmetic.

In `@ThawTests/MenuBar/Items/MenuBarTestFixturesTests.swift`:
- Around line 20-26: Update appItemTagBuildsExpectedNamespaceAndTitle to assert
tag.namespace directly against the expected Namespace case/value instead of
comparing String(describing: tag.namespace). Preserve the existing assertions
for title, instanceIndex, and windowID.

In `@ThawTests/MenuBar/Section/MenuBarSectionNameTests.swift`:
- Around line 90-111: Update localizedVisible, localizedHidden, and
localizedAlwaysHidden to compare each returned LocalizedStringKey directly with
the corresponding LocalizedStringKey literal using Equatable. Remove the
misleading String(describing:) comment and comparisons, while preserving the
existing expected keys.

In `@ThawTests/Settings/Models/AdvancedSettingsTests.swift`:
- Around line 46-80: Update AdvancedSettings to accept an injectable
NotificationCenter, matching the existing center injection pattern used by
debouncedNotificationTask, and use that center when observing settings changes.
In AdvancedSettingsTests, create a private NotificationCenter per test and pass
it to the model, then post through the same isolated center instead of
NotificationCenter.default so external-change assertions cannot receive
notifications from other suites.

In `@ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift`:
- Around line 47-71: Introduce one reusable display-UUID availability condition
and apply it via .enabled(if:) to resolvedDisplayUsesExplicitConfiguration and
resolvedDisplayWithoutOverrideFallsBackToGlobalConfiguration in
ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift lines
47-71, removing the try `#require` and early return; apply the same condition to
typedLookupsMatchTheConfiguration and connectedDisplaysAreWellFormed in
ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift lines
130-149 and 242-254, removing their early-return or vacuous-loop gating so
headless environments report skipped tests consistently.

In `@ThawTests/Settings/Models/GeneralSettingsTests.swift`:
- Around line 27-35: Update GeneralSettingsTests to use the injectable Defaults
store: first confirm GeneralSettings reads and writes through Defaults, then
wrap every test with withScratchDefaults. Remove the per-key snapshot and
restore logic and revise the related documentation to describe the scratch-store
isolation instead.

In `@ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift`:
- Around line 38-40: Strengthen the clearing assertion in the hotkey persistence
test by first unwrapping the dictionary returned by Defaults.dictionary(forKey:
.hotkeys), then assert that action.rawValue is absent from the non-optional
dictionary. Avoid optional chaining so a missing dictionary cannot make the
assertion pass.

In `@ThawTests/Settings/Models/ProfileDecodingDefaultsTests.swift`:
- Around line 590-608: Update the documentation above
duplicateIdentifierResolvesToOneSection by either naming the specific report
file or removing the sentence referring to an accompanying report. Keep the
existing explanation about Dictionary iteration order and the test’s assertion
unchanged.
- Around line 732-748: Update applyPreservesLastCustomIceIcon to set
snapshot.iceIcon to a .custom icon before applying it, while retaining a
distinct remembered value in snapshot.lastCustomIceIcon. Use the existing
custom-icon setup from applyGeneralSettingsCustomIconKeepsLastCustom so
iceIcon’s didSet mirror executes and the test distinguishes the assignment
order.

In `@ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift`:
- Around line 31-36: Update the affected doc comments in the
ProfileLayoutLogReplayTests suite to remove the obsolete XCTest “test” prefix
and reference the migrated methods withoutExclusionTheOrphanWouldBeUnmanaged,
buggyCycleDoesNotPlanMoveForUnresolvedOrphan,
loggedDesiredVisibleIsUsedInsteadOfInference, and
displayReconnectNegativeBudgetYieldsNoOverflow.

In `@ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift`:
- Around line 29-53: Move the shared notifications(named:during:) helper and
NotificationBox class into ThawTests/Support/, then remove both private local
copies from SettingsURIHandlerApplyTests and SettingsURIHandlerCoverageTests.
Preserve their existing behavior and signatures so both test suites reuse the
shared implementation.

In `@ThawTests/Settings/URI/SettingsURIParserTests.swift`:
- Around line 163-177: Update parsingIsTotal to require every test argument to
produce a URL instead of returning silently when URL(string:) is nil. Reuse the
file’s existing helper that enforces URL creation, then pass the resulting URL
to SettingsURIParser.parse.

In `@ThawTests/Shared/DiagnosticLoggerFileTests.swift`:
- Around line 311-324: Update latestLogFileIsSelfConsistent to remove the
cross-read assertion comparing logger.hasLogFiles with latest, since both
accessors independently scan the shared log directory. Retain the existing
latestLogFile shape checks, including the .log extension and logDirectory
parent-path validation.

In `@ThawTests/Support/ProfileTestFixtures.swift`:
- Around line 111-135: The profile test fixtures duplicate encoder and
manifest-seeding logic across three files; centralize it to prevent
configuration drift. In ThawTests/Support/ProfileTestFixtures.swift lines
111-135, add shared makeProfileEncoder() and extend seedManifest with
omittingFilesFor: Set<UUID> = [], skipping those profile files while still
writing the manifest. In
ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift lines 585-624,
remove private seedManifest and makeEncoder, use the shared helpers, and
preserve withManager and writeBundle. In
ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift lines
120-129, replace local JSONEncoder setup in writeProfile with
makeProfileEncoder().

In `@ThawTests/Support/ScratchDefaults.swift`:
- Around line 45-53: Update acquire() to handle task cancellation while waiting
for the lock by wrapping the waiter continuation in withTaskCancellationHandler
and ensuring cancellation resumes or removes the suspended waiter safely.
Preserve the existing immediate-acquisition behavior and prevent cancelled test
tasks from remaining suspended indefinitely.
- Around line 133-143: Unify the synchronization used by the synchronous and
asynchronous ScratchDefaults overloads so they cannot interleave while modifying
the global Defaults.store. Update scratchDefaultsLock and scratchDefaultsMutex
usage so both overloads share one mutual-exclusion mechanism, preserving each
overload’s existing setup, restoration, and cleanup behavior without allowing
one caller to restore another caller’s deleted store.

In `@ThawTests/Utilities/CGImageAnalysisTests.swift`:
- Around line 197-210: Update fallbackAgreesWithFastPath to assert the expected
transparency value for each opaque input in addition to comparing fallback and
normal results. Use opaque to derive the absolute expectation, preserving the
existing agreement assertion.

In `@ThawTests/Utilities/CGImageDetachedCopyTests.swift`:
- Around line 45-63: Update pixelData(of:) to create and use the CGContext
inside data.withUnsafeMutableBytes, passing the buffer pointer as the context
data and performing context.draw within the same closure. Preserve the existing
bitmap configuration, context-creation failure handling, and returned pixel
data.

---

Outside diff comments:
In `@Thaw/Events/HIDEventManager.swift`:
- Around line 1085-1091: Update the sleep guard inside the Task closure in
HIDEventManager to use the Swift-required operator order, placing try? before
await: guard (try? await Task.sleep(...)) != nil. Preserve the existing
cancellation behavior and 250-millisecond delay.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 202-207: Change isDegradedIdentityEnrichmentEnabled from a cached
static let to a computed static property that reads Defaults.store.bool(forKey:
"EnableDegradedItemAXEnrichment") on every access, preserving the existing key
and nonisolated behavior.
- Around line 7051-7057: Update the post-arm abort handling in the functions
containing the unresolved-sourcePID, open-menu, missing-control-item, and
post-Phase-1 control-loss paths to call restoreProfileStateAfterCancelledApply
with the same apply token guard instead of clearProfileState. Preserve
clearProfileState for successful completion, and ensure each cancellation
restores all armed profile state and releases priorProfileApplySnapshot without
persisting changes.

In `@Thaw/Settings/Models/AdvancedSettings.swift`:
- Around line 270-279: Correct the documentation for
sanitizedSearchSectionOrder(from:) to state that empty or entirely unrecognized
input falls back to MenuBarSection.Name.allCases in declaration order, not
Defaults.DefaultValue.searchSectionOrder. Keep the implementation unchanged
unless intentionally aligning its behavior with the documented default.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4245c0bb-afa2-49e9-8370-65b05294d623

📥 Commits

Reviewing files that changed from the base of the PR and between 4155e4f and f3d078b.

📒 Files selected for processing (243)
  • .github/workflows/ci.yml
  • .gitignore
  • .swiftformat
  • MenuBarItemService/Listener.swift
  • MenuBarItemService/SourcePIDCache.swift
  • MenuBarItemService/main.swift
  • Shared/Bridging/Shims.swift
  • Shared/Utilities/DiagnosticLogger.swift
  • Shared/Utilities/MarkerPairResolver.swift
  • Thaw/Events/EventTap.swift
  • Thaw/Events/HIDEventManager.swift
  • Thaw/Events/RunLoopLocalEventMonitor.swift
  • Thaw/Hotkeys/Hotkey.swift
  • Thaw/Hotkeys/HotkeyRegistry.swift
  • Thaw/Main/AppState.swift
  • Thaw/Main/Updates.swift
  • Thaw/MenuBar/Appearance/Configurations/MenuBarAppearanceConfigurationV2.swift
  • Thaw/MenuBar/Appearance/Configurations/MenuBarShapes.swift
  • Thaw/MenuBar/Appearance/MissionControlDetector.swift
  • Thaw/MenuBar/ControlItem/ControlItem.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift
  • Thaw/MenuBar/MenuBarItems/AXIdentityCatalog.swift
  • Thaw/MenuBar/MenuBarItems/AXItemActivator.swift
  • Thaw/MenuBar/MenuBarItems/ClickReactionVerifier.swift
  • Thaw/MenuBar/MenuBarItems/LayoutSolver.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemFailureLedger.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemImageCache.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemServiceConnection.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemTag.swift
  • Thaw/MenuBar/MenuBarManager.swift
  • Thaw/MenuBar/MenuBarSection.swift
  • Thaw/MenuBar/MenuBarSectionLayout.swift
  • Thaw/MenuBar/Search/MenuBarSearchPanel.swift
  • Thaw/MenuBar/Spacing/MenuBarItemSpacingManager.swift
  • Thaw/Onboarding/Tour/OnboardingTour.swift
  • Thaw/Onboarding/Tour/TourMockups.swift
  • Thaw/Onboarding/Tour/WelcomeMockup.swift
  • Thaw/Settings/Models/AdvancedSettings.swift
  • Thaw/Settings/Models/DisplaySettingsManager.swift
  • Thaw/Settings/Models/FocusFilterIntent.swift
  • Thaw/Settings/Models/Profile.swift
  • Thaw/Settings/Models/ProfileManager.swift
  • Thaw/UI/Views/CustomTooltip.swift
  • Thaw/Utilities/ConcurrencyHelpers.swift
  • Thaw/Utilities/Defaults.swift
  • Thaw/Utilities/Extensions.swift
  • Thaw/Utilities/ScreenCapture.swift
  • Thaw/Utilities/SettingsURIHandler.swift
  • Thaw/Utilities/SettingsURIParser.swift
  • ThawTests/AdvancedSettingsTests.swift
  • ThawTests/AutomationSettingsTests.swift
  • ThawTests/CaptureBoundsValidationTests.swift
  • ThawTests/CrossCutting/CoverageSweep1Tests.swift
  • ThawTests/CrossCutting/CoverageSweep2Tests.swift
  • ThawTests/CrossCutting/CoverageSweep3Tests.swift
  • ThawTests/CrossCutting/CoverageSweep4Tests.swift
  • ThawTests/CrossCutting/CoverageSweep5Tests.swift
  • ThawTests/DefaultsKeyTests.swift
  • ThawTests/DiagnosticLoggerTests.swift
  • ThawTests/DisplayIceBarConfigurationTests.swift
  • ThawTests/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/DisplaySettingsManagerMutationTests.swift
  • ThawTests/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/ExtensionsTests.swift
  • ThawTests/GeneralSettingsSnapshotTests.swift
  • ThawTests/HotkeyActionTests.swift
  • ThawTests/Hotkeys/HotkeyActionTests.swift
  • ThawTests/Hotkeys/HotkeyLifecycleTests.swift
  • ThawTests/Hotkeys/KeyCodeTests.swift
  • ThawTests/Hotkeys/KeyCombinationTests.swift
  • ThawTests/Hotkeys/ModifiersTests.swift
  • ThawTests/HotkeysSettingsPersistenceTests.swift
  • ThawTests/IceBarLocationTests.swift
  • ThawTests/IceSettingsImporterTests.swift
  • ThawTests/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarShapesTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarTintKindTests.swift
  • ThawTests/MenuBar/Appearance/MissionControlDetectorTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemImageTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemOcclusionTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemRecoveryTests.swift
  • ThawTests/MenuBar/IceBar/IceBarLocationTests.swift
  • ThawTests/MenuBar/Items/AXIdentityCatalogTests.swift
  • ThawTests/MenuBar/Items/AXItemActivatorTests.swift
  • ThawTests/MenuBar/Items/AnchorDestinationTests.swift
  • ThawTests/MenuBar/Items/BadgeIndexTests.swift
  • ThawTests/MenuBar/Items/CaptureBoundsValidationTests.swift
  • ThawTests/MenuBar/Items/CapturedImageScaleTests.swift
  • ThawTests/MenuBar/Items/ClickReactionVerifierTests.swift
  • ThawTests/MenuBar/Items/ControlCenterHostedMatchLogReplayTests.swift
  • ThawTests/MenuBar/Items/DisplaySpreadGateTests.swift
  • ThawTests/MenuBar/Items/DivergencePersistenceGateTests.swift
  • ThawTests/MenuBar/Items/FlattenCurrentSectionsTests.swift
  • ThawTests/MenuBar/Items/GhostControlItemWindowTests.swift
  • ThawTests/MenuBar/Items/HiddenDragFailureClassificationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheDisplayResolutionTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheGatingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheObservationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemManagerRearmTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemOrderingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemServiceTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagCodingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemValueTests.swift
  • ThawTests/MenuBar/Items/MenuBarTestFixturesTests.swift
  • ThawTests/MenuBar/Items/MidSectionTransitionTests.swift
  • ThawTests/MenuBar/Items/MoveEventCoordinatesTests.swift
  • ThawTests/MenuBar/Items/MoveFailureBackoffTests.swift
  • ThawTests/MenuBar/Items/MoveMenuGuardTests.swift
  • ThawTests/MenuBar/Items/NewItemsPlacementTests.swift
  • ThawTests/MenuBar/Items/PartitionUnmanagedUIDsTests.swift
  • ThawTests/MenuBar/Items/PendingRehideTagIdentifiersTests.swift
  • ThawTests/MenuBar/Items/SavedLayoutSectionLookupTests.swift
  • ThawTests/MenuBar/Items/SavedPositionLookupTests.swift
  • ThawTests/MenuBar/Items/SectionGeometryGateTests.swift
  • ThawTests/MenuBar/Items/SelectWindowForBatchScanTests.swift
  • ThawTests/MenuBar/Items/ShouldPersistSavedOrderTests.swift
  • ThawTests/MenuBar/Items/SourcePIDResolutionGateTests.swift
  • ThawTests/MenuBar/Items/WindowIDsChangedGateTests.swift
  • ThawTests/MenuBar/Layout/LayoutReconcilerTests.swift
  • ThawTests/MenuBar/Layout/LayoutReconcilerUnmanagedPlacementTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetReentrancyTests.swift
  • ThawTests/MenuBar/Layout/NotchOverflowRevealTests.swift
  • ThawTests/MenuBar/Layout/PlanFullSortSequenceTests.swift
  • ThawTests/MenuBar/Layout/PlanLCSMoveSequenceTests.swift
  • ThawTests/MenuBar/Layout/PlanLeftmostMoveTests.swift
  • ThawTests/MenuBar/Layout/PlanNotchOverflowTests.swift
  • ThawTests/MenuBar/Layout/PlanPendingMoveTests.swift
  • ThawTests/MenuBar/Layout/PlanSectionOrderTests.swift
  • ThawTests/MenuBar/Layout/PlanUnmanagedPlacementTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionGeometryTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/MenuBarItemServiceTests.swift
  • ThawTests/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBarItemTagTests.swift
  • ThawTests/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarShapesTests.swift
  • ThawTests/MenuBarTestFixturesTests.swift
  • ThawTests/ModifiersTests.swift
  • ThawTests/MoveFailureBackoffTests.swift
  • ThawTests/Onboarding/OnboardingMockupsTests.swift
  • ThawTests/Onboarding/OnboardingSlideTests.swift
  • ThawTests/OnboardingMockupsTests.swift
  • ThawTests/OnboardingSlideTests.swift
  • ThawTests/PermissionTests.swift
  • ThawTests/Permissions/PermissionTests.swift
  • ThawTests/ProfileEntityTests.swift
  • ThawTests/ProfileExportBundleTests.swift
  • ThawTests/ProfileManagerDeleteTests.swift
  • ThawTests/ProfileManagerRearmGateTests.swift
  • ThawTests/ProfileTests.swift
  • ThawTests/RehideStrategyTests.swift
  • ThawTests/SectionDividerStyleTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsTests.swift
  • ThawTests/Settings/Models/AutomationSettingsTests.swift
  • ThawTests/Settings/Models/DisplayIceBarConfigurationTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerLookupTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerURINotificationTests.swift
  • ThawTests/Settings/Models/ExternalSettingsChangeTests.swift
  • ThawTests/Settings/Models/FocusFilterIntentTests.swift
  • ThawTests/Settings/Models/GeneralSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/GeneralSettingsTests.swift
  • ThawTests/Settings/Models/HookScriptTests.swift
  • ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileApplyInFlightFlagTests.swift
  • ThawTests/Settings/Models/ProfileDecodingDefaultsTests.swift
  • ThawTests/Settings/Models/ProfileEntityTests.swift
  • ThawTests/Settings/Models/ProfileExportBundleTests.swift
  • ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeepTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeleteTests.swift
  • ThawTests/Settings/Models/ProfileManagerObservationTasksTests.swift
  • ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileManagerRearmGateTests.swift
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift
  • ThawTests/Settings/Models/ProfileTests.swift
  • ThawTests/Settings/Models/RehideStrategyTests.swift
  • ThawTests/Settings/Models/SectionDividerStyleTests.swift
  • ThawTests/Settings/Models/SettingsResetterTests.swift
  • ThawTests/Settings/Search/SearchIndexTests.swift
  • ThawTests/Settings/Search/SearchModelTests.swift
  • ThawTests/Settings/Search/SettingsSearchNavigationTests.swift
  • ThawTests/Settings/URI/IntegrationURIContractTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerGetTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTailTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTests.swift
  • ThawTests/Settings/URI/SettingsURIParserTests.swift
  • ThawTests/SettingsResetterTests.swift
  • ThawTests/SettingsURIHandlerApplyTests.swift
  • ThawTests/SettingsURIHandlerGetTests.swift
  • ThawTests/SettingsURIHandlerTests.swift
  • ThawTests/Shared/CodeSigningInfoTests.swift
  • ThawTests/Shared/DiagnosticLoggerFileTests.swift
  • ThawTests/Shared/DiagnosticLoggerTests.swift
  • ThawTests/Shared/MarkerPairResolverTests.swift
  • ThawTests/Shared/SharedExtensionsTests.swift
  • ThawTests/Shared/WindowInfoDecodingTests.swift
  • ThawTests/Shared/WindowInfoDerivedTests.swift
  • ThawTests/Shared/WindowInfoTests.swift
  • ThawTests/SharedExtensionsTests.swift
  • ThawTests/Support/GraphicsTestFixtures.swift
  • ThawTests/Support/MenuBarTestFixtures.swift
  • ThawTests/Support/ProfileTestFixtures.swift
  • ThawTests/Support/ScratchDefaults.swift
  • ThawTests/Utilities/CGImageAnalysisTests.swift
  • ThawTests/Utilities/CGImageDetachedCopyTests.swift
  • ThawTests/Utilities/ConcurrencyHelpersTests.swift
  • ThawTests/Utilities/CustomTooltipPanelTests.swift
  • ThawTests/Utilities/DefaultsKeyTests.swift
  • ThawTests/Utilities/ExtensionsCoverageTests.swift
  • ThawTests/Utilities/ExtensionsGraphicsTests.swift
  • ThawTests/Utilities/ExtensionsRemainderTests.swift
  • ThawTests/Utilities/ExtensionsTests.swift
  • ThawTests/Utilities/HookRunnerTests.swift
  • ThawTests/Utilities/IceSettingsImporterTailTests.swift
  • ThawTests/Utilities/IceSettingsImporterTests.swift
  • ThawTests/Utilities/LocalizedErrorWrapperTests.swift
  • ThawTests/Utilities/MigrationTests.swift
  • ThawTests/Utilities/MouseMovedThrottleTests.swift
  • ThawTests/Utilities/ObjectStorageTests.swift
  • ThawTests/Utilities/PredicatesTests.swift
  • ThawTests/Utilities/ShareableContentCacheTests.swift
  • ThawTests/Utilities/SimpleSemaphoreTests.swift
  • ThawTests/WindowInfoTests.swift
  • docs/ASSURANCE_CASE.md
  • scripts/coverage-report.py
  • scripts/swiftlint-inputs.xcfilelist
  • sonar-project.properties
💤 Files with no reviewable changes (28)
  • ThawTests/DiagnosticLoggerTests.swift
  • ThawTests/GeneralSettingsSnapshotTests.swift
  • ThawTests/ModifiersTests.swift
  • ThawTests/IceBarLocationTests.swift
  • ThawTests/LayoutResetErrorTests.swift
  • ThawTests/ExtensionsTests.swift
  • ThawTests/MenuBarTestFixturesTests.swift
  • ThawTests/AdvancedSettingsTests.swift
  • ThawTests/DisplayIceBarConfigurationTests.swift
  • ThawTests/HotkeysSettingsPersistenceTests.swift
  • ThawTests/IceSettingsImporterTests.swift
  • ThawTests/MoveFailureBackoffTests.swift
  • ThawTests/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/MenuBarSectionNameTests.swift
  • ThawTests/DisplaySettingsManagerMutationTests.swift
  • ThawTests/AutomationSettingsTests.swift
  • ThawTests/MenuBarItemTagCanonicalizationTests.swift
  • Thaw/MenuBar/Appearance/MissionControlDetector.swift
  • ThawTests/DefaultsKeyTests.swift
  • ThawTests/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/CaptureBoundsValidationTests.swift
  • Thaw/MenuBar/MenuBarSection.swift
  • ThawTests/MenuBarShapesTests.swift
  • ThawTests/MenuBarItemTagTests.swift
  • ThawTests/MenuBar/Items/GhostControlItemWindowTests.swift
  • ThawTests/MenuBarItemServiceTests.swift
  • ThawTests/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/HotkeyActionTests.swift

Comment on lines +125 to +151
# Files under Shared/ are compiled into both the app and the XPC service, so
# xccov reports them once per target. Sonar counts each source file once, so
# keep the best result per path -- a line covered by either target is covered.
best: dict[str, dict] = {}
excluded_files = 0
for target in report.get("targets", []):
for entry in target.get("files", []):
path = entry["path"]
# xccov reports absolute paths; make them repo-relative.
marker = str(REPO_ROOT) + "/"
path = path[len(marker):] if path.startswith(marker) else path
if not path.startswith(sources):
continue
if is_excluded(path, exclusions):
excluded_files += 1
continue
lines = entry["executableLines"]
covered = entry["coveredLines"]
previous = best.get(path)
if previous is None or covered > previous["covered"]:
best[path] = {
"path": path,
"lines": lines,
"covered": covered,
"uncovered": lines - covered,
"coverage": round(100 * covered / lines, 1) if lines else 100.0,
}

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.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

State the limit of the per-file dedup rule.

The comment says a line covered by either target is covered, but the code keeps the whole entry with the largest coveredLines rather than a union of covered lines. If the two targets cover different line sets, or report different executableLines, the reported figure comes from one entry only. The result stays close to Sonar's, so this is an accuracy note, not a defect. Adjust the comment to describe the approximation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/coverage-report.py` around lines 125 - 151, Update the explanatory
comment above the `best` map and per-file deduplication loop to state that the
implementation approximates Sonar by retaining the entry with the greatest
`coveredLines`, rather than unioning covered or executable line sets across
targets. Preserve the existing deduplication logic.

Comment thread sonar-project.properties
Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift,\
Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift,\
Thaw/MenuBar/MenuBarItems/AXItemActivator.swift,\

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Excluding AXItemActivator.swift discards the coverage from its new test suite.

This PR adds ThawTests/MenuBar/Items/AXItemActivatorTests.swift. Line 85 now excludes Thaw/MenuBar/MenuBarItems/AXItemActivator.swift from coverage measurement, so whatever that suite exercises no longer counts.

That also conflicts with the rule this file states at Lines 22-26: pure logic stays measured, and files that bury both kinds of code are split rather than excluded whole. MenuBarSection and MenuBarItem were handled that way in this same PR; AXItemActivator was not.

Either split the file along the same seam and exclude only the AX-interaction half, or drop the exclusion. If the whole file is genuinely AX-driven, state why the new test suite exists and record the reason in the comment block above, as the other whole-file exclusions do.

#!/bin/bash
# Compare what the new suite exercises against the excluded file's contents.
fd -g 'AXItemActivator*.swift' --exec ast-grep outline {} --items all
rg -n 'AXItemActivator\.' ThawTests
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sonar-project.properties` at line 85, Remove the whole-file coverage
exclusion for AXItemActivator.swift unless it is genuinely entirely AX-driven;
preserve coverage for the pure logic exercised by AXItemActivatorTests. If the
file cannot be split, document the specific AX-only rationale in the exclusion
comment block above, consistent with the existing whole-file exclusions.

Comment thread Thaw/Main/Updates.swift
Comment on lines +58 to +62
return Defaults.store.bool(forKey: "AllowsBetaUpdates")
}
set {
withMutation(keyPath: \.allowsBetaUpdates) {
UserDefaults.standard.set(newValue, forKey: "AllowsBetaUpdates")
Defaults.store.set(newValue, forKey: "AllowsBetaUpdates")

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the centralized Defaults.Key for beta updates.

Lines 58-62 and Line 180 duplicate "AllowsBetaUpdates" and bypass the typed Defaults interface. Use Defaults.bool(forKey: .allowsBetaUpdates) and Defaults.set(_:forKey: .allowsBetaUpdates) so reads and writes share the centralized key.

Proposed fix
- return Defaults.store.bool(forKey: "AllowsBetaUpdates")
+ return Defaults.bool(forKey: .allowsBetaUpdates)

- Defaults.store.set(newValue, forKey: "AllowsBetaUpdates")
+ Defaults.set(newValue, forKey: .allowsBetaUpdates)

- if Defaults.store.bool(forKey: "AllowsBetaUpdates") {
+ if Defaults.bool(forKey: .allowsBetaUpdates) {

Also applies to: 180-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/Main/Updates.swift` around lines 58 - 62, Update the allowsBetaUpdates
property getter and setter to use the centralized typed Defaults interface:
replace the raw "AllowsBetaUpdates" accesses with Defaults.bool(forKey:
.allowsBetaUpdates) and Defaults.set(_:forKey: .allowsBetaUpdates), including
the additional occurrence near the related update settings code.

Comment on lines +132 to +149
for (_, indices) in groups where indices.count > 1 {
let sorted = indices.sorted { items[$0].windowID < items[$1].windowID }
for (instanceIndex, itemIndex) in sorted.enumerated() where instanceIndex > 0 {
if let sourcePID = items[itemIndex].sourcePID {
items[itemIndex] = MenuBarItem(
uncheckedItemWindow: windows[itemIndex],
sourcePID: sourcePID,
instanceIndex: instanceIndex
)
} else {
items[itemIndex] = MenuBarItem(
uncheckedItemWindow: windows[itemIndex],
sourcePID: nil,
instanceIndex: instanceIndex
)
}
}
}

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.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Collapse the duplicated branches in the instance-index rebuild.

items[itemIndex].sourcePID is already pid_t?. Both branches call the same initializer with the same arguments; only the sourcePID value differs. Pass the optional directly.

♻️ Proposed refactor
         for (instanceIndex, itemIndex) in sorted.enumerated() where instanceIndex > 0 {
-                if let sourcePID = items[itemIndex].sourcePID {
-                    items[itemIndex] = MenuBarItem(
-                        uncheckedItemWindow: windows[itemIndex],
-                        sourcePID: sourcePID,
-                        instanceIndex: instanceIndex
-                    )
-                } else {
-                    items[itemIndex] = MenuBarItem(
-                        uncheckedItemWindow: windows[itemIndex],
-                        sourcePID: nil,
-                        instanceIndex: instanceIndex
-                    )
-                }
+                items[itemIndex] = MenuBarItem(
+                    uncheckedItemWindow: windows[itemIndex],
+                    sourcePID: items[itemIndex].sourcePID,
+                    instanceIndex: instanceIndex
+                )
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (_, indices) in groups where indices.count > 1 {
let sorted = indices.sorted { items[$0].windowID < items[$1].windowID }
for (instanceIndex, itemIndex) in sorted.enumerated() where instanceIndex > 0 {
if let sourcePID = items[itemIndex].sourcePID {
items[itemIndex] = MenuBarItem(
uncheckedItemWindow: windows[itemIndex],
sourcePID: sourcePID,
instanceIndex: instanceIndex
)
} else {
items[itemIndex] = MenuBarItem(
uncheckedItemWindow: windows[itemIndex],
sourcePID: nil,
instanceIndex: instanceIndex
)
}
}
}
for (_, indices) in groups where indices.count > 1 {
let sorted = indices.sorted { items[$0].windowID < items[$1].windowID }
for (instanceIndex, itemIndex) in sorted.enumerated() where instanceIndex > 0 {
items[itemIndex] = MenuBarItem(
uncheckedItemWindow: windows[itemIndex],
sourcePID: items[itemIndex].sourcePID,
instanceIndex: instanceIndex
)
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItem`+Enumeration.swift around lines 132 -
149, In the instance-index rebuild loop within the enumeration logic, replace
the duplicated sourcePID branches with one MenuBarItem initializer that passes
items[itemIndex].sourcePID directly, while preserving the existing
uncheckedItemWindow and instanceIndex arguments.

Comment on lines +5207 to 5215
// The anchor math below treats index adjacency as physical
// adjacency, but the item list arrives in Window Server order.
// Sort by each item's leading edge so successor/predecessor mean
// the item's actual on-screen neighbors.
let orderedItems = items.sorted { $0.bounds.minX < $1.bounds.minX }

guard let index = orderedItems.firstIndex(matching: item.tag) else {
return nil
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a deterministic tie-break to the leading-edge sort.

sorted(by:) is not stable in Swift, and this call site resolves neighbors for items in the hidden or always-hidden section. Those items are parked off-screen horizontally, so two of them can report the same or near-identical bounds.minX. When they tie, the relative order of orderedItems is unspecified, so anchors.successor can select a different anchor on two runs over the same bar. The rehide then returns the item to a different slot each time.

A secondary key on windowID makes the order total and reproducible.

🐛 Proposed fix
-        let orderedItems = items.sorted { $0.bounds.minX < $1.bounds.minX }
+        let orderedItems = items.sorted {
+            if $0.bounds.minX != $1.bounds.minX {
+                return $0.bounds.minX < $1.bounds.minX
+            }
+            return $0.windowID < $1.windowID
+        }

Confirm whether off-screen items in the same section can share a leading edge.

#!/bin/bash
# Look for the off-screen parking coordinate used for hidden items.
rg -n -C3 'pushed far off-screen|offscreen|offScreen|x=-1|origin\.x == -1' --type=swift Thaw/MenuBar
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 5207 - 5215,
Update the orderedItems sort in the neighbor-resolution flow to use windowID as
a deterministic secondary key whenever bounds.minX values are equal, producing a
total reproducible order while preserving leading-edge ordering as the primary
key.

Comment on lines +29 to +53
/// Collects the notifications posted on `name` while `body` runs.
private func notifications(
named name: Notification.Name,
during body: () -> Void
) -> [Notification] {
let box = NotificationBox()
let token = NotificationCenter.default.addObserver(
forName: name,
object: nil,
queue: nil
) { box.append($0) }
defer { NotificationCenter.default.removeObserver(token) }
body()
return box.contents
}

/// Notifications are delivered synchronously on the posting thread, but
/// the observer closure is `@Sendable`, so the collector needs to be a
/// reference type rather than a captured `var`.
private final class NotificationBox: @unchecked Sendable {
private(set) var contents: [Notification] = []
func append(_ notification: Notification) {
contents.append(notification)
}
}

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.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the notification collector into the shared support target.

notifications(named:during:) and NotificationBox exist here and again in ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift at lines 17-41, with identical bodies. Both copies are private, so neither suite can reuse the other. This PR already established ThawTests/Support/ for shared fixtures. Put one copy there and delete both local copies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift` around lines 29 -
53, Move the shared notifications(named:during:) helper and NotificationBox
class into ThawTests/Support/, then remove both private local copies from
SettingsURIHandlerApplyTests and SettingsURIHandlerCoverageTests. Preserve their
existing behavior and signatures so both test suites reuse the shared
implementation.

Comment on lines +111 to +135
func seedManifest(with profiles: [Profile], into directory: URL) throws {
let fileManager = FileManager.default
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

for profile in profiles {
let data = try encoder.encode(profile)
try data.write(
to: directory.appendingPathComponent("\(profile.id.uuidString).json"),
options: .atomic
)
}

let metadata = profiles.map {
ProfileMetadata(id: $0.id, name: $0.name, createdAt: $0.createdAt, modifiedAt: $0.modifiedAt)
}
let manifestData = try encoder.encode(metadata)
try manifestData.write(
to: directory.appendingPathComponent("profiles.json"),
options: .atomic
)
}

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

The profile encoder and manifest seeding are centralized only partially. Three files each configure the same JSONEncoder (.iso8601, [.prettyPrinted, .sortedKeys]) and two of them seed profile JSON. That encoder configuration must mirror the one ProfileManager builds in its initializer, or seeded files stop decoding through the manager's decoder. Three independent copies can drift apart silently.

  • ThawTests/Support/ProfileTestFixtures.swift#L111-L135: extract the encoder configuration into a shared makeProfileEncoder() factory, and add an omittingFilesFor: Set<UUID> = [] parameter to seedManifest so it also covers the missing-file case.
  • ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift#L585-L624: delete the private seedManifest and makeEncoder, and call the shared seedManifest(with:omittingFilesFor:into:) and makeProfileEncoder() instead. Keep withManager and writeBundle.
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift#L120-L129: replace the local encoder configuration in writeProfile with makeProfileEncoder().
📍 Affects 3 files
  • ThawTests/Support/ProfileTestFixtures.swift#L111-L135 (this comment)
  • ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift#L585-L624
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift#L120-L129
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Support/ProfileTestFixtures.swift` around lines 111 - 135, The
profile test fixtures duplicate encoder and manifest-seeding logic across three
files; centralize it to prevent configuration drift. In
ThawTests/Support/ProfileTestFixtures.swift lines 111-135, add shared
makeProfileEncoder() and extend seedManifest with omittingFilesFor: Set<UUID> =
[], skipping those profile files while still writing the manifest. In
ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift lines 585-624,
remove private seedManifest and makeEncoder, use the shared helpers, and
preserve withManager and writeBundle. In
ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift lines
120-129, replace local JSONEncoder setup in writeProfile with
makeProfileEncoder().

Comment on lines +45 to +53
func acquire() async {
if !isLocked {
isLocked = true
return
}
await withCheckedContinuation { continuation in
waiters.append(continuation)
}
}

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.

🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

acquire() ignores cancellation, so a cancelled test hangs instead of failing.

withCheckedContinuation has no cancellation handler. If the enclosing test task is cancelled while a waiter is suspended, the continuation is never resumed and the test never returns. Use withTaskCancellationHandler, or document that callers must not cancel.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Support/ScratchDefaults.swift` around lines 45 - 53, Update
acquire() to handle task cancellation while waiting for the lock by wrapping the
waiter continuation in withTaskCancellationHandler and ensuring cancellation
resumes or removes the suspended waiter safely. Preserve the existing
immediate-acquisition behavior and prevent cancelled test tasks from remaining
suspended indefinitely.

Comment on lines +133 to +143
await scratchDefaultsMutex.acquire()
let previous = Defaults.store
Defaults.store = suite
defer {
// The restore and cleanup below run synchronously before the release
// task is spawned, so the next acquirer can only ever see the
// restored store.
Defaults.store = previous
suite.removePersistentDomain(forName: suiteName)
Task { await scratchDefaultsMutex.release() }
}

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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

The two overloads still do not exclude each other, so a cross-suite interleave can publish a deleted store.

The async overload serializes through ScratchDefaultsMutex. The synchronous overload serializes through scratchDefaultsLock. The doc comments at lines 37-40 and 119-121 state that the two are independent. Defaults.store is one global, so a synchronous caller in suite A and an async caller in suite B can interleave their save-install-restore windows. The async caller then restores a store that suite A has already deleted with removePersistentDomain, and every later test reads through a dead store.

.serialized does not close this gap, because it orders tests inside one suite only. ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift uses both overloads, so both lock domains are live in this test target.

Route both overloads through one mutual-exclusion mechanism. Options: make every caller use the async overload, or make the store swap task-local rather than global so no caller has to restore a value another caller owns.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Support/ScratchDefaults.swift` around lines 133 - 143, Unify the
synchronization used by the synchronous and asynchronous ScratchDefaults
overloads so they cannot interleave while modifying the global Defaults.store.
Update scratchDefaultsLock and scratchDefaultsMutex usage so both overloads
share one mutual-exclusion mechanism, preserving each overload’s existing setup,
restoration, and cleanup behavior without allowing one caller to restore another
caller’s deleted store.

Comment on lines +45 to 63
private func pixelData(of image: CGImage) throws -> [UInt8] {
let width = image.width
let height = image.height
var data = [UInt8](repeating: 0, count: width * height * 4)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
let context = CGContext(
let bitmapContext = CGContext(
data: &data,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: width * 4,
space: colorSpace,
bitmapInfo: bitmapInfo
)!
)
let context = try #require(bitmapContext, "Could not create a bitmap context")
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
return data
}

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.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

pixelData(of:) hands CGContext a pointer into a Swift array that outlives the call.

&data produces a pointer that is only guaranteed valid for the duration of the CGContext initializer call. context.draw writes through that pointer afterwards. The array storage may be moved or copied in between, so the reads at Line 63 are not guaranteed to observe the drawn pixels. Wrap the context creation and the draw in withUnsafeMutableBytes so the buffer stays fixed for the whole lifetime of the context.

♻️ Proposed change
     private func pixelData(of image: CGImage) throws -> [UInt8] {
         let width = image.width
         let height = image.height
         var data = [UInt8](repeating: 0, count: width * height * 4)
         let colorSpace = CGColorSpaceCreateDeviceRGB()
         let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
-        let bitmapContext = CGContext(
-            data: &data,
-            width: width,
-            height: height,
-            bitsPerComponent: 8,
-            bytesPerRow: width * 4,
-            space: colorSpace,
-            bitmapInfo: bitmapInfo
-        )
-        let context = try `#require`(bitmapContext, "Could not create a bitmap context")
-        context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
+        try data.withUnsafeMutableBytes { buffer in
+            let bitmapContext = CGContext(
+                data: buffer.baseAddress,
+                width: width,
+                height: height,
+                bitsPerComponent: 8,
+                bytesPerRow: width * 4,
+                space: colorSpace,
+                bitmapInfo: bitmapInfo
+            )
+            let context = try `#require`(bitmapContext, "Could not create a bitmap context")
+            context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
+        }
         return data
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private func pixelData(of image: CGImage) throws -> [UInt8] {
let width = image.width
let height = image.height
var data = [UInt8](repeating: 0, count: width * height * 4)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
let context = CGContext(
let bitmapContext = CGContext(
data: &data,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: width * 4,
space: colorSpace,
bitmapInfo: bitmapInfo
)!
)
let context = try #require(bitmapContext, "Could not create a bitmap context")
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
return data
}
private func pixelData(of image: CGImage) throws -> [UInt8] {
let width = image.width
let height = image.height
var data = [UInt8](repeating: 0, count: width * height * 4)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
try data.withUnsafeMutableBytes { buffer in
let bitmapContext = CGContext(
data: buffer.baseAddress,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: width * 4,
space: colorSpace,
bitmapInfo: bitmapInfo
)
let context = try `#require`(bitmapContext, "Could not create a bitmap context")
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
}
return data
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Utilities/CGImageDetachedCopyTests.swift` around lines 45 - 63,
Update pixelData(of:) to create and use the CGContext inside
data.withUnsafeMutableBytes, passing the buffer pointer as the context data and
performing context.draw within the same closure. Preserve the existing bitmap
configuration, context-creation failure handling, and returned pixel data.

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 28

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift (2)

202-207: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the enrichment gate read Defaults.store on each access.

static nonisolated let initializes once, on first access, and caches the value for the process lifetime. Defaults.store is now injectable, so the cached value depends on which store happens to be installed when some test first touches this property. A test that installs a scratch store afterwards reads the stale flag, and a test that touches the property while a scratch store is installed bakes that scratch value in for every later test in the same process.

The flag only gates a diagnostic pass, so re-reading it per access costs one UserDefaults lookup per cache cycle.

🛡️ Proposed fix
-    static nonisolated let isDegradedIdentityEnrichmentEnabled =
-        Defaults.store.bool(forKey: "EnableDegradedItemAXEnrichment")
+    static nonisolated var isDegradedIdentityEnrichmentEnabled: Bool {
+        Defaults.store.bool(forKey: "EnableDegradedItemAXEnrichment")
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 202 - 207,
Change isDegradedIdentityEnrichmentEnabled from a cached static let to a
computed static property that reads Defaults.store.bool(forKey:
"EnableDegradedItemAXEnrichment") on every access, preserving the existing key
and nonisolated behavior.

7051-7057: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Abort paths leave the never-committed profile spec armed in memory.

armProfileState overwrites pinnedHiddenBundleIDs, pinnedAlwaysHiddenBundleIDs, savedSectionOrder, activeProfileLayout, and activeProfileItemIdentifiers, and defers disk persistence to persistProfileStateOnSuccess. The new restoreProfileStateAfterCancelledApply undoes that overwrite, but only Phase 7 cancellation calls it (Line 8088).

Four post-arm aborts call clearProfileState instead, which clears isApplyingProfileLayout and nothing else:

  • Line 7222: unresolved-sourcePID majority.
  • Line 7231: a menu bar item menu is open.
  • Line 7243: missing control items.
  • Line 7955: control items lost after Phase 1.

No moves ran and nothing was persisted on any of these paths, so memory now describes a profile that disk does not hold. That is the exact condition the snapshot doc comment at Lines 495-500 names: the late-arrival re-sort path keeps sorting toward a profile that never committed. priorProfileApplySnapshot also stays retained until the next arm replaces it.

Roll back on these aborts as well, using the same token guard.

🐛 Proposed fix
     /// Profile-only exit cleanup: refresh the sorted snapshot and clear
     /// the in-flight profile flag. No-op for .savedOrder.
     private func clearProfileState(source: ApplySource, items: [MenuBarItem]) {
         updateProfileSortedSnapshot(source: source, items: items)
         guard case .profile = source else { return }
         isApplyingProfileLayout = false
     }
+
+    /// Exit cleanup for a profile apply that aborted after arming without
+    /// moving anything and without persisting. Reverts the armed spec so
+    /// memory matches what disk still holds, then runs the normal teardown.
+    private func abandonProfileApply(
+        source: ApplySource,
+        items: [MenuBarItem],
+        token: Int
+    ) {
+        if case .profile = source {
+            restoreProfileStateAfterCancelledApply(token: token)
+        }
+        clearProfileState(source: source, items: items)
+    }

Then replace the four abort calls, for example at Line 7231:

         if await isAnyMenuBarItemMenuOpen() {
             MenuBarItemManager.diagLog.info("applyProfileLayout: skipping, a menu bar item menu is open")
-            clearProfileState(source: source, items: items)
+            abandonProfileApply(source: source, items: items, token: applyToken)
             return
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift` around lines 7051 - 7057,
Update the post-arm abort handling in the functions containing the
unresolved-sourcePID, open-menu, missing-control-item, and post-Phase-1
control-loss paths to call restoreProfileStateAfterCancelledApply with the same
apply token guard instead of clearProfileState. Preserve clearProfileState for
successful completion, and ensure each cancellation restores all armed profile
state and releases priorProfileApplySnapshot without persisting changes.
Thaw/Events/HIDEventManager.swift (1)

1085-1091: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use the documented try? await order for the sleep guard.

Swift requires try before await in expressions that use both operators. guard await (try? Task.sleep(...)) violates that order; use guard (try? await Task.sleep(...)) so the line is guaranteed to compile and matches the existing try await Task.sleep(...) usage pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/Events/HIDEventManager.swift` around lines 1085 - 1091, Update the sleep
guard inside the Task closure in HIDEventManager to use the Swift-required
operator order, placing try? before await: guard (try? await Task.sleep(...)) !=
nil. Preserve the existing cancellation behavior and 250-millisecond delay.
Thaw/Settings/Models/AdvancedSettings.swift (1)

270-279: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Correct the doc comment: this function never returns the default order.

The doc states "Returns the default order if the input is unusable." The implementation does not do that. When rawValues is empty or holds only unrecognized values, preferred is empty and the result is MenuBarSection.Name.allCases, which is the declaration order rather than Defaults.DefaultValue.searchSectionOrder.

SettingsResetterTests.resetAdvancedRestoresSearchSectionOrder derives its expectation by calling this same function, so it cannot detect the difference. Either update the doc to describe the allCases fallback, or make the empty-input case return the sanitized default order.

📝 Proposed doc correction
     /// Returns a search-section order that contains each `MenuBarSection.Name`
     /// case exactly once, using the supplied raw values as the preferred order
-    /// and filling any missing cases at the end. Returns the default order if
-    /// the input is unusable.
+    /// and filling any missing cases at the end in `allCases` order. Unusable
+    /// raw values are dropped, so an input with no recognized value yields
+    /// `MenuBarSection.Name.allCases`.
     static func sanitizedSearchSectionOrder(from rawValues: [String]) -> [MenuBarSection.Name] {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Thaw/Settings/Models/AdvancedSettings.swift` around lines 270 - 279, Correct
the documentation for sanitizedSearchSectionOrder(from:) to state that empty or
entirely unrecognized input falls back to MenuBarSection.Name.allCases in
declaration order, not Defaults.DefaultValue.searchSectionOrder. Keep the
implementation unchanged unless intentionally aligning its behavior with the
documented default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/coverage-report.py`:
- Around line 125-151: Update the explanatory comment above the `best` map and
per-file deduplication loop to state that the implementation approximates Sonar
by retaining the entry with the greatest `coveredLines`, rather than unioning
covered or executable line sets across targets. Preserve the existing
deduplication logic.

In `@sonar-project.properties`:
- Line 85: Remove the whole-file coverage exclusion for AXItemActivator.swift
unless it is genuinely entirely AX-driven; preserve coverage for the pure logic
exercised by AXItemActivatorTests. If the file cannot be split, document the
specific AX-only rationale in the exclusion comment block above, consistent with
the existing whole-file exclusions.

In `@Thaw/Main/Updates.swift`:
- Around line 58-62: Update the allowsBetaUpdates property getter and setter to
use the centralized typed Defaults interface: replace the raw
"AllowsBetaUpdates" accesses with Defaults.bool(forKey: .allowsBetaUpdates) and
Defaults.set(_:forKey: .allowsBetaUpdates), including the additional occurrence
near the related update settings code.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItem`+Enumeration.swift:
- Around line 132-149: In the instance-index rebuild loop within the enumeration
logic, replace the duplicated sourcePID branches with one MenuBarItem
initializer that passes items[itemIndex].sourcePID directly, while preserving
the existing uncheckedItemWindow and instanceIndex arguments.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 5207-5215: Update the orderedItems sort in the neighbor-resolution
flow to use windowID as a deterministic secondary key whenever bounds.minX
values are equal, producing a total reproducible order while preserving
leading-edge ordering as the primary key.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemTag.swift`:
- Line 9: Confirm that the package configuration declares the swift-algorithms
dependency required by the Algorithms import in MenuBarItemTag. Ensure the
target containing MenuBarItemTag links that product, distinguishing it from
swift-async-algorithms, and preserve the order-preserving deduplication
implementation.

In `@Thaw/MenuBar/Search/MenuBarSearchPanel.swift`:
- Around line 401-402: Remove the deprecated Defaults.store.synchronize() call
from the panel frame save path, while preserving the existing
Defaults.store.set(...) frame persistence.

In `@Thaw/Settings/Models/Profile.swift`:
- Around line 334-347: Update resolvedItemSectionMap to treat a non-nil empty
itemSectionMap as absent and fall back to the saved section map, matching
resolvedItemOrder. Also update captureCurrentLayout so empty derived itemOrder
and itemSectionMap collections are not persisted over existing valid saved
values.
- Around line 71-76: Update the snapshot-application logic around the
lastCustomIceIcon assignment in Profile so a nil snapshot value does not
overwrite the value populated by the .custom iceIcon didSet. Apply
lastCustomIceIcon only when the snapshot contains a value, while preserving the
reordered assignment for snapshots that provide one.

In `@Thaw/Settings/Models/ProfileManager.swift`:
- Around line 652-660: Update ProfileManager.captureCurrentLayout() to read
savedSectionOrder, pinnedHiddenBundleIDs, and pinnedAlwaysHiddenBundleIDs using
their typed Defaults.Key cases from MenuBarItemManager instead of raw string
literals. Ensure the corresponding MenuBarItemManager writers and these readers
share the same compile-time key definitions.

In `@ThawTests/MenuBar/Items/CapturedImageScaleTests.swift`:
- Around line 118-135: Update resolvedScaleRestoresPointSize() to declare
throws, then replace try? `#require`(scale) with direct try `#require`(scale) so a
nil resolved scale fails with the framework’s clear requirement error instead of
falling through to the fallback arithmetic.

In `@ThawTests/MenuBar/Items/MenuBarTestFixturesTests.swift`:
- Around line 20-26: Update appItemTagBuildsExpectedNamespaceAndTitle to assert
tag.namespace directly against the expected Namespace case/value instead of
comparing String(describing: tag.namespace). Preserve the existing assertions
for title, instanceIndex, and windowID.

In `@ThawTests/MenuBar/Section/MenuBarSectionNameTests.swift`:
- Around line 90-111: Update localizedVisible, localizedHidden, and
localizedAlwaysHidden to compare each returned LocalizedStringKey directly with
the corresponding LocalizedStringKey literal using Equatable. Remove the
misleading String(describing:) comment and comparisons, while preserving the
existing expected keys.

In `@ThawTests/Settings/Models/AdvancedSettingsTests.swift`:
- Around line 46-80: Update AdvancedSettings to accept an injectable
NotificationCenter, matching the existing center injection pattern used by
debouncedNotificationTask, and use that center when observing settings changes.
In AdvancedSettingsTests, create a private NotificationCenter per test and pass
it to the model, then post through the same isolated center instead of
NotificationCenter.default so external-change assertions cannot receive
notifications from other suites.

In `@ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift`:
- Around line 47-71: Introduce one reusable display-UUID availability condition
and apply it via .enabled(if:) to resolvedDisplayUsesExplicitConfiguration and
resolvedDisplayWithoutOverrideFallsBackToGlobalConfiguration in
ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift lines
47-71, removing the try `#require` and early return; apply the same condition to
typedLookupsMatchTheConfiguration and connectedDisplaysAreWellFormed in
ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift lines
130-149 and 242-254, removing their early-return or vacuous-loop gating so
headless environments report skipped tests consistently.

In `@ThawTests/Settings/Models/GeneralSettingsTests.swift`:
- Around line 27-35: Update GeneralSettingsTests to use the injectable Defaults
store: first confirm GeneralSettings reads and writes through Defaults, then
wrap every test with withScratchDefaults. Remove the per-key snapshot and
restore logic and revise the related documentation to describe the scratch-store
isolation instead.

In `@ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift`:
- Around line 38-40: Strengthen the clearing assertion in the hotkey persistence
test by first unwrapping the dictionary returned by Defaults.dictionary(forKey:
.hotkeys), then assert that action.rawValue is absent from the non-optional
dictionary. Avoid optional chaining so a missing dictionary cannot make the
assertion pass.

In `@ThawTests/Settings/Models/ProfileDecodingDefaultsTests.swift`:
- Around line 590-608: Update the documentation above
duplicateIdentifierResolvesToOneSection by either naming the specific report
file or removing the sentence referring to an accompanying report. Keep the
existing explanation about Dictionary iteration order and the test’s assertion
unchanged.
- Around line 732-748: Update applyPreservesLastCustomIceIcon to set
snapshot.iceIcon to a .custom icon before applying it, while retaining a
distinct remembered value in snapshot.lastCustomIceIcon. Use the existing
custom-icon setup from applyGeneralSettingsCustomIconKeepsLastCustom so
iceIcon’s didSet mirror executes and the test distinguishes the assignment
order.

In `@ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift`:
- Around line 31-36: Update the affected doc comments in the
ProfileLayoutLogReplayTests suite to remove the obsolete XCTest “test” prefix
and reference the migrated methods withoutExclusionTheOrphanWouldBeUnmanaged,
buggyCycleDoesNotPlanMoveForUnresolvedOrphan,
loggedDesiredVisibleIsUsedInsteadOfInference, and
displayReconnectNegativeBudgetYieldsNoOverflow.

In `@ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift`:
- Around line 29-53: Move the shared notifications(named:during:) helper and
NotificationBox class into ThawTests/Support/, then remove both private local
copies from SettingsURIHandlerApplyTests and SettingsURIHandlerCoverageTests.
Preserve their existing behavior and signatures so both test suites reuse the
shared implementation.

In `@ThawTests/Settings/URI/SettingsURIParserTests.swift`:
- Around line 163-177: Update parsingIsTotal to require every test argument to
produce a URL instead of returning silently when URL(string:) is nil. Reuse the
file’s existing helper that enforces URL creation, then pass the resulting URL
to SettingsURIParser.parse.

In `@ThawTests/Shared/DiagnosticLoggerFileTests.swift`:
- Around line 311-324: Update latestLogFileIsSelfConsistent to remove the
cross-read assertion comparing logger.hasLogFiles with latest, since both
accessors independently scan the shared log directory. Retain the existing
latestLogFile shape checks, including the .log extension and logDirectory
parent-path validation.

In `@ThawTests/Support/ProfileTestFixtures.swift`:
- Around line 111-135: The profile test fixtures duplicate encoder and
manifest-seeding logic across three files; centralize it to prevent
configuration drift. In ThawTests/Support/ProfileTestFixtures.swift lines
111-135, add shared makeProfileEncoder() and extend seedManifest with
omittingFilesFor: Set<UUID> = [], skipping those profile files while still
writing the manifest. In
ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift lines 585-624,
remove private seedManifest and makeEncoder, use the shared helpers, and
preserve withManager and writeBundle. In
ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift lines
120-129, replace local JSONEncoder setup in writeProfile with
makeProfileEncoder().

In `@ThawTests/Support/ScratchDefaults.swift`:
- Around line 45-53: Update acquire() to handle task cancellation while waiting
for the lock by wrapping the waiter continuation in withTaskCancellationHandler
and ensuring cancellation resumes or removes the suspended waiter safely.
Preserve the existing immediate-acquisition behavior and prevent cancelled test
tasks from remaining suspended indefinitely.
- Around line 133-143: Unify the synchronization used by the synchronous and
asynchronous ScratchDefaults overloads so they cannot interleave while modifying
the global Defaults.store. Update scratchDefaultsLock and scratchDefaultsMutex
usage so both overloads share one mutual-exclusion mechanism, preserving each
overload’s existing setup, restoration, and cleanup behavior without allowing
one caller to restore another caller’s deleted store.

In `@ThawTests/Utilities/CGImageAnalysisTests.swift`:
- Around line 197-210: Update fallbackAgreesWithFastPath to assert the expected
transparency value for each opaque input in addition to comparing fallback and
normal results. Use opaque to derive the absolute expectation, preserving the
existing agreement assertion.

In `@ThawTests/Utilities/CGImageDetachedCopyTests.swift`:
- Around line 45-63: Update pixelData(of:) to create and use the CGContext
inside data.withUnsafeMutableBytes, passing the buffer pointer as the context
data and performing context.draw within the same closure. Preserve the existing
bitmap configuration, context-creation failure handling, and returned pixel
data.

---

Outside diff comments:
In `@Thaw/Events/HIDEventManager.swift`:
- Around line 1085-1091: Update the sleep guard inside the Task closure in
HIDEventManager to use the Swift-required operator order, placing try? before
await: guard (try? await Task.sleep(...)) != nil. Preserve the existing
cancellation behavior and 250-millisecond delay.

In `@Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift`:
- Around line 202-207: Change isDegradedIdentityEnrichmentEnabled from a cached
static let to a computed static property that reads Defaults.store.bool(forKey:
"EnableDegradedItemAXEnrichment") on every access, preserving the existing key
and nonisolated behavior.
- Around line 7051-7057: Update the post-arm abort handling in the functions
containing the unresolved-sourcePID, open-menu, missing-control-item, and
post-Phase-1 control-loss paths to call restoreProfileStateAfterCancelledApply
with the same apply token guard instead of clearProfileState. Preserve
clearProfileState for successful completion, and ensure each cancellation
restores all armed profile state and releases priorProfileApplySnapshot without
persisting changes.

In `@Thaw/Settings/Models/AdvancedSettings.swift`:
- Around line 270-279: Correct the documentation for
sanitizedSearchSectionOrder(from:) to state that empty or entirely unrecognized
input falls back to MenuBarSection.Name.allCases in declaration order, not
Defaults.DefaultValue.searchSectionOrder. Keep the implementation unchanged
unless intentionally aligning its behavior with the documented default.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4245c0bb-afa2-49e9-8370-65b05294d623

📥 Commits

Reviewing files that changed from the base of the PR and between 4155e4f and f3d078b.

📒 Files selected for processing (243)
  • .github/workflows/ci.yml
  • .gitignore
  • .swiftformat
  • MenuBarItemService/Listener.swift
  • MenuBarItemService/SourcePIDCache.swift
  • MenuBarItemService/main.swift
  • Shared/Bridging/Shims.swift
  • Shared/Utilities/DiagnosticLogger.swift
  • Shared/Utilities/MarkerPairResolver.swift
  • Thaw/Events/EventTap.swift
  • Thaw/Events/HIDEventManager.swift
  • Thaw/Events/RunLoopLocalEventMonitor.swift
  • Thaw/Hotkeys/Hotkey.swift
  • Thaw/Hotkeys/HotkeyRegistry.swift
  • Thaw/Main/AppState.swift
  • Thaw/Main/Updates.swift
  • Thaw/MenuBar/Appearance/Configurations/MenuBarAppearanceConfigurationV2.swift
  • Thaw/MenuBar/Appearance/Configurations/MenuBarShapes.swift
  • Thaw/MenuBar/Appearance/MissionControlDetector.swift
  • Thaw/MenuBar/ControlItem/ControlItem.swift
  • Thaw/MenuBar/LayoutBar/LayoutBarPaddingView.swift
  • Thaw/MenuBar/MenuBarItems/AXIdentityCatalog.swift
  • Thaw/MenuBar/MenuBarItems/AXItemActivator.swift
  • Thaw/MenuBar/MenuBarItems/ClickReactionVerifier.swift
  • Thaw/MenuBar/MenuBarItems/LayoutSolver.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem+Enumeration.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItem.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemFailureLedger.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemImageCache.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemManager.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemServiceConnection.swift
  • Thaw/MenuBar/MenuBarItems/MenuBarItemTag.swift
  • Thaw/MenuBar/MenuBarManager.swift
  • Thaw/MenuBar/MenuBarSection.swift
  • Thaw/MenuBar/MenuBarSectionLayout.swift
  • Thaw/MenuBar/Search/MenuBarSearchPanel.swift
  • Thaw/MenuBar/Spacing/MenuBarItemSpacingManager.swift
  • Thaw/Onboarding/Tour/OnboardingTour.swift
  • Thaw/Onboarding/Tour/TourMockups.swift
  • Thaw/Onboarding/Tour/WelcomeMockup.swift
  • Thaw/Settings/Models/AdvancedSettings.swift
  • Thaw/Settings/Models/DisplaySettingsManager.swift
  • Thaw/Settings/Models/FocusFilterIntent.swift
  • Thaw/Settings/Models/Profile.swift
  • Thaw/Settings/Models/ProfileManager.swift
  • Thaw/UI/Views/CustomTooltip.swift
  • Thaw/Utilities/ConcurrencyHelpers.swift
  • Thaw/Utilities/Defaults.swift
  • Thaw/Utilities/Extensions.swift
  • Thaw/Utilities/ScreenCapture.swift
  • Thaw/Utilities/SettingsURIHandler.swift
  • Thaw/Utilities/SettingsURIParser.swift
  • ThawTests/AdvancedSettingsTests.swift
  • ThawTests/AutomationSettingsTests.swift
  • ThawTests/CaptureBoundsValidationTests.swift
  • ThawTests/CrossCutting/CoverageSweep1Tests.swift
  • ThawTests/CrossCutting/CoverageSweep2Tests.swift
  • ThawTests/CrossCutting/CoverageSweep3Tests.swift
  • ThawTests/CrossCutting/CoverageSweep4Tests.swift
  • ThawTests/CrossCutting/CoverageSweep5Tests.swift
  • ThawTests/DefaultsKeyTests.swift
  • ThawTests/DiagnosticLoggerTests.swift
  • ThawTests/DisplayIceBarConfigurationTests.swift
  • ThawTests/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/DisplaySettingsManagerMutationTests.swift
  • ThawTests/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/ExtensionsTests.swift
  • ThawTests/GeneralSettingsSnapshotTests.swift
  • ThawTests/HotkeyActionTests.swift
  • ThawTests/Hotkeys/HotkeyActionTests.swift
  • ThawTests/Hotkeys/HotkeyLifecycleTests.swift
  • ThawTests/Hotkeys/KeyCodeTests.swift
  • ThawTests/Hotkeys/KeyCombinationTests.swift
  • ThawTests/Hotkeys/ModifiersTests.swift
  • ThawTests/HotkeysSettingsPersistenceTests.swift
  • ThawTests/IceBarLocationTests.swift
  • ThawTests/IceSettingsImporterTests.swift
  • ThawTests/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarShapesTests.swift
  • ThawTests/MenuBar/Appearance/MenuBarTintKindTests.swift
  • ThawTests/MenuBar/Appearance/MissionControlDetectorTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemImageTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemOcclusionTests.swift
  • ThawTests/MenuBar/ControlItem/ControlItemRecoveryTests.swift
  • ThawTests/MenuBar/IceBar/IceBarLocationTests.swift
  • ThawTests/MenuBar/Items/AXIdentityCatalogTests.swift
  • ThawTests/MenuBar/Items/AXItemActivatorTests.swift
  • ThawTests/MenuBar/Items/AnchorDestinationTests.swift
  • ThawTests/MenuBar/Items/BadgeIndexTests.swift
  • ThawTests/MenuBar/Items/CaptureBoundsValidationTests.swift
  • ThawTests/MenuBar/Items/CapturedImageScaleTests.swift
  • ThawTests/MenuBar/Items/ClickReactionVerifierTests.swift
  • ThawTests/MenuBar/Items/ControlCenterHostedMatchLogReplayTests.swift
  • ThawTests/MenuBar/Items/DisplaySpreadGateTests.swift
  • ThawTests/MenuBar/Items/DivergencePersistenceGateTests.swift
  • ThawTests/MenuBar/Items/FlattenCurrentSectionsTests.swift
  • ThawTests/MenuBar/Items/GhostControlItemWindowTests.swift
  • ThawTests/MenuBar/Items/HiddenDragFailureClassificationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemFailureLedgerTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheDisplayResolutionTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheGatingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemImageCacheObservationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemManagerRearmTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemOrderingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemServiceTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagCodingTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemTagTests.swift
  • ThawTests/MenuBar/Items/MenuBarItemValueTests.swift
  • ThawTests/MenuBar/Items/MenuBarTestFixturesTests.swift
  • ThawTests/MenuBar/Items/MidSectionTransitionTests.swift
  • ThawTests/MenuBar/Items/MoveEventCoordinatesTests.swift
  • ThawTests/MenuBar/Items/MoveFailureBackoffTests.swift
  • ThawTests/MenuBar/Items/MoveMenuGuardTests.swift
  • ThawTests/MenuBar/Items/NewItemsPlacementTests.swift
  • ThawTests/MenuBar/Items/PartitionUnmanagedUIDsTests.swift
  • ThawTests/MenuBar/Items/PendingRehideTagIdentifiersTests.swift
  • ThawTests/MenuBar/Items/SavedLayoutSectionLookupTests.swift
  • ThawTests/MenuBar/Items/SavedPositionLookupTests.swift
  • ThawTests/MenuBar/Items/SectionGeometryGateTests.swift
  • ThawTests/MenuBar/Items/SelectWindowForBatchScanTests.swift
  • ThawTests/MenuBar/Items/ShouldPersistSavedOrderTests.swift
  • ThawTests/MenuBar/Items/SourcePIDResolutionGateTests.swift
  • ThawTests/MenuBar/Items/WindowIDsChangedGateTests.swift
  • ThawTests/MenuBar/Layout/LayoutReconcilerTests.swift
  • ThawTests/MenuBar/Layout/LayoutReconcilerUnmanagedPlacementTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetErrorTests.swift
  • ThawTests/MenuBar/Layout/LayoutResetReentrancyTests.swift
  • ThawTests/MenuBar/Layout/NotchOverflowRevealTests.swift
  • ThawTests/MenuBar/Layout/PlanFullSortSequenceTests.swift
  • ThawTests/MenuBar/Layout/PlanLCSMoveSequenceTests.swift
  • ThawTests/MenuBar/Layout/PlanLeftmostMoveTests.swift
  • ThawTests/MenuBar/Layout/PlanNotchOverflowTests.swift
  • ThawTests/MenuBar/Layout/PlanPendingMoveTests.swift
  • ThawTests/MenuBar/Layout/PlanSectionOrderTests.swift
  • ThawTests/MenuBar/Layout/PlanUnmanagedPlacementTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionGeometryTests.swift
  • ThawTests/MenuBar/Section/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/MenuBarItemServiceTests.swift
  • ThawTests/MenuBarItemTagCanonicalizationTests.swift
  • ThawTests/MenuBarItemTagTests.swift
  • ThawTests/MenuBarSectionNameTests.swift
  • ThawTests/MenuBarShapesTests.swift
  • ThawTests/MenuBarTestFixturesTests.swift
  • ThawTests/ModifiersTests.swift
  • ThawTests/MoveFailureBackoffTests.swift
  • ThawTests/Onboarding/OnboardingMockupsTests.swift
  • ThawTests/Onboarding/OnboardingSlideTests.swift
  • ThawTests/OnboardingMockupsTests.swift
  • ThawTests/OnboardingSlideTests.swift
  • ThawTests/PermissionTests.swift
  • ThawTests/Permissions/PermissionTests.swift
  • ThawTests/ProfileEntityTests.swift
  • ThawTests/ProfileExportBundleTests.swift
  • ThawTests/ProfileManagerDeleteTests.swift
  • ThawTests/ProfileManagerRearmGateTests.swift
  • ThawTests/ProfileTests.swift
  • ThawTests/RehideStrategyTests.swift
  • ThawTests/SectionDividerStyleTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/AdvancedSettingsTests.swift
  • ThawTests/Settings/Models/AutomationSettingsTests.swift
  • ThawTests/Settings/Models/DisplayIceBarConfigurationTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerLookupTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerMutationTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/Settings/Models/DisplaySettingsManagerURINotificationTests.swift
  • ThawTests/Settings/Models/ExternalSettingsChangeTests.swift
  • ThawTests/Settings/Models/FocusFilterIntentTests.swift
  • ThawTests/Settings/Models/GeneralSettingsSnapshotTests.swift
  • ThawTests/Settings/Models/GeneralSettingsTests.swift
  • ThawTests/Settings/Models/HookScriptTests.swift
  • ThawTests/Settings/Models/HotkeysSettingsPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileApplyInFlightFlagTests.swift
  • ThawTests/Settings/Models/ProfileDecodingDefaultsTests.swift
  • ThawTests/Settings/Models/ProfileEntityTests.swift
  • ThawTests/Settings/Models/ProfileExportBundleTests.swift
  • ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift
  • ThawTests/Settings/Models/ProfileManagerCRUDTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeepTests.swift
  • ThawTests/Settings/Models/ProfileManagerDeleteTests.swift
  • ThawTests/Settings/Models/ProfileManagerObservationTasksTests.swift
  • ThawTests/Settings/Models/ProfileManagerPersistenceTests.swift
  • ThawTests/Settings/Models/ProfileManagerRearmGateTests.swift
  • ThawTests/Settings/Models/ProfileManagerUpdateRearmIntegrationTests.swift
  • ThawTests/Settings/Models/ProfileTests.swift
  • ThawTests/Settings/Models/RehideStrategyTests.swift
  • ThawTests/Settings/Models/SectionDividerStyleTests.swift
  • ThawTests/Settings/Models/SettingsResetterTests.swift
  • ThawTests/Settings/Search/SearchIndexTests.swift
  • ThawTests/Settings/Search/SearchModelTests.swift
  • ThawTests/Settings/Search/SettingsSearchNavigationTests.swift
  • ThawTests/Settings/URI/IntegrationURIContractTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerApplyTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerCoverageTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerGetTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTailTests.swift
  • ThawTests/Settings/URI/SettingsURIHandlerTests.swift
  • ThawTests/Settings/URI/SettingsURIParserTests.swift
  • ThawTests/SettingsResetterTests.swift
  • ThawTests/SettingsURIHandlerApplyTests.swift
  • ThawTests/SettingsURIHandlerGetTests.swift
  • ThawTests/SettingsURIHandlerTests.swift
  • ThawTests/Shared/CodeSigningInfoTests.swift
  • ThawTests/Shared/DiagnosticLoggerFileTests.swift
  • ThawTests/Shared/DiagnosticLoggerTests.swift
  • ThawTests/Shared/MarkerPairResolverTests.swift
  • ThawTests/Shared/SharedExtensionsTests.swift
  • ThawTests/Shared/WindowInfoDecodingTests.swift
  • ThawTests/Shared/WindowInfoDerivedTests.swift
  • ThawTests/Shared/WindowInfoTests.swift
  • ThawTests/SharedExtensionsTests.swift
  • ThawTests/Support/GraphicsTestFixtures.swift
  • ThawTests/Support/MenuBarTestFixtures.swift
  • ThawTests/Support/ProfileTestFixtures.swift
  • ThawTests/Support/ScratchDefaults.swift
  • ThawTests/Utilities/CGImageAnalysisTests.swift
  • ThawTests/Utilities/CGImageDetachedCopyTests.swift
  • ThawTests/Utilities/ConcurrencyHelpersTests.swift
  • ThawTests/Utilities/CustomTooltipPanelTests.swift
  • ThawTests/Utilities/DefaultsKeyTests.swift
  • ThawTests/Utilities/ExtensionsCoverageTests.swift
  • ThawTests/Utilities/ExtensionsGraphicsTests.swift
  • ThawTests/Utilities/ExtensionsRemainderTests.swift
  • ThawTests/Utilities/ExtensionsTests.swift
  • ThawTests/Utilities/HookRunnerTests.swift
  • ThawTests/Utilities/IceSettingsImporterTailTests.swift
  • ThawTests/Utilities/IceSettingsImporterTests.swift
  • ThawTests/Utilities/LocalizedErrorWrapperTests.swift
  • ThawTests/Utilities/MigrationTests.swift
  • ThawTests/Utilities/MouseMovedThrottleTests.swift
  • ThawTests/Utilities/ObjectStorageTests.swift
  • ThawTests/Utilities/PredicatesTests.swift
  • ThawTests/Utilities/ShareableContentCacheTests.swift
  • ThawTests/Utilities/SimpleSemaphoreTests.swift
  • ThawTests/WindowInfoTests.swift
  • docs/ASSURANCE_CASE.md
  • scripts/coverage-report.py
  • scripts/swiftlint-inputs.xcfilelist
  • sonar-project.properties
💤 Files with no reviewable changes (28)
  • ThawTests/DiagnosticLoggerTests.swift
  • ThawTests/GeneralSettingsSnapshotTests.swift
  • ThawTests/ModifiersTests.swift
  • ThawTests/IceBarLocationTests.swift
  • ThawTests/LayoutResetErrorTests.swift
  • ThawTests/ExtensionsTests.swift
  • ThawTests/MenuBarTestFixturesTests.swift
  • ThawTests/AdvancedSettingsTests.swift
  • ThawTests/DisplayIceBarConfigurationTests.swift
  • ThawTests/HotkeysSettingsPersistenceTests.swift
  • ThawTests/IceSettingsImporterTests.swift
  • ThawTests/MoveFailureBackoffTests.swift
  • ThawTests/DisplaySettingsManagerGlobalFallbackTests.swift
  • ThawTests/MenuBarSectionNameTests.swift
  • ThawTests/DisplaySettingsManagerMutationTests.swift
  • ThawTests/AutomationSettingsTests.swift
  • ThawTests/MenuBarItemTagCanonicalizationTests.swift
  • Thaw/MenuBar/Appearance/MissionControlDetector.swift
  • ThawTests/DefaultsKeyTests.swift
  • ThawTests/DisplaySettingsManagerSpacingGateTests.swift
  • ThawTests/CaptureBoundsValidationTests.swift
  • Thaw/MenuBar/MenuBarSection.swift
  • ThawTests/MenuBarShapesTests.swift
  • ThawTests/MenuBarItemTagTests.swift
  • ThawTests/MenuBar/Items/GhostControlItemWindowTests.swift
  • ThawTests/MenuBarItemServiceTests.swift
  • ThawTests/MenuBarAppearanceConfigurationTests.swift
  • ThawTests/HotkeyActionTests.swift
🛑 Comments failed to post (6)
ThawTests/MenuBar/Items/CapturedImageScaleTests.swift (1)

118-135: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use try #require`` instead of try? for a clearer failure message.

resolvedScaleRestoresPointSize() is not marked throws, so try? #require(scale) converts a #require failure into an Optional. If scale is nil, the test does not fail with a clear "required value is nil" message. It instead falls through to resolved ?? 1 and fails later with a confusing arithmetic mismatch.

Mark the test throws and call try #require(scale) directly.

♻️ Proposed fix
-    `@Test`("The resolved scale gives the item back its true point size")
-    func resolvedScaleRestoresPointSize() {
+    `@Test`("The resolved scale gives the item back its true point size")
+    func resolvedScaleRestoresPointSize() throws {
         // The property that actually matters downstream: CapturedImage
         // divides pixels by scale to get points, and the layout bar sizes
         // its rows from that. Under the old behavior this produced 1850pt
         // instead of 925pt — the doubled row height in the screenshot.
         let boundsWidth: CGFloat = 925
         let pixelWidth = 1850

         let scale = MenuBarItemImageCache.resolvedScale(
             imagePixelWidth: pixelWidth,
             boundsWidth: boundsWidth,
             expected: 1
         )

-        let resolved = try? `#require`(scale)
-        `#expect`(CGFloat(pixelWidth) / (resolved ?? 1) == boundsWidth)
+        let resolved = try `#require`(scale)
+        `#expect`(CGFloat(pixelWidth) / resolved == boundsWidth)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    `@Test`("The resolved scale gives the item back its true point size")
    func resolvedScaleRestoresPointSize() throws {
        // The property that actually matters downstream: CapturedImage
        // divides pixels by scale to get points, and the layout bar sizes
        // its rows from that. Under the old behavior this produced 1850pt
        // instead of 925pt — the doubled row height in the screenshot.
        let boundsWidth: CGFloat = 925
        let pixelWidth = 1850

        let scale = MenuBarItemImageCache.resolvedScale(
            imagePixelWidth: pixelWidth,
            boundsWidth: boundsWidth,
            expected: 1
        )

        let resolved = try `#require`(scale)
        `#expect`(CGFloat(pixelWidth) / resolved == boundsWidth)
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/MenuBar/Items/CapturedImageScaleTests.swift` around lines 118 -
135, Update resolvedScaleRestoresPointSize() to declare throws, then replace
try? `#require`(scale) with direct try `#require`(scale) so a nil resolved scale
fails with the framework’s clear requirement error instead of falling through to
the fallback arithmetic.
ThawTests/Settings/Models/GeneralSettingsTests.swift (1)

27-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the injectable Defaults store instead of snapshotting real UserDefaults.

The doc comment states that Defaults is hardcoded to .standard. This PR adds Defaults.store and a withScratchDefaults helper, and sibling suites such as DisplaySettingsManagerURINotificationTests already use it. With a scratch store, this suite would not write into the developer's own settings, and the cross-suite interference documented at Lines 106-110 and Lines 308-311 would disappear.

Confirm that GeneralSettings reads and writes only through Defaults, then wrap each test in withScratchDefaults and drop the snapshot/restore code.

#!/bin/bash
# Confirm the injectable store and the scratch helper, then check GeneralSettings' storage path.
fd -i 'ScratchDefaults\.swift$' .
fd -i 'ScratchDefaults\.swift$' . --exec cat -n

fd -i '^Defaults\.swift$' . --exec rg -n 'static (var|let) store|withScratchDefaults'

fd -i '^GeneralSettings\.swift$' . --exec rg -n 'UserDefaults|Defaults\.'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/GeneralSettingsTests.swift` around lines 27 - 35,
Update GeneralSettingsTests to use the injectable Defaults store: first confirm
GeneralSettings reads and writes through Defaults, then wrap every test with
withScratchDefaults. Remove the per-key snapshot and restore logic and revise
the related documentation to describe the scratch-store isolation instead.
ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift (1)

31-36: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the doc comments to the migrated test names.

These comments still name the XCTest methods with the test prefix: testWithoutExclusionTheOrphanWouldBeUnmanaged, testBuggyCycleDoesNotPlanMoveForUnresolvedOrphan, testLoggedDesiredVisibleIsUsedInsteadOfInference, and testDisplayReconnectNegativeBudgetYieldsNoOverflow. The suite now declares withoutExclusionTheOrphanWouldBeUnmanaged, buggyCycleDoesNotPlanMoveForUnresolvedOrphan, loggedDesiredVisibleIsUsedInsteadOfInference, and displayReconnectNegativeBudgetYieldsNoOverflow. A reader who searches for the documented names finds nothing.

📝 Proposed fix
-/// unmanaged set inside the live partitioner. Two tests pin it down:
-/// testWithoutExclusionTheOrphanWouldBeUnmanaged documents the bug mechanism
-/// (with no exclusion the orphan is classified unmanaged, matching the field
-/// log), and testBuggyCycleDoesNotPlanMoveForUnresolvedOrphan is the regression
-/// lock that fails before the fix and passes after it.
+/// unmanaged set inside the live partitioner. Two tests pin it down:
+/// withoutExclusionTheOrphanWouldBeUnmanaged documents the bug mechanism
+/// (with no exclusion the orphan is classified unmanaged, matching the field
+/// log), and buggyCycleDoesNotPlanMoveForUnresolvedOrphan is the regression
+/// lock that fails before the fix and passes after it.
-    /// desiredVisible (older captures do not log it; see
-    /// testLoggedDesiredVisibleIsUsedInsteadOfInference) or the two
-    /// notch-overflow lines (see testDisplayReconnectNegativeBudgetYieldsNoOverflow).
+    /// desiredVisible (older captures do not log it; see
+    /// loggedDesiredVisibleIsUsedInsteadOfInference) or the two
+    /// notch-overflow lines (see displayReconnectNegativeBudgetYieldsNoOverflow).

Also applies to: 69-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/Models/ProfileLayoutLogReplayTests.swift` around lines 31
- 36, Update the affected doc comments in the ProfileLayoutLogReplayTests suite
to remove the obsolete XCTest “test” prefix and reference the migrated methods
withoutExclusionTheOrphanWouldBeUnmanaged,
buggyCycleDoesNotPlanMoveForUnresolvedOrphan,
loggedDesiredVisibleIsUsedInsteadOfInference, and
displayReconnectNegativeBudgetYieldsNoOverflow.
ThawTests/Settings/URI/SettingsURIParserTests.swift (1)

163-177: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the totality test fail when an input is not a URL.

parsingIsTotal returns silently when URL(string:) returns nil. That case then passes without calling the parser, so an input that stops being a valid URL becomes a dead case with no signal. The file already has a helper that requires a URL; use the same rule here.

💚 Proposed fix
-    func parsingIsTotal(uri: String) {
-        guard let url = URL(string: uri) else { return }
-        _ = SettingsURIParser.parse(url)
+    func parsingIsTotal(uri: String) throws {
+        let url = try `#require`(URL(string: uri), "Could not build a URL from \(uri)")
+        _ = SettingsURIParser.parse(url)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    /// The parser is total: any URL yields a request and nothing traps.
    `@Test`("Parsing is total over awkward input", arguments: [
        "thaw://set?key=a&value=b&key=c",
        "thaw://set?key=%00&value=%FF",
        "thaw://get?callback=javascript:alert(1)",
        "thaw://toggle?key=" + String(repeating: "x", count: 8192),
        "thaw://set?=&=&=",
        "thaw://%20",
        "thaw://get?key=a#fragment",
    ])
    func parsingIsTotal(uri: String) throws {
        let url = try `#require`(URL(string: uri), "Could not build a URL from \(uri)")
        _ = SettingsURIParser.parse(url)
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Settings/URI/SettingsURIParserTests.swift` around lines 163 - 177,
Update parsingIsTotal to require every test argument to produce a URL instead of
returning silently when URL(string:) is nil. Reuse the file’s existing helper
that enforces URL creation, then pass the resulting URL to
SettingsURIParser.parse.
ThawTests/Shared/DiagnosticLoggerFileTests.swift (1)

311-324: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

The hasLogFiles == (latest != nil) assertion reads the shared real log directory twice and can flake.

latest is read at Line 317 and hasLogFiles at Line 319. Both accessors scan ~/Library/Logs/Thaw, which is not owned by this suite. Another suite that enables DiagnosticLogger, or the developer's own running app, can create or rotate a file between the two reads. The equality then fails for a reason unrelated to the accessors.

Assert the shape of latestLogFile and drop the cross-read equality, or derive both values from one directory listing.

🛠️ Proposed fix
-        let logger = DiagnosticLogger.shared
-        let latest = logger.latestLogFile
-
-        `#expect`(logger.hasLogFiles == (latest != nil))
-        if let latest {
+        let logger = DiagnosticLogger.shared
+
+        if let latest = logger.latestLogFile {
+            // A file was found, so the directory is non-empty by definition.
+            `#expect`(logger.hasLogFiles)
             `#expect`(latest.pathExtension == "log")
             `#expect`(latest.deletingLastPathComponent().path == logger.logDirectory.path)
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Shared/DiagnosticLoggerFileTests.swift` around lines 311 - 324,
Update latestLogFileIsSelfConsistent to remove the cross-read assertion
comparing logger.hasLogFiles with latest, since both accessors independently
scan the shared log directory. Retain the existing latestLogFile shape checks,
including the .log extension and logDirectory parent-path validation.
ThawTests/Utilities/CGImageAnalysisTests.swift (1)

197-210: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin the expected transparency value, not only the agreement between the two paths.

The assertion compares the fallback result with the fast-path result. If both routes regress in the same direction, the test still passes. Add the absolute expectation so the case fails when either route is wrong.

♻️ Proposed change
-        `#expect`(fallback.isTransparent() == normal.isTransparent())
+        `#expect`(fallback.isTransparent() == !opaque)
+        `#expect`(normal.isTransparent() == !opaque)
+        `#expect`(fallback.isTransparent() == normal.isTransparent())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    `@Test`("The fallback path agrees with the fast path", arguments: [true, false])
    func fallbackAgreesWithFastPath(_ opaque: Bool) throws {
        // An image with no explicit byte order is outside the fast path, so
        // this exercises the TransparencyContext fallback.
        let fallback = try makeDefaultByteOrderImage(width: 8, height: 8, opaque: opaque)
        let normal = try makeCanvas(width: 8, height: 8) { context in
            if opaque {
                context.setFillColor(red: 1, green: 0, blue: 0, alpha: 1)
                context.fill(CGRect(x: 0, y: 0, width: 8, height: 8))
            }
        }

        `#expect`(fallback.isTransparent() == !opaque)
        `#expect`(normal.isTransparent() == !opaque)
        `#expect`(fallback.isTransparent() == normal.isTransparent())
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ThawTests/Utilities/CGImageAnalysisTests.swift` around lines 197 - 210,
Update fallbackAgreesWithFastPath to assert the expected transparency value for
each opaque input in addition to comparing fallback and normal results. Use
opaque to derive the absolute expectation, preserving the existing agreement
assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

appearance Tint, shapes, menu bar appearance editor docs Improvements or additions to documentation enhancement Iteration, improvement, or optimization of an existing feature fix PR that fixes a defect (issue reports use bug) hotkeys Hotkey recording and bindings layout Saved layouts, LayoutBar, reorder, spacing menubar Hide/show, sections, control items, backends, capacity onboarding First-run / tour ops CI, release, GitHub hygiene, scripts — repo operations, not a product surface profiles Profiles and layout snapshots refactor Code restructuring without behavior change settings Settings UI not covered by a more specific area test Test additions or updates updates Sparkle / release channels / appcast

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant