@colixsystems/widget-sdk 0.113.0 → 0.114.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
@@ -17,7 +17,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
17
17
 
18
18
  | Group | Hook (signature) | Returns | Reads / scope |
19
19
  | ----- | ---------------- | ------- | ------------- |
20
- | **CORE** | `useTheme()` | `{ colors, elevation, spacing, spacingScale, radii, typography, components, widgetStyles }` | `ctx.workspace.theme` — no scope. `elevation` is the shared depth scale (`none / sm / md / lg / xl`) you spread into a style; `colors` includes the accent's quiet tiers (`primarySoft` / `onPrimarySoft` / `primaryStrong`) and `loader`, the spinner colour for your own loading state. `components` is HOST-OWNED (the theme's per-component style tokens); the host has already folded it into your `props.style`, so read `useWidgetStyle()` and ignore this slice. |
20
+ | **CORE** | `useTheme()` | `{ colors, elevation, interaction, spacing, spacingScale, radii, typography, components, widgetStyles }` | `ctx.workspace.theme` — no scope. `elevation` is the shared depth scale (`none / sm / md / lg / xl`) you spread into a style; `colors` includes the accent's quiet tiers (`primarySoft` / `onPrimarySoft` / `primaryStrong`) and `loader`, the spinner colour for your own loading state. `components` is HOST-OWNED (the theme's per-component style tokens); the host has already folded it into your `props.style`, so read `useWidgetStyle()` and ignore this slice. |
21
21
  | **CORE** | `useWorkspaceCurrency()` | `{ currency, formatMoney }` | `ctx.workspace.currency` — no scope. The currency this workspace charges its app users in, resolved at RENDER time. Render every price as `formatMoney(minorUnits)` and never write a currency symbol or code into a widget: the owner can change it after the widget ships, and a baked label then contradicts the charge. |
22
22
  | **CORE** | `useWidgetStyle()` | `{ [styleField]: value }` | `ctx.props.style` — no scope. The author-set per-widget style values declared in `manifest.styleSchema`; apply each onto whatever element you choose. |
23
23
  | **CORE** | `useUser()` | `{ id, email, displayName, roles, groupIds }` | `ctx.user` (host-built context, **camelCase** — not a wire payload; `id` null when anonymous) — no scope |
@@ -1094,15 +1094,16 @@ A widget that works but looks unfinished is only half done. `useTheme()` is the
1094
1094
  - **Contain and elevate.** Wrap a logical unit in a surface: `colors.surface` + padding + `radii.lg` + `...theme.elevation.sm`. Give it the elevation **or** a `colors.border` hairline, not both — and prefer the elevation, because a hairline-only card reads as a wireframe. `theme.elevation` is a token table you spread into a style (`...theme.elevation.md`), covering `none / sm / md / lg / xl`; never hand-write `shadowOpacity` / `shadowRadius` / `boxShadow`. Use the status roles (`danger / success / warning / info`) for state.
1095
1095
  - **Tint the supporting cast.** `colors.primarySoft` is a tint of the workspace accent over the surface and `colors.onPrimarySoft` is guaranteed readable on it (WCAG AA, on light and dark themes alike). Use the pair for chips, secondary buttons, progress tracks, icon badges and selected rows. One saturated accent moment surrounded by several pale echoes of the same hue is what reads as designed — a row of grey-outlined buttons reads as a form. Never hand-mix a tint with `rgba(...)` or a translucent overlay.
1096
1096
  - **Spend one gradient.** `<Gradient colors={[theme.colors.primary, theme.colors.primaryStrong]} angle={160} style={…}>` is a `View` that paints a gradient behind its children, so it replaces the `View` you'd otherwise give a flat `backgroundColor`. `angle` is CSS degrees (0 = to top, 90 = to right, default 180); text on it uses `colors.onPrimary`. Exactly **one** per widget — on the focal element — and never behind body text. Both hosts render it identically (web paints CSS, native uses `expo-linear-gradient`), so there is no per-platform branching to write; don't import `expo-linear-gradient` yourself and don't write a `backgroundImage` string.
1097
+ - **Answer the touch.** Every tappable card, row and list entry lifts while the pointer is over it (web) or it is pressed (touch). One declaration does both: give the Pressable a style FUNCTION and spread `pressableLift` — `<Pressable onPress={open} style={(state) => [styles.card, ...pressableLift(state)]}>`. The lift is a -2px nudge plus one elevation step from `theme.interaction`, with the web transition built in. Never hand-write hover logic or your own pressed shadows, and never fake feedback with `opacity` — a dimmed surface reads as disabling itself.
1097
1098
  - **Size to your container — measure it, don't stretch into it.** The same widget sits in a full-width desktop section (~1400px), a half-width grid cell (~700px) and a phone (~360px), so layout built only from `flex: 1` stretches to fill whatever it is handed — a month calendar ends up with 200px day cells and swallows the page. Measure your own width with `onLayout={(e) => setWidth(e.nativeEvent.layout.width)}` on the root `View` (a React Native primitive, so it behaves identically on both hosts), render nothing size-dependent while `width === 0`, and compute every threshold from the measured value: a widget gets no declared breakpoint prop, but it can always measure. **Cap a repeating cell** rather than giving a grid `flex: 1` — `const cell = Math.max(32, Math.min(Math.floor((usable - gap * (columns - 1)) / columns), 64));`, with a calendar day cell topping out at 56–72px on `aspectRatio: 1`, and the grid given its exact computed width plus `alignSelf: 'center'` when the cap leaves slack. **Split two co-equal surfaces above ~720px measured width** (`flexDirection: width >= 720 ? 'row' : 'column'`, each half `{ flex: 1, minWidth: 0 }`) — a picker beside the form it feeds on a wide canvas, stacked in reading order below it. Never hardcode a width, never put `flex: 1` / `height: '100%'` on a content widget's root, and don't read the screen with `Dimensions` — the screen is not the widget.
1098
1099
  - **Compose forms — pair fields into rows, don't stack one per row.** Put short, related fields side by side (first + last name, city + postal code, expiry + CVC): a row of `{ flexDirection: 'row', flexWrap: 'wrap', gap: theme.spacing.md }` with each field cell `{ flexGrow: 1, flexBasis: 160 }` splits the width on a wide card and wraps to stacked on a narrow phone — the right recipe for field pairs because it needs no measurement (a fixed-width column overflows a phone; when a layout needs a real column count instead of wrapping, measure your width as above). Keep wide fields (email, address, notes) full-width, cap it at two–three per row, group a long form into labelled sections, and label every input above it (not placeholder-only). Give a `multiline` field BOTH a floor and a ceiling (`{ minHeight: 150, maxHeight: 260 }`) so a long value scrolls inside the box instead of growing past its card, and keep the Save / Cancel row in normal flow below the fields, never positioned over them.
1099
- - **Respond to touch.** Give every `Pressable` a pressed state via the function-style `style={({ pressed }) => [base, pressed && { opacity: 0.7 }]}`.
1100
+ - **Respond to touch.** Give every `Pressable` the lift via the function-style `style={(state) => [base, ...pressableLift(state)]}` see "Answer the touch" above. Never dim with `opacity`, which reads as the surface disabling itself.
1100
1101
  - **Drag and drop — show what is being dragged.** A drag where the item stays put reads as broken. Three things change the moment a drag starts: the **drag proxy** (the item lifts and follows the finger — `...theme.elevation.lg`, `{ scale: 1.03 }`, `opacity: 0.9`; for a tall or full-width item drag a compact `primarySoft` pill with its icon + one line of label instead), the **source placeholder** (the vacated slot keeps its height as a quiet `colors.surfaceMuted` block so the list doesn't collapse), and the **drop target** (one slot at a time highlighted with `primarySoft` or a 2px `colors.primary` border). Always animate the release — settle into the new slot, or `Animated.spring(pan, { toValue: { x: 0, y: 0 }, useNativeDriver: false })` back to the origin on cancel. Build it with `Animated` + `PanResponder` from `react-native` (the only mechanism that behaves identically on both hosts) — never HTML5 drag events (`draggable` / `onDragStart` / `dataTransfer` are web-only, and `document` / `window` are banned) — and start the drag from a `GripVertical` grip handle whenever the row is also tappable or sits in a `ScrollView`.
1101
1102
  - **Use icons for clarity.** Pair a `lucide-react-native` icon with its label at a consistent size, coloured from the theme. The label never repeats the icon as a character — with a `Plus` icon the button says "Add item", never "+ Add item" (that renders a doubled plus).
1102
1103
  - **Use imagery deliberately.** Render pictures with the `Image` primitive (`source` takes a URL or `{ uri }`); resolve workspace assets via `useAsset()`. Give every image a sized, `radii`-clipped container so it never renders as a raw rectangle, and never hardcode a credentialed image URL — expose an `image`-type property instead. The frame is your decision, never the picture's: size it for the role (a 40–56 square avatar, a 72–96 square row thumbnail, a `16 / 9` card cover, a 160–240 tall band) and let `resizeMode="cover"` crop the photo into it — photos arrive at every size and ratio, so one left to its own proportions breaks the layout. Keep `contain` for art whose whole subject must stay visible (a logo, a diagram), inside a fixed frame.
1103
1104
  - **Design the empty, loading, and error states.** A blank box on a fresh install reads as broken — show a short helper line when a list is empty, a calm loading line, and a single human sentence in `colors.danger` on error.
1104
1105
 
1105
- **Honest ceilings:** the styling surface is React Native style objects, not full CSS. Gradients come from the `<Gradient>` primitive (not a CSS `linear-gradient` string), depth comes from `theme.elevation` (not arbitrary `box-shadow` stacks), and there are no custom CSS keyframe animations or `transition` strings, no `filter` / `backdrop-filter` / `clip-path` / `mask` / blend modes, and no opacity-faked tints (that's what `primarySoft` is for). Aim for clean, confident, professional polish within those bounds — lifted surfaces, generous corners, one accent moment.
1106
+ **Honest ceilings:** the styling surface is React Native style objects, not full CSS. Gradients come from the `<Gradient>` primitive (not a CSS `linear-gradient` string), depth comes from `theme.elevation` (not arbitrary `box-shadow` stacks), and there are no custom CSS keyframe animations or `transition` strings (the hover/press lift comes built into `pressableLift` — never write your own), no `filter` / `backdrop-filter` / `clip-path` / `mask` / blend modes, and no opacity-faked tints (that's what `primarySoft` is for). Aim for clean, confident, professional polish within those bounds — lifted surfaces, generous corners, one accent moment.
1106
1107
 
1107
1108
  ## Managing app users from a widget
1108
1109
 
package/dist/contract.cjs CHANGED
@@ -45,6 +45,24 @@ const ELEVATION = Object.freeze({
45
45
  }),
46
46
  });
47
47
 
48
+ // sc-6531 (REQ-AI-AGENT-DESIGN-LIFT): the ONE cross-platform interaction
49
+ // vocabulary. A tappable surface answers the pointer over it (web) and the
50
+ // press on it (touch) by LIFTING. `raised` is one elevation step up plus a
51
+ // nudge — the scale above stays the only shadow vocabulary — and is what a
52
+ // widget's own surface wears via `pressableLift`; `translateY` alone is the
53
+ // surface-agnostic nudge a tappable layout REGION gets on both hosts.
54
+ const INTERACTION = Object.freeze({
55
+ lift: Object.freeze({
56
+ translateY: -2,
57
+ raised: Object.freeze({
58
+ transform: Object.freeze([Object.freeze({ translateY: -2 })]),
59
+ ...ELEVATION.lg,
60
+ }),
61
+ // Web-only smoothing; a touch state change is instant.
62
+ transitionMs: 150,
63
+ }),
64
+ });
65
+
48
66
  const DEFAULT_THEME_TOKENS = Object.freeze({
49
67
  colors: Object.freeze({
50
68
  primary: "#3b82f6",
@@ -76,6 +94,7 @@ const DEFAULT_THEME_TOKENS = Object.freeze({
76
94
  info: "#0284c7",
77
95
  }),
78
96
  elevation: ELEVATION,
97
+ interaction: INTERACTION,
79
98
  spacing: Object.freeze({ xs: 4, sm: 8, md: 16, lg: 24, xl: 32 }),
80
99
  // REQ-THEME-LOOK: multiplies every layout spacing value at render. 1 is
81
100
  // unchanged, so a theme that never sets it renders exactly as before.
@@ -1740,6 +1759,17 @@ const PRIMITIVES = [
1740
1759
  rnComponent: "expo-linear-gradient",
1741
1760
  docsUrl: "https://docs.expo.dev/versions/latest/sdk/linear-gradient/",
1742
1761
  },
1762
+ // sc-6531 (REQ-AI-AGENT-DESIGN-LIFT) — the hover/press lift affordance. A
1763
+ // FUNCTION primitive, not a component: it resolves the interaction tokens per
1764
+ // input, so one declaration answers the pointer on web and the press on
1765
+ // native.
1766
+ {
1767
+ name: "pressableLift",
1768
+ description:
1769
+ "Interaction feedback for a tappable surface. Call it inside a Pressable's style FUNCTION and spread the result: `<Pressable style={(state) => [styles.card, ...pressableLift(state)]}>`. While the pointer hovers the surface (web) or it is pressed (touch), the surface lifts: a -2px nudge plus one elevation step, from `themeTokens.interaction`, with the web transition built in. Give it to EVERY tappable card, row and list entry; never hand-write hover logic, `transition` strings, or your own pressed shadows.",
1770
+ rnComponent: null,
1771
+ docsUrl: null,
1772
+ },
1743
1773
  ];
1744
1774
 
1745
1775
  const CATEGORIES = [
@@ -3433,7 +3463,12 @@ const CONTRACT = deepFreeze({
3433
3463
  // docs/design/req-widget-sdk-cross-platform-primitives.md, which was
3434
3464
  // about vetting a full camera surface (permissions, multi-step UX, frame
3435
3465
  // processing) as a widget import — none of which this adds.
3436
- version: "1.86.0",
3466
+ // 1.87.0: additive (sc-6531) — `themeTokens.interaction`: the interaction-
3467
+ // state vocabulary (`lift.raised`, `lift.translateY`, `lift.transitionMs`),
3468
+ // plus the `pressableLift` primitive that resolves it per input — hover on
3469
+ // web, press on native. Tappable layout containers and widget surfaces lift
3470
+ // from this ONE table.
3471
+ version: "1.87.0",
3437
3472
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3438
3473
  hooks: HOOKS,
3439
3474
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -45,6 +45,24 @@ const ELEVATION = Object.freeze({
45
45
  }),
46
46
  });
47
47
 
48
+ // sc-6531 (REQ-AI-AGENT-DESIGN-LIFT): the ONE cross-platform interaction
49
+ // vocabulary. A tappable surface answers the pointer over it (web) and the
50
+ // press on it (touch) by LIFTING. `raised` is one elevation step up plus a
51
+ // nudge — the scale above stays the only shadow vocabulary — and is what a
52
+ // widget's own surface wears via `pressableLift`; `translateY` alone is the
53
+ // surface-agnostic nudge a tappable layout REGION gets on both hosts.
54
+ const INTERACTION = Object.freeze({
55
+ lift: Object.freeze({
56
+ translateY: -2,
57
+ raised: Object.freeze({
58
+ transform: Object.freeze([Object.freeze({ translateY: -2 })]),
59
+ ...ELEVATION.lg,
60
+ }),
61
+ // Web-only smoothing; a touch state change is instant.
62
+ transitionMs: 150,
63
+ }),
64
+ });
65
+
48
66
  const DEFAULT_THEME_TOKENS = Object.freeze({
49
67
  colors: Object.freeze({
50
68
  primary: "#3b82f6",
@@ -76,6 +94,7 @@ const DEFAULT_THEME_TOKENS = Object.freeze({
76
94
  info: "#0284c7",
77
95
  }),
78
96
  elevation: ELEVATION,
97
+ interaction: INTERACTION,
79
98
  spacing: Object.freeze({ xs: 4, sm: 8, md: 16, lg: 24, xl: 32 }),
80
99
  // REQ-THEME-LOOK: multiplies every layout spacing value at render. 1 is
81
100
  // unchanged, so a theme that never sets it renders exactly as before.
@@ -1740,6 +1759,17 @@ const PRIMITIVES = [
1740
1759
  rnComponent: "expo-linear-gradient",
1741
1760
  docsUrl: "https://docs.expo.dev/versions/latest/sdk/linear-gradient/",
1742
1761
  },
1762
+ // sc-6531 (REQ-AI-AGENT-DESIGN-LIFT) — the hover/press lift affordance. A
1763
+ // FUNCTION primitive, not a component: it resolves the interaction tokens per
1764
+ // input, so one declaration answers the pointer on web and the press on
1765
+ // native.
1766
+ {
1767
+ name: "pressableLift",
1768
+ description:
1769
+ "Interaction feedback for a tappable surface. Call it inside a Pressable's style FUNCTION and spread the result: `<Pressable style={(state) => [styles.card, ...pressableLift(state)]}>`. While the pointer hovers the surface (web) or it is pressed (touch), the surface lifts: a -2px nudge plus one elevation step, from `themeTokens.interaction`, with the web transition built in. Give it to EVERY tappable card, row and list entry; never hand-write hover logic, `transition` strings, or your own pressed shadows.",
1770
+ rnComponent: null,
1771
+ docsUrl: null,
1772
+ },
1743
1773
  ];
1744
1774
 
1745
1775
  const CATEGORIES = [
@@ -3433,7 +3463,12 @@ const CONTRACT = deepFreeze({
3433
3463
  // docs/design/req-widget-sdk-cross-platform-primitives.md, which was
3434
3464
  // about vetting a full camera surface (permissions, multi-step UX, frame
3435
3465
  // processing) as a widget import — none of which this adds.
3436
- version: "1.86.0",
3466
+ // 1.87.0: additive (sc-6531) — `themeTokens.interaction`: the interaction-
3467
+ // state vocabulary (`lift.raised`, `lift.translateY`, `lift.transitionMs`),
3468
+ // plus the `pressableLift` primitive that resolves it per input — hover on
3469
+ // web, press on native. Tappable layout containers and widget surfaces lift
3470
+ // from this ONE table.
3471
+ version: "1.87.0",
3437
3472
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3438
3473
  hooks: HOOKS,
3439
3474
  primitives: PRIMITIVES,
package/dist/index.d.ts CHANGED
@@ -2312,3 +2312,15 @@ export const CONTRACT: AiWidgetContract;
2312
2312
 
2313
2313
  export function isHookAllowed(name: string): boolean;
2314
2314
  export function requiredContextKeys(): string[];
2315
+
2316
+ /**
2317
+ * sc-6531 (REQ-AI-AGENT-DESIGN-LIFT) — interaction feedback for a tappable
2318
+ * surface. Call inside a Pressable's style function and spread the result:
2319
+ * `<Pressable style={(state) => [styles.card, ...pressableLift(state)]}>`.
2320
+ * Web raises the surface while hovered or pressed (with the transition built
2321
+ * in); native raises it while pressed.
2322
+ */
2323
+ export function pressableLift(state?: {
2324
+ hovered?: boolean;
2325
+ pressed?: boolean;
2326
+ }): Array<Record<string, unknown> | null>;
package/dist/index.js CHANGED
@@ -129,3 +129,4 @@ export {
129
129
  scaleSpacing,
130
130
  } from "./contract.js";
131
131
  export { normalizeLucideIconName } from "./lucideIconName.js";
132
+ export { pressableLift } from "./interaction.js";
@@ -127,3 +127,4 @@ export {
127
127
  scaleSpacing,
128
128
  } from "./contract.js";
129
129
  export { normalizeLucideIconName } from "./lucideIconName.js";
130
+ export { pressableLift } from "./interaction.native.js";
@@ -0,0 +1,16 @@
1
+ // sc-6531 (REQ-AI-AGENT-DESIGN-LIFT) — the web half of the lift affordance.
2
+ // react-native-web's Pressable hands its style function `{ hovered, pressed }`;
3
+ // either one raises the surface, and the smoothing rides the RESTING style so
4
+ // the lift animates in both directions. The touch half is interaction.native.js
5
+ // — no hover exists there, and a pressed state change is instant.
6
+ import { CONTRACT } from "./contract.js";
7
+
8
+ const LIFT = CONTRACT.themeTokens.interaction.lift;
9
+ const SMOOTHING = Object.freeze({
10
+ transitionProperty: "transform, box-shadow",
11
+ transitionDuration: `${LIFT.transitionMs}ms`,
12
+ });
13
+
14
+ export function pressableLift(state = {}) {
15
+ return [SMOOTHING, state.hovered || state.pressed ? LIFT.raised : null];
16
+ }
@@ -0,0 +1,10 @@
1
+ // sc-6531 (REQ-AI-AGENT-DESIGN-LIFT) — the touch half of the lift affordance:
2
+ // there is no hover, the press state raises the surface, and the change is
3
+ // instant (RN styles carry no transitions). The pointer half is interaction.js.
4
+ import { CONTRACT } from "./contract.js";
5
+
6
+ const LIFT = CONTRACT.themeTokens.interaction.lift;
7
+
8
+ export function pressableLift(state = {}) {
9
+ return [state.pressed ? LIFT.raised : null];
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.113.0",
3
+ "version": "0.114.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__/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__/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__/toast-host.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"
51
+ "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__/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__/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"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=18"