Your Mac can host a Notch on each connected display. Click Add → Mac Notch in Preen’s display strip, then choose a screen. Already-paired screens are shown disabled. There is no automatic display choice. Choose iOS from the same Add flow to pair another iPhone or iPad.
Each Notch stays with its chosen display. Unplugging that screen makes its bird go offline; reconnecting it brings the same Notch back. It never moves to another screen. On a display with a notch, the surface sits around the camera cutout. On other displays, it becomes a small island below the menu bar.
Each Notch has its own widget, bird color, settings, and alert rules, and works without a paired phone. Pairing offers the Power starter when there is room in your widget library. To select another widget, click a Notch bird and then a widget in the list, or use Show on Notch → display name in a widget’s menu. Pick several birds to send the same widget to all of them. Hover a bird to see its display name.
Preferred widget size
Set a preferred content size in your widget’s <head>:
<meta name="preen-preferred-size-mac" content="auto 180">
<meta name="preen-preferred-size-mac-notch" content="auto 150">
The two values are width height in points. Each is a positive number or
auto, which uses the host’s default for that axis. The Mac tag provides a
platform default; the -notch tag replaces the entire declaration for a Notch.
Here, the Notch uses its normal width and a 150-point content height.
A specific auto uses the host default, not the corresponding Mac-tag value.
The naming pattern is preen-preferred-size-<platform>-<surface>. Currently
only Notches consume these preferences; Text Select, desktop widgets, phones,
and browsers retain their existing sizing. Future surfaces can adopt their
own suffix and fall back to the platform tag.
These are preferences within the size you configured. On a Notch, width is
clamped between 320 points and the configured width. Content height is clamped
between 80 points and two-thirds of the configured height (about 213 points
with the default configuration). The camera band is added above that content.
Camera clearance can increase the width; available screen space can reduce
either dimension. Use PREEN.surface.width and .height for the actual
renderer dimensions, including the top band when using a full-viewport layout.
Files and AirDrop use the normal size, with an animated resize when switching between them and a compact widget. Reduce Motion makes resizing immediate. Resting and notification sizes are unaffected.
Only static tags inside <head> count; tags inside comments, scripts, styles,
or templates are ignored. Use plain decimal numbers without units, percentages,
or expressions. The first declaration for each name wins. An invalid specific
declaration falls back to the Mac tag, then to the normal size. Publishing or
updating through MCP reports malformed and duplicate declarations and sizes
outside the Notch’s supported range. Preferences are read before rendering;
republish the HTML to change them.
Open it and send a signal
Hover briefly to reveal the surface. Click the top edge or drag it downward to keep it open. Escape, clicking the top edge again, or clicking outside closes it. You can turn off hover opening, choose a bird color, or change the low-battery threshold by right-clicking the Notch bird. The chosen bird color is saved and also colors its selection border and subtle background tint. That menu also contains Open surface and Show surface. Turning off Show surface hides it while keeping its pairing. Remove Notch deletes that pairing and its rules, freeing the display for Add → Mac Notch again. Moving a Notch to a different display requires removing it and pairing the new display.
Power connected, unplugged, and low-battery alerts briefly widen the compact surface. Custom alerts use the same presentation. They update an open surface’s context without switching its widget. Power alerts show the battery icon and percentage on the left and the event text on the right. Connected power is green; on battery, the displayed percentage selects green above 20%, yellow at 11–20%, orange at 3–10%, and red at 0–2%. They use bold matching ink and the default 9.6-second duration.
preen surface status # connected display IDs + paired Notch IDs
preen surface pair DISPLAY_ID # returns the new Notch's id
preen surface show SURFACE_ID "My widget"
preen surface event SURFACE_ID '{"title":"Build finished","detail":"12 passed","key":"build"}'
preen surface configure SURFACE_ID '{"width":560,"height":340,"hoverEnabled":true}'
preen surface hide SURFACE_ID
preen surface remove SURFACE_ID
Replace DISPLAY_ID with a connected display’s id, and SURFACE_ID with a
paired Notch’s id. These are different identities: removing and pairing a
screen again creates a new Notch ID.
The matching MCP tools are get_mac_surface, pair_mac_surface,
remove_mac_surface, configure_mac_surface, notify_mac_surface, and
set_mac_surface_rules. Call get_mac_surface without arguments to list displays
and pairings, or with surfaceID to inspect one Notch and its rules. Pair with
displayID; configure, remove, notify, and set rules with surfaceID.
Publishing and updating HTML still use the normal widget tools.
Keep files nearby or AirDrop them
Drag one or more files over the Notch to reveal a Files landing. Drop them to keep them in a recent-files tray shared by this Mac’s Notches. With Preen’s keyboard control enabled, hold Control–Option (⌃⌥) while hovering over the Notch to reveal that tray. Release the keys and drag files out to Finder or another app. The tray stays open during the drag.
Hold Control–Option while dragging files into the Notch to switch to an AirDrop landing. Dropping opens the system AirDrop recipient picker for those files; choose a recipient there. Pressing or releasing the keys before dropping changes the destination. An AirDrop drop does not add files to the tray.
The tray remembers up to 20 recent files across app launches. Ordinary drops keep references to the originals, so removing a card or clearing the tray does not delete those originals. Apps that export a file during a drag may create a local copy for the tray. Files that are no longer available appear dimmed.
These interactions work with any selected widget, including an empty Notch. Ordinary hover still opens the widget. Battery and thermal alerts use the top row while the Files or AirDrop view is open.
Use the same widget framework
The first Mac renderer uses a web view with the same SDK and style kit as the
iOS display. Use PREEN.onData, declared actions through PREEN.trigger, and
the widget’s PREEN.db as usual. The widget cannot make network requests.
Actions still require the widget’s current code and capabilities to be approved
in Preen. Mac haptics are unavailable; PREEN.hapticsAvailable is false.
An ordinary widget fills the expanded content area. Above it, the default top row shows a battery icon and percentage on the left, with local date and time on the right. It updates live, uses the Mac’s battery regardless of the widget’s data feed, and shows AC power on Macs without an internal battery. Alerts temporarily replace the default text. There are no bottom controls.
The top row is a template: your widget can replace either side independently.
Omit leading to keep the battery; omit trailing to keep the clock. The clock
stops while hidden. For a custom top row and content, add a data-preen-surface
wrapper with the regions you want to supply:
<main data-preen-surface>
<div data-preen-region="leading" id="signal">Build</div>
<div data-preen-region="trailing" id="summary">Ready</div>
<header data-preen-region="header">Your own tabs or controls</header>
<section data-preen-region="body">Your own content</section>
</main>
The Mac host places the leading/trailing regions beside the measured cutout.
The header spans the width below the cutout; the body occupies the space
underneath. The default header height is 48 points, configurable with
--preen-header-height. Omit header to place the body directly under the top
row. The body extends to the bottom of the surface. Use responsive layouts and modest px or rem
sizes on this small canvas. Write your phone layout separately in CSS when a
widget also needs to fill a phone screen.
let values = {};
let surface = window.PREEN.surface;
function render() {
const alert = surface.alert;
document.getElementById('signal').textContent = alert?.title || 'Build';
document.getElementById('summary').textContent =
alert?.detail || values.status || 'Ready';
for (const id of ['signal', 'summary']) {
const style = document.getElementById(id).style;
style.color = alert ? (alert.textColor === 'alert'
? alert.color || '#8e8e93' : alert.textColor || '#f5f5f7') : '';
style.fontWeight = alert ? (alert.bold ? '700' : '400') : '';
}
}
window.PREEN.onData(data => { values = data || {}; render(); });
window.PREEN.onSurface(context => { surface = context; render(); });
window.PREEN.ready();
PREEN.surface is read-only display context. onSurface calls your callback
immediately, then again when that context changes. On a phone, kind is
"display". On the Mac, it is "mac-top-edge", with these fields:
| Field | Meaning |
|---|---|
id, displayID | This Notch’s pairing ID and its bound display ID |
mode | "rest", "glance", or "expanded" |
visible | Whether content is being presented |
width, height | Current document canvas dimensions in points |
topBand, cutoutWidth | Reserved top band and camera exclusion width |
hasCutout | Whether the selected screen has useful cutout geometry |
pointer, keyboard | Input capabilities |
reducedMotion | The user’s Reduce Motion preference |
alert | The current {title, detail?, key?, color?, textColor?, bold?, durationMs?} object, or null |
power | {available, percent, onAC, charging} for the Mac’s internal battery; only {available:false} when absent |
The Mac host adds preen-mac-surface and data-preen-mode to the document root,
and injects --preen-top-band and --preen-cutout-width. Never place a control
inside the cutout. Compact regions are for display; open the surface to interact
with its header and body.
For an outline that follows the canvas, use
border-radius: var(--preen-screen-corners). It includes each corner separately:
the ordinary content canvas has a square top and rounded bottom. The host uses
the same radii for its own clipping and updates them as the surface resizes.
Individual --preen-screen-radius-{top-left,top-right,bottom-right,bottom-left}
values support inset frames; see Follow the device’s curve.
Use surface.power in a custom battery indicator, or PREEN.onData to fill
either slot from your own feeds and formulas. The clock can use JavaScript’s
Date and locale formatting; stop its timer when surface.visible is false.
PREEN.pulse({ring:true}) chirps the Notch bird in the hub. It leaves the
surface background opaque; any animation inside the widget belongs to its HTML.
The host may unload a resting document after eight seconds. Keep durable state in the widget datastore while the surface is open, and use injected data to restore the current display. Alert rules belong on the hub, where they keep working while the document is hidden.
The expanded Notch can show a persistent native border glow, independent of
notifications. Supply surface: {borderColor: "#F0883E"} in the widget’s data
feed; use null or omit it in the next data snapshot to clear it. The System
widget defaults to its current thermal color, including silent cooldowns;
explicit surface.borderColor overrides it, and null disables it. Unavailable
data clears the glow. The native border spans the top row and widget body,
stays clear of the physical top edge, and is hidden in the Files shelf. Widget backgrounds remain authored web content. System uses the shared
preen-glow bottom-to-top gradient at full-height intensity. Notifications keep
a flat near-black background derived from their alert color.
Rules over your data
The built-in System widget includes thermal-pressure alerts when selected on
a Notch, including while the Notch is collapsed. It alerts on each rise through
yellow (moderate), orange (heavy), and red (critical), and when pressure returns
to green (normal). Alert text is bold and matches the glow color: green #3FB950, yellow
#E3B341, orange #F0883E, or red #C2143A. Each thermal alert stays for 9.6
seconds, the default notification duration, unless a newer notification replaces
it. Partial decreases and unchanged readings stay quiet. The first
reading after selection, wake, or unavailable data establishes a baseline without
an alert. No custom rules are needed for this behavior.
A rule watches one widget’s feed and sends an alert when its condition becomes
true. Set the full array with preen surface rules SURFACE_ID @rules.json, or call
set_mac_surface_rules with an object containing surfaceID and a rules array. Rules and their
cooldowns belong to that Notch. Alerts are dropped while its screen is offline.
[
{
"id": "busy-mac",
"widgetId": "wgt_your_widget_id",
"condition": {
"op": "all",
"conditions": [
{ "op": "above", "path": "cpu.percent", "value": 85 },
{ "op": "equals", "path": "rendering", "value": true }
]
},
"alert": {
"title": "Render is working hard",
"key": "render",
"color": "#F0883E",
"durationMs": 9600
},
"cooldownMs": 5000
}
]
Conditions support below, above, equals, and changed. Paths use dot
notation for nested objects. Combine conditions with all or any.
Threshold/equality conditions fire when entering the matching state, rather
than on every reading. changed compares successive values. Installing rules
establishes the current feed as a baseline; it doesn’t replay an old condition.
The cooldown defaults to five seconds. Up to 32 rules are supported; nested
conditions are limited to five levels and eight children per group.
For arbitrary formulas or integrations, compute the result in your producer and push it as JSON, or send an alert directly. Widget HTML does not execute backend scripts.
HTTP hooks and settings
These endpoints use your existing write token on the hub’s localhost admin
address, normally http://127.0.0.1:8444. They are intended for a backend running
on the Mac. Remote producers can push widget data over the existing authenticated
data endpoint and let a rule decide when to alert.
| Method and path | Body or response |
|---|---|
GET /admin/surfaces | {displays, surfaces}: connected displays and saved Notch configurations |
POST /admin/surfaces | {displayID, widgetId?}; returns the new Notch configuration |
GET /admin/surfaces/:id | One Notch’s configuration |
POST /admin/surfaces/:id | Partial settings update; returns the saved configuration |
DELETE /admin/surfaces/:id | Remove the pairing and its rules; 204 on success |
POST /admin/surfaces/:id/event | Alert; 202 when queued |
GET /admin/surfaces/:id/rules | Rule array for this Notch |
POST /admin/surfaces/:id/rules | Replacement rule array; [] removes this Notch’s rules |
:id is the Notch’s ID, not its display ID. Pairing requires a connected,
unclaimed display; a duplicate or unavailable display returns 409.
Configurations include immutable id, displayID, and displayName. Editable
fields are enabled, widgetId, width, height, hoverEnabled,
batteryAlerts, lowBatteryPercent, and birdIndex. Empty widgetId clears
content. A settings request containing displayID returns 400.
Width accepts 320–900 points; height accepts 180–800 content points. The actual
panel is clamped to its bound screen. Low battery accepts 1–100 percent.
birdIndex uses the shared bird palette (0–4).
Alerts require a customizable title and accept detail, key, color,
textColor, bold, and durationMs.
Title/detail each accept up to 120 characters; key is optional identifying
metadata up to 128 characters. color accepts a six-digit hex color (#RRGGBB)
for the inner glow along the sides and bottom of the black notification.
textColor: "alert" matches that glow; a #RRGGBB value sets independent text
color. Omit textColor for bright ink. bold: true makes both title and detail
bold; omitted or false uses regular weight. Custom top-row regions receive these
properties in surface.alert and apply them as in the example above.
For example: {"title":"Thermals rising","detail":"High","color":"#F0883E","textColor":"alert","bold":true}.
Duration accepts integer values from
500–15000 ms (default 9600).
Every new notification immediately replaces the current one and starts its own
full duration, regardless of key. Replaced notifications never return. The
current notification clears when the display sleeps. Up to ten requests per
second are accepted per Notch. A hidden
Notch or disconnected display rejects alerts with 409; invalid
settings/rules/alerts return 422; an unknown widget or Notch returns 404. Every endpoint requires authentication.