Building widgets

Mac Desktop

Publish a native counter to a medium macOS desktop widget.

Mac Desktop is experimental and disabled by default. Local opt-in uses the preen.features.desktopWidget preference in the Mac app’s own defaults domain; quit and reopen the app after changing it. Saved assignments are preserved.

When enabled, Mac Desktop is a native WidgetKit destination in Preen. It uses the medium desktop size: two units wide and one unit tall. macOS chooses the exact bounds.

Connect the surface

  1. In Preen, choose Add a display → Mac Desktop → Connect Mac Desktop.
  2. Choose a library widget, or Add an example counter.
  3. Right-click the desktop and choose Edit Widgets → Preen → Preen Desktop. Add the medium widget.

Select the Desktop bird and click a library widget to change its source. The bird indicates that the local destination is enabled; it does not confirm that macOS has placed or refreshed the widget. Additional macOS placements show the same assigned source in this first version.

Publish native content

The desktop consumes the nativeSurface field in the assigned widget’s existing JSON data feed. Use the same authenticated CLI, MCP push_data, or collector endpoint that supplies the widget’s other data. The Mac hub validates the native content and mirrors it to the WidgetKit extension through an App Group. The extension holds no Preen credentials and does not execute the widget’s HTML.

This is a specific counter format, not a general native-code loader: publishing Swift or Kotlin source does not create a renderer. HTML and counter content can share one widget and one producer; supply both views’ data in the same snapshot.

After adding the example counter:

preen data 'Native counter' '{"nativeSurface":{"schemaVersion":1,"type":"counter","title":"Daily progress","subtitle":"One step at a time","value":25,"target":40,"unit":"tasks","style":{"accent":"#B64832"}}}'

Data pushes replace the feed object. Include any other fields your HTML widget needs in the same push. HTML-only widgets remain available on other destinations; the desktop shows a message until their producer supplies native content.

The native content contract is shared with the planned Live Activity surface:

FieldMeaning
schemaVersionRequired; 1.
typeRequired; "counter".
titleRequired nonblank text, at most 80 characters.
subtitleOptional text, at most 160 characters.
valueRequired finite number.
targetOptional positive finite number; draws progress.
unitOptional text, at most 24 characters.
style.accentOptional #RRGGBB.
style.backgroundOptional #RRGGBB.
style.foregroundOptional #RRGGBB.

The encoded content is capped at 2,048 bytes. Omitted colors use platform defaults; macOS appearance settings may alter the presentation. Reaching or exceeding a target does not end anything. The progress bar clamps visually while the numeric value remains unchanged.

An absent or null nativeSurface clears native content. Malformed or unsupported content replaces the previous counter with a message. Changing the assignment, pausing, removing the destination, or deleting its source also clears that presentation when macOS next refreshes.

Refresh and lifecycle

The hub mirrors the latest feed during its status refresh and coalesces automatic WidgetKit reload requests to at most one per minute. The widget also requests a timeline refresh after 15 minutes. Both are requests: macOS decides when the desktop actually refreshes. The setup preview reflects the hub’s latest read; it does not prove that the desktop has caught up.

When Preen is closed, the extension retains the last shared content. Keep the Mac hub and your producer running for new updates. This destination needs no phone, pairing, cloud relay, or subscription.

The first version displays a counter and opens Preen when clicked. It does not provide JavaScript execution, arbitrary native code, or action buttons. The shared native model/view are ready for a future iOS ActivityKit adapter; Live Activity creation, APNs delivery, and lifecycle APIs are separate work.