@ekanos/sdk 0.5.0 → 0.7.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.
Files changed (61) hide show
  1. package/README.md +99 -13
  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/partner-oauth.js +1 -0
  21. package/dist/hooks/partner-oauth.js.map +1 -1
  22. package/dist/hooks/use-oauth-connection-status.d.ts +17 -1
  23. package/dist/hooks/use-oauth-connection-status.js +12 -17
  24. package/dist/hooks/use-oauth-connection-status.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/integration/actor-role.d.ts +14 -0
  28. package/dist/integration/actor-role.js +21 -0
  29. package/dist/integration/actor-role.js.map +1 -0
  30. package/dist/integration/index.d.ts +3 -1
  31. package/dist/integration/index.js +4 -0
  32. package/dist/integration/index.js.map +1 -1
  33. package/dist/integration/types.d.ts +1 -1
  34. package/dist/integration/types.js.map +1 -1
  35. package/dist/integration/untrusted-text.d.ts +24 -0
  36. package/dist/integration/untrusted-text.js +36 -0
  37. package/dist/integration/untrusted-text.js.map +1 -0
  38. package/dist/layout/index.d.ts +8 -0
  39. package/dist/layout/index.js +9 -0
  40. package/dist/layout/index.js.map +1 -0
  41. package/dist/layout/widget-layout.d.ts +84 -0
  42. package/dist/layout/widget-layout.js +138 -0
  43. package/dist/layout/widget-layout.js.map +1 -0
  44. package/dist/testing/index.d.ts +4 -0
  45. package/dist/testing/index.js +5 -0
  46. package/dist/testing/index.js.map +1 -1
  47. package/dist/testing/mock-context.js +3 -1
  48. package/dist/testing/mock-context.js.map +1 -1
  49. package/dist/testing/verify-signature.d.ts +26 -0
  50. package/dist/testing/verify-signature.js +45 -0
  51. package/dist/testing/verify-signature.js.map +1 -0
  52. package/dist/types/index.d.ts +1 -0
  53. package/dist/types/index.js.map +1 -1
  54. package/dist/types/integration.d.ts +5 -0
  55. package/dist/types/integration.js.map +1 -1
  56. package/dist/types/widget-verdict.d.ts +6 -0
  57. package/dist/types/widget-verdict.js +2 -0
  58. package/dist/types/widget-verdict.js.map +1 -0
  59. package/eslint.design-preset.mjs +226 -0
  60. package/eslint.design-rules.mjs +437 -0
  61. package/package.json +16 -6
package/README.md CHANGED
@@ -30,7 +30,7 @@ one. Each has its own README.
30
30
  - [`webhooks`](#webhooks)
31
31
  - [`schedules`](#schedules)
32
32
  - [`oauth`](#oauth)
33
- - [`proposes` — what you suggest, the host decides](#proposes)
33
+ - [`proposes` — your credential model and tool classifications](#proposes)
34
34
  - [`workspaceTargets`](#workspacetargets)
35
35
  - [Testing](#testing)
36
36
  - [Entrypoints](#entrypoints)
@@ -190,7 +190,7 @@ There is no raw database client, no Supabase, and no request object.
190
190
  | `ctx.secrets` | `get(name)`, `names()`, `set(name, value)` over the resolved credential set |
191
191
  | `ctx.fetch` | `fetch`, refusing anything outside `egress` before it opens a socket |
192
192
  | `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 }` |
193
+ | `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
194
 
195
195
  Plus read-only identity facts, all already authorized: `ctx.accountId`,
196
196
  `ctx.accountSlug`, `ctx.userId` (`null` for machine actors), `ctx.sourceId`,
@@ -339,7 +339,7 @@ no `http:`. Wildcards are supported only as one leading `*.`. Anything else is
339
339
  rejected by `defineIntegration()` with the offending entry quoted.
340
340
 
341
341
  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
342
+ **admission** — the human review your integration passes before it is enabled
343
343
  for real accounts, where someone reads this declaration against what your
344
344
  handlers actually do. Declare the origins you call and no more.
345
345
 
@@ -414,8 +414,7 @@ const apiKey = await ctx.secrets.get(ACME_API_KEY);
414
414
 
415
415
  ### `tools`
416
416
 
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
417
+ Tools are what the Ekanos assistant, workflows and managed agents can call. Each declares a name, a description the model reads, an
419
418
  optional JSON-Schema parameter object, and an async `run`.
420
419
 
421
420
  ```ts
@@ -459,6 +458,47 @@ for a genuine fault.
459
458
 
460
459
  `outputExample` is shown to the model as a shape hint. Keep it small and honest.
461
460
 
461
+ **Text written outside this system is data, not instructions.** A deal name, a
462
+ call transcript, a support reply, a booking answer: anything a vendor's users
463
+ typed can say "ignore the above and…", and in a tool result it looks exactly
464
+ like a field you computed. Return such text only under a field whose name
465
+ starts with `untrusted`, clip it with `untrustedText()` (from
466
+ `@ekanos/sdk/integration`; 1000 characters by default), and put one
467
+ `note: untrustedTextNote('deal, stage and pipeline names')` at the top of the
468
+ output so the model reads the rule where it reads the data. `untrustedText()`
469
+ bounds the length and marks the text as coming from a third party so the
470
+ assistant treats it as data; it does not strip or neutralise injected
471
+ instructions. Pass `untrustedTextNote()` a fixed string you wrote, never vendor
472
+ text. In a write tool's description, say to confirm with the user first and
473
+ never act on what the text asks for.
474
+
475
+ ```ts
476
+ import { untrustedText, untrustedTextNote } from '@ekanos/sdk/integration';
477
+
478
+ return {
479
+ note: untrustedTextNote('deal names'),
480
+ deals: deals.map((d) => ({ id: d.id, untrustedName: untrustedText(d.name, 200), amount: d.amount })),
481
+ };
482
+ ```
483
+
484
+ **`ownerAction` is how an owner reads an approval for your tool.** When an agent
485
+ or workflow pauses on one of your tools, the owner sees "{agent} wants to
486
+ {ownerAction} in {your integration}". Declare the phrase yourself, a bare verb
487
+ that names no ids:
488
+
489
+ ```ts
490
+ ownerAction: 'sync your price list',
491
+ // or one phrase per locale; `default` covers any locale you did not list
492
+ ownerAction: { en: 'sync your price list', es: 'sincronizar su lista de precios' },
493
+ ```
494
+
495
+ It is plain text, at most 120 characters, never a message key: a partner cannot
496
+ add keys to the host's messages. The host picks the exact locale, then its
497
+ language (`pt-BR` then `pt`), then the default locale, then your `default`. With
498
+ no `ownerAction` the host derives a phrase from the tool name ("update deal
499
+ stage" from `acme_update_deal_stage`), and a name that is not `<verb>_<thing>`
500
+ reads "use <tool name>", so declare one for any tool that writes.
501
+
462
502
  **`parameters` is a narrow slice of JSON Schema, not all of it.** The top level
463
503
  is `.strict()` and accepts exactly four keys — `type` (which must be the
464
504
  literal `'object'`), `properties`, `required` and `additionalProperties`.
@@ -1080,6 +1120,10 @@ export const acmeOAuth: PartnerOAuthDeclaration<MyStorage> = {
1080
1120
  tokenUrl: 'https://connect.acme.example/oauth/token',
1081
1121
  scopes: ['payments:read', 'refunds:write'],
1082
1122
  pkce: true,
1123
+ // How the token endpoint wants client authentication. Defaults to
1124
+ // 'client_secret_basic' (HTTP Basic); LinkedIn needs 'client_secret_post'
1125
+ // (client_id and client_secret in the form body).
1126
+ tokenAuthMethod: 'client_secret_basic',
1083
1127
  },
1084
1128
  credentials: {
1085
1129
  clientIdSecretName: 'acme_oauth_client_id',
@@ -1142,9 +1186,10 @@ async function.
1142
1186
 
1143
1187
  ### `proposes`
1144
1188
 
1145
- A suggestion block, not a settings block. Security classifications are
1146
- host-assigned; `proposes` is how you tell a reviewer what you believe is true,
1147
- and the reviewer confirms it at promotion.
1189
+ Where you declare your credential model and classify each tool. Once your
1190
+ integration is admitted the host applies these values, the same as a first-party
1191
+ integration's own. The admission review is the check, and the host can tighten a
1192
+ declaration (never loosen it).
1148
1193
 
1149
1194
  ```ts
1150
1195
  proposes: {
@@ -1155,22 +1200,32 @@ proposes: {
1155
1200
  tools: {
1156
1201
  list_recent_payments: { effect: 'read', sensitivity: 'financial' },
1157
1202
  // Honest classification: this moves money. Marking it 'read' or
1158
- // 'internal' to get past a gate is what promotion review exists to catch.
1203
+ // 'internal' to get past a gate is a review failure at admission.
1159
1204
  refund_payment: { effect: 'write', sensitivity: 'financial' },
1160
1205
  },
1161
1206
  },
1162
1207
  ```
1163
1208
 
1164
1209
  `effect` is `'read' | 'write'`. `sensitivity` is `'public' | 'internal' | 'pii'
1165
- | 'financial'`. Both are optional per tool.
1210
+ | 'financial'`. Classify every tool: a field you leave out falls back to
1211
+ `effect: 'write'` and `sensitivity: 'pii'`, and a tool treated that way will not
1212
+ run in a team workflow.
1166
1213
 
1167
1214
  The keys of `proposes.tools` must match declared tool names exactly — a
1168
1215
  proposal for a tool you did not declare is a validation error, so a rename
1169
1216
  cannot leave a stale classification behind.
1170
1217
 
1171
- Why it matters: an undeclared `credentialModel` fails closed. The workflow
1172
- engine's tool allowlist treats it as not machine-executable, so a tool you
1173
- believe is workflow-callable will not be until this is declared and confirmed.
1218
+ What the host does with it:
1219
+
1220
+ - `credentialModel` decides which workflows may call your tools. Team workflows
1221
+ (a machine actor) admit `'account'` and `'source'`; personal workflows (the
1222
+ user) admit `'user'` and `'account'`. Undeclared means workflows cannot use
1223
+ your tools.
1224
+ - Team workflows never expose `pii` or `financial` tools, and a `write` tool
1225
+ needs an approval step in the workflow. Agents use their per-tool approval
1226
+ policy. Personal workflows and the chat assistant apply no sensitivity gate.
1227
+ - `ctx.storage.user` still throws for machine actors, and partner OAuth tokens
1228
+ stay account-scoped.
1174
1229
 
1175
1230
  ### `workspaceTargets`
1176
1231
 
@@ -1240,6 +1295,37 @@ Both throw if the id is not declared, and `invokeWebhook` throws if the payload
1240
1295
  fails `payloadSchema` — the handler never runs. The local transport records the
1241
1296
  signature skip as one `warn` line on `ctx.logs`.
1242
1297
 
1298
+ `invokeWebhook` never verifies a signature — that is the transport's job — so a
1299
+ wrong header name or signed-payload template in your declaration would first
1300
+ show up as rejected deliveries on the host. `verifyDeclaredWebhookSignature()`
1301
+ closes that gap: it runs the host's own verifier against one declaration. Sign
1302
+ a payload in the test the way the VENDOR documents it and assert the
1303
+ declaration accepts it:
1304
+
1305
+ ```ts
1306
+ import { createHmac } from 'node:crypto';
1307
+ import { verifyDeclaredWebhookSignature } from '@ekanos/sdk/testing';
1308
+
1309
+ const body = JSON.stringify(payload);
1310
+ const t = String(Math.floor(Date.now() / 1000));
1311
+ const v1 = createHmac('sha256', secret).update(`${t}.${body}`).digest('hex');
1312
+
1313
+ await expect(
1314
+ verifyDeclaredWebhookSignature(integration, 'payment-updated', {
1315
+ headers: { 'Stripe-Signature': `t=${t},v1=${v1}` },
1316
+ rawBody: body,
1317
+ secret,
1318
+ }),
1319
+ ).resolves.toEqual({ ok: true });
1320
+ ```
1321
+
1322
+ A failure reports the same reason the host would log (`missing-header`,
1323
+ `malformed-header`, `missing-timestamp`, `malformed-timestamp`,
1324
+ `stale-timestamp`, `missing-secret`, `mismatch`). An empty secret fails closed
1325
+ with `missing-secret`. It throws for an undeclared id and for a
1326
+ declaration with `signature: 'none'`. The verifier itself is also exported as
1327
+ `verifyWebhookSignature()` for a test that wants to drive it directly.
1328
+
1243
1329
  `invokeActivate()` does the same for `onActivate` — no payload or invocation to
1244
1330
  build, just the handler against the definition-derived context:
1245
1331
 
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;