49 lines
3.8 KiB
Markdown
49 lines
3.8 KiB
Markdown
# Conjure
|
||
|
||
A native macOS application launcher. Open it normally, search for an application, and press Return to open it or bring its existing instance forward. Global shortcuts are deliberately deferred.
|
||
|
||
## Build and run
|
||
|
||
Requires macOS 14 or later and a Swift 6 toolchain (Xcode 16 or later).
|
||
|
||
```sh
|
||
make run
|
||
```
|
||
|
||
This builds a release executable, assembles an ad-hoc-signed application at `.build/Conjure.app`, gracefully quits any running instance of that exact development bundle, and launches a fresh process through Launch Services. This ensures rebuilt code is loaded; simply opening an already-running app in Finder does not reload its executable. Other copies of Conjure are left running. The local signature is for development, not notarized distribution.
|
||
|
||
```sh
|
||
make build # Build the application without opening it.
|
||
make test # Run the discovery and search behavior tests.
|
||
```
|
||
|
||
Open `Package.swift` in Xcode to edit the project. Use the bundled app above to run it with its application identity and metadata.
|
||
|
||
## Controls
|
||
|
||
- Type to filter applications; the first match is selected automatically.
|
||
- Up/Down or Control-K/Control-J changes the selection without leaving search or modifying its text.
|
||
- Return, keypad Enter, the Open Application button, or a double-click opens the selection.
|
||
- Escape, Command-W, or switching focus away dismisses the launcher.
|
||
- Reopen from Finder or the Dock to show it again with an empty search. Dismissal leaves the process running; Command-Q quits it.
|
||
- Standard macOS text-editing shortcuts work in the search field.
|
||
- Drag the panel background to move it.
|
||
|
||
## Discovery and search
|
||
|
||
Applications are discovered in `/Applications`, `/System/Applications`, `/System/Library/CoreServices/Applications`, and `~/Applications`, including nested folders. Discovery runs off the UI thread and refreshes whenever the launcher is presented. Hidden items, invalid bundles, and helpers inside application packages are excluded; symlinked applications are deduplicated by their canonical paths.
|
||
|
||
Search is case- and accent-insensitive. Ranking is exact name, prefix, substring, then ordered-subsequence fuzzy matches. Fuzzy matches favor smaller gaps and earlier starts. Ties and empty searches are alphabetical. Hover over a result to see its path. Launch failures appear in a native alert without dismissing the launcher.
|
||
|
||
## Native UI
|
||
|
||
The launcher now follows the Raycast reference: a 750 × 474-point, titlebar-free dark panel with rounded corners, native translucency and shadow, a large borderless search field, compact icon rows, neutral rounded selection, right-aligned application labels, and a bottom-right launch pill with a Return key hint. It deliberately uses dark appearance for this initial visual direction.
|
||
|
||
AppKit still provides text editing, table navigation, scrolling, application icons, controls, menus, fonts, semantic colors, and visual-effect materials. Custom styling is limited to layout, rounded surfaces, and selection drawing; increased-contrast settings add a selection outline. No third-party UI dependencies, copied Raycast branding, or nonfunctional Actions menu.
|
||
|
||
- `Sources/Conjure`: application lifecycle, native window, and application launching.
|
||
- `Sources/ConjureKit`: filesystem discovery and search ranking.
|
||
- `Tests/ConjureKitTests`: deterministic discovery and ranking checks.
|
||
|
||
The launcher uses Apple's [visual-effect materials](https://developer.apple.com/documentation/appkit/nsvisualeffectview), [table row selection drawing](https://developer.apple.com/documentation/appkit/nstablerowview/drawselection(in:)), [application launching](https://developer.apple.com/documentation/appkit/nsworkspace/openapplication(at:configuration:completionhandler:)), and [reopen lifecycle](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/applicationshouldhandlereopen(_:hasvisiblewindows:)).
|