Repository navigation
feat: expose containers and machines to Spotlight and Shortcuts - #188
Merged
Merged
Conversation
Adds ContainerEntity and MachineEntity (IndexedEntity) with Start, Stop and Open intents, and a SpotlightIndexer that keeps the index in step with the service. All APIs are macOS 15+, so this ships on macOS 26 and 27 with no gating; the macOS 27 IndexedEntityQuery reindex callback is not adopted. Indexing is skipped under tests so test hosts never write into the system Spotlight index.
Replaces the duplicated per-kind snapshot/diff/index blocks with one generic helper, drops the unused Equatable on IndexDiff, and builds snapshots with uniquingKeysWith so a duplicate id cannot trap.
henrywang
added a commit
that referenced
this pull request
Oct 10, 2026
Closes #189 ## What - **New `BerthlyUITests/AppIntentsSystemTests.swift`:** two tests that drive Berthly's intents through the system, the path Shortcuts and Spotlight use. - Open Container: resolves the mock `worker` container through Berthly's entity query, runs the intent, and checks the detail pane shows "worker". - Start Container: starts the stopped `worker`, then checks the detail pane shows the Stop button. - **Guards:** `#if canImport(AppIntentsTesting)`, because the framework ships only with Xcode 27, plus `@available(macOS 27, *)`. - **Safety:** each test launches Berthly in mock mode first. The system routes intents to the running instance, so a system-launched instance never acts on a real daemon. - **`project.pbxproj`:** sets `DEVELOPMENT_TEAM` on the `BerthlyUITests` target only. ## Why the signing change (spike result) `AppIntentsTesting` answers only a test runner signed by the same development team as the app. Apple's WWDC26 session "Validate your App Intents adoption with AppIntentsTesting" states this, and I verified it locally: | Runner signing | Result | |---|---| | Team-signed | both tests pass | | Ad-hoc (the previous default) | `AppIntentsServicesSecurityErrorDomain` code 800, "Your app does not have permission to perform this" | | Everything ad-hoc, as `ci.yml` builds | same error | ## CI behavior - **macOS 26 leg:** compiles the file out (Xcode 26). - **macOS 27 leg:** builds ad-hoc, so the tests catch the code 800 error and `XCTSkip`. The run stays green. So like `BerthlyE2ETests`, these run only on a developer's Mac, with no extra arguments: a normal ⌘U or `xcodebuild test` on Xcode 27 / macOS 27 includes them. Running them on CI would need a development certificate in CI secrets, which is not part of this PR. They stay in `BerthlyUITests`, not `BerthlyE2ETests`. They use mock data, and `scripts/e2e.sh` re-signs its runner ad-hoc, which would make them skip. ## Verification - Local, project signing (team runner): both tests pass. - Local, CI-style ad-hoc signing: both skip, and the run succeeds. - Full `BerthlyUITests` with the team-signed runner: 84 tests, 0 failures. - `swiftlint lint --strict` clean. ## Local gotcha If stale copies of `app.berthly.Berthly` are registered with LaunchServices (old DerivedData builds, an installed release), macOS may not see the intents, and the tests fail on "exactly one container named worker". Unregister the stale copies with `lsregister -u` (see the PR #188 description). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #167
What
ContainerEntityandMachineEntity(AppEntity+IndexedEntity), searchable by name or image. Utility machines (the internal default VM) are excluded, as in the sidebar.MenuBarBridge.pendingIntent = .selectCompute), so it lands on the item's detail pane.SpotlightIndexerindexes both kinds and re-sends only what changed, by fingerprint (the service replaces its arrays every poll). On first connect after launch it clears and re-indexes, so entries for items deleted while Berthly was not running don't linger. It does not run under unit or UI tests.BerthlyApp.initnow creates the service and menu bar bridge and registers them withAppDependencyManager, so intents can reach them outside the view tree.Scope decisions (differ from the issue)
IndexedEntitywas macOS 27-only; it is macOS 15+. Every API used is macOS 15 or older, so the feature ships on macOS 26 and 27 alike (agreed with the maintainer). The macOS 27-onlyIndexedEntityQueryreindex callback is not adopted, since the indexer already re-sends everything on launch.AppIntentsTestingcoverage. It is macOS 27 / Xcode 27 only and cannot run on the macOS 26 gate. Callingperform()directly in unit tests crashes the host, because@Parameter/@Dependencyassert outside the intent runtime. So each intent delegates to a plainComputeIntentActionsfunction, which is unit-tested.Verification
BerthlyTestspasses;SecondaryViewTestspasses, confirming the app still launches after theinitchange;swiftlint lint --strictclean.Note for local testing
macOS registers an app's actions by bundle ID. With several copies of
app.berthly.Berthlyregistered (old DerivedData builds,dist/export, an installed release without intents), the indexer can read the wrong copy and the actions silently don't appear. Unregister stale copies withlsregister -uand checklinkdin/usr/bin/logforChecking app.berthly.Berthly at .../extract.actionsdata. Shipped releases are a single copy carrying the metadata, so users are not affected.🤖 Generated with Claude Code