Host support
Which of DroppyKit's surfaces the shipping Droppy actually mounts, and what happens when you reach for one it does not.
Every protocol in this SDK has a stable shape. That is not the same as every protocol having a host behind it. Droppy adopts these surfaces one at a time, and this page is the record of where that has got to, so you can decide what to build before you build it rather than after. It names a Droppy version because the answer moves: a column here is what that release mounts, and a later one never mounts less.
The rule the host follows is the same everywhere: a surface Droppy cannot mount refuses, and says so. It never silently succeeds. A refused call returns false or nil and writes one line into Droppy's log naming what it refused and why, which you can read with:
log stream --predicate 'subsystem == "app.getdroppy.Droppy" AND category == "droplet"'A droplet that believes it put something on screen behaves worse than one that knows it did not, which is why nothing here fails open.
The matrix
| Surface | Protocol | Droppy 15.3 |
|---|---|---|
| Preferences | DropletPreferencesService | Mounted |
| Environment | DropletEnvironmentService | Mounted |
| Logging | DropletLogService | Mounted |
| Install state | DropletInstallStateService | Mounted |
| Workspace | DropletWorkspaceService | Mounted |
| Feedback | DropletFeedbackService | Mounted |
| Permissions | DropletPermissionsService | Mounted |
| Global shortcuts | DropletShortcutsService | Mounted |
| Mini HUD | DropletHUDService | Mounted |
| Shelf open and close | DropletShelfService | Mounted |
| Live activity seat | DropletLiveActivityService | Reports only |
| Shelf widgets | ShelfWidgetProviding | Not yet mounted |
| Expanded surfaces | ExpandedSurfaceProviding | Not yet mounted |
| Settings panes | SettingsPaneProviding | Not yet mounted |
Getting a droplet onto a Mac
Droppy loads every droplet it finds under
~/Library/Application Support/Droppy/Droplets/<droplet-id>/<Name>.dropletat launch, one directory per droplet, named for the id in its manifest — the folder, the bundle identifier and the manifest id are cross-checked against each other, so a renamed folder is refused rather than loaded under the wrong identity.
That is also how an accepted droplet reaches a user today. The Store lists droplets by maker and credits yours to you, but the install button behind the row, the droppy://extension/<id> deep link and catalogue auto-update are still being built. Nothing about your droplet changes when they land: the bundle and the admission checks are the same either way.
While you are developing, droppykit build writes the bundle into .build/; copying it into that directory and relaunching Droppy is the whole install. Use the harness for the edit-run loop, though — see The harness — and Droppy only for the last mile.
What "mounted" means
Your droplet's call reaches Droppy's own surface and the user sees the result. A refusal from a mounted surface is a real refusal — a missing capability, a higher band already owning the notch — and never a missing implementation.
What "reports only" means
DropletLiveActivityService tells you truthfully which seat you hold, and yield(reason:) stands you down. Droppy does not yet award a seat to an external droplet, so today the seat you are told about is always .none. Publish liveActivityState anyway: nothing about your droplet changes when the host starts awarding, and a droplet that was not publishing gets nothing on the day it does.
What "not yet mounted" means
The protocol compiles, the harness renders it, and the shapes are settled — the host end is the part that is still being built. Reaching for one of these from inside Droppy refuses and logs. Reaching for one inside DroppyKitHarness works, which is where to develop against them today.
Build for them now if the surface is what your droplet is for. Do not build a droplet whose only visible output is one of them and expect a user to see anything yet.
Designing against a surface that is not mounted
The harness is the reason this is worth doing ahead of the host. It renders shelf widgets in both the solo and grouped compositions, expanded surfaces at their real sizes, live activities in both seats, and settings panes in Droppy's own Settings chrome. A droplet that looks right in the harness will look right in Droppy, because the harness draws with the same design system and the same metrics the app does — see The harness.
What the harness cannot give you is arbitration: which droplet wins the compact seat, how the shelf behaves when a takeover is up, what happens when the user collapses the shelf mid-surface. Those are host decisions and they arrive with the host.
Checking from inside a droplet
Nothing in the SDK reports "is this surface mounted", deliberately: a droplet that branches on host capability ages badly, because the branch is still there long after the capability arrives. Handle a refusal instead — that code stays correct in both worlds:
if !host.hud.present(request) {
// Not an error. The surface was taken, or this Droppy does not
// mount it. Either way the moment has passed; do not retry.
host.log.debug("HUD not shown")
}Use isGranted(_:) to hide affordances for a capability the user did not grant. That is a different question from whether the host mounts the surface, and it is the one worth asking in your own UI.