@colixsystems/widget-sdk 0.125.0 → 0.127.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 +39 -1
- package/dist/contract.cjs +107 -6
- package/dist/contract.js +107 -6
- package/dist/dev-shims.js +8 -0
- package/dist/hooks.js +167 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +2 -0
- package/dist/index.native.js +2 -0
- package/package.json +2 -2
- package/src/dev-shims.js +8 -0
package/README.md
CHANGED
|
@@ -70,7 +70,45 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
70
70
|
|
|
71
71
|
## Status
|
|
72
72
|
|
|
73
|
-
`v0.
|
|
73
|
+
`v0.127.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**.
|
|
74
|
+
|
|
75
|
+
### What's new in 0.127.0 (contract 1.99.0)
|
|
76
|
+
|
|
77
|
+
**Widgets can do maths now — five pure-JS packages join the vetted import allowlist (sc-7191).** The list had 31 entries and exactly one non-UI utility (`date-fns`), so anything numeric a widget needed had to be hand-rolled in a sibling file. Two gaps in particular:
|
|
78
|
+
|
|
79
|
+
- `decimal.js` — exact decimal arithmetic. The platform has payments, invoicing and VAT, and a total accumulated in IEEE-754 floats drifts from what the backend actually charged. `new Decimal(a).plus(b).toFixed(2)` does not.
|
|
80
|
+
- `d3-scale` + `d3-shape` + `d3-array` — the scale, path-generator and domain maths a bespoke chart needs. `d3-shape` emits path strings you hand straight to the already-vetted `react-native-svg`'s `<Path d={…} />`, so a custom line/area/donut chart is ONE source file that renders identically in the Player and the Expo export.
|
|
81
|
+
- `simple-statistics` — mean/median/quantile/regression/correlation, for summarising a datastore table without shipping a maths framework.
|
|
82
|
+
|
|
83
|
+
All five are `platforms: ["web", "native"]` with no native module, so this is **full parity**, not a §8 native-only case. Each is host-shimmed in the Player and pinned in the export for the same reason `date-fns` is: an AI-agent widget is transpiled rather than bundled, so its bare import has to resolve at runtime on both hosts.
|
|
84
|
+
|
|
85
|
+
`mathjs` was considered and deliberately left off — 9.4 MB unpacked with nine transitive dependencies, and a web-vetted package is bundled into the Studio.
|
|
86
|
+
|
|
87
|
+
Additive: no existing entry, hook, primitive or `propertySchema` type changed shape. `CONTRACT.version` → `1.99.0`.
|
|
88
|
+
|
|
89
|
+
### What's new in 0.126.0 (contract 1.98.0)
|
|
90
|
+
|
|
91
|
+
**A widget can edit an image now, not just take one — new `useImageEditor()` (sc-7193).** `useCamera()` (0.121.0) let a widget capture a photo and `ctx.assets.upload` let it send one, but nothing could *change* one: no resize before upload, no crop to an aspect ratio, no straightening a sideways shot. A profile-picture widget had to upload the full-resolution original and hope the server-side normaliser did something acceptable.
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
const { capture } = useCamera();
|
|
95
|
+
const { edit } = useImageEditor();
|
|
96
|
+
|
|
97
|
+
const shot = await capture();
|
|
98
|
+
const small = await edit(shot.uri, [{ resize: { width: 800 } }], { format: "jpeg", compress: 0.8 });
|
|
99
|
+
|
|
100
|
+
const fd = new FormData();
|
|
101
|
+
fd.append("file", small.file);
|
|
102
|
+
await ctx.assets.upload(fd);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`edit(uri, actions, options?)` applies `actions` in order and resolves the SAME normalised asset shape `useCamera()` yields, so capture → edit → upload is one code path on both hosts. Actions are `{ resize: { width?, height? } }`, `{ crop: { originX, originY, width, height } }`, `{ rotate: degrees }` and `{ flip: "horizontal" | "vertical" }`; output is `{ format: "jpeg" | "png" | "webp", compress, base64? }`.
|
|
106
|
+
|
|
107
|
+
**Host-brokered, not a vetted import** — the same call `expo-image-picker` and `expo-speech-recognition` already got. Widgets reach it through the hook and never import the package, so it stays out of every widget bundle and parity is the SDK's problem rather than each author's. The web Player brokers it on a canvas *inside the host* (your widget never touches the DOM); the Expo export uses `expo-image-manipulator`.
|
|
108
|
+
|
|
109
|
+
There is deliberately **no `extent` action**. It exists only on web in `expo-image-manipulator`, and a capability the Player has but the export does not is the direction CLAUDE.md §8 forbids.
|
|
110
|
+
|
|
111
|
+
The `device.imageEditor` slice is OPTIONAL, so a host that brokers nothing degrades the hook to `supported: false` rather than throwing — gate your edit control on it. Additive: no existing hook, primitive, manifest field or `propertySchema` type changed. `CONTRACT.version` → `1.98.0`.
|
|
74
112
|
|
|
75
113
|
### What's new in 0.125.0 (contract 1.97.0)
|
|
76
114
|
|
package/dist/contract.cjs
CHANGED
|
@@ -1670,6 +1670,38 @@ const HOOKS = [
|
|
|
1670
1670
|
requiredContextSlice: [],
|
|
1671
1671
|
scopes: null,
|
|
1672
1672
|
},
|
|
1673
|
+
// sc-7193 — host-brokered image editing. Optional slice; the hook reports
|
|
1674
|
+
// supported:false rather than throwing at render.
|
|
1675
|
+
{
|
|
1676
|
+
name: "useImageEditor",
|
|
1677
|
+
signature: "useImageEditor()",
|
|
1678
|
+
description:
|
|
1679
|
+
"Resize, crop, rotate or flip an image. Returns { result, editing, error, supported, edit, reset }. " +
|
|
1680
|
+
"Editing is IMPERATIVE — call edit() from an event handler, never during render. " +
|
|
1681
|
+
"edit(uri, actions, options?) applies `actions` IN ORDER and resolves the SAME normalised asset shape useCamera() " +
|
|
1682
|
+
"yields — { uri, name, mimeType, width, height, size, base64?, file } — so capture -> edit -> upload is ONE code path: " +
|
|
1683
|
+
"append result.file to a FormData as `file` and pass it to ctx.assets.upload(fd). " +
|
|
1684
|
+
"Each action entry carries exactly one of { resize: { width?, height? } } (aspect preserved when only one is given), " +
|
|
1685
|
+
"{ crop: { originX, originY, width, height } }, { rotate: degrees } (positive is clockwise), or " +
|
|
1686
|
+
"{ flip: \"horizontal\" | \"vertical\" }. options: { format: \"jpeg\" | \"png\" | \"webp\" (default jpeg), " +
|
|
1687
|
+
"compress: 0..1 (default 0.8, ignored for png), base64: boolean (off by default — it is expensive) }. " +
|
|
1688
|
+
"Rejects with an ImageEditorError whose .code is one of UNSUPPORTED | INVALID_ACTION | DECODE_FAILED | ENCODE_FAILED | " +
|
|
1689
|
+
"INTERNAL. There is deliberately NO `extent` action: it exists only on web, and a web-only capability is the direction " +
|
|
1690
|
+
"CLAUDE.md §8 forbids. Check `supported` before rendering an edit control. Identical on web (a canvas in the host, so " +
|
|
1691
|
+
"the widget never touches the DOM) and the Expo export (expo-image-manipulator).",
|
|
1692
|
+
returnShape: {
|
|
1693
|
+
result:
|
|
1694
|
+
"{ uri, name, mimeType, width, height, size, base64?, file } | null",
|
|
1695
|
+
editing: "boolean",
|
|
1696
|
+
error: "ImageEditorError | null",
|
|
1697
|
+
supported: "boolean // false when the host brokers no image editor",
|
|
1698
|
+
edit:
|
|
1699
|
+
"(uri, actions, options?) => Promise<result | null> // rejects with ImageEditorError",
|
|
1700
|
+
reset: "() => void // clear the result + error and release it",
|
|
1701
|
+
},
|
|
1702
|
+
requiredContextSlice: [],
|
|
1703
|
+
scopes: null,
|
|
1704
|
+
},
|
|
1673
1705
|
];
|
|
1674
1706
|
|
|
1675
1707
|
// REQ-WSDK-RN-WEB: the SDK exposes the React Native primitive API
|
|
@@ -2310,10 +2342,13 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2310
2342
|
"isBackgroundWatching() -> boolean, subscribeBackgroundPositions(cb) -> unsubscribe, " +
|
|
2311
2343
|
"subscribeBackgroundWatchState(cb) -> unsubscribe }, " +
|
|
2312
2344
|
"speech: { isSupported() -> boolean, start(options, { onResult, onError, onEnd }) -> Promise<{ stop(), abort() }> }, " +
|
|
2313
|
-
"camera: { isSupported() -> boolean, capture(options?) -> Promise<asset | null>, pick(options?) -> Promise<asset | null> }
|
|
2314
|
-
"
|
|
2315
|
-
"
|
|
2316
|
-
"
|
|
2345
|
+
"camera: { isSupported() -> boolean, capture(options?) -> Promise<asset | null>, pick(options?) -> Promise<asset | null> }, " +
|
|
2346
|
+
"imageEditor: { isSupported() -> boolean, edit(uri, actions, options?) -> Promise<asset> } }. " +
|
|
2347
|
+
"Backs useGeolocation(), useSpeechToText(), useCamera() and useImageEditor(). The web Player brokers them via " +
|
|
2348
|
+
"navigator.geolocation, window.SpeechRecognition, a getUserMedia camera preview and a host-side canvas; the Expo export via " +
|
|
2349
|
+
"expo-location, expo-speech-recognition, expo-image-picker and expo-image-manipulator. " +
|
|
2350
|
+
"imageEditor.edit applies resize / crop / rotate / flip in order and rejects with an ImageEditorError " +
|
|
2351
|
+
"(.code UNSUPPORTED | INVALID_ACTION | DECODE_FAILED | ENCODE_FAILED | INTERNAL). " +
|
|
2317
2352
|
"getCurrentPosition rejects with a GeolocationError (.code PERMISSION_DENIED | UNAVAILABLE | TIMEOUT | UNSUPPORTED | INTERNAL). " +
|
|
2318
2353
|
"speech.start streams { transcript, isFinal } to onResult and runs ON DEVICE — it uploads no audio and spends no AI credit; " +
|
|
2319
2354
|
"its onError carries the Web Speech error vocabulary (not-allowed | no-speech | language-not-supported | network | aborted). " +
|
|
@@ -2327,7 +2362,12 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2327
2362
|
"foreground service), and a sibling widget may start or stop it — so subscribeBackgroundWatchState is how every mounted " +
|
|
2328
2363
|
"widget stays truthful, and isBackgroundWatching() is only the synchronous first read.",
|
|
2329
2364
|
required: false,
|
|
2330
|
-
fields: {
|
|
2365
|
+
fields: {
|
|
2366
|
+
geolocation: "object",
|
|
2367
|
+
speech: "object",
|
|
2368
|
+
camera: "object",
|
|
2369
|
+
imageEditor: "object",
|
|
2370
|
+
},
|
|
2331
2371
|
},
|
|
2332
2372
|
};
|
|
2333
2373
|
|
|
@@ -2637,6 +2677,41 @@ const VETTED_IMPORTS = [
|
|
|
2637
2677
|
description:
|
|
2638
2678
|
"Device motion hardware on the Expo export: Accelerometer, Gyroscope, Magnetometer, DeviceMotion, Barometer, Pedometer and LightSensor, each read as an addListener subscription with setUpdateInterval — always remove the subscription on unmount, a sensor left running drains the battery. Expo SDK 56 ships 56.0.x. Native-only on purpose: the package's own web build derives acceleration from deviceorientation ANGLES rather than real motion, so a shake or tilt threshold tuned on one host would read differently on the other. Author it in widget.native.jsx and pair it with a widget.web.jsx reading window.DeviceMotionEvent (accelerationIncludingGravity / rotationRate), the browser API the same hardware exposes. Both hosts need a user gesture before readings start, and iOS Safari additionally needs an explicit DeviceMotionEvent.requestPermission() grant — so gate the reading behind a Pressable, never start it on mount.",
|
|
2639
2679
|
},
|
|
2680
|
+
{
|
|
2681
|
+
specifier: "decimal.js",
|
|
2682
|
+
platforms: ["web", "native"],
|
|
2683
|
+
category: "utility",
|
|
2684
|
+
description:
|
|
2685
|
+
"Arbitrary-precision decimal arithmetic. Reach for it whenever a widget computes MONEY: JavaScript numbers are binary floats, so a price total, a VAT line or a discount accumulated in `+`/`*` drifts by fractions of a cent and disagrees with what the backend charged. `new Decimal(a).plus(b).toFixed(2)` does not. Pure JS with zero dependencies, identical on both platforms. Not for general maths — it is slower than a number and only earns its cost where exactness is the point.",
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
specifier: "d3-scale",
|
|
2689
|
+
platforms: ["web", "native"],
|
|
2690
|
+
category: "utility",
|
|
2691
|
+
description:
|
|
2692
|
+
"Maps data values to pixel positions — `scaleLinear`, `scaleTime`, `scaleBand`, `scaleOrdinal`, plus the `.ticks()` an axis is labelled from. The maths half of a custom chart; pair it with d3-shape for the path and react-native-svg to draw. Pure JS (its only deps are other d3 modules), so one implementation covers both platforms.",
|
|
2693
|
+
},
|
|
2694
|
+
{
|
|
2695
|
+
specifier: "d3-shape",
|
|
2696
|
+
platforms: ["web", "native"],
|
|
2697
|
+
category: "drawing",
|
|
2698
|
+
description:
|
|
2699
|
+
"SVG path generators — `line`, `area`, `arc`, `pie`, `curve*`. Each returns a path string you hand to the vetted react-native-svg's `<Path d={…} />`, so a bespoke line/area/donut chart renders identically in the Player and the Expo export from ONE source file. Pure JS.",
|
|
2700
|
+
},
|
|
2701
|
+
{
|
|
2702
|
+
specifier: "d3-array",
|
|
2703
|
+
platforms: ["web", "native"],
|
|
2704
|
+
category: "utility",
|
|
2705
|
+
description:
|
|
2706
|
+
"Array statistics and binning — `extent`, `min`/`max`, `bisect`, `bin`, `group`, `rollup`. Chiefly how you compute the domain d3-scale expects from a dataset. Pure JS.",
|
|
2707
|
+
},
|
|
2708
|
+
{
|
|
2709
|
+
specifier: "simple-statistics",
|
|
2710
|
+
platforms: ["web", "native"],
|
|
2711
|
+
category: "utility",
|
|
2712
|
+
description:
|
|
2713
|
+
"Descriptive statistics and simple models — mean/median/mode, standard deviation, quantiles, linear regression, correlation. Small and pure JS, so a widget can summarise a datastore table without shipping a maths framework. For exact decimal arithmetic (money) use decimal.js instead: this operates on JS numbers.",
|
|
2714
|
+
},
|
|
2640
2715
|
];
|
|
2641
2716
|
|
|
2642
2717
|
// sc-1064: CORE React infrastructure specifiers the host RESOLVES at runtime
|
|
@@ -3667,7 +3742,33 @@ const CONTRACT = deepFreeze({
|
|
|
3667
3742
|
// tell apart. A host now branches on `menuType` alone; a stored
|
|
3668
3743
|
// `navigation.topBarMenuStyle` is inert rather than migrated, so a
|
|
3669
3744
|
// `top-bar` app that never chose `tabs` moves to the tab row.
|
|
3670
|
-
|
|
3745
|
+
// 1.98.0: additive (sc-7193) — new `useImageEditor()` hook + the optional
|
|
3746
|
+
// `device.imageEditor` host slice it reads. A widget could take a photo
|
|
3747
|
+
// (useCamera) and upload one, but not CHANGE one: no resize before
|
|
3748
|
+
// upload, no crop to an aspect ratio, no straightening a sideways shot.
|
|
3749
|
+
// Host-brokered rather than a vetted import, the same call expo-image-picker
|
|
3750
|
+
// and expo-speech-recognition already got — the package stays out of every
|
|
3751
|
+
// widget bundle and parity is the SDK's problem, not each author's. The web
|
|
3752
|
+
// Player brokers it on a host-side canvas (the widget never touches the
|
|
3753
|
+
// DOM), the Expo export via expo-image-manipulator; both implement the same
|
|
3754
|
+
// four actions and the same three output formats. `extent` is deliberately
|
|
3755
|
+
// absent — it is web-only in expo-image-manipulator, and a web-only
|
|
3756
|
+
// capability is the direction §8 forbids. The slice is OPTIONAL, so a host
|
|
3757
|
+
// that brokers nothing degrades the hook to supported:false rather than
|
|
3758
|
+
// throwing. Minor bump on the pre-1.0 channel.
|
|
3759
|
+
// 1.99.0: additive (sc-7191) — the vetted import allowlist gains five PURE-JS
|
|
3760
|
+
// packages, the first non-UI maths available to a widget: `decimal.js`
|
|
3761
|
+
// (exact decimal arithmetic — the platform has payments, invoicing and
|
|
3762
|
+
// VAT, and float money math disagrees with what the backend charged),
|
|
3763
|
+
// `d3-scale` + `d3-shape` + `d3-array` (the scale/path/domain maths a
|
|
3764
|
+
// bespoke chart needs, drawn through the already-vetted react-native-svg),
|
|
3765
|
+
// and `simple-statistics`. All five are `["web", "native"]` with no native
|
|
3766
|
+
// module, so this is full parity, not a §8 native-only case. Each is
|
|
3767
|
+
// host-shimmed in widgetLoader.js and pinned in the export for the same
|
|
3768
|
+
// reason date-fns is: an AI-agent widget is transpiled, never bundled, so
|
|
3769
|
+
// its bare import must resolve at runtime on both hosts. No existing entry
|
|
3770
|
+
// changed shape — minor bump on the pre-1.0 channel.
|
|
3771
|
+
version: "1.99.0",
|
|
3671
3772
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
3672
3773
|
hooks: HOOKS,
|
|
3673
3774
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1670,6 +1670,38 @@ const HOOKS = [
|
|
|
1670
1670
|
requiredContextSlice: [],
|
|
1671
1671
|
scopes: null,
|
|
1672
1672
|
},
|
|
1673
|
+
// sc-7193 — host-brokered image editing. Optional slice; the hook reports
|
|
1674
|
+
// supported:false rather than throwing at render.
|
|
1675
|
+
{
|
|
1676
|
+
name: "useImageEditor",
|
|
1677
|
+
signature: "useImageEditor()",
|
|
1678
|
+
description:
|
|
1679
|
+
"Resize, crop, rotate or flip an image. Returns { result, editing, error, supported, edit, reset }. " +
|
|
1680
|
+
"Editing is IMPERATIVE — call edit() from an event handler, never during render. " +
|
|
1681
|
+
"edit(uri, actions, options?) applies `actions` IN ORDER and resolves the SAME normalised asset shape useCamera() " +
|
|
1682
|
+
"yields — { uri, name, mimeType, width, height, size, base64?, file } — so capture -> edit -> upload is ONE code path: " +
|
|
1683
|
+
"append result.file to a FormData as `file` and pass it to ctx.assets.upload(fd). " +
|
|
1684
|
+
"Each action entry carries exactly one of { resize: { width?, height? } } (aspect preserved when only one is given), " +
|
|
1685
|
+
"{ crop: { originX, originY, width, height } }, { rotate: degrees } (positive is clockwise), or " +
|
|
1686
|
+
"{ flip: \"horizontal\" | \"vertical\" }. options: { format: \"jpeg\" | \"png\" | \"webp\" (default jpeg), " +
|
|
1687
|
+
"compress: 0..1 (default 0.8, ignored for png), base64: boolean (off by default — it is expensive) }. " +
|
|
1688
|
+
"Rejects with an ImageEditorError whose .code is one of UNSUPPORTED | INVALID_ACTION | DECODE_FAILED | ENCODE_FAILED | " +
|
|
1689
|
+
"INTERNAL. There is deliberately NO `extent` action: it exists only on web, and a web-only capability is the direction " +
|
|
1690
|
+
"CLAUDE.md §8 forbids. Check `supported` before rendering an edit control. Identical on web (a canvas in the host, so " +
|
|
1691
|
+
"the widget never touches the DOM) and the Expo export (expo-image-manipulator).",
|
|
1692
|
+
returnShape: {
|
|
1693
|
+
result:
|
|
1694
|
+
"{ uri, name, mimeType, width, height, size, base64?, file } | null",
|
|
1695
|
+
editing: "boolean",
|
|
1696
|
+
error: "ImageEditorError | null",
|
|
1697
|
+
supported: "boolean // false when the host brokers no image editor",
|
|
1698
|
+
edit:
|
|
1699
|
+
"(uri, actions, options?) => Promise<result | null> // rejects with ImageEditorError",
|
|
1700
|
+
reset: "() => void // clear the result + error and release it",
|
|
1701
|
+
},
|
|
1702
|
+
requiredContextSlice: [],
|
|
1703
|
+
scopes: null,
|
|
1704
|
+
},
|
|
1673
1705
|
];
|
|
1674
1706
|
|
|
1675
1707
|
// REQ-WSDK-RN-WEB: the SDK exposes the React Native primitive API
|
|
@@ -2310,10 +2342,13 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2310
2342
|
"isBackgroundWatching() -> boolean, subscribeBackgroundPositions(cb) -> unsubscribe, " +
|
|
2311
2343
|
"subscribeBackgroundWatchState(cb) -> unsubscribe }, " +
|
|
2312
2344
|
"speech: { isSupported() -> boolean, start(options, { onResult, onError, onEnd }) -> Promise<{ stop(), abort() }> }, " +
|
|
2313
|
-
"camera: { isSupported() -> boolean, capture(options?) -> Promise<asset | null>, pick(options?) -> Promise<asset | null> }
|
|
2314
|
-
"
|
|
2315
|
-
"
|
|
2316
|
-
"
|
|
2345
|
+
"camera: { isSupported() -> boolean, capture(options?) -> Promise<asset | null>, pick(options?) -> Promise<asset | null> }, " +
|
|
2346
|
+
"imageEditor: { isSupported() -> boolean, edit(uri, actions, options?) -> Promise<asset> } }. " +
|
|
2347
|
+
"Backs useGeolocation(), useSpeechToText(), useCamera() and useImageEditor(). The web Player brokers them via " +
|
|
2348
|
+
"navigator.geolocation, window.SpeechRecognition, a getUserMedia camera preview and a host-side canvas; the Expo export via " +
|
|
2349
|
+
"expo-location, expo-speech-recognition, expo-image-picker and expo-image-manipulator. " +
|
|
2350
|
+
"imageEditor.edit applies resize / crop / rotate / flip in order and rejects with an ImageEditorError " +
|
|
2351
|
+
"(.code UNSUPPORTED | INVALID_ACTION | DECODE_FAILED | ENCODE_FAILED | INTERNAL). " +
|
|
2317
2352
|
"getCurrentPosition rejects with a GeolocationError (.code PERMISSION_DENIED | UNAVAILABLE | TIMEOUT | UNSUPPORTED | INTERNAL). " +
|
|
2318
2353
|
"speech.start streams { transcript, isFinal } to onResult and runs ON DEVICE — it uploads no audio and spends no AI credit; " +
|
|
2319
2354
|
"its onError carries the Web Speech error vocabulary (not-allowed | no-speech | language-not-supported | network | aborted). " +
|
|
@@ -2327,7 +2362,12 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2327
2362
|
"foreground service), and a sibling widget may start or stop it — so subscribeBackgroundWatchState is how every mounted " +
|
|
2328
2363
|
"widget stays truthful, and isBackgroundWatching() is only the synchronous first read.",
|
|
2329
2364
|
required: false,
|
|
2330
|
-
fields: {
|
|
2365
|
+
fields: {
|
|
2366
|
+
geolocation: "object",
|
|
2367
|
+
speech: "object",
|
|
2368
|
+
camera: "object",
|
|
2369
|
+
imageEditor: "object",
|
|
2370
|
+
},
|
|
2331
2371
|
},
|
|
2332
2372
|
};
|
|
2333
2373
|
|
|
@@ -2637,6 +2677,41 @@ const VETTED_IMPORTS = [
|
|
|
2637
2677
|
description:
|
|
2638
2678
|
"Device motion hardware on the Expo export: Accelerometer, Gyroscope, Magnetometer, DeviceMotion, Barometer, Pedometer and LightSensor, each read as an addListener subscription with setUpdateInterval — always remove the subscription on unmount, a sensor left running drains the battery. Expo SDK 56 ships 56.0.x. Native-only on purpose: the package's own web build derives acceleration from deviceorientation ANGLES rather than real motion, so a shake or tilt threshold tuned on one host would read differently on the other. Author it in widget.native.jsx and pair it with a widget.web.jsx reading window.DeviceMotionEvent (accelerationIncludingGravity / rotationRate), the browser API the same hardware exposes. Both hosts need a user gesture before readings start, and iOS Safari additionally needs an explicit DeviceMotionEvent.requestPermission() grant — so gate the reading behind a Pressable, never start it on mount.",
|
|
2639
2679
|
},
|
|
2680
|
+
{
|
|
2681
|
+
specifier: "decimal.js",
|
|
2682
|
+
platforms: ["web", "native"],
|
|
2683
|
+
category: "utility",
|
|
2684
|
+
description:
|
|
2685
|
+
"Arbitrary-precision decimal arithmetic. Reach for it whenever a widget computes MONEY: JavaScript numbers are binary floats, so a price total, a VAT line or a discount accumulated in `+`/`*` drifts by fractions of a cent and disagrees with what the backend charged. `new Decimal(a).plus(b).toFixed(2)` does not. Pure JS with zero dependencies, identical on both platforms. Not for general maths — it is slower than a number and only earns its cost where exactness is the point.",
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
specifier: "d3-scale",
|
|
2689
|
+
platforms: ["web", "native"],
|
|
2690
|
+
category: "utility",
|
|
2691
|
+
description:
|
|
2692
|
+
"Maps data values to pixel positions — `scaleLinear`, `scaleTime`, `scaleBand`, `scaleOrdinal`, plus the `.ticks()` an axis is labelled from. The maths half of a custom chart; pair it with d3-shape for the path and react-native-svg to draw. Pure JS (its only deps are other d3 modules), so one implementation covers both platforms.",
|
|
2693
|
+
},
|
|
2694
|
+
{
|
|
2695
|
+
specifier: "d3-shape",
|
|
2696
|
+
platforms: ["web", "native"],
|
|
2697
|
+
category: "drawing",
|
|
2698
|
+
description:
|
|
2699
|
+
"SVG path generators — `line`, `area`, `arc`, `pie`, `curve*`. Each returns a path string you hand to the vetted react-native-svg's `<Path d={…} />`, so a bespoke line/area/donut chart renders identically in the Player and the Expo export from ONE source file. Pure JS.",
|
|
2700
|
+
},
|
|
2701
|
+
{
|
|
2702
|
+
specifier: "d3-array",
|
|
2703
|
+
platforms: ["web", "native"],
|
|
2704
|
+
category: "utility",
|
|
2705
|
+
description:
|
|
2706
|
+
"Array statistics and binning — `extent`, `min`/`max`, `bisect`, `bin`, `group`, `rollup`. Chiefly how you compute the domain d3-scale expects from a dataset. Pure JS.",
|
|
2707
|
+
},
|
|
2708
|
+
{
|
|
2709
|
+
specifier: "simple-statistics",
|
|
2710
|
+
platforms: ["web", "native"],
|
|
2711
|
+
category: "utility",
|
|
2712
|
+
description:
|
|
2713
|
+
"Descriptive statistics and simple models — mean/median/mode, standard deviation, quantiles, linear regression, correlation. Small and pure JS, so a widget can summarise a datastore table without shipping a maths framework. For exact decimal arithmetic (money) use decimal.js instead: this operates on JS numbers.",
|
|
2714
|
+
},
|
|
2640
2715
|
];
|
|
2641
2716
|
|
|
2642
2717
|
// sc-1064: CORE React infrastructure specifiers the host RESOLVES at runtime
|
|
@@ -3667,7 +3742,33 @@ const CONTRACT = deepFreeze({
|
|
|
3667
3742
|
// tell apart. A host now branches on `menuType` alone; a stored
|
|
3668
3743
|
// `navigation.topBarMenuStyle` is inert rather than migrated, so a
|
|
3669
3744
|
// `top-bar` app that never chose `tabs` moves to the tab row.
|
|
3670
|
-
|
|
3745
|
+
// 1.98.0: additive (sc-7193) — new `useImageEditor()` hook + the optional
|
|
3746
|
+
// `device.imageEditor` host slice it reads. A widget could take a photo
|
|
3747
|
+
// (useCamera) and upload one, but not CHANGE one: no resize before
|
|
3748
|
+
// upload, no crop to an aspect ratio, no straightening a sideways shot.
|
|
3749
|
+
// Host-brokered rather than a vetted import, the same call expo-image-picker
|
|
3750
|
+
// and expo-speech-recognition already got — the package stays out of every
|
|
3751
|
+
// widget bundle and parity is the SDK's problem, not each author's. The web
|
|
3752
|
+
// Player brokers it on a host-side canvas (the widget never touches the
|
|
3753
|
+
// DOM), the Expo export via expo-image-manipulator; both implement the same
|
|
3754
|
+
// four actions and the same three output formats. `extent` is deliberately
|
|
3755
|
+
// absent — it is web-only in expo-image-manipulator, and a web-only
|
|
3756
|
+
// capability is the direction §8 forbids. The slice is OPTIONAL, so a host
|
|
3757
|
+
// that brokers nothing degrades the hook to supported:false rather than
|
|
3758
|
+
// throwing. Minor bump on the pre-1.0 channel.
|
|
3759
|
+
// 1.99.0: additive (sc-7191) — the vetted import allowlist gains five PURE-JS
|
|
3760
|
+
// packages, the first non-UI maths available to a widget: `decimal.js`
|
|
3761
|
+
// (exact decimal arithmetic — the platform has payments, invoicing and
|
|
3762
|
+
// VAT, and float money math disagrees with what the backend charged),
|
|
3763
|
+
// `d3-scale` + `d3-shape` + `d3-array` (the scale/path/domain maths a
|
|
3764
|
+
// bespoke chart needs, drawn through the already-vetted react-native-svg),
|
|
3765
|
+
// and `simple-statistics`. All five are `["web", "native"]` with no native
|
|
3766
|
+
// module, so this is full parity, not a §8 native-only case. Each is
|
|
3767
|
+
// host-shimmed in widgetLoader.js and pinned in the export for the same
|
|
3768
|
+
// reason date-fns is: an AI-agent widget is transpiled, never bundled, so
|
|
3769
|
+
// its bare import must resolve at runtime on both hosts. No existing entry
|
|
3770
|
+
// changed shape — minor bump on the pre-1.0 channel.
|
|
3771
|
+
version: "1.99.0",
|
|
3671
3772
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
3672
3773
|
hooks: HOOKS,
|
|
3673
3774
|
primitives: PRIMITIVES,
|
package/dist/dev-shims.js
CHANGED
|
@@ -117,6 +117,14 @@ const HOST_EXTERNAL_SPECIFIERS = [
|
|
|
117
117
|
// bundle shares one RN-web (StyleSheet/context) instead of inlining a second
|
|
118
118
|
// copy. On native, Metro resolves the real react-native in the export.
|
|
119
119
|
"react-native",
|
|
120
|
+
// sc-7191: the vetted pure-JS maths set. Externalised for the same reason as
|
|
121
|
+
// date-fns — an AI-agent widget is transpiled, never bundled, so the host
|
|
122
|
+
// must resolve the bare import at runtime.
|
|
123
|
+
"decimal.js",
|
|
124
|
+
"d3-scale",
|
|
125
|
+
"d3-shape",
|
|
126
|
+
"d3-array",
|
|
127
|
+
"simple-statistics",
|
|
120
128
|
];
|
|
121
129
|
|
|
122
130
|
/**
|
package/dist/hooks.js
CHANGED
|
@@ -1589,6 +1589,173 @@ export function useCamera(options) {
|
|
|
1589
1589
|
return { asset, loading, error, supported, capture, pick, reset };
|
|
1590
1590
|
}
|
|
1591
1591
|
|
|
1592
|
+
/**
|
|
1593
|
+
* Structured error thrown by `useImageEditor` callbacks.
|
|
1594
|
+
*
|
|
1595
|
+
* `code` is one of:
|
|
1596
|
+
* - "UNSUPPORTED" — this host brokers no image editor.
|
|
1597
|
+
* - "INVALID_ACTION" — an action or output option the contract doesn't define.
|
|
1598
|
+
* - "DECODE_FAILED" — the source could not be read as an image.
|
|
1599
|
+
* - "ENCODE_FAILED" — the host could not write the requested output format.
|
|
1600
|
+
* - "INTERNAL" — anything else.
|
|
1601
|
+
*/
|
|
1602
|
+
export class ImageEditorError extends Error {
|
|
1603
|
+
constructor(code, message, opts) {
|
|
1604
|
+
super(message);
|
|
1605
|
+
this.name = "ImageEditorError";
|
|
1606
|
+
this.code = code;
|
|
1607
|
+
if (opts && opts.cause) this.cause = opts.cause;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/** Coerce a thrown value into an ImageEditorError with a stable code. */
|
|
1612
|
+
function toImageEditorError(err) {
|
|
1613
|
+
if (err instanceof ImageEditorError) return err;
|
|
1614
|
+
const raw = err && err.code !== undefined ? err.code : null;
|
|
1615
|
+
const known = [
|
|
1616
|
+
"UNSUPPORTED",
|
|
1617
|
+
"INVALID_ACTION",
|
|
1618
|
+
"DECODE_FAILED",
|
|
1619
|
+
"ENCODE_FAILED",
|
|
1620
|
+
];
|
|
1621
|
+
const code = known.includes(raw) ? raw : "INTERNAL";
|
|
1622
|
+
const message =
|
|
1623
|
+
(err && typeof err.message === "string" && err.message) ||
|
|
1624
|
+
"Image edit failed";
|
|
1625
|
+
return new ImageEditorError(code, message, { cause: err });
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* Resize, crop, rotate or flip an image. Returns
|
|
1630
|
+
* `{ result, editing, error, supported, edit, reset }`.
|
|
1631
|
+
*
|
|
1632
|
+
* Editing is IMPERATIVE — call `edit()` from an event handler, never during
|
|
1633
|
+
* render. It resolves the SAME normalised asset shape `useCamera()` yields, so
|
|
1634
|
+
* capture → edit → upload is one code path on both hosts:
|
|
1635
|
+
*
|
|
1636
|
+
* const { asset, capture } = useCamera();
|
|
1637
|
+
* const { edit } = useImageEditor();
|
|
1638
|
+
* const shot = await capture();
|
|
1639
|
+
* const small = await edit(shot.uri, [{ resize: { width: 800 } }]);
|
|
1640
|
+
* const fd = new FormData();
|
|
1641
|
+
* fd.append("file", small.file);
|
|
1642
|
+
* await ctx.assets.upload(fd);
|
|
1643
|
+
*
|
|
1644
|
+
* `actions` is an ordered array applied in sequence; each entry carries exactly
|
|
1645
|
+
* one of:
|
|
1646
|
+
* - `{ resize: { width?, height? } }` — aspect preserved when one is given
|
|
1647
|
+
* - `{ crop: { originX, originY, width, height } }`
|
|
1648
|
+
* - `{ rotate: degrees }` — positive is clockwise
|
|
1649
|
+
* - `{ flip: "horizontal" | "vertical" }`
|
|
1650
|
+
*
|
|
1651
|
+
* `options` is `{ format: "jpeg" | "png" | "webp", compress: 0..1, base64? }`.
|
|
1652
|
+
* There is deliberately NO `extent` action: it exists only on web, and a
|
|
1653
|
+
* web-only capability is the direction CLAUDE.md §8 forbids.
|
|
1654
|
+
*
|
|
1655
|
+
* Check `supported` before rendering an edit control; a host with no broker
|
|
1656
|
+
* reports false rather than throwing at render.
|
|
1657
|
+
*/
|
|
1658
|
+
export function useImageEditor() {
|
|
1659
|
+
const ctx = useWidgetContextOrThrow("useImageEditor");
|
|
1660
|
+
const [result, setResult] = useState(null);
|
|
1661
|
+
const [editing, setEditing] = useState(false);
|
|
1662
|
+
const [error, setError] = useState(null);
|
|
1663
|
+
|
|
1664
|
+
// `ctx` is a fresh identity every host render — hold the live client in a ref
|
|
1665
|
+
// so the returned callbacks stay stable.
|
|
1666
|
+
const clientRef = useRef(ctx.device && ctx.device.imageEditor);
|
|
1667
|
+
clientRef.current = ctx.device && ctx.device.imageEditor;
|
|
1668
|
+
// Web hands back a blob: URL per result; abandoning it leaks the blob for the
|
|
1669
|
+
// life of the document, so the hook owns revoking the one it replaced.
|
|
1670
|
+
const releaseRef = useRef(null);
|
|
1671
|
+
const runRef = useRef(0);
|
|
1672
|
+
|
|
1673
|
+
const supported = Boolean(
|
|
1674
|
+
clientRef.current &&
|
|
1675
|
+
typeof clientRef.current.edit === "function" &&
|
|
1676
|
+
(typeof clientRef.current.isSupported !== "function" ||
|
|
1677
|
+
clientRef.current.isSupported()),
|
|
1678
|
+
);
|
|
1679
|
+
|
|
1680
|
+
const release = useCallback(() => {
|
|
1681
|
+
const revoke = releaseRef.current;
|
|
1682
|
+
releaseRef.current = null;
|
|
1683
|
+
if (typeof revoke === "function") {
|
|
1684
|
+
try {
|
|
1685
|
+
revoke();
|
|
1686
|
+
} catch {
|
|
1687
|
+
/* the host already released it */
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}, []);
|
|
1691
|
+
|
|
1692
|
+
useEffect(() => () => release(), [release]);
|
|
1693
|
+
|
|
1694
|
+
const reset = useCallback(() => {
|
|
1695
|
+
runRef.current += 1;
|
|
1696
|
+
release();
|
|
1697
|
+
setResult(null);
|
|
1698
|
+
setError(null);
|
|
1699
|
+
}, [release]);
|
|
1700
|
+
|
|
1701
|
+
const edit = useCallback(
|
|
1702
|
+
async (uri, actions, options) => {
|
|
1703
|
+
const client = clientRef.current;
|
|
1704
|
+
if (
|
|
1705
|
+
!client ||
|
|
1706
|
+
typeof client.edit !== "function" ||
|
|
1707
|
+
(typeof client.isSupported === "function" && !client.isSupported())
|
|
1708
|
+
) {
|
|
1709
|
+
const e = new ImageEditorError(
|
|
1710
|
+
"UNSUPPORTED",
|
|
1711
|
+
"This host does not provide image editing.",
|
|
1712
|
+
);
|
|
1713
|
+
setError(e);
|
|
1714
|
+
throw e;
|
|
1715
|
+
}
|
|
1716
|
+
if (typeof uri !== "string" || uri === "") {
|
|
1717
|
+
const e = new ImageEditorError(
|
|
1718
|
+
"INVALID_ACTION",
|
|
1719
|
+
"edit(uri, actions) needs a source uri.",
|
|
1720
|
+
);
|
|
1721
|
+
setError(e);
|
|
1722
|
+
throw e;
|
|
1723
|
+
}
|
|
1724
|
+
const run = (runRef.current += 1);
|
|
1725
|
+
setEditing(true);
|
|
1726
|
+
setError(null);
|
|
1727
|
+
try {
|
|
1728
|
+
const next = await client.edit(
|
|
1729
|
+
uri,
|
|
1730
|
+
Array.isArray(actions) ? actions : [],
|
|
1731
|
+
options || {},
|
|
1732
|
+
);
|
|
1733
|
+
// A reset() or a newer edit landed while this one was running — drop
|
|
1734
|
+
// the result rather than clobbering what the widget now shows.
|
|
1735
|
+
if (run !== runRef.current) {
|
|
1736
|
+
if (next && typeof next.release === "function") next.release();
|
|
1737
|
+
return null;
|
|
1738
|
+
}
|
|
1739
|
+
if (!next) return null;
|
|
1740
|
+
release();
|
|
1741
|
+
releaseRef.current =
|
|
1742
|
+
typeof next.release === "function" ? next.release : null;
|
|
1743
|
+
setResult(next);
|
|
1744
|
+
return next;
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
const ie = toImageEditorError(err);
|
|
1747
|
+
if (run === runRef.current) setError(ie);
|
|
1748
|
+
throw ie;
|
|
1749
|
+
} finally {
|
|
1750
|
+
if (run === runRef.current) setEditing(false);
|
|
1751
|
+
}
|
|
1752
|
+
},
|
|
1753
|
+
[release],
|
|
1754
|
+
);
|
|
1755
|
+
|
|
1756
|
+
return { result, editing, error, supported, edit, reset };
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1592
1759
|
/* ============================================================================
|
|
1593
1760
|
* DATASTORE CLIENT — ctx.datastore (@colixsystems/datastore-client)
|
|
1594
1761
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1666,6 +1666,86 @@ export class CameraError extends Error {
|
|
|
1666
1666
|
);
|
|
1667
1667
|
}
|
|
1668
1668
|
|
|
1669
|
+
/** One edit step. Exactly one key per entry; the array applies in order. */
|
|
1670
|
+
export type ImageEditAction =
|
|
1671
|
+
| { resize: { width?: number; height?: number } }
|
|
1672
|
+
| { crop: { originX: number; originY: number; width: number; height: number } }
|
|
1673
|
+
| { rotate: number }
|
|
1674
|
+
| { flip: "horizontal" | "vertical" };
|
|
1675
|
+
|
|
1676
|
+
/** Output settings for `useImageEditor().edit(...)`. */
|
|
1677
|
+
export interface ImageEditOptions {
|
|
1678
|
+
/** Encoding of the result. Defaults to "jpeg". */
|
|
1679
|
+
format?: "jpeg" | "png" | "webp";
|
|
1680
|
+
/** 0–1 quality for the lossy formats. Defaults to 0.8. Ignored for png. */
|
|
1681
|
+
compress?: number;
|
|
1682
|
+
/** Also return the bytes as base64. Off by default — it is expensive. */
|
|
1683
|
+
base64?: boolean;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* An edited image, normalised across hosts. Structurally the same shape
|
|
1688
|
+
* `useCamera()` yields, so capture → edit → upload is one code path.
|
|
1689
|
+
*/
|
|
1690
|
+
export interface EditedImage {
|
|
1691
|
+
uri: string;
|
|
1692
|
+
name: string;
|
|
1693
|
+
mimeType: string;
|
|
1694
|
+
width: number | null;
|
|
1695
|
+
height: number | null;
|
|
1696
|
+
size: number | null;
|
|
1697
|
+
/** Present only when `base64` was requested. */
|
|
1698
|
+
base64?: string;
|
|
1699
|
+
/** Ready-to-upload part — a `File` on web, `{ uri, name, type }` on native. */
|
|
1700
|
+
file: unknown;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
export interface ImageEditorResult {
|
|
1704
|
+
/** The most recent edit, or null before the first call / after reset. */
|
|
1705
|
+
result: EditedImage | null;
|
|
1706
|
+
editing: boolean;
|
|
1707
|
+
error: ImageEditorError | null;
|
|
1708
|
+
/** False when the host brokers no image editor. */
|
|
1709
|
+
supported: boolean;
|
|
1710
|
+
/** Apply `actions` in order and encode per `options`. */
|
|
1711
|
+
edit(
|
|
1712
|
+
uri: string,
|
|
1713
|
+
actions: ImageEditAction[],
|
|
1714
|
+
options?: ImageEditOptions,
|
|
1715
|
+
): Promise<EditedImage | null>;
|
|
1716
|
+
/** Clear `result` and `error`, releasing the held image. */
|
|
1717
|
+
reset(): void;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
/**
|
|
1721
|
+
* Resize, crop, rotate or flip an image. Imperative — call `edit()` from an
|
|
1722
|
+
* event handler, never during render. The web Player brokers it on a canvas,
|
|
1723
|
+
* the Expo export via `expo-image-manipulator`; both implement the same four
|
|
1724
|
+
* actions and the same output formats. There is deliberately no `extent`
|
|
1725
|
+
* action — it exists only on web, and a web-only capability is the direction
|
|
1726
|
+
* CLAUDE.md §8 forbids. Safe to call on a host that brokers no editor:
|
|
1727
|
+
* `supported` is then false, so gate the control on it.
|
|
1728
|
+
*/
|
|
1729
|
+
export function useImageEditor(): ImageEditorResult;
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* Error surfaced by `useImageEditor()` — thrown by `edit()` and stored in the
|
|
1733
|
+
* hook's `error` slot. `code` is a stable categorisation.
|
|
1734
|
+
*/
|
|
1735
|
+
export class ImageEditorError extends Error {
|
|
1736
|
+
code:
|
|
1737
|
+
| "UNSUPPORTED"
|
|
1738
|
+
| "INVALID_ACTION"
|
|
1739
|
+
| "DECODE_FAILED"
|
|
1740
|
+
| "ENCODE_FAILED"
|
|
1741
|
+
| "INTERNAL";
|
|
1742
|
+
constructor(
|
|
1743
|
+
code: ImageEditorError["code"],
|
|
1744
|
+
message: string,
|
|
1745
|
+
opts?: { cause?: unknown },
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1669
1749
|
/**
|
|
1670
1750
|
* Error class thrown by useDatastoreMutation callbacks (and surfaced by
|
|
1671
1751
|
* useDatastoreQuery in its `error` slot). The `code` is a stable
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.127.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "node scripts/build.js",
|
|
52
|
-
"test": "node --test src/__tests__/contract.test.js src/__tests__/vetted-imports-audit.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-invites.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-filestore-file.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-payments.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-section-empty.test.js src/__tests__/hooks-widget-event.test.js src/__tests__/hooks-widget-input.test.js src/__tests__/hooks-identification.test.js src/__tests__/hooks-subscription.test.js src/__tests__/hooks-volatile-query-key.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-translation-api.test.js src/__tests__/linter-page-url.test.js src/__tests__/linter-image-height.test.js src/__tests__/linter-hardcoded-design.test.js src/__tests__/linter-measured-padding.test.js src/__tests__/linter-payment-error.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/hooks-translate.test.js src/__tests__/devserver.test.js src/__tests__/flatten-entry.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/corner-radius.test.js src/__tests__/theme-components-parity.test.js src/__tests__/navigation-parity.test.js src/__tests__/theme-depth-tokens.test.js src/__tests__/interaction-lift.test.js src/__tests__/toast-host.test.js src/__tests__/overlay-tokens.test.js src/__tests__/hooks-domain-error-mapping.test.js src/__tests__/linter-datastore-error.test.js src/__tests__/linter-write-gating.test.js src/__tests__/hooks-speech-to-text.test.js src/__tests__/hooks-camera.test.js src/__tests__/hooks-bound-columns.test.js src/__tests__/hooks-stable-query.test.js src/__tests__/hooks-can-write.test.js src/__tests__/widget-route.test.js src/__tests__/linter-html-in-content.test.js src/__tests__/markdown.test.js src/__tests__/markdown-edit.test.js src/__tests__/richtext-tokens.test.js"
|
|
52
|
+
"test": "node --test src/__tests__/contract.test.js src/__tests__/vetted-imports-audit.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-invites.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-filestore-file.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-payments.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-section-empty.test.js src/__tests__/hooks-widget-event.test.js src/__tests__/hooks-widget-input.test.js src/__tests__/hooks-identification.test.js src/__tests__/hooks-subscription.test.js src/__tests__/hooks-volatile-query-key.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-translation-api.test.js src/__tests__/linter-page-url.test.js src/__tests__/linter-image-height.test.js src/__tests__/linter-hardcoded-design.test.js src/__tests__/linter-measured-padding.test.js src/__tests__/linter-payment-error.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/hooks-translate.test.js src/__tests__/devserver.test.js src/__tests__/flatten-entry.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/corner-radius.test.js src/__tests__/theme-components-parity.test.js src/__tests__/navigation-parity.test.js src/__tests__/theme-depth-tokens.test.js src/__tests__/interaction-lift.test.js src/__tests__/toast-host.test.js src/__tests__/overlay-tokens.test.js src/__tests__/hooks-domain-error-mapping.test.js src/__tests__/linter-datastore-error.test.js src/__tests__/linter-write-gating.test.js src/__tests__/hooks-speech-to-text.test.js src/__tests__/hooks-camera.test.js src/__tests__/hooks-image-editor.test.js src/__tests__/hooks-bound-columns.test.js src/__tests__/hooks-stable-query.test.js src/__tests__/hooks-can-write.test.js src/__tests__/widget-route.test.js src/__tests__/linter-html-in-content.test.js src/__tests__/markdown.test.js src/__tests__/markdown-edit.test.js src/__tests__/richtext-tokens.test.js"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=18"
|
package/src/dev-shims.js
CHANGED
|
@@ -117,6 +117,14 @@ const HOST_EXTERNAL_SPECIFIERS = [
|
|
|
117
117
|
// bundle shares one RN-web (StyleSheet/context) instead of inlining a second
|
|
118
118
|
// copy. On native, Metro resolves the real react-native in the export.
|
|
119
119
|
"react-native",
|
|
120
|
+
// sc-7191: the vetted pure-JS maths set. Externalised for the same reason as
|
|
121
|
+
// date-fns — an AI-agent widget is transpiled, never bundled, so the host
|
|
122
|
+
// must resolve the bare import at runtime.
|
|
123
|
+
"decimal.js",
|
|
124
|
+
"d3-scale",
|
|
125
|
+
"d3-shape",
|
|
126
|
+
"d3-array",
|
|
127
|
+
"simple-statistics",
|
|
120
128
|
];
|
|
121
129
|
|
|
122
130
|
/**
|