@colixsystems/widget-sdk 0.85.0 → 0.86.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 +15 -2
- package/dist/contract.cjs +17 -2
- package/dist/contract.js +17 -2
- package/dist/linter.cjs +69 -0
- package/dist/linter.js +87 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
24
24
|
| **CORE** | `useNavigation()` | `{ goTo, goBack, push, replace, back, currentRoute }` | `ctx.navigation` — no scope (external URLs use the `Linking` primitive) |
|
|
25
25
|
| **CORE** | `useRouteParams()` | `{ [paramKey]: value }` | `ctx.navigation.currentRoute.params` — no scope. The nav params the previous page passed via `goTo(pageId, params)`; the flat accessor for master→detail (read `recordId` on a detail page). Empty object when none. |
|
|
26
26
|
| **CORE** | `usePageContext()` | `{ params, records }` | `ctx.pageContext` — no scope. The page's DECLARED parameters, resolved once by the host: `params` are coerced to their declared types, `records` holds the row already fetched for each `record` param (read it instead of fetching again). Both empty when the page declares none. |
|
|
27
|
-
| **CORE** | `useWidgetEvent(name)` | `(payload?) => void` | `ctx.events.emit` — no scope |
|
|
27
|
+
| **CORE** | `useWidgetEvent(name)` | `(payload?) => void` | `ctx.events.emit` — no scope. The hook IS the emitter: `const emitSlot = useWidgetEvent("slotChosen")`, then `emitSlot(payload)`. Never destructure the result — there is no `emit` member. |
|
|
28
28
|
| **CORE** | `useWidgetInput(inputName)` | the published payload, or `undefined` | `ctx.inputs` — no scope. Reads a value ANOTHER widget on the same page published with `useWidgetEvent`. Declare the input in `manifest.inputs`; the page author wires it to one sibling's declared event. The channel retains the last payload, so a widget that mounts later still reads it. `undefined` while unwired or before the first publish — always render a sensible default. Page-scoped and ephemeral: use `useRouteParams()` for state that must survive navigation, the datastore for state that must persist. |
|
|
29
29
|
| **CORE** | `useChildRenderer()` | `{ renderNode(node) }` | `ctx.renderer` — no scope (prefer the `WidgetTree` component) |
|
|
30
30
|
| **CORE** | `useFill()` | `boolean` | `ctx.fill` — no scope. `true` when the host sized this widget to fill its page-grid tile's reserved height (containers + media fill by default; the author can override per tile). Media-style widgets switch to a `flex: 1` / `height: "100%"` layout; others ignore it. Defaults `false`. |
|
|
@@ -61,7 +61,20 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
61
61
|
|
|
62
62
|
## Status
|
|
63
63
|
|
|
64
|
-
`v0.
|
|
64
|
+
`v0.86.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**.
|
|
65
|
+
|
|
66
|
+
### What's new in 0.86.0 (contract unchanged)
|
|
67
|
+
|
|
68
|
+
**New linter rule `measured-width-ignores-padding` — a measured width includes the measuring element's own padding (sc-4913).**
|
|
69
|
+
|
|
70
|
+
- **`measured-width-ignores-padding` (severity `warning`, non-blocking).** A widget that puts `onLayout` on an element which sets its own `padding` (or `paddingHorizontal`/`Left`/`Right`), and then sizes grid cells from the measured number, is flagged. `onLayout` reports the element's **frame** width and padding sits inside that frame, so the space the children really get is `width - paddingLeft - paddingRight`. Cells sized to fill the raw measurement overflow the content box, the last one wraps, and the widget ships a whole empty column of whitespace beside its cards — on both the web Player and the native Expo export, with nothing in the console. Author fix: spread `onLayout` on an **unpadded** element (keep the padding on a parent, or measure an inner `<View>` inside the padded root) so the number you hold is the usable width. Better still for content-sized cells: skip the measurement entirely and wrap with flex — a `{ flexDirection: "row", flexWrap: "wrap", gap }` row whose cards take `{ flexGrow: 1, flexBasis: CARD_MIN }` splits the row's real content width itself and can never leave a leftover band.
|
|
71
|
+
- **Why a warning.** The rule fires only when the measured value feeds sizing arithmetic — a padded box measured just to pick a wide/narrow form (`isNarrowWidth(width)`) is off by one padding pair and stays silent. A widget that already subtracts its own padding by hand matches too, because no text scan can verify the subtraction; that is deliberate — the remediation is correct for it as well and retires the arithmetic. Comments are not scanned, so documenting the anti-pattern is safe.
|
|
72
|
+
|
|
73
|
+
`CONTRACT` is unchanged (no new field), and no export changed signature.
|
|
74
|
+
|
|
75
|
+
### What's new in 0.85.1 (contract 1.60.1)
|
|
76
|
+
|
|
77
|
+
**`useWidgetEvent(name)` returns the emitter FUNCTION — the declared contract said otherwise (sc-4753).** `CONTRACT.hooks`'s entry for the hook declared `returnShape: { emit }`, so every surface derived from it — chiefly the Widget Builder Agent's hooks table — told authors the hook resolves to an object. It never did: `useWidgetEvent("slotChosen")` hands back the callable you invoke directly (`emitSlot({ courtId })`), exactly as the typings and the Developer guide have always documented. A widget written against the declared shape destructured a function, got `undefined`, and threw the moment a user interacted — a cross-widget wire that rendered perfectly and only failed on click. The declaration is now a bare callable and the publish-time render harness models the same shape, so a wrong destructure is caught instead of waved through. `CONTRACT.version` → `1.60.1`. Documentation-only correction: no export, signature, or runtime behaviour changed — a widget already calling the result is unaffected.
|
|
65
78
|
|
|
66
79
|
### What's new in 0.85.0 (contract 1.60.0)
|
|
67
80
|
|
package/dist/contract.cjs
CHANGED
|
@@ -782,7 +782,16 @@ const HOOKS = [
|
|
|
782
782
|
{
|
|
783
783
|
name: "useWidgetEvent",
|
|
784
784
|
signature: "useWidgetEvent(eventName)",
|
|
785
|
-
|
|
785
|
+
// sc-4753 — the hook IS the emitter, so its return is declared as a bare
|
|
786
|
+
// callable. Naming an `emit` member here made every derived surface tell
|
|
787
|
+
// authors to destructure a function, which yields undefined at run time.
|
|
788
|
+
returnShape: {
|
|
789
|
+
"(returns)":
|
|
790
|
+
"(payload?) => void // the hook returns the emitter FUNCTION itself, " +
|
|
791
|
+
"not a wrapper object. Call it directly: " +
|
|
792
|
+
"const emitSlot = useWidgetEvent(\"slotChosen\"); " +
|
|
793
|
+
"emitSlot({ courtId }). Never destructure the result.",
|
|
794
|
+
},
|
|
786
795
|
requiredContextSlice: ["events.emit"],
|
|
787
796
|
scopes: null,
|
|
788
797
|
},
|
|
@@ -2651,7 +2660,13 @@ const CONTRACT = deepFreeze({
|
|
|
2651
2660
|
// `recipient_expr` shape and no recipient column can name them. The member
|
|
2652
2661
|
// list stays host-side — a script receives a count, never the ids — and
|
|
2653
2662
|
// `exclude_user_id` keeps an author off their own message.
|
|
2654
|
-
|
|
2663
|
+
// 1.60.1: fix (sc-4753) — `useWidgetEvent`'s declared `returnShape` claimed
|
|
2664
|
+
// the hook resolves to an object with an `emit` member. It returns the
|
|
2665
|
+
// emitter FUNCTION itself, exactly as hooks.js, index.d.ts and the
|
|
2666
|
+
// Developer guide have always said, so a widget written against the
|
|
2667
|
+
// declared shape destructured a function and threw on first interaction.
|
|
2668
|
+
// Declared as a bare callable now; no runtime behaviour changed.
|
|
2669
|
+
version: "1.60.1",
|
|
2655
2670
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2656
2671
|
hooks: HOOKS,
|
|
2657
2672
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -782,7 +782,16 @@ const HOOKS = [
|
|
|
782
782
|
{
|
|
783
783
|
name: "useWidgetEvent",
|
|
784
784
|
signature: "useWidgetEvent(eventName)",
|
|
785
|
-
|
|
785
|
+
// sc-4753 — the hook IS the emitter, so its return is declared as a bare
|
|
786
|
+
// callable. Naming an `emit` member here made every derived surface tell
|
|
787
|
+
// authors to destructure a function, which yields undefined at run time.
|
|
788
|
+
returnShape: {
|
|
789
|
+
"(returns)":
|
|
790
|
+
"(payload?) => void // the hook returns the emitter FUNCTION itself, " +
|
|
791
|
+
"not a wrapper object. Call it directly: " +
|
|
792
|
+
"const emitSlot = useWidgetEvent(\"slotChosen\"); " +
|
|
793
|
+
"emitSlot({ courtId }). Never destructure the result.",
|
|
794
|
+
},
|
|
786
795
|
requiredContextSlice: ["events.emit"],
|
|
787
796
|
scopes: null,
|
|
788
797
|
},
|
|
@@ -2651,7 +2660,13 @@ const CONTRACT = deepFreeze({
|
|
|
2651
2660
|
// `recipient_expr` shape and no recipient column can name them. The member
|
|
2652
2661
|
// list stays host-side — a script receives a count, never the ids — and
|
|
2653
2662
|
// `exclude_user_id` keeps an author off their own message.
|
|
2654
|
-
|
|
2663
|
+
// 1.60.1: fix (sc-4753) — `useWidgetEvent`'s declared `returnShape` claimed
|
|
2664
|
+
// the hook resolves to an object with an `emit` member. It returns the
|
|
2665
|
+
// emitter FUNCTION itself, exactly as hooks.js, index.d.ts and the
|
|
2666
|
+
// Developer guide have always said, so a widget written against the
|
|
2667
|
+
// declared shape destructured a function and threw on first interaction.
|
|
2668
|
+
// Declared as a bare callable now; no runtime behaviour changed.
|
|
2669
|
+
version: "1.60.1",
|
|
2655
2670
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2656
2671
|
hooks: HOOKS,
|
|
2657
2672
|
primitives: PRIMITIVES,
|
package/dist/linter.cjs
CHANGED
|
@@ -877,6 +877,72 @@ function _imagePercentHeightRules(source) {
|
|
|
877
877
|
return findings;
|
|
878
878
|
}
|
|
879
879
|
|
|
880
|
+
// sc-4913 — a measured frame INCLUDES the element's own padding, so cells sized
|
|
881
|
+
// from it overflow the content box and the last one wraps into an empty column.
|
|
882
|
+
// Mirror of linter.js (see there for the full rationale).
|
|
883
|
+
const _JSX_TAG_RE = /<([A-Z][\w.]*)\b/g;
|
|
884
|
+
const _ON_LAYOUT_ATTR_RE = /\bonLayout\s*=/;
|
|
885
|
+
const _OWN_PADDING_RE = /\bpadding(?:Horizontal|Left|Right|Start|End)?\s*:/;
|
|
886
|
+
const _WIDTH_HOLDER_RE =
|
|
887
|
+
/const\s*\[\s*([\w$]+)\s*,\s*([\w$]+)\s*\]\s*=\s*(useState|useContainerWidth)\b/g;
|
|
888
|
+
const _LAYOUT_FEED_WINDOW = 80;
|
|
889
|
+
|
|
890
|
+
function _measuredWidthNames(code) {
|
|
891
|
+
const names = [];
|
|
892
|
+
_WIDTH_HOLDER_RE.lastIndex = 0;
|
|
893
|
+
let held;
|
|
894
|
+
while ((held = _WIDTH_HOLDER_RE.exec(code))) {
|
|
895
|
+
const [, value, setter, hook] = held;
|
|
896
|
+
if (hook === "useContainerWidth") {
|
|
897
|
+
names.push(value);
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const fedByLayout = new RegExp(
|
|
901
|
+
`\\b${setter}\\s*\\([\\s\\S]{0,${_LAYOUT_FEED_WINDOW}}?\\blayout\\.width\\b`,
|
|
902
|
+
);
|
|
903
|
+
if (fedByLayout.test(code)) names.push(value);
|
|
904
|
+
}
|
|
905
|
+
return names;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function _sizesFromMeasuredWidth(code, names) {
|
|
909
|
+
return names.some((name) =>
|
|
910
|
+
new RegExp(`\\b${name}\\s*[-/*]|[-/*]\\s*\\b${name}\\b`).test(code),
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function _measuredPaddingRules(source) {
|
|
915
|
+
const code = _stripNonCode(source, { keepStrings: true });
|
|
916
|
+
const measured = _measuredWidthNames(code);
|
|
917
|
+
if (!_sizesFromMeasuredWidth(code, measured)) return [];
|
|
918
|
+
const sourceLines = source.split(/\r?\n/);
|
|
919
|
+
const findings = [];
|
|
920
|
+
_JSX_TAG_RE.lastIndex = 0;
|
|
921
|
+
let tag;
|
|
922
|
+
while ((tag = _JSX_TAG_RE.exec(code))) {
|
|
923
|
+
const end = _jsxOpenTagEnd(code, tag.index + tag[0].length);
|
|
924
|
+
const attrs = code.slice(tag.index, end);
|
|
925
|
+
_JSX_TAG_RE.lastIndex = end;
|
|
926
|
+
if (!_ON_LAYOUT_ATTR_RE.test(attrs)) continue;
|
|
927
|
+
const padded = _OWN_PADDING_RE.exec(attrs);
|
|
928
|
+
if (!padded) continue;
|
|
929
|
+
const line = code
|
|
930
|
+
.slice(0, tag.index + padded.index)
|
|
931
|
+
.split(/\r?\n/).length;
|
|
932
|
+
findings.push({
|
|
933
|
+
rule: "measured-width-ignores-padding",
|
|
934
|
+
severity: "warning",
|
|
935
|
+
label:
|
|
936
|
+
`<${tag[1]}> has onLayout AND its own padding: the measured width ` +
|
|
937
|
+
`INCLUDES that padding, so cells sized from it overflow and the last ` +
|
|
938
|
+
`wraps, leaving an empty column. Measure an unpadded inner View.`,
|
|
939
|
+
line,
|
|
940
|
+
snippet: (sourceLines[line - 1] || "").trim().slice(0, 200),
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return findings;
|
|
944
|
+
}
|
|
945
|
+
|
|
880
946
|
// Narrow a split-impl widget's manifest to the platform a single bundle file
|
|
881
947
|
// ships to, so `import-platform-mismatch` lints each file against what it
|
|
882
948
|
// actually targets. Mirror of linter.js.
|
|
@@ -939,6 +1005,9 @@ function lintSource(source, options) {
|
|
|
939
1005
|
findings.push(..._lucideIconRules(source));
|
|
940
1006
|
findings.push(..._reactInScopeRules(source));
|
|
941
1007
|
findings.push(..._imagePercentHeightRules(source));
|
|
1008
|
+
// sc-4913 — soft warning: a measured width that includes the widget's own
|
|
1009
|
+
// padding wraps the last grid column into an empty one.
|
|
1010
|
+
findings.push(..._measuredPaddingRules(source));
|
|
942
1011
|
// sc-4650 — soft warning: every payment refusal reported as "try again".
|
|
943
1012
|
findings.push(..._paymentCurrencyRules(source));
|
|
944
1013
|
findings.push(..._hardcodedCurrencyLabelRules(source));
|
package/dist/linter.js
CHANGED
|
@@ -1013,6 +1013,90 @@ function _imagePercentHeightRules(source) {
|
|
|
1013
1013
|
return findings;
|
|
1014
1014
|
}
|
|
1015
1015
|
|
|
1016
|
+
// sc-4913 — a measured frame INCLUDES the element's own padding.
|
|
1017
|
+
//
|
|
1018
|
+
// `onLayout` reports the frame width, and padding sits inside that frame. A
|
|
1019
|
+
// widget that measures its padded root and then sizes fixed-pixel grid columns
|
|
1020
|
+
// to fill the measured number overflows its own content box by left+right
|
|
1021
|
+
// padding, so the last column wraps: a whole empty column of whitespace beside
|
|
1022
|
+
// the cards, on both hosts. Nothing else catches it — the widget renders and
|
|
1023
|
+
// every other gate passes.
|
|
1024
|
+
//
|
|
1025
|
+
// Fires only when the measurement feeds sizing arithmetic. A padded box
|
|
1026
|
+
// measured just to branch narrow/wide is off by one padding pair and correct
|
|
1027
|
+
// enough; `isNarrowWidth` is the sanctioned reader for that. A widget that DID
|
|
1028
|
+
// subtract its own padding matches too — no text scan can verify the
|
|
1029
|
+
// subtraction — and that is deliberate: the remediation (measure an unpadded
|
|
1030
|
+
// inner View) is correct for it as well, and retires the arithmetic entirely.
|
|
1031
|
+
const _JSX_TAG_RE = /<([A-Z][\w.]*)\b/g;
|
|
1032
|
+
const _ON_LAYOUT_ATTR_RE = /\bonLayout\s*=/;
|
|
1033
|
+
const _OWN_PADDING_RE = /\bpadding(?:Horizontal|Left|Right|Start|End)?\s*:/;
|
|
1034
|
+
const _WIDTH_HOLDER_RE =
|
|
1035
|
+
/const\s*\[\s*([\w$]+)\s*,\s*([\w$]+)\s*\]\s*=\s*(useState|useContainerWidth)\b/g;
|
|
1036
|
+
// A setter fed a layout width within this many chars of the call is measuring:
|
|
1037
|
+
// covers `setW(e.nativeEvent.layout.width)` and `setW(Math.round(…))` alike.
|
|
1038
|
+
const _LAYOUT_FEED_WINDOW = 80;
|
|
1039
|
+
|
|
1040
|
+
function _measuredWidthNames(code) {
|
|
1041
|
+
const names = [];
|
|
1042
|
+
_WIDTH_HOLDER_RE.lastIndex = 0;
|
|
1043
|
+
let held;
|
|
1044
|
+
while ((held = _WIDTH_HOLDER_RE.exec(code))) {
|
|
1045
|
+
const [, value, setter, hook] = held;
|
|
1046
|
+
if (hook === "useContainerWidth") {
|
|
1047
|
+
names.push(value);
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
const fedByLayout = new RegExp(
|
|
1051
|
+
`\\b${setter}\\s*\\([\\s\\S]{0,${_LAYOUT_FEED_WINDOW}}?\\blayout\\.width\\b`,
|
|
1052
|
+
);
|
|
1053
|
+
if (fedByLayout.test(code)) names.push(value);
|
|
1054
|
+
}
|
|
1055
|
+
return names;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function _sizesFromMeasuredWidth(code, names) {
|
|
1059
|
+
return names.some((name) =>
|
|
1060
|
+
new RegExp(`\\b${name}\\s*[-/*]|[-/*]\\s*\\b${name}\\b`).test(code),
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function _measuredPaddingRules(source) {
|
|
1065
|
+
// Comments are blanked so this rule's own documented anti-pattern, and a
|
|
1066
|
+
// commented-out draft, never fire.
|
|
1067
|
+
const code = _stripNonCode(source, { keepStrings: true });
|
|
1068
|
+
const measured = _measuredWidthNames(code);
|
|
1069
|
+
if (!_sizesFromMeasuredWidth(code, measured)) return [];
|
|
1070
|
+
const sourceLines = source.split(/\r?\n/);
|
|
1071
|
+
const findings = [];
|
|
1072
|
+
_JSX_TAG_RE.lastIndex = 0;
|
|
1073
|
+
let tag;
|
|
1074
|
+
while ((tag = _JSX_TAG_RE.exec(code))) {
|
|
1075
|
+
const end = _jsxOpenTagEnd(code, tag.index + tag[0].length);
|
|
1076
|
+
const attrs = code.slice(tag.index, end);
|
|
1077
|
+
_JSX_TAG_RE.lastIndex = end;
|
|
1078
|
+
if (!_ON_LAYOUT_ATTR_RE.test(attrs)) continue;
|
|
1079
|
+
const padded = _OWN_PADDING_RE.exec(attrs);
|
|
1080
|
+
if (!padded) continue;
|
|
1081
|
+
// Report the padding, not the tag: on a multi-line open tag the style line
|
|
1082
|
+
// is the actionable one.
|
|
1083
|
+
const line = code
|
|
1084
|
+
.slice(0, tag.index + padded.index)
|
|
1085
|
+
.split(/\r?\n/).length;
|
|
1086
|
+
findings.push({
|
|
1087
|
+
rule: "measured-width-ignores-padding",
|
|
1088
|
+
severity: "warning",
|
|
1089
|
+
label:
|
|
1090
|
+
`<${tag[1]}> has onLayout AND its own padding: the measured width ` +
|
|
1091
|
+
`INCLUDES that padding, so cells sized from it overflow and the last ` +
|
|
1092
|
+
`wraps, leaving an empty column. Measure an unpadded inner View.`,
|
|
1093
|
+
line,
|
|
1094
|
+
snippet: (sourceLines[line - 1] || "").trim().slice(0, 200),
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
return findings;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1016
1100
|
/**
|
|
1017
1101
|
* Narrow a split-impl widget's manifest to the platform a single bundle file
|
|
1018
1102
|
* ships to, so `import-platform-mismatch` lints each file against what it
|
|
@@ -1083,6 +1167,9 @@ export function lintSource(source, options) {
|
|
|
1083
1167
|
findings.push(..._reactInScopeRules(source));
|
|
1084
1168
|
// sc-3493 — soft warning: percentage height on an <Image> collapses to 0.
|
|
1085
1169
|
findings.push(..._imagePercentHeightRules(source));
|
|
1170
|
+
// sc-4913 — soft warning: a measured width that includes the widget's own
|
|
1171
|
+
// padding wraps the last grid column into an empty one.
|
|
1172
|
+
findings.push(..._measuredPaddingRules(source));
|
|
1086
1173
|
// sc-4650 — soft warning: every payment refusal reported as "try again".
|
|
1087
1174
|
findings.push(..._paymentCurrencyRules(source));
|
|
1088
1175
|
findings.push(..._hardcodedCurrencyLabelRules(source));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.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",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
],
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "node scripts/build.js",
|
|
51
|
-
"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-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-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-image-height.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__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/theme-components-parity.test.js src/__tests__/theme-depth-tokens.test.js"
|
|
51
|
+
"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-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-image-height.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__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/theme-components-parity.test.js src/__tests__/theme-depth-tokens.test.js"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=18"
|