craft-native 0.0.77 → 0.0.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,9 +62,15 @@ export interface SpawnOptions {
62
62
  cwd?: string;
63
63
  env?: Record<string, string>;
64
64
  /**
65
- * Run the command through a shell. The Craft bridge translates this
66
- * automatically (cmd.exe on Windows, /bin/sh elsewhere). On the Node
67
- * fallback the command is rewritten to `cmd.exe /c <cmd>` on win32.
65
+ * Run `command` as a command line through a shell (cmd.exe on Windows,
66
+ * /bin/sh elsewhere) instead of executing it directly.
67
+ *
68
+ * Off by default, as in Node. Without it `command` is a program and `args`
69
+ * are its arguments, so a `;` or `|` inside an argument reaches the program
70
+ * as a plain character rather than as shell syntax.
71
+ *
72
+ * A shell takes one string, so `args` cannot be passed with it — supplying
73
+ * both is refused rather than silently honouring one of them.
68
74
  */
69
75
  shell?: boolean;
70
76
  }
@@ -101,11 +101,6 @@ export interface WindowCreateOptions {
101
101
  webSidebarMaterialOpacity?: number;
102
102
  /** Titlebar style (macOS) */
103
103
  titlebarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';
104
- /** Traffic light position (macOS) */
105
- trafficLightPosition?: {
106
- x: number;
107
- y: number;
108
- };
109
104
  /** Vibrancy effect (macOS) */
110
105
  vibrancy?: 'appearance-based' | 'light' | 'dark' | 'titlebar' | 'selection' | 'menu' | 'popover' | 'sidebar' | 'header' | 'sheet' | 'window' | 'hud' | 'fullscreen-ui' | 'tooltip' | 'content' | 'under-window' | 'under-page';
111
106
  /** Background material (Windows 11) */
@@ -338,12 +333,23 @@ export declare class Window {
338
333
  */
339
334
  setVibrancy(vibrancy: WindowCreateOptions['vibrancy'] | null): Promise<void>;
340
335
  /**
341
- * Set traffic light position (macOS)
342
- */
343
- setTrafficLightPosition(position: {
344
- x: number;
345
- y: number;
346
- }): Promise<void>;
336
+ * Where the platform's window buttons are — read, not written.
337
+ *
338
+ * There was a `setTrafficLightPosition` here, and a `trafficLightPosition`
339
+ * window option beside it. Neither ever moved anything: the host has no
340
+ * handler for the call, and AppKit re-lays out the standard window buttons
341
+ * after any one-shot `setFrameOrigin:`, which is why `macos.zig` leaves them
342
+ * where the window server puts them and says so at length.
343
+ *
344
+ * What a layout actually needs is the opposite direction — where they *are*,
345
+ * which the host measures and publishes on every window:
346
+ *
347
+ * window.craft.windowControls { style, x, y, width, height, ... }
348
+ * --craft-window-controls-width the room to leave, in CSS
349
+ *
350
+ * See `CraftWindowControls`, and `docs/features/window-management.md`.
351
+ */
352
+ get windowControls(): import('../types.js').CraftWindowControls | undefined;
347
353
  /**
348
354
  * Set window level (macOS)
349
355
  */
package/dist/cli.js CHANGED
@@ -4191,7 +4191,7 @@ function craftBinaryNotFoundMessage(triedPath) {
4191
4191
  `);
4192
4192
  }
4193
4193
  // package.json
4194
- var version = "0.0.77";
4194
+ var version = "0.0.78";
4195
4195
 
4196
4196
  // bin/cli.ts
4197
4197
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
package/dist/index.cjs CHANGED
@@ -2532,8 +2532,8 @@ class Window {
2532
2532
  async setVibrancy(vibrancy) {
2533
2533
  await this._call("setVibrancy", { vibrancy });
2534
2534
  }
2535
- async setTrafficLightPosition(position) {
2536
- await this._call("setTrafficLightPosition", position);
2535
+ get windowControls() {
2536
+ return globalThis.craft?.windowControls;
2537
2537
  }
2538
2538
  async setWindowLevel(level) {
2539
2539
  await this._call("setWindowLevel", { level });
@@ -10745,6 +10745,10 @@ class CraftApp {
10745
10745
  args.push("--titlebar-hidden");
10746
10746
  if (window3?.headless)
10747
10747
  args.push("--headless");
10748
+ if (window3?.keepRunning === true)
10749
+ args.push("--keep-running");
10750
+ else if (window3?.keepRunning === false)
10751
+ args.push("--quit-on-close");
10748
10752
  if (window3?.webSidebarMaterial) {
10749
10753
  args.push("--web-sidebar-material");
10750
10754
  if (window3?.webSidebarWidth)
package/dist/index.js CHANGED
@@ -2302,8 +2302,8 @@ class Window {
2302
2302
  async setVibrancy(vibrancy) {
2303
2303
  await this._call("setVibrancy", { vibrancy });
2304
2304
  }
2305
- async setTrafficLightPosition(position) {
2306
- await this._call("setTrafficLightPosition", position);
2305
+ get windowControls() {
2306
+ return globalThis.craft?.windowControls;
2307
2307
  }
2308
2308
  async setWindowLevel(level) {
2309
2309
  await this._call("setWindowLevel", { level });
@@ -10515,6 +10515,10 @@ class CraftApp {
10515
10515
  args.push("--titlebar-hidden");
10516
10516
  if (window3?.headless)
10517
10517
  args.push("--headless");
10518
+ if (window3?.keepRunning === true)
10519
+ args.push("--keep-running");
10520
+ else if (window3?.keepRunning === false)
10521
+ args.push("--quit-on-close");
10518
10522
  if (window3?.webSidebarMaterial) {
10519
10523
  args.push("--web-sidebar-material");
10520
10524
  if (window3?.webSidebarWidth)
@@ -2222,41 +2222,33 @@ struct CraftWebView: UIViewRepresentable {
2222
2222
  _progressCallbacks: [],
2223
2223
  _statusCallbacks: [],
2224
2224
 
2225
+ // OTA is not implemented natively. These five used to post
2226
+ // to actions the switch below does not handle, and register
2227
+ // a resolve/reject pair that nothing would ever call — they
2228
+ // also bypassed _createCallback, which is what owns the 30s
2229
+ // timeout, so the promises hung forever rather than
2230
+ // rejecting. An immediate rejection is the honest answer:
2231
+ // an app can catch it, where it could never catch a hang.
2232
+ _unavailable: function(name) {
2233
+ return Promise.reject(new Error(
2234
+ 'craft.ota.' + name + ' is not implemented on this platform'
2235
+ ));
2236
+ },
2225
2237
  configure: function(options) {
2226
2238
  this._config = options;
2227
- window.webkit.messageHandlers.craft.postMessage({action: 'otaConfigure', config: options});
2239
+ console.warn('[craft] ota.configure stored locally; OTA is not implemented natively');
2228
2240
  },
2229
2241
  checkForUpdate: function() {
2230
- var self = window.craft;
2231
- var id = 'cb_' + (++self._callbackId);
2232
- window.webkit.messageHandlers.craft.postMessage({action: 'otaCheckForUpdate', callbackId: id});
2233
- return new Promise(function(resolve, reject) {
2234
- self._callbacks[id] = {resolve: resolve, reject: reject};
2235
- });
2242
+ return window.craft.ota._unavailable('checkForUpdate');
2236
2243
  },
2237
2244
  downloadUpdate: function(options) {
2238
- var self = window.craft;
2239
- var id = 'cb_' + (++self._callbackId);
2240
- window.webkit.messageHandlers.craft.postMessage({action: 'otaDownloadUpdate', options: options || {}, callbackId: id});
2241
- return new Promise(function(resolve, reject) {
2242
- self._callbacks[id] = {resolve: resolve, reject: reject};
2243
- });
2245
+ return window.craft.ota._unavailable('downloadUpdate');
2244
2246
  },
2245
2247
  applyUpdate: function() {
2246
- var self = window.craft;
2247
- var id = 'cb_' + (++self._callbackId);
2248
- window.webkit.messageHandlers.craft.postMessage({action: 'otaApplyUpdate', callbackId: id});
2249
- return new Promise(function(resolve, reject) {
2250
- self._callbacks[id] = {resolve: resolve, reject: reject};
2251
- });
2248
+ return window.craft.ota._unavailable('applyUpdate');
2252
2249
  },
2253
2250
  rollback: function() {
2254
- var self = window.craft;
2255
- var id = 'cb_' + (++self._callbackId);
2256
- window.webkit.messageHandlers.craft.postMessage({action: 'otaRollback', callbackId: id});
2257
- return new Promise(function(resolve, reject) {
2258
- self._callbacks[id] = {resolve: resolve, reject: reject};
2259
- });
2251
+ return window.craft.ota._unavailable('rollback');
2260
2252
  },
2261
2253
  getCurrentBundle: function() {
2262
2254
  // This returns synchronously from stored data
package/dist/types.d.ts CHANGED
@@ -73,6 +73,22 @@ export interface WindowOptions {
73
73
  * error rather than a silent no-op.
74
74
  */
75
75
  headless?: boolean;
76
+ /**
77
+ * Keep the process running after the last window closes (macOS).
78
+ *
79
+ * Craft decides this from the shape of the app when it is not set: an app
80
+ * with a tray icon or in menubar-only mode stays running, because outliving
81
+ * its window is what those are for; an ordinary windowed app quits.
82
+ *
83
+ * Set it to `true` for a windowed app that should stay resident and be
84
+ * brought back by clicking the Dock icon, or `false` for a tray app that
85
+ * should genuinely go away when its window closes.
86
+ *
87
+ * Before this existed the answer was AppKit's default, `false`, for every
88
+ * app: closing the last window left a process with no window and no way to
89
+ * get one back.
90
+ */
91
+ keepRunning?: boolean;
76
92
  /**
77
93
  * Remember this window's size and position across launches, under this name
78
94
  * (macOS).
@@ -673,11 +689,46 @@ export interface AppInfo {
673
689
  /**
674
690
  * Notification options
675
691
  */
692
+ /**
693
+ * A button on a notification banner.
694
+ *
695
+ * macOS shows two directly on a banner and puts any others behind an
696
+ * "Options" disclosure, so craft accepts at most four — past that they are
697
+ * a menu the user has to go looking for rather than a choice they can see.
698
+ *
699
+ * Two spellings are accepted. `{ id, label }` is the one to use. `{ action,
700
+ * title }` is what this type has said since before anything implemented it,
701
+ * and it keeps working rather than being deleted out from under whoever wrote
702
+ * against it — the field was published, it just never did anything.
703
+ */
704
+ export type NotificationAction = {
705
+ /** Comes back as `actionId` in `craft.notifications.onAction`. */
706
+ id: string;
707
+ /** The text on the button. */
708
+ label: string;
709
+ } | {
710
+ /** @deprecated Use `id`. */
711
+ action: string;
712
+ /** @deprecated Use `label`. */
713
+ title: string;
714
+ };
676
715
  export interface NotificationOptions {
677
716
  /**
678
717
  * Notification title (required)
679
718
  */
680
719
  title: string;
720
+ /**
721
+ * Buttons on the banner.
722
+ *
723
+ * Pressing one brings the app forward and fires
724
+ * `craft.notifications.onAction` with `{ notificationId, actionId }` —
725
+ * which is what lets a prompt be answered without switching to the app
726
+ * first.
727
+ *
728
+ * Two buttons cannot share an `id`: the response names the button by id,
729
+ * and for an Approve/Deny prompt that name is the entire answer.
730
+ */
731
+ actions?: NotificationAction[];
681
732
  /**
682
733
  * Notification body text
683
734
  */
@@ -694,13 +745,6 @@ export interface NotificationOptions {
694
745
  * - Or any system sound name
695
746
  */
696
747
  sound?: string;
697
- /**
698
- * Action buttons (platform dependent)
699
- */
700
- actions?: Array<{
701
- action: string;
702
- title: string;
703
- }>;
704
748
  /**
705
749
  * Notification tag (for grouping/replacing)
706
750
  */
@@ -916,7 +960,83 @@ export interface CraftMobileAPI {
916
960
  * defines the CraftBridge interface with additional mobile-only features
917
961
  * such as AR, ML, deep links, OTA updates, widgets, and auth persistence.
918
962
  */
963
+ /**
964
+ * Where the platform drew this window's window buttons — the macOS traffic
965
+ * lights, and their equivalents elsewhere.
966
+ *
967
+ * The buttons are the window server's on every desktop window Craft opens that
968
+ * is not frameless: real, correctly styled, wired to the keyboard and to
969
+ * accessibility. A page must never draw replicas beside them, and this is what
970
+ * makes that unnecessary — it says where they are, so a layout can leave room
971
+ * instead of inventing its own.
972
+ *
973
+ * Every number is measured from the live window and re-sent when it changes: a
974
+ * resize, a fullscreen transition, a new document. Listen for the
975
+ * `craft:windowcontrols` event on `window` for layout that CSS cannot express;
976
+ * everything else is better served by the four CSS variables, which the host
977
+ * sets before the document is parsed.
978
+ *
979
+ * The same facts reach CSS as `--craft-window-controls-width` / `-height` /
980
+ * `-inset-x` / `-inset-y` / `-replicas`, and the document as
981
+ * `<html data-craft-window-controls="...">`.
982
+ */
983
+ export interface CraftWindowControls {
984
+ /**
985
+ * `titlebar` — real buttons, in a titlebar above the page. Nothing to do.
986
+ * `overlay` — real buttons, over the page's own top-left corner. Leave room.
987
+ * `custom` — a frameless window: no buttons, and the page's own are the only
988
+ * ones there can be.
989
+ * `none` — no window chrome in this environment at all (iOS, Android).
990
+ */
991
+ style: 'titlebar' | 'overlay' | 'custom' | 'none';
992
+ /** The platform drew real buttons for this window. */
993
+ native: boolean;
994
+ /**
995
+ * ...and they are on screen right now. False in fullscreen, where macOS
996
+ * takes them into an auto-hiding titlebar — which is why a layout should
997
+ * reserve `reserveWidth` rather than a remembered constant.
998
+ */
999
+ visible: boolean;
1000
+ /**
1001
+ * The block's true position, in CSS px from the top-left of the web
1002
+ * viewport. Negative where the buttons are not over the page at all: above
1003
+ * it in a plain titlebar window, to its left in a window whose web content
1004
+ * starts after a native sidebar.
1005
+ */
1006
+ x: number;
1007
+ y: number;
1008
+ width: number;
1009
+ height: number;
1010
+ /**
1011
+ * The room to leave inside the page — the block's far edge, or zero when it
1012
+ * does not reach into the page. `--craft-window-controls-width` / `-height`.
1013
+ */
1014
+ reserveWidth: number;
1015
+ reserveHeight: number;
1016
+ /**
1017
+ * Where the block starts inside the page, zero unless it overlaps.
1018
+ * `--craft-window-controls-inset-x` / `-inset-y`.
1019
+ */
1020
+ insetX: number;
1021
+ insetY: number;
1022
+ /**
1023
+ * The `display` a replica should take: `'none'` wherever real buttons exist
1024
+ * and wherever there is no window to control, and `null` in a frameless
1025
+ * window, where the page's own controls are the real ones. Published as
1026
+ * `--craft-window-controls-replicas`, which is *removed* rather than set when
1027
+ * this is null, so the page's own fallback applies.
1028
+ */
1029
+ replicas: 'none' | null;
1030
+ }
919
1031
  export interface CraftBridgeAPI {
1032
+ /**
1033
+ * Where the platform drew this window's close/minimise/zoom buttons.
1034
+ *
1035
+ * Present in every Craft window, so a UI that has to lay out around them can
1036
+ * ask instead of guessing — and so a UI shared with the browser can tell the
1037
+ * two apart. See `CraftWindowControls`.
1038
+ */
1039
+ windowControls?: CraftWindowControls;
920
1040
  /**
921
1041
  * Trackpad gesture phases (desktop only).
922
1042
  *
@@ -1038,13 +1158,18 @@ export interface CapabilityNamespace {
1038
1158
  export interface CraftCapabilities {
1039
1159
  namespaces: Record<string, CapabilityNamespace>;
1040
1160
  /**
1041
- * Every `craft:*` event channel, and whether anything native emits on it.
1161
+ * Every `craft:*` event channel, and what craft can say about it.
1162
+ *
1163
+ * `'live'` means something in this build took out a permit to emit on it.
1164
+ * `'unknown'` means craft cannot prove it either way — subscribe and see, and
1165
+ * do **not** disable a feature over it.
1042
1166
  *
1043
- * A `false` here means subscribing would work and never fire — which cannot
1044
- * be derived from the action tables, so it is tracked separately by the
1045
- * emitters themselves.
1167
+ * There is deliberately no `'dead'`. Craft cannot establish absence: the
1168
+ * `craft:window:*` names are composed in JavaScript from
1169
+ * `__craftDeliverWindowEvent('focus')`, so no source scan finds the literal
1170
+ * even though the emitter is right there.
1046
1171
  */
1047
- channels: Record<string, boolean>;
1172
+ channels: Record<string, 'live' | 'unknown'>;
1048
1173
  }
1049
1174
  /**
1050
1175
  * The only value types `craft.prefs` stores.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.77",
3
+ "version": "0.0.78",
4
4
  "type": "module",
5
5
  "description": "Build desktop apps with web languages - TypeScript SDK for Craft",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",