@colixsystems/widget-sdk 0.85.0 → 0.85.1

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
@@ -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`. |
@@ -63,6 +63,10 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
63
63
 
64
64
  `v0.77.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
65
 
66
+ ### What's new in 0.85.1 (contract 1.60.1)
67
+
68
+ **`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.
69
+
66
70
  ### What's new in 0.85.0 (contract 1.60.0)
67
71
 
68
72
  **A widget must never name a currency — `useWorkspaceCurrency()` resolves it at render time (sc-4686).** A workspace picks the currency it charges its app users in, and the owner usually sets that *after* the app is built (the normal order is prompt first, billing later). So anything a widget wrote down — a `"kr"` in JSX, a `€` in a `manifest.translations` string, a `currency` argument on `requestPayment` — kept displaying the old currency over a charge that had correctly followed the change: one price shown, another taken. Currency is workspace configuration that changes after authoring, exactly like `theme` and `locale`, so it now joins them on the host-resolved `ctx.workspace` slice. `useWorkspaceCurrency()` returns `{ currency, formatMoney }`; `formatMoney(45000)` renders `"450,00 kr"` or `"450,00 €"` from `CONTRACT.currencyFormats` — an explicit table, not `Intl.NumberFormat`, which does not agree between the exported Expo app and react-native-web. Omit `currency` on `requestPayment` and the platform applies the workspace's own, so it can never be wrong. Enforcement tightened to match: `payment-currency` now rejects **any** currency literal (one that matches today still lies tomorrow) and so needs no per-workspace option — it fires in a bare `appstudio-widget lint`, and `lintSource`'s `paymentCurrency` option is removed; a new `no-hardcoded-currency-label` warning catches a symbol or code beside a price in a charging widget. `CONTRACT.version` → `1.60.0`. Additive for a widget that already omits `currency`.
package/dist/contract.cjs CHANGED
@@ -782,7 +782,16 @@ const HOOKS = [
782
782
  {
783
783
  name: "useWidgetEvent",
784
784
  signature: "useWidgetEvent(eventName)",
785
- returnShape: { emit: "(payload?) => void" },
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
- version: "1.60.0",
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
- returnShape: { emit: "(payload?) => void" },
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
- version: "1.60.0",
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.85.0",
3
+ "version": "0.85.1",
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-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"