@colixsystems/widget-sdk 0.48.0 → 0.49.0

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.
package/README.md CHANGED
@@ -27,6 +27,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
27
27
  | **CORE** | `useRefresh(handler)` | `void` | `ctx.refresh.subscribe` — no scope. Subscribes the handler to the page-level refresh tick (pull-to-refresh on mobile). Handler may return a Promise — the host waits for `allSettled` before clearing the spinner. The three datastore hooks auto-subscribe their own `refetch`; widgets only call this directly to re-run non-datastore work. No-op on a host that doesn't implement refresh. |
28
28
  | **CORE** | `useClipboard()` | `{ copy, paste, hasContent }` | platform clipboard (web `navigator.clipboard` / native `expo-clipboard`); rejects with `ClipboardError` — no scope |
29
29
  | **CORE** | `useToast()` | `{ showToast }` | `ctx.toast.showToast` (falls back to a CustomEvent / console) — no scope |
30
+ | **CORE** | `useGeolocation(options?)` | `{ latitude, longitude, accuracy, loading, error, getCurrentPosition }` | `ctx.device.geolocation` — no scope. Capture is IMPERATIVE: call `getCurrentPosition()` from a user gesture (a tap), never on mount. Resolves to `{ latitude, longitude, accuracy }`; rejects with `GeolocationError` (`.code` in `PERMISSION_DENIED \| UNAVAILABLE \| TIMEOUT \| UNSUPPORTED \| INTERNAL`). Identical on web (`navigator.geolocation`) and the Expo export (`expo-location`). |
30
31
  | **CORE** | `useI18n()` | `{ t, locale }` | `ctx.i18n` — no scope. `t(key)` resolves the widget-namespaced key (`widget.<id>.<key>`, declared in `manifest.translations`) first, then a **predefined shared key** (`shared.<key>`) when `key` is one of the standard strings (`submit`, `cancel`, `save`, `loading`, …), then the raw key. Use a shared key for an identical default string so it translates once and any per-instance `widget.<id>.<key>` override still wins. |
31
32
  | **DATASTORE** (`ctx.datastore`) | `useDatastoreQuery(table, options?)` | `{ data, loading, error, refetch }` | `records(table).list` (unwraps `{ data, meta }` to `data: []`) — `datastore.read:*` |
32
33
  | **DATASTORE** | `useDatastoreRecord(table, id)` | `{ data, loading, error, refetch }` | `records(table).get` — `datastore.read:<table>` |
@@ -50,7 +51,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
50
51
 
51
52
  ## Status
52
53
 
53
- `v0.48.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
54
+ `v0.49.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
55
+
56
+ ### What's new in 0.49.0
57
+
58
+ **New `useGeolocation()` hook — read the device's current position (sc-1584).** A new CORE hook reading a newly-injected `ctx.device` slice (host-brokered device capabilities). Returns `{ latitude, longitude, accuracy, loading, error, getCurrentPosition }`. Capture is **imperative** — call `getCurrentPosition()` from a user gesture (a `Pressable.onPress`); browsers and the mobile OS gate the permission prompt on a gesture, so it NEVER fires on mount. The promise resolves to `{ latitude, longitude, accuracy }` and mirrors the same values onto the hook; `options` (`{ enableHighAccuracy, timeout, maximumAge }`) pass through to the host. Rejections surface as a structured `GeolocationError` (new named export) with a stable `.code` (`PERMISSION_DENIED` / `UNAVAILABLE` / `TIMEOUT` / `UNSUPPORTED` / `INTERNAL`). It needs **no manifest scope** and **no `requestedScopes` entry**. The web Player brokers it via `navigator.geolocation`; the Expo export via `expo-location` — so device access is identical on both platforms. The `ctx.device` slice is optional: a host that can't broker the sensor omits it and the hook degrades to an `UNSUPPORTED` error rather than throwing at render. `CONTRACT.version` → `1.36.0`. Additive — one new hook, one new optional context slice, one new error class; no existing export changed signature.
54
59
 
55
60
  ### What's new in 0.48.0
56
61
 
@@ -369,6 +374,7 @@ The "split-implementation + vetted package list" pivot.
369
374
  ### What was in 0.4.1
370
375
 
371
376
  - **`useDatastoreQuery` returns a stable `refetch` identity.** The hook no longer rebinds the underlying callback when the host's `WidgetContext` value (a fresh object identity on every render in Studio + PageRenderer) changes. Widgets that put `refetch` in a `useEffect` dep array no longer loop.
377
+ - **Keep the `useDatastoreQuery` argument stable across renders.** The hook re-fetches whenever `[table, JSON.stringify(query)]` changes, so a query whose serialized value differs every render refetches forever and the widget is stuck on its loading state. The classic trap is a time-relative filter built with `new Date()` / `Date.now()` inline in the query (a "this week" / "today" range) — the timestamp advances each render, so the key is never the same twice. Compute the date/time bound once with `useMemo(() => …, [])` (round to the day if you only need day granularity) and pass the stable value in. The same applies to any per-render value (a freshly built object, `Math.random()`): memoize it before it enters the query.
372
378
 
373
379
  ### What's new in 0.4.0
374
380
 
package/dist/contract.cjs CHANGED
@@ -637,6 +637,30 @@ const HOOKS = [
637
637
  requiredContextSlice: ["toast.showToast"],
638
638
  scopes: null,
639
639
  },
640
+ // sc-1584 — host-brokered device geolocation. Optional slice; the hook
641
+ // degrades to an UNSUPPORTED error rather than throwing at render.
642
+ {
643
+ name: "useGeolocation",
644
+ signature: "useGeolocation(options?)",
645
+ description:
646
+ "Read the device's current position. Returns { latitude, longitude, accuracy, loading, error, getCurrentPosition }. " +
647
+ "Capture is IMPERATIVE — call getCurrentPosition() from a user gesture (a tap); browsers and the mobile OS gate the " +
648
+ "permission prompt on a gesture, so it NEVER fires on mount. The promise resolves to { latitude, longitude, accuracy } " +
649
+ "and stores the same values on the hook; it rejects with a GeolocationError whose .code is one of PERMISSION_DENIED | " +
650
+ "UNAVAILABLE | TIMEOUT | UNSUPPORTED | INTERNAL. options pass through to the host ({ enableHighAccuracy, timeout, " +
651
+ "maximumAge }). Identical on web (navigator.geolocation) and the Expo export (expo-location).",
652
+ returnShape: {
653
+ latitude: "number | null",
654
+ longitude: "number | null",
655
+ accuracy: "number | null // metres, best-effort",
656
+ loading: "boolean",
657
+ error: "GeolocationError | null",
658
+ getCurrentPosition:
659
+ "() => Promise<{ latitude, longitude, accuracy }> // rejects with GeolocationError",
660
+ },
661
+ requiredContextSlice: [],
662
+ scopes: null,
663
+ },
640
664
  ];
641
665
 
642
666
  // REQ-WSDK-RN-WEB: the SDK exposes the React Native primitive API
@@ -1083,6 +1107,19 @@ const WIDGET_CONTEXT_SHAPE = {
1083
1107
  required: false,
1084
1108
  fields: { showToast: "function" },
1085
1109
  },
1110
+ // sc-1584 — host-brokered device capabilities. Optional: a host that can't
1111
+ // broker a sensor omits the slice and useGeolocation() surfaces UNSUPPORTED.
1112
+ // Both the web Player (navigator.geolocation) and the Expo export
1113
+ // (expo-location) inject it, so device access is identical on both platforms.
1114
+ device: {
1115
+ description:
1116
+ "Optional host-brokered device capabilities. " +
1117
+ "{ geolocation: { getCurrentPosition(options?) -> Promise<{ latitude, longitude, accuracy }> } }. " +
1118
+ "Backs useGeolocation(). The web Player brokers it via navigator.geolocation; the Expo export via expo-location. " +
1119
+ "getCurrentPosition rejects with a GeolocationError (.code PERMISSION_DENIED | UNAVAILABLE | TIMEOUT | UNSUPPORTED | INTERNAL).",
1120
+ required: false,
1121
+ fields: { geolocation: "object" },
1122
+ },
1086
1123
  };
1087
1124
 
1088
1125
  const BUNDLE_EXPORT_CONTRACT = [
@@ -1778,7 +1815,7 @@ const CONTRACT = deepFreeze({
1778
1815
  // `notifications.send:appUser` scope it gates on. No existing hook,
1779
1816
  // primitive, manifest field, or token changed shape — minor bump on the
1780
1817
  // pre-1.0 channel.
1781
- version: "1.35.0",
1818
+ version: "1.36.0",
1782
1819
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
1783
1820
  hooks: HOOKS,
1784
1821
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -637,6 +637,30 @@ const HOOKS = [
637
637
  requiredContextSlice: ["toast.showToast"],
638
638
  scopes: null,
639
639
  },
640
+ // sc-1584 — host-brokered device geolocation. Optional slice; the hook
641
+ // degrades to an UNSUPPORTED error rather than throwing at render.
642
+ {
643
+ name: "useGeolocation",
644
+ signature: "useGeolocation(options?)",
645
+ description:
646
+ "Read the device's current position. Returns { latitude, longitude, accuracy, loading, error, getCurrentPosition }. " +
647
+ "Capture is IMPERATIVE — call getCurrentPosition() from a user gesture (a tap); browsers and the mobile OS gate the " +
648
+ "permission prompt on a gesture, so it NEVER fires on mount. The promise resolves to { latitude, longitude, accuracy } " +
649
+ "and stores the same values on the hook; it rejects with a GeolocationError whose .code is one of PERMISSION_DENIED | " +
650
+ "UNAVAILABLE | TIMEOUT | UNSUPPORTED | INTERNAL. options pass through to the host ({ enableHighAccuracy, timeout, " +
651
+ "maximumAge }). Identical on web (navigator.geolocation) and the Expo export (expo-location).",
652
+ returnShape: {
653
+ latitude: "number | null",
654
+ longitude: "number | null",
655
+ accuracy: "number | null // metres, best-effort",
656
+ loading: "boolean",
657
+ error: "GeolocationError | null",
658
+ getCurrentPosition:
659
+ "() => Promise<{ latitude, longitude, accuracy }> // rejects with GeolocationError",
660
+ },
661
+ requiredContextSlice: [],
662
+ scopes: null,
663
+ },
640
664
  ];
641
665
 
642
666
  // REQ-WSDK-RN-WEB: the SDK exposes the React Native primitive API
@@ -1083,6 +1107,19 @@ const WIDGET_CONTEXT_SHAPE = {
1083
1107
  required: false,
1084
1108
  fields: { showToast: "function" },
1085
1109
  },
1110
+ // sc-1584 — host-brokered device capabilities. Optional: a host that can't
1111
+ // broker a sensor omits the slice and useGeolocation() surfaces UNSUPPORTED.
1112
+ // Both the web Player (navigator.geolocation) and the Expo export
1113
+ // (expo-location) inject it, so device access is identical on both platforms.
1114
+ device: {
1115
+ description:
1116
+ "Optional host-brokered device capabilities. " +
1117
+ "{ geolocation: { getCurrentPosition(options?) -> Promise<{ latitude, longitude, accuracy }> } }. " +
1118
+ "Backs useGeolocation(). The web Player brokers it via navigator.geolocation; the Expo export via expo-location. " +
1119
+ "getCurrentPosition rejects with a GeolocationError (.code PERMISSION_DENIED | UNAVAILABLE | TIMEOUT | UNSUPPORTED | INTERNAL).",
1120
+ required: false,
1121
+ fields: { geolocation: "object" },
1122
+ },
1086
1123
  };
1087
1124
 
1088
1125
  const BUNDLE_EXPORT_CONTRACT = [
@@ -1778,7 +1815,7 @@ const CONTRACT = deepFreeze({
1778
1815
  // `notifications.send:appUser` scope it gates on. No existing hook,
1779
1816
  // primitive, manifest field, or token changed shape — minor bump on the
1780
1817
  // pre-1.0 channel.
1781
- version: "1.35.0",
1818
+ version: "1.36.0",
1782
1819
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
1783
1820
  hooks: HOOKS,
1784
1821
  primitives: PRIMITIVES,
package/dist/hooks.js CHANGED
@@ -336,6 +336,141 @@ export function useI18n() {
336
336
  return { t, locale };
337
337
  }
338
338
 
339
+ /* ============================================================================
340
+ * DEVICE — ctx.device (host-brokered device capabilities)
341
+ *
342
+ * Sensor / hardware the host brokers for the widget — geolocation today. The
343
+ * host injects `ctx.device.<cap>` on BOTH platforms (web Player via
344
+ * navigator.geolocation; the Expo export via expo-location), so a widget reads
345
+ * the device identically on web and native (widget-parity skill). The slice is
346
+ * optional — a host that cannot broker a capability omits it and the hook
347
+ * surfaces an UNSUPPORTED error instead of throwing at render. Covers:
348
+ * useGeolocation.
349
+ * ==========================================================================*/
350
+
351
+ /**
352
+ * Error thrown by `useGeolocation().getCurrentPosition()` (and surfaced in the
353
+ * hook's `error` slot). Carries a stable `code` so widgets branch on the error
354
+ * class without parsing message strings.
355
+ *
356
+ * `code` is one of:
357
+ * - "PERMISSION_DENIED" — the user (or OS) refused location access.
358
+ * - "UNAVAILABLE" — position could not be determined (no fix / sensor).
359
+ * - "TIMEOUT" — the request exceeded the host/option timeout.
360
+ * - "UNSUPPORTED" — this host does not broker geolocation.
361
+ * - "INTERNAL" — anything else.
362
+ */
363
+ export class GeolocationError extends Error {
364
+ constructor(code, message, opts) {
365
+ super(message);
366
+ this.name = "GeolocationError";
367
+ this.code = code;
368
+ if (opts && opts.cause) this.cause = opts.cause;
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Coerce a thrown value into a GeolocationError with a stable `.code`. Maps the
374
+ * browser PositionError numeric codes (1/2/3) and the host clients' string
375
+ * codes onto one vocabulary.
376
+ */
377
+ function toGeolocationError(err) {
378
+ if (err instanceof GeolocationError) return err;
379
+ const raw = err && err.code !== undefined ? err.code : null;
380
+ let code = "INTERNAL";
381
+ if (raw === 1 || raw === "PERMISSION_DENIED") code = "PERMISSION_DENIED";
382
+ else if (raw === 2 || raw === "UNAVAILABLE" || raw === "POSITION_UNAVAILABLE")
383
+ code = "UNAVAILABLE";
384
+ else if (raw === 3 || raw === "TIMEOUT") code = "TIMEOUT";
385
+ else if (raw === "UNSUPPORTED") code = "UNSUPPORTED";
386
+ const message =
387
+ (err && typeof err.message === "string" && err.message) ||
388
+ "Geolocation request failed";
389
+ return new GeolocationError(code, message, { cause: err });
390
+ }
391
+
392
+ /**
393
+ * Read the device's current position. Returns
394
+ * `{ latitude, longitude, accuracy, loading, error, getCurrentPosition }`.
395
+ *
396
+ * Capture is IMPERATIVE — call `getCurrentPosition()` from a user gesture (a
397
+ * tap on a button). Browsers and the mobile OS gate the permission prompt on a
398
+ * user gesture, so the hook never fires on mount. The promise resolves to
399
+ * `{ latitude, longitude, accuracy }` and the same values are stored on the
400
+ * hook; it rejects with a `GeolocationError` carrying a stable `.code`.
401
+ *
402
+ * `options` pass through to the host (`{ enableHighAccuracy, timeout,
403
+ * maximumAge }`). The SAME hook drives both platforms: the web Player brokers
404
+ * it through `navigator.geolocation`, the Expo export through `expo-location`.
405
+ *
406
+ * Safe-by-default: on a host that does not inject `ctx.device.geolocation`,
407
+ * `getCurrentPosition()` rejects with `code: "UNSUPPORTED"` rather than
408
+ * throwing at render, so a widget can call the hook unconditionally.
409
+ */
410
+ export function useGeolocation(options) {
411
+ const ctx = useWidgetContextOrThrow("useGeolocation");
412
+ const [coords, setCoords] = useState(null);
413
+ const [loading, setLoading] = useState(false);
414
+ const [error, setError] = useState(null);
415
+
416
+ // `ctx` is a fresh identity every host render — hold the live client +
417
+ // options in refs so getCurrentPosition is a stable callback.
418
+ const clientRef = useRef(ctx.device && ctx.device.geolocation);
419
+ clientRef.current = ctx.device && ctx.device.geolocation;
420
+ const optionsRef = useRef(options);
421
+ optionsRef.current = options;
422
+ const runRef = useRef(0);
423
+
424
+ const getCurrentPosition = useCallback(async () => {
425
+ const myRun = ++runRef.current;
426
+ const client = clientRef.current;
427
+ if (!client || typeof client.getCurrentPosition !== "function") {
428
+ const e = new GeolocationError(
429
+ "UNSUPPORTED",
430
+ "This host does not provide device geolocation.",
431
+ );
432
+ if (runRef.current === myRun) {
433
+ setError(e);
434
+ setLoading(false);
435
+ }
436
+ throw e;
437
+ }
438
+ setLoading(true);
439
+ setError(null);
440
+ try {
441
+ const pos = await client.getCurrentPosition(optionsRef.current);
442
+ const next = {
443
+ latitude:
444
+ pos && typeof pos.latitude === "number" ? pos.latitude : null,
445
+ longitude:
446
+ pos && typeof pos.longitude === "number" ? pos.longitude : null,
447
+ accuracy:
448
+ pos && typeof pos.accuracy === "number" ? pos.accuracy : null,
449
+ };
450
+ if (runRef.current !== myRun) return next;
451
+ setCoords(next);
452
+ setLoading(false);
453
+ return next;
454
+ } catch (err) {
455
+ const ge = toGeolocationError(err);
456
+ if (runRef.current === myRun) {
457
+ setError(ge);
458
+ setLoading(false);
459
+ }
460
+ throw ge;
461
+ }
462
+ }, []);
463
+
464
+ return {
465
+ latitude: coords ? coords.latitude : null,
466
+ longitude: coords ? coords.longitude : null,
467
+ accuracy: coords ? coords.accuracy : null,
468
+ loading,
469
+ error,
470
+ getCurrentPosition,
471
+ };
472
+ }
473
+
339
474
  /* ============================================================================
340
475
  * DATASTORE CLIENT — ctx.datastore (@colixsystems/datastore-client)
341
476
  *
package/dist/index.d.ts CHANGED
@@ -464,6 +464,16 @@ export interface WidgetContext<TProps = unknown> {
464
464
  toast?: {
465
465
  showToast(args: { kind?: string; message: string }): void;
466
466
  };
467
+ /** Optional host-brokered device capabilities; backs useGeolocation. */
468
+ device?: {
469
+ geolocation?: {
470
+ getCurrentPosition(options?: GeolocationOptions): Promise<{
471
+ latitude: number;
472
+ longitude: number;
473
+ accuracy: number;
474
+ }>;
475
+ };
476
+ };
467
477
  }
468
478
 
469
479
  /**
@@ -921,6 +931,60 @@ export function useRefresh(
921
931
  handler: () => void | Promise<unknown>,
922
932
  ): void;
923
933
 
934
+ /** Pass-through options for `useGeolocation().getCurrentPosition(...)`. */
935
+ export interface GeolocationOptions {
936
+ enableHighAccuracy?: boolean;
937
+ timeout?: number;
938
+ maximumAge?: number;
939
+ }
940
+
941
+ export interface GeolocationResult {
942
+ latitude: number | null;
943
+ longitude: number | null;
944
+ /** Best-effort accuracy in metres. */
945
+ accuracy: number | null;
946
+ loading: boolean;
947
+ error: GeolocationError | null;
948
+ /**
949
+ * Imperatively read the device position — call from a user gesture. Resolves
950
+ * to `{ latitude, longitude, accuracy }` and stores the same on the hook;
951
+ * rejects with a `GeolocationError`.
952
+ */
953
+ getCurrentPosition(): Promise<{
954
+ latitude: number;
955
+ longitude: number;
956
+ accuracy: number;
957
+ }>;
958
+ }
959
+
960
+ /**
961
+ * sc-1584 — read the device's current position. Capture is imperative (call
962
+ * `getCurrentPosition()` from a user gesture; it never fires on mount). The
963
+ * same hook drives both platforms — the web Player brokers it via
964
+ * `navigator.geolocation`, the Expo export via `expo-location`. Safe to call on
965
+ * a host that doesn't broker geolocation: `getCurrentPosition()` then rejects
966
+ * with `code: "UNSUPPORTED"`.
967
+ */
968
+ export function useGeolocation(options?: GeolocationOptions): GeolocationResult;
969
+
970
+ /**
971
+ * sc-1584 — error thrown by `useGeolocation().getCurrentPosition()` and
972
+ * surfaced in the hook's `error` slot. `code` is a stable categorisation.
973
+ */
974
+ export class GeolocationError extends Error {
975
+ code:
976
+ | "PERMISSION_DENIED"
977
+ | "UNAVAILABLE"
978
+ | "TIMEOUT"
979
+ | "UNSUPPORTED"
980
+ | "INTERNAL";
981
+ constructor(
982
+ code: GeolocationError["code"],
983
+ message: string,
984
+ opts?: { cause?: unknown },
985
+ );
986
+ }
987
+
924
988
  /**
925
989
  * Error class thrown by useDatastoreMutation callbacks (and surfaced by
926
990
  * useDatastoreQuery in its `error` slot). The `code` is a stable
package/dist/index.js CHANGED
@@ -42,6 +42,8 @@ export {
42
42
  useNavigation,
43
43
  useChildRenderer,
44
44
  useRefresh,
45
+ useGeolocation,
46
+ GeolocationError,
45
47
  WidgetTree,
46
48
  } from "./hooks.js";
47
49
  // REQ-WSDK-PLATFORM §6 — Tier A hooks. Each ships in a per-platform file
@@ -40,6 +40,8 @@ export {
40
40
  useNavigation,
41
41
  useChildRenderer,
42
42
  useRefresh,
43
+ useGeolocation,
44
+ GeolocationError,
43
45
  WidgetTree,
44
46
  } from "./hooks.js";
45
47
  // REQ-WSDK-PLATFORM §6 — Tier A hooks (native variants).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,7 +42,7 @@
42
42
  ],
43
43
  "scripts": {
44
44
  "build": "node scripts/build.js",
45
- "test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-subscription.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js"
45
+ "test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-subscription.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js"
46
46
  },
47
47
  "engines": {
48
48
  "node": ">=18"