@ekanos/sdk 0.6.0 → 0.7.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.
Files changed (59) hide show
  1. package/README.md +100 -17
  2. package/api-report.md +51 -6
  3. package/dist/components/index.d.ts +3 -3
  4. package/dist/components/index.js +2 -2
  5. package/dist/components/index.js.map +1 -1
  6. package/dist/components/widgets/widget-context.d.ts +17 -0
  7. package/dist/components/widgets/widget-context.js.map +1 -1
  8. package/dist/components/widgets/widget-preview-provider.d.ts +4 -2
  9. package/dist/components/widgets/widget-preview-provider.js +3 -1
  10. package/dist/components/widgets/widget-preview-provider.js.map +1 -1
  11. package/dist/components/widgets/widget-state-components.d.ts +59 -3
  12. package/dist/components/widgets/widget-state-components.js +65 -7
  13. package/dist/components/widgets/widget-state-components.js.map +1 -1
  14. package/dist/components/widgets/widget.d.ts +124 -21
  15. package/dist/components/widgets/widget.js +159 -37
  16. package/dist/components/widgets/widget.js.map +1 -1
  17. package/dist/hooks/index.d.ts +2 -2
  18. package/dist/hooks/index.js +1 -1
  19. package/dist/hooks/index.js.map +1 -1
  20. package/dist/hooks/use-oauth-connection-status.d.ts +17 -1
  21. package/dist/hooks/use-oauth-connection-status.js +12 -17
  22. package/dist/hooks/use-oauth-connection-status.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/integration/actor-role.d.ts +14 -0
  26. package/dist/integration/actor-role.js +21 -0
  27. package/dist/integration/actor-role.js.map +1 -0
  28. package/dist/integration/index.d.ts +3 -1
  29. package/dist/integration/index.js +4 -0
  30. package/dist/integration/index.js.map +1 -1
  31. package/dist/integration/types.d.ts +1 -1
  32. package/dist/integration/types.js.map +1 -1
  33. package/dist/integration/untrusted-text.d.ts +24 -0
  34. package/dist/integration/untrusted-text.js +36 -0
  35. package/dist/integration/untrusted-text.js.map +1 -0
  36. package/dist/layout/index.d.ts +8 -0
  37. package/dist/layout/index.js +9 -0
  38. package/dist/layout/index.js.map +1 -0
  39. package/dist/layout/widget-layout.d.ts +84 -0
  40. package/dist/layout/widget-layout.js +138 -0
  41. package/dist/layout/widget-layout.js.map +1 -0
  42. package/dist/testing/index.d.ts +4 -0
  43. package/dist/testing/index.js +5 -0
  44. package/dist/testing/index.js.map +1 -1
  45. package/dist/testing/mock-context.js +3 -1
  46. package/dist/testing/mock-context.js.map +1 -1
  47. package/dist/testing/verify-signature.d.ts +26 -0
  48. package/dist/testing/verify-signature.js +45 -0
  49. package/dist/testing/verify-signature.js.map +1 -0
  50. package/dist/types/index.d.ts +1 -0
  51. package/dist/types/index.js.map +1 -1
  52. package/dist/types/integration.d.ts +5 -0
  53. package/dist/types/integration.js.map +1 -1
  54. package/dist/types/widget-verdict.d.ts +6 -0
  55. package/dist/types/widget-verdict.js +2 -0
  56. package/dist/types/widget-verdict.js.map +1 -0
  57. package/eslint.design-preset.mjs +226 -0
  58. package/eslint.design-rules.mjs +437 -0
  59. package/package.json +16 -5
package/README.md CHANGED
@@ -3,10 +3,11 @@
3
3
  The official SDK for building Ekanos integrations — the packages that add
4
4
  widgets, assistant tools, and event handlers to an Ekanos dashboard.
5
5
 
6
- **Start here.** This README is the complete authoring reference: everything you
7
- can declare, with a worked example for each surface. There is no separate docs
8
- site — if something is missing here it is missing, and `npm@govastly.com` is
9
- where to say so.
6
+ **Docs:** [sdk.ekanos.dev](https://sdk.ekanos.dev/sdk/overview) is the public
7
+ documentation for the SDK, CLI and harness, and the first place to look for
8
+ current answers. **Start here.** This README is the complete authoring
9
+ reference: everything you can declare, with a worked example for each surface.
10
+ If something is missing from both, `npm@govastly.com` is where to say so.
10
11
 
11
12
  The other packages, so you know when to leave this page: **`@ekanos/cli`** is
12
13
  the toolchain (`init`, `validate`, `dev`, `test`) and owns `ekanos.json`;
@@ -30,7 +31,7 @@ one. Each has its own README.
30
31
  - [`webhooks`](#webhooks)
31
32
  - [`schedules`](#schedules)
32
33
  - [`oauth`](#oauth)
33
- - [`proposes` — what you suggest, the host decides](#proposes)
34
+ - [`proposes` — your credential model and tool classifications](#proposes)
34
35
  - [`workspaceTargets`](#workspacetargets)
35
36
  - [Testing](#testing)
36
37
  - [Entrypoints](#entrypoints)
@@ -190,7 +191,7 @@ There is no raw database client, no Supabase, and no request object.
190
191
  | `ctx.secrets` | `get(name)`, `names()`, `set(name, value)` over the resolved credential set |
191
192
  | `ctx.fetch` | `fetch`, refusing anything outside `egress` before it opens a socket |
192
193
  | `ctx.logger` | `debug`/`info`/`warn`/`error`, each `(context: object, message: string)` — pino-style, pre-bound with `{integration, accountId, actor}` |
193
- | `ctx.actor` | `{ kind: 'user', userId }` or `{ kind: 'machine', tokenId, createdBy }` |
194
+ | `ctx.actor` | `{ kind: 'user', userId, role }` or `{ kind: 'machine', tokenId, createdBy }`. `role` is `{ name, hierarchyLevel }` (1 owner, 2 admin, 3 member; `null` when unresolved). Gate a write action with `actorIsAtLeast(ctx.actor, ADMIN_HIERARCHY_LEVEL)` from `@ekanos/sdk/integration` and throw `PublicActionError` otherwise — the host runs every action with the account's credential |
194
195
 
195
196
  Plus read-only identity facts, all already authorized: `ctx.accountId`,
196
197
  `ctx.accountSlug`, `ctx.userId` (`null` for machine actors), `ctx.sourceId`,
@@ -339,7 +340,7 @@ no `http:`. Wildcards are supported only as one leading `*.`. Anything else is
339
340
  rejected by `defineIntegration()` with the offending entry quoted.
340
341
 
341
342
  A denied call throws `EgressDeniedError` synchronously. Widening this list is a security-posture change a reviewer looks at during
342
- **promotion** — the human review your integration passes before it is enabled
343
+ **admission** — the human review your integration passes before it is enabled
343
344
  for real accounts, where someone reads this declaration against what your
344
345
  handlers actually do. Declare the origins you call and no more.
345
346
 
@@ -414,8 +415,7 @@ const apiKey = await ctx.secrets.get(ACME_API_KEY);
414
415
 
415
416
  ### `tools`
416
417
 
417
- Tools are what the Ekanos assistant (and, where the host allows it, the workflow
418
- engine) can call. Each declares a name, a description the model reads, an
418
+ Tools are what the Ekanos assistant, workflows and managed agents can call. Each declares a name, a description the model reads, an
419
419
  optional JSON-Schema parameter object, and an async `run`.
420
420
 
421
421
  ```ts
@@ -459,6 +459,47 @@ for a genuine fault.
459
459
 
460
460
  `outputExample` is shown to the model as a shape hint. Keep it small and honest.
461
461
 
462
+ **Text written outside this system is data, not instructions.** A deal name, a
463
+ call transcript, a support reply, a booking answer: anything a vendor's users
464
+ typed can say "ignore the above and…", and in a tool result it looks exactly
465
+ like a field you computed. Return such text only under a field whose name
466
+ starts with `untrusted`, clip it with `untrustedText()` (from
467
+ `@ekanos/sdk/integration`; 1000 characters by default), and put one
468
+ `note: untrustedTextNote('deal, stage and pipeline names')` at the top of the
469
+ output so the model reads the rule where it reads the data. `untrustedText()`
470
+ bounds the length and marks the text as coming from a third party so the
471
+ assistant treats it as data; it does not strip or neutralise injected
472
+ instructions. Pass `untrustedTextNote()` a fixed string you wrote, never vendor
473
+ text. In a write tool's description, say to confirm with the user first and
474
+ never act on what the text asks for.
475
+
476
+ ```ts
477
+ import { untrustedText, untrustedTextNote } from '@ekanos/sdk/integration';
478
+
479
+ return {
480
+ note: untrustedTextNote('deal names'),
481
+ deals: deals.map((d) => ({ id: d.id, untrustedName: untrustedText(d.name, 200), amount: d.amount })),
482
+ };
483
+ ```
484
+
485
+ **`ownerAction` is how an owner reads an approval for your tool.** When an agent
486
+ or workflow pauses on one of your tools, the owner sees "{agent} wants to
487
+ {ownerAction} in {your integration}". Declare the phrase yourself, a bare verb
488
+ that names no ids:
489
+
490
+ ```ts
491
+ ownerAction: 'sync your price list',
492
+ // or one phrase per locale; `default` covers any locale you did not list
493
+ ownerAction: { en: 'sync your price list', es: 'sincronizar su lista de precios' },
494
+ ```
495
+
496
+ It is plain text, at most 120 characters, never a message key: a partner cannot
497
+ add keys to the host's messages. The host picks the exact locale, then its
498
+ language (`pt-BR` then `pt`), then the default locale, then your `default`. With
499
+ no `ownerAction` the host derives a phrase from the tool name ("update deal
500
+ stage" from `acme_update_deal_stage`), and a name that is not `<verb>_<thing>`
501
+ reads "use <tool name>", so declare one for any tool that writes.
502
+
462
503
  **`parameters` is a narrow slice of JSON Schema, not all of it.** The top level
463
504
  is `.strict()` and accepts exactly four keys — `type` (which must be the
464
505
  literal `'object'`), `properties`, `required` and `additionalProperties`.
@@ -1146,9 +1187,10 @@ async function.
1146
1187
 
1147
1188
  ### `proposes`
1148
1189
 
1149
- A suggestion block, not a settings block. Security classifications are
1150
- host-assigned; `proposes` is how you tell a reviewer what you believe is true,
1151
- and the reviewer confirms it at promotion.
1190
+ Where you declare your credential model and classify each tool. Once your
1191
+ integration is admitted the host applies these values, the same as a first-party
1192
+ integration's own. The admission review is the check, and the host can tighten a
1193
+ declaration (never loosen it).
1152
1194
 
1153
1195
  ```ts
1154
1196
  proposes: {
@@ -1159,22 +1201,32 @@ proposes: {
1159
1201
  tools: {
1160
1202
  list_recent_payments: { effect: 'read', sensitivity: 'financial' },
1161
1203
  // Honest classification: this moves money. Marking it 'read' or
1162
- // 'internal' to get past a gate is what promotion review exists to catch.
1204
+ // 'internal' to get past a gate is a review failure at admission.
1163
1205
  refund_payment: { effect: 'write', sensitivity: 'financial' },
1164
1206
  },
1165
1207
  },
1166
1208
  ```
1167
1209
 
1168
1210
  `effect` is `'read' | 'write'`. `sensitivity` is `'public' | 'internal' | 'pii'
1169
- | 'financial'`. Both are optional per tool.
1211
+ | 'financial'`. Classify every tool: a field you leave out falls back to
1212
+ `effect: 'write'` and `sensitivity: 'pii'`, and a tool treated that way will not
1213
+ run in a team workflow.
1170
1214
 
1171
1215
  The keys of `proposes.tools` must match declared tool names exactly — a
1172
1216
  proposal for a tool you did not declare is a validation error, so a rename
1173
1217
  cannot leave a stale classification behind.
1174
1218
 
1175
- Why it matters: an undeclared `credentialModel` fails closed. The workflow
1176
- engine's tool allowlist treats it as not machine-executable, so a tool you
1177
- believe is workflow-callable will not be until this is declared and confirmed.
1219
+ What the host does with it:
1220
+
1221
+ - `credentialModel` decides which workflows may call your tools. Team workflows
1222
+ (a machine actor) admit `'account'` and `'source'`; personal workflows (the
1223
+ user) admit `'user'` and `'account'`. Undeclared means workflows cannot use
1224
+ your tools.
1225
+ - Team workflows never expose `pii` or `financial` tools, and a `write` tool
1226
+ needs an approval step in the workflow. Agents use their per-tool approval
1227
+ policy. Personal workflows and the chat assistant apply no sensitivity gate.
1228
+ - `ctx.storage.user` still throws for machine actors, and partner OAuth tokens
1229
+ stay account-scoped.
1178
1230
 
1179
1231
  ### `workspaceTargets`
1180
1232
 
@@ -1244,6 +1296,37 @@ Both throw if the id is not declared, and `invokeWebhook` throws if the payload
1244
1296
  fails `payloadSchema` — the handler never runs. The local transport records the
1245
1297
  signature skip as one `warn` line on `ctx.logs`.
1246
1298
 
1299
+ `invokeWebhook` never verifies a signature — that is the transport's job — so a
1300
+ wrong header name or signed-payload template in your declaration would first
1301
+ show up as rejected deliveries on the host. `verifyDeclaredWebhookSignature()`
1302
+ closes that gap: it runs the host's own verifier against one declaration. Sign
1303
+ a payload in the test the way the VENDOR documents it and assert the
1304
+ declaration accepts it:
1305
+
1306
+ ```ts
1307
+ import { createHmac } from 'node:crypto';
1308
+ import { verifyDeclaredWebhookSignature } from '@ekanos/sdk/testing';
1309
+
1310
+ const body = JSON.stringify(payload);
1311
+ const t = String(Math.floor(Date.now() / 1000));
1312
+ const v1 = createHmac('sha256', secret).update(`${t}.${body}`).digest('hex');
1313
+
1314
+ await expect(
1315
+ verifyDeclaredWebhookSignature(integration, 'payment-updated', {
1316
+ headers: { 'Stripe-Signature': `t=${t},v1=${v1}` },
1317
+ rawBody: body,
1318
+ secret,
1319
+ }),
1320
+ ).resolves.toEqual({ ok: true });
1321
+ ```
1322
+
1323
+ A failure reports the same reason the host would log (`missing-header`,
1324
+ `malformed-header`, `missing-timestamp`, `malformed-timestamp`,
1325
+ `stale-timestamp`, `missing-secret`, `mismatch`). An empty secret fails closed
1326
+ with `missing-secret`. It throws for an undeclared id and for a
1327
+ declaration with `signature: 'none'`. The verifier itself is also exported as
1328
+ `verifyWebhookSignature()` for a test that wants to drive it directly.
1329
+
1247
1330
  `invokeActivate()` does the same for `onActivate` — no payload or invocation to
1248
1331
  build, just the handler against the definition-derived context:
1249
1332
 
package/api-report.md CHANGED
@@ -9,9 +9,9 @@ This is the complete surface of every entrypoint. The authoring guide
9
9
  for these symbols is README.md. Entrypoints marked WORKSPACE-ONLY are
10
10
  not reachable from a published install — see the note in each section.
11
11
 
12
- Total exported names: **172** across 7 entrypoints.
12
+ Total exported names: **212** across 8 entrypoints.
13
13
 
14
- ## `@ekanos/sdk` (32 exports)
14
+ ## `@ekanos/sdk` (35 exports)
15
15
 
16
16
  | Export | Kind |
17
17
  | --- | --- |
@@ -19,6 +19,7 @@ Total exported names: **172** across 7 entrypoints.
19
19
  | `AccountProductDataType` | type alias |
20
20
  | `ActivationFormProps` | type alias |
21
21
  | `ActivationResult` | interface |
22
+ | `AttentionSignal` | interface |
22
23
  | `HealthCheckResult` | interface |
23
24
  | `HealthStatus` | type alias |
24
25
  | `IntegrationActor` | type alias |
@@ -46,9 +47,11 @@ Total exported names: **172** across 7 entrypoints.
46
47
  | `WidgetConfig` | interface |
47
48
  | `WidgetSnapshot` | type alias |
48
49
  | `WidgetSuggestedPrompt` | interface |
50
+ | `WidgetVerdict` | interface |
51
+ | `WidgetVerdictTone` | type alias |
49
52
  | `WorkspaceTargetDefinition` | interface |
50
53
 
51
- ## `@ekanos/sdk/components` (25 exports)
54
+ ## `@ekanos/sdk/components` (34 exports)
52
55
 
53
56
  | Export | Kind |
54
57
  | --- | --- |
@@ -70,15 +73,24 @@ Total exported names: **172** across 7 entrypoints.
70
73
  | `WidgetContextValue` | interface |
71
74
  | `WidgetData` | interface |
72
75
  | `WidgetDataLoading` | function |
76
+ | `WidgetEmptyStateProps` | interface |
77
+ | `WidgetHeaderTrailing` | type alias |
73
78
  | `WidgetHealth` | interface |
79
+ | `WidgetInactiveReason` | type alias |
80
+ | `WidgetInactiveState` | function |
81
+ | `WidgetInactiveStateProps` | interface |
74
82
  | `WidgetMeta` | interface |
75
83
  | `WidgetPreviewProvider` | function |
76
84
  | `WidgetPreviewProviderProps` | interface |
77
85
  | `WidgetRenderState` | type alias |
86
+ | `WidgetSkeletonVariant` | type alias |
87
+ | `WidgetSource` | interface |
78
88
  | `WidgetState` | interface |
89
+ | `WidgetStateAction` | interface |
79
90
  | `currentReturnPath` | function |
91
+ | `useWidgetVerdict` | function |
80
92
 
81
- ## `@ekanos/sdk/hooks` (43 exports)
93
+ ## `@ekanos/sdk/hooks` (45 exports)
82
94
 
83
95
  | Export | Kind |
84
96
  | --- | --- |
@@ -99,6 +111,7 @@ Total exported names: **172** across 7 entrypoints.
99
111
  | `IntegrationActivationProvider` | function |
100
112
  | `IntegrationFetchProvider` | function |
101
113
  | `OAuthErrorParam` | interface |
114
+ | `OAuthWidgetGateOptions` | type alias |
102
115
  | `PartnerOAuthPathOptions` | interface |
103
116
  | `PublishEventInput` | interface |
104
117
  | `UseIntegrationActionOptions` | interface |
@@ -111,6 +124,7 @@ Total exported names: **172** across 7 entrypoints.
111
124
  | `fetchIntegrationStorage` | function |
112
125
  | `integrationActionUrl` | function |
113
126
  | `integrationStorageUrl` | function |
127
+ | `oauthWidgetGateOptionsFor` | function |
114
128
  | `partnerOAuthConnectPath` | function |
115
129
  | `partnerOAuthErrorParam` | function |
116
130
  | `partnerOAuthStatusUrl` | function |
@@ -169,7 +183,7 @@ Total exported names: **172** across 7 entrypoints.
169
183
  | `requireContext` | function |
170
184
  | `resolveStorageKeyDeclaration` | function |
171
185
 
172
- ## `@ekanos/sdk/testing` (17 exports)
186
+ ## `@ekanos/sdk/testing` (22 exports)
173
187
 
174
188
  | Export | Kind |
175
189
  | --- | --- |
@@ -184,18 +198,27 @@ Total exported names: **172** across 7 entrypoints.
184
198
  | `RecordedFetchCall` | interface |
185
199
  | `RecordedLog` | interface |
186
200
  | `ScheduleInvocationOutcome` | interface |
201
+ | `SignatureVerification` | type alias |
202
+ | `VerifyDeclaredWebhookSignatureOptions` | interface |
203
+ | `VerifyWebhookSignatureArgs` | interface |
187
204
  | `WebhookInvocationOutcome` | interface |
188
205
  | `createMockContext` | function |
189
206
  | `createMockFetchProvider` | function |
190
207
  | `invokeActivate` | function |
191
208
  | `invokeSchedule` | function |
192
209
  | `invokeWebhook` | function |
210
+ | `verifyDeclaredWebhookSignature` | function |
211
+ | `verifyWebhookSignature` | function |
193
212
 
194
- ## `@ekanos/sdk/integration` (31 exports)
213
+ ## `@ekanos/sdk/integration` (41 exports)
195
214
 
196
215
  | Export | Kind |
197
216
  | --- | --- |
217
+ | `ADMIN_HIERARCHY_LEVEL` | variable |
218
+ | `AttentionHandler` | type alias |
219
+ | `AttentionSignal` | interface |
198
220
  | `DEFAULT_SIGNATURE_TOLERANCE_SECONDS` | variable |
221
+ | `DEFAULT_UNTRUSTED_TEXT_MAX_CHARS` | variable |
199
222
  | `DefinitionCollisionInput` | interface |
200
223
  | `FirstPartyInventory` | interface |
201
224
  | `IntegrationCapabilityDeclaration` | type alias |
@@ -206,6 +229,7 @@ Total exported names: **172** across 7 entrypoints.
206
229
  | `IntegrationProposals` | type alias |
207
230
  | `OAuthProviderDeclaration` | interface |
208
231
  | `OAuthTokens` | interface |
232
+ | `OWNER_HIERARCHY_LEVEL` | variable |
209
233
  | `OnActivateHandler` | type alias |
210
234
  | `PARTNER_STORAGE_LIMITS` | variable |
211
235
  | `PartnerActionDeclaration` | interface |
@@ -223,6 +247,27 @@ Total exported names: **172** across 7 entrypoints.
223
247
  | `WebhookResult` | interface |
224
248
  | `WebhookSignatureDeclaration` | type alias |
225
249
  | `WebhookSignatureScheme` | interface |
250
+ | `WidgetVerdict` | interface |
251
+ | `WidgetVerdictTone` | type alias |
252
+ | `actorIsAtLeast` | function |
226
253
  | `defineIntegration` | function |
227
254
  | `parseCronExpression` | function |
255
+ | `untrustedText` | function |
256
+ | `untrustedTextNote` | function |
228
257
  | `validateIntegrationDefinitions` | function |
258
+
259
+ ## `@ekanos/sdk/layout` (11 exports)
260
+
261
+ | Export | Kind |
262
+ | --- | --- |
263
+ | `COMPACT_FROM_WIDTH` | variable |
264
+ | `SizedWidget` | interface |
265
+ | `WidgetRowGroup` | type alias |
266
+ | `WidgetSize` | type alias |
267
+ | `buildRowGroups` | function |
268
+ | `getColSpan` | function |
269
+ | `hasTrailingGap` | function |
270
+ | `pairRows` | function |
271
+ | `resolveWidgetSize` | function |
272
+ | `snapFullWidthToRowBoundary` | function |
273
+ | `splitBalanced` | function |
@@ -25,14 +25,14 @@
25
25
  * Surface discipline: additions require an entry in
26
26
  * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.
27
27
  */
28
- export { Widget } from './widgets/widget.js';
29
- export { WidgetContext, type WidgetActions, type WidgetContextValue, type WidgetData, type WidgetHealth, type WidgetMeta, type WidgetRenderState, type WidgetState, } from './widgets/widget-context.js';
28
+ export { Widget, useWidgetVerdict, type WidgetHeaderTrailing, } from './widgets/widget.js';
29
+ export { WidgetContext, type WidgetActions, type WidgetContextValue, type WidgetData, type WidgetHealth, type WidgetMeta, type WidgetRenderState, type WidgetSource, type WidgetState, } from './widgets/widget-context.js';
30
30
  export { WidgetPreviewProvider } from './widgets/widget-preview-provider.js';
31
31
  export type { WidgetPreviewProviderProps } from './widgets/widget-preview-provider.js';
32
32
  export { AiPromptChip } from './widgets/ai-prompt-chip.js';
33
33
  export { AskIcon } from './widgets/ask-icon.js';
34
34
  export { WidgetAskBar } from './widgets/widget-ask-bar.js';
35
- export { WidgetDataLoading } from './widgets/widget-state-components.js';
35
+ export { WidgetDataLoading, type WidgetEmptyStateProps, WidgetInactiveState, type WidgetInactiveReason, type WidgetInactiveStateProps, type WidgetSkeletonVariant, type WidgetStateAction, } from './widgets/widget-state-components.js';
36
36
  export { BaseActivationForm } from './base-activation-form.js';
37
37
  export type { BaseActivationFormProps } from './base-activation-form.js';
38
38
  export { ActivationDialog, ActivationInline, BaseActivationDialog, } from './base-activation-dialog.js';
@@ -25,13 +25,13 @@
25
25
  * Surface discipline: additions require an entry in
26
26
  * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.
27
27
  */
28
- export { Widget } from './widgets/widget.js';
28
+ export { Widget, useWidgetVerdict, } from './widgets/widget.js';
29
29
  export { WidgetContext, } from './widgets/widget-context.js';
30
30
  export { WidgetPreviewProvider } from './widgets/widget-preview-provider.js';
31
31
  export { AiPromptChip } from './widgets/ai-prompt-chip.js';
32
32
  export { AskIcon } from './widgets/ask-icon.js';
33
33
  export { WidgetAskBar } from './widgets/widget-ask-bar.js';
34
- export { WidgetDataLoading } from './widgets/widget-state-components.js';
34
+ export { WidgetDataLoading, WidgetInactiveState, } from './widgets/widget-state-components.js';
35
35
  export { BaseActivationForm } from './base-activation-form.js';
36
36
  export { ActivationDialog, ActivationInline, BaseActivationDialog, } from './base-activation-dialog.js';
37
37
  export { BaseMarketplaceTile } from './base-marketplace-tile.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EACL,aAAa,GAQd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAEtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["/**\n * @ekanos/sdk/components — client-side building blocks.\n *\n * Import from client components only. `Widget` is the compound API every\n * dashboard widget renders through; the activation forms are the two\n * sanctioned activation UX bases (credential-style and OAuth-style), and\n * `BaseMarketplaceTile` is the matching base for the marketplace tile.\n *\n * The SDK OWNS these implementations — the pre-inversion re-exports through\n * `@kit/integrations-core/components/*` are gone; core now re-exports FROM\n * here for first-party code (one source of truth, two consumption paths —\n * one source of truth with two consumption paths. UI primitives come from\n * `@ekanos/ui`\n * (the published slice of the host design system), never `@kit/ui`.\n *\n * Server seam: nothing here imports a server module. `OAuthActivationForm`\n * performs activation through the host-injected actions seam\n * (`IntegrationActivationProvider`, @ekanos/sdk/hooks) — a server-action\n * REFERENCE bound by the host, not server code shipped to the browser.\n *\n * `WidgetContext` is the single context identity both sides share: the\n * host's `DashboardWidgetProvider` (host chrome — persistence callbacks,\n * config hierarchy) provides it, the SDK's `Widget.*` reads it.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { Widget } from './widgets/widget';\nexport {\n WidgetContext,\n type WidgetActions,\n type WidgetContextValue,\n type WidgetData,\n type WidgetHealth,\n type WidgetMeta,\n type WidgetRenderState,\n type WidgetState,\n} from './widgets/widget-context';\nexport { WidgetPreviewProvider } from './widgets/widget-preview-provider';\nexport type { WidgetPreviewProviderProps } from './widgets/widget-preview-provider';\nexport { AiPromptChip } from './widgets/ai-prompt-chip';\nexport { AskIcon } from './widgets/ask-icon';\nexport { WidgetAskBar } from './widgets/widget-ask-bar';\nexport { WidgetDataLoading } from './widgets/widget-state-components';\n\nexport { BaseActivationForm } from './base-activation-form';\nexport type { BaseActivationFormProps } from './base-activation-form';\nexport {\n ActivationDialog,\n ActivationInline,\n BaseActivationDialog,\n} from './base-activation-dialog';\n\nexport { BaseMarketplaceTile } from './base-marketplace-tile';\nexport type { BaseMarketplaceTileProps } from './base-marketplace-tile';\n\nexport { OAuthActivationForm } from './oauth-activation-form';\nexport type { OAuthActivationFormConfig } from './oauth-activation-form';\n\nexport { currentReturnPath } from './current-return-path';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACL,MAAM,EACN,gBAAgB,GAEjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,GASd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAG1E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,GAKpB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["/**\n * @ekanos/sdk/components — client-side building blocks.\n *\n * Import from client components only. `Widget` is the compound API every\n * dashboard widget renders through; the activation forms are the two\n * sanctioned activation UX bases (credential-style and OAuth-style), and\n * `BaseMarketplaceTile` is the matching base for the marketplace tile.\n *\n * The SDK OWNS these implementations — the pre-inversion re-exports through\n * `@kit/integrations-core/components/*` are gone; core now re-exports FROM\n * here for first-party code (one source of truth, two consumption paths —\n * one source of truth with two consumption paths. UI primitives come from\n * `@ekanos/ui`\n * (the published slice of the host design system), never `@kit/ui`.\n *\n * Server seam: nothing here imports a server module. `OAuthActivationForm`\n * performs activation through the host-injected actions seam\n * (`IntegrationActivationProvider`, @ekanos/sdk/hooks) — a server-action\n * REFERENCE bound by the host, not server code shipped to the browser.\n *\n * `WidgetContext` is the single context identity both sides share: the\n * host's `DashboardWidgetProvider` (host chrome — persistence callbacks,\n * config hierarchy) provides it, the SDK's `Widget.*` reads it.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport {\n Widget,\n useWidgetVerdict,\n type WidgetHeaderTrailing,\n} from './widgets/widget';\nexport {\n WidgetContext,\n type WidgetActions,\n type WidgetContextValue,\n type WidgetData,\n type WidgetHealth,\n type WidgetMeta,\n type WidgetRenderState,\n type WidgetSource,\n type WidgetState,\n} from './widgets/widget-context';\nexport { WidgetPreviewProvider } from './widgets/widget-preview-provider';\n\nexport type { WidgetPreviewProviderProps } from './widgets/widget-preview-provider';\nexport { AiPromptChip } from './widgets/ai-prompt-chip';\nexport { AskIcon } from './widgets/ask-icon';\nexport { WidgetAskBar } from './widgets/widget-ask-bar';\nexport {\n WidgetDataLoading,\n type WidgetEmptyStateProps,\n WidgetInactiveState,\n type WidgetInactiveReason,\n type WidgetInactiveStateProps,\n type WidgetSkeletonVariant,\n type WidgetStateAction,\n} from './widgets/widget-state-components';\n\nexport { BaseActivationForm } from './base-activation-form';\nexport type { BaseActivationFormProps } from './base-activation-form';\nexport {\n ActivationDialog,\n ActivationInline,\n BaseActivationDialog,\n} from './base-activation-dialog';\n\nexport { BaseMarketplaceTile } from './base-marketplace-tile';\nexport type { BaseMarketplaceTileProps } from './base-marketplace-tile';\n\nexport { OAuthActivationForm } from './oauth-activation-form';\nexport type { OAuthActivationFormConfig } from './oauth-activation-form';\n\nexport { currentReturnPath } from './current-return-path';\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { WidgetAskContext } from '../../types/widget-ask-context.js';
2
+ import type { WidgetVerdict } from '../../types/widget-verdict.js';
2
3
  export type WidgetRenderState = 'active' | 'disabled' | 'loading' | 'error' | 'inactive';
3
4
  export interface WidgetState {
4
5
  state: WidgetRenderState;
@@ -35,6 +36,16 @@ export interface WidgetMeta {
35
36
  * page; the header menu shows "Open in workspace" when present.
36
37
  */
37
38
  openHref?: string;
39
+ /**
40
+ * Whose data the widget shows: the integration's name and bundled logo.
41
+ * `Widget.Header` draws the logo left of the title (the design system's widget rules).
42
+ */
43
+ source?: WidgetSource;
44
+ }
45
+ export interface WidgetSource {
46
+ name: string;
47
+ /** A logo URL; the header shows the name's first letter without one. */
48
+ logoPath?: string;
38
49
  }
39
50
  /**
40
51
  * What the widget's own data layer reports through `Widget.DataState`. The
@@ -49,6 +60,12 @@ export interface WidgetData {
49
60
  refreshing: boolean;
50
61
  /** Reloads the widget's data. */
51
62
  refresh?: () => void;
63
+ /**
64
+ * The widget's own conclusion about its data (the design system's widget rules): the
65
+ * header shows it in place of the context line, and the "Ask about this"
66
+ * snapshot carries it.
67
+ */
68
+ verdict?: WidgetVerdict;
52
69
  }
53
70
  export interface WidgetContextValue {
54
71
  state: WidgetState;
@@ -1 +1 @@
1
- {"version":3,"file":"widget-context.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAyEtC,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAA4B,IAAI,CAAC,CAAC","sourcesContent":["'use client';\n\nimport { createContext } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\n\nexport type WidgetRenderState =\n | 'active'\n | 'disabled'\n | 'loading'\n | 'error'\n | 'inactive';\n\nexport interface WidgetState {\n state: WidgetRenderState;\n collapsed: boolean;\n pinned: boolean;\n pendingCollapse: boolean;\n pendingPin: boolean;\n}\n\nexport interface WidgetActions {\n toggleCollapsed: () => void;\n togglePinned: () => void;\n}\n\nexport interface WidgetHealth {\n status: 'healthy' | 'degraded' | 'unhealthy' | 'unknown';\n lastSuccessAt?: string | null;\n errorMessage?: string | null;\n}\n\nexport interface WidgetMeta {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n maxContentHeight?: string;\n isCollapsible: boolean;\n isPinnable: boolean;\n askContext?: WidgetAskContext;\n aiFooterEnabled: boolean;\n className?: string;\n /** Integration health for this widget's product, if known. */\n health?: WidgetHealth;\n /**\n * Where the widget's full surface lives, e.g. the workspace a pinned widget\n * came from. Set by the host only where following it leaves the current\n * page; the header menu shows \"Open in workspace\" when present.\n */\n openHref?: string;\n}\n\n/**\n * What the widget's own data layer reports through `Widget.DataState`. The\n * host cannot see a widget's queries, so every field is opt-in: the header\n * menu's Refresh, the error view's Try again, and the \"Updated …\" footer each\n * appear only when the widget supplies what they need.\n */\nexport interface WidgetData {\n /** Epoch milliseconds of the last successful load; absent when unknown. */\n updatedAt?: number;\n /** A reload is in flight while data is already on screen. */\n refreshing: boolean;\n /** Reloads the widget's data. */\n refresh?: () => void;\n}\n\nexport interface WidgetContextValue {\n state: WidgetState;\n actions: WidgetActions;\n meta: WidgetMeta;\n data?: WidgetData;\n}\n\nexport const WidgetContext = createContext<WidgetContextValue | null>(null);\n"]}
1
+ {"version":3,"file":"widget-context.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AA2FtC,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAA4B,IAAI,CAAC,CAAC","sourcesContent":["'use client';\n\nimport { createContext } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport type { WidgetVerdict } from '../../types/widget-verdict';\n\nexport type WidgetRenderState =\n | 'active'\n | 'disabled'\n | 'loading'\n | 'error'\n | 'inactive';\n\nexport interface WidgetState {\n state: WidgetRenderState;\n collapsed: boolean;\n pinned: boolean;\n pendingCollapse: boolean;\n pendingPin: boolean;\n}\n\nexport interface WidgetActions {\n toggleCollapsed: () => void;\n togglePinned: () => void;\n}\n\nexport interface WidgetHealth {\n status: 'healthy' | 'degraded' | 'unhealthy' | 'unknown';\n lastSuccessAt?: string | null;\n errorMessage?: string | null;\n}\n\nexport interface WidgetMeta {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n maxContentHeight?: string;\n isCollapsible: boolean;\n isPinnable: boolean;\n askContext?: WidgetAskContext;\n aiFooterEnabled: boolean;\n className?: string;\n /** Integration health for this widget's product, if known. */\n health?: WidgetHealth;\n /**\n * Where the widget's full surface lives, e.g. the workspace a pinned widget\n * came from. Set by the host only where following it leaves the current\n * page; the header menu shows \"Open in workspace\" when present.\n */\n openHref?: string;\n /**\n * Whose data the widget shows: the integration's name and bundled logo.\n * `Widget.Header` draws the logo left of the title (the design system's widget rules).\n */\n source?: WidgetSource;\n}\n\nexport interface WidgetSource {\n name: string;\n /** A logo URL; the header shows the name's first letter without one. */\n logoPath?: string;\n}\n\n/**\n * What the widget's own data layer reports through `Widget.DataState`. The\n * host cannot see a widget's queries, so every field is opt-in: the header\n * menu's Refresh, the error view's Try again, and the \"Updated …\" footer each\n * appear only when the widget supplies what they need.\n */\nexport interface WidgetData {\n /** Epoch milliseconds of the last successful load; absent when unknown. */\n updatedAt?: number;\n /** A reload is in flight while data is already on screen. */\n refreshing: boolean;\n /** Reloads the widget's data. */\n refresh?: () => void;\n /**\n * The widget's own conclusion about its data (the design system's widget rules): the\n * header shows it in place of the context line, and the \"Ask about this\"\n * snapshot carries it.\n */\n verdict?: WidgetVerdict;\n}\n\nexport interface WidgetContextValue {\n state: WidgetState;\n actions: WidgetActions;\n meta: WidgetMeta;\n data?: WidgetData;\n}\n\nexport const WidgetContext = createContext<WidgetContextValue | null>(null);\n"]}
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { WidgetAskContext } from '../../types/widget-ask-context.js';
3
- import { type WidgetHealth, type WidgetRenderState } from './widget-context.js';
3
+ import { type WidgetHealth, type WidgetRenderState, type WidgetSource } from './widget-context.js';
4
4
  export interface WidgetPreviewProviderProps {
5
5
  widgetId: string;
6
6
  title?: string;
@@ -17,6 +17,8 @@ export interface WidgetPreviewProviderProps {
17
17
  askContext?: WidgetAskContext;
18
18
  aiFooterEnabled?: boolean;
19
19
  health?: WidgetHealth;
20
+ /** Whose data the widget shows; the header draws its logo. */
21
+ source?: WidgetSource;
20
22
  children: ReactNode;
21
23
  }
22
24
  /**
@@ -38,4 +40,4 @@ export interface WidgetPreviewProviderProps {
38
40
  * Flip `state` to walk every branch your widget renders (`'loading'`,
39
41
  * `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.
40
42
  */
41
- export declare function WidgetPreviewProvider({ widgetId, title, subtitle, description, className, state, isCollapsible, isPinnable, defaultCollapsed, defaultPinned, maxContentHeight, askContext, aiFooterEnabled, health, children, }: WidgetPreviewProviderProps): import("react").JSX.Element;
43
+ export declare function WidgetPreviewProvider({ widgetId, title, subtitle, description, className, state, isCollapsible, isPinnable, defaultCollapsed, defaultPinned, maxContentHeight, askContext, aiFooterEnabled, health, source, children, }: WidgetPreviewProviderProps): import("react").JSX.Element;
@@ -21,7 +21,7 @@ import { WidgetContext, } from './widget-context.js';
21
21
  * Flip `state` to walk every branch your widget renders (`'loading'`,
22
22
  * `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.
23
23
  */
24
- export function WidgetPreviewProvider({ widgetId, title, subtitle, description, className, state = 'active', isCollapsible = false, isPinnable = false, defaultCollapsed = false, defaultPinned = false, maxContentHeight, askContext, aiFooterEnabled = false, health, children, }) {
24
+ export function WidgetPreviewProvider({ widgetId, title, subtitle, description, className, state = 'active', isCollapsible = false, isPinnable = false, defaultCollapsed = false, defaultPinned = false, maxContentHeight, askContext, aiFooterEnabled = false, health, source, children, }) {
25
25
  const [collapsed, setCollapsed] = useState(defaultCollapsed);
26
26
  const [pinned, setPinned] = useState(defaultPinned);
27
27
  const toggleCollapsed = useCallback(() => setCollapsed((previous) => !previous), []);
@@ -47,6 +47,7 @@ export function WidgetPreviewProvider({ widgetId, title, subtitle, description,
47
47
  askContext,
48
48
  aiFooterEnabled,
49
49
  health,
50
+ source,
50
51
  },
51
52
  }), [
52
53
  aiFooterEnabled,
@@ -59,6 +60,7 @@ export function WidgetPreviewProvider({ widgetId, title, subtitle, description,
59
60
  isPinnable,
60
61
  maxContentHeight,
61
62
  pinned,
63
+ source,
62
64
  state,
63
65
  subtitle,
64
66
  title,
@@ -1 +1 @@
1
- {"version":3,"file":"widget-preview-provider.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-preview-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,EACL,aAAa,GAId,MAAM,kBAAkB,CAAC;AAqB1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,GAAG,QAAQ,EAChB,aAAa,GAAG,KAAK,EACrB,UAAU,GAAG,KAAK,EAClB,gBAAgB,GAAG,KAAK,EACxB,aAAa,GAAG,KAAK,EACrB,gBAAgB,EAChB,UAAU,EACV,eAAe,GAAG,KAAK,EACvB,MAAM,EACN,QAAQ,GACmB;IAC3B,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC7D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAG,WAAW,CACjC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC3C,EAAE,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAC9B,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EACxC,EAAE,CACH,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE;YACL,KAAK;YACL,SAAS;YACT,MAAM;YACN,eAAe,EAAE,KAAK;YACtB,UAAU,EAAE,KAAK;SAClB;QACD,OAAO,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;QAC1C,IAAI,EAAE;YACJ,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW;YACX,SAAS;YACT,gBAAgB;YAChB,aAAa;YACb,UAAU;YACV,UAAU;YACV,eAAe;YACf,MAAM;SACP;KACF,CAAC,EACF;QACE,eAAe;QACf,UAAU;QACV,SAAS;QACT,SAAS;QACT,WAAW;QACX,MAAM;QACN,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,MAAM;QACN,KAAK;QACL,QAAQ;QACR,KAAK;QACL,eAAe;QACf,YAAY;QACZ,QAAQ;KACT,CACF,CAAC;IAEF,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiB,CAAC;AACjE,CAAC","sourcesContent":["'use client';\n\nimport { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport {\n WidgetContext,\n type WidgetContextValue,\n type WidgetHealth,\n type WidgetRenderState,\n} from './widget-context';\n\nexport interface WidgetPreviewProviderProps {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n className?: string;\n /** Defaults to `'active'` — the state you almost always want to look at. */\n state?: WidgetRenderState;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n defaultCollapsed?: boolean;\n defaultPinned?: boolean;\n maxContentHeight?: string;\n askContext?: WidgetAskContext;\n aiFooterEnabled?: boolean;\n health?: WidgetHealth;\n children: ReactNode;\n}\n\n/**\n * Renders a widget outside the Ekanos dashboard.\n *\n * `Widget.*` reads everything — render state, collapse/pin, title — from\n * `WidgetContext`, which in production is filled by the host's\n * `DashboardWidgetProvider` (config hierarchy, persistence callbacks, health\n * resolution). None of that exists in a partner's own app, a Storybook story,\n * or a component test, and without SOME provider every `Widget.*` throws. This\n * is that provider: same context value, local state only, nothing persisted.\n *\n * ```tsx\n * <WidgetPreviewProvider widgetId=\"my-widget\" title=\"My widget\">\n * <MyWidget accountId={accountId} productSlug=\"my-integration\" />\n * </WidgetPreviewProvider>\n * ```\n *\n * Flip `state` to walk every branch your widget renders (`'loading'`,\n * `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.\n */\nexport function WidgetPreviewProvider({\n widgetId,\n title,\n subtitle,\n description,\n className,\n state = 'active',\n isCollapsible = false,\n isPinnable = false,\n defaultCollapsed = false,\n defaultPinned = false,\n maxContentHeight,\n askContext,\n aiFooterEnabled = false,\n health,\n children,\n}: WidgetPreviewProviderProps) {\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const [pinned, setPinned] = useState(defaultPinned);\n\n const toggleCollapsed = useCallback(\n () => setCollapsed((previous) => !previous),\n [],\n );\n\n const togglePinned = useCallback(\n () => setPinned((previous) => !previous),\n [],\n );\n\n const value = useMemo<WidgetContextValue>(\n () => ({\n state: {\n state,\n collapsed,\n pinned,\n pendingCollapse: false,\n pendingPin: false,\n },\n actions: { toggleCollapsed, togglePinned },\n meta: {\n widgetId,\n title,\n subtitle,\n description,\n className,\n maxContentHeight,\n isCollapsible,\n isPinnable,\n askContext,\n aiFooterEnabled,\n health,\n },\n }),\n [\n aiFooterEnabled,\n askContext,\n className,\n collapsed,\n description,\n health,\n isCollapsible,\n isPinnable,\n maxContentHeight,\n pinned,\n state,\n subtitle,\n title,\n toggleCollapsed,\n togglePinned,\n widgetId,\n ],\n );\n\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n"]}
1
+ {"version":3,"file":"widget-preview-provider.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-preview-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,EACL,aAAa,GAKd,MAAM,kBAAkB,CAAC;AAuB1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,GAAG,QAAQ,EAChB,aAAa,GAAG,KAAK,EACrB,UAAU,GAAG,KAAK,EAClB,gBAAgB,GAAG,KAAK,EACxB,aAAa,GAAG,KAAK,EACrB,gBAAgB,EAChB,UAAU,EACV,eAAe,GAAG,KAAK,EACvB,MAAM,EACN,MAAM,EACN,QAAQ,GACmB;IAC3B,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC7D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAG,WAAW,CACjC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC3C,EAAE,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAC9B,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EACxC,EAAE,CACH,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE;YACL,KAAK;YACL,SAAS;YACT,MAAM;YACN,eAAe,EAAE,KAAK;YACtB,UAAU,EAAE,KAAK;SAClB;QACD,OAAO,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;QAC1C,IAAI,EAAE;YACJ,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW;YACX,SAAS;YACT,gBAAgB;YAChB,aAAa;YACb,UAAU;YACV,UAAU;YACV,eAAe;YACf,MAAM;YACN,MAAM;SACP;KACF,CAAC,EACF;QACE,eAAe;QACf,UAAU;QACV,SAAS;QACT,SAAS;QACT,WAAW;QACX,MAAM;QACN,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,MAAM;QACN,MAAM;QACN,KAAK;QACL,QAAQ;QACR,KAAK;QACL,eAAe;QACf,YAAY;QACZ,QAAQ;KACT,CACF,CAAC;IAEF,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiB,CAAC;AACjE,CAAC","sourcesContent":["'use client';\n\nimport { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport {\n WidgetContext,\n type WidgetContextValue,\n type WidgetHealth,\n type WidgetRenderState,\n type WidgetSource,\n} from './widget-context';\n\nexport interface WidgetPreviewProviderProps {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n className?: string;\n /** Defaults to `'active'` — the state you almost always want to look at. */\n state?: WidgetRenderState;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n defaultCollapsed?: boolean;\n defaultPinned?: boolean;\n maxContentHeight?: string;\n askContext?: WidgetAskContext;\n aiFooterEnabled?: boolean;\n health?: WidgetHealth;\n /** Whose data the widget shows; the header draws its logo. */\n source?: WidgetSource;\n children: ReactNode;\n}\n\n/**\n * Renders a widget outside the Ekanos dashboard.\n *\n * `Widget.*` reads everything — render state, collapse/pin, title — from\n * `WidgetContext`, which in production is filled by the host's\n * `DashboardWidgetProvider` (config hierarchy, persistence callbacks, health\n * resolution). None of that exists in a partner's own app, a Storybook story,\n * or a component test, and without SOME provider every `Widget.*` throws. This\n * is that provider: same context value, local state only, nothing persisted.\n *\n * ```tsx\n * <WidgetPreviewProvider widgetId=\"my-widget\" title=\"My widget\">\n * <MyWidget accountId={accountId} productSlug=\"my-integration\" />\n * </WidgetPreviewProvider>\n * ```\n *\n * Flip `state` to walk every branch your widget renders (`'loading'`,\n * `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.\n */\nexport function WidgetPreviewProvider({\n widgetId,\n title,\n subtitle,\n description,\n className,\n state = 'active',\n isCollapsible = false,\n isPinnable = false,\n defaultCollapsed = false,\n defaultPinned = false,\n maxContentHeight,\n askContext,\n aiFooterEnabled = false,\n health,\n source,\n children,\n}: WidgetPreviewProviderProps) {\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const [pinned, setPinned] = useState(defaultPinned);\n\n const toggleCollapsed = useCallback(\n () => setCollapsed((previous) => !previous),\n [],\n );\n\n const togglePinned = useCallback(\n () => setPinned((previous) => !previous),\n [],\n );\n\n const value = useMemo<WidgetContextValue>(\n () => ({\n state: {\n state,\n collapsed,\n pinned,\n pendingCollapse: false,\n pendingPin: false,\n },\n actions: { toggleCollapsed, togglePinned },\n meta: {\n widgetId,\n title,\n subtitle,\n description,\n className,\n maxContentHeight,\n isCollapsible,\n isPinnable,\n askContext,\n aiFooterEnabled,\n health,\n source,\n },\n }),\n [\n aiFooterEnabled,\n askContext,\n className,\n collapsed,\n description,\n health,\n isCollapsible,\n isPinnable,\n maxContentHeight,\n pinned,\n source,\n state,\n subtitle,\n title,\n toggleCollapsed,\n togglePinned,\n widgetId,\n ],\n );\n\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n"]}
@@ -1,8 +1,64 @@
1
- export declare function WidgetLoadingState(): import("react").JSX.Element;
2
- export declare function WidgetErrorState({ onRetry, retrying, }: {
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * The one action a state view offers: "Connect QuickBooks", "Ask an admin",
4
+ * "Create a deal". A link with `href`, otherwise a button running `onClick`.
5
+ */
6
+ export interface WidgetStateAction {
7
+ label: ReactNode;
8
+ href?: string;
9
+ /** Open `href` in a new tab. */
10
+ external?: boolean;
11
+ onClick?: () => void;
12
+ }
13
+ /** The body shape a loading skeleton stands in for. */
14
+ export type WidgetSkeletonVariant = 'rows' | 'stat' | 'chart' | 'schedule';
15
+ /** Why an active integration still can't show this widget's data. */
16
+ export type WidgetInactiveReason = 'not-connected' | 'no-access' | 'setup-needed';
17
+ export interface WidgetEmptyStateProps {
18
+ /**
19
+ * `positive` (the default) says nothing needs the owner: "You're all caught
20
+ * up". `neutral` is for an empty list that isn't good news, like a search
21
+ * with no results.
22
+ */
23
+ tone?: 'positive' | 'neutral';
24
+ title?: ReactNode;
25
+ description?: ReactNode;
26
+ action?: WidgetStateAction;
27
+ }
28
+ export interface WidgetInactiveStateProps {
29
+ reason?: WidgetInactiveReason;
30
+ /** The integration's name, for the default copy ("Connect QuickBooks"). */
31
+ integrationName?: string;
32
+ title?: ReactNode;
33
+ /** One sentence on what connecting or access gives the owner. */
34
+ description?: ReactNode;
35
+ action?: WidgetStateAction;
36
+ }
37
+ /**
38
+ * The empty state for a widget with nothing to show. Positive by default:
39
+ * an empty review queue is good news, so it reassures instead of apologizing.
40
+ */
41
+ export declare function WidgetEmptyState({ tone, title, description, action, }: WidgetEmptyStateProps): import("react").JSX.Element;
42
+ /**
43
+ * Why the widget can't show data. `not-connected` offers one Connect button;
44
+ * `no-access` is a calm note that points to an admin, never a lock screen;
45
+ * `setup-needed` points to the integration's settings. Without a `reason` it
46
+ * keeps the original one-line "Widget inactive".
47
+ */
48
+ export declare function WidgetInactiveState({ reason, integrationName, title, description, action, }?: WidgetInactiveStateProps): import("react").JSX.Element;
49
+ /**
50
+ * Loading, drawn as the body it stands in for so the card doesn't jump when
51
+ * data lands. Rows suit lists and feeds, `stat` a headline figure, `chart` a
52
+ * chart, `schedule` a day of events.
53
+ */
54
+ export declare function WidgetLoadingSkeleton({ variant, }: {
55
+ variant?: WidgetSkeletonVariant;
56
+ }): import("react").JSX.Element;
57
+ export declare function WidgetErrorState({ title, description, onRetry, retrying, }: {
58
+ title?: ReactNode;
59
+ description?: ReactNode;
3
60
  onRetry?: () => void;
4
61
  retrying?: boolean;
5
62
  }): import("react").JSX.Element;
6
- export declare function WidgetInactiveState(): import("react").JSX.Element;
7
63
  export declare function WidgetDataLoading(): import("react").JSX.Element;
8
64
  export declare function WidgetDisabledState(): import("react").JSX.Element;