@nodii/onboarding 0.1.0 → 0.2.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 (56) hide show
  1. package/README.md +46 -0
  2. package/dist/{chunk-KQTS47FC.js → chunk-A3DE2IAH.js} +30 -5
  3. package/dist/chunk-A3DE2IAH.js.map +1 -0
  4. package/dist/{chunk-U6AXKGYQ.js → chunk-DWFZA5T6.js} +10 -8
  5. package/dist/chunk-DWFZA5T6.js.map +1 -0
  6. package/dist/{chunk-F75ZJERM.js → chunk-KFHVZGX6.js} +34 -14
  7. package/dist/chunk-KFHVZGX6.js.map +1 -0
  8. package/dist/{chunk-U56GQITG.js → chunk-NDSBIB3N.js} +25 -5
  9. package/dist/chunk-NDSBIB3N.js.map +1 -0
  10. package/dist/chunk-OLWR5COK.js +10 -0
  11. package/dist/chunk-OLWR5COK.js.map +1 -0
  12. package/dist/{chunk-IPAKPLIF.js → chunk-OVULPWNZ.js} +341 -80
  13. package/dist/chunk-OVULPWNZ.js.map +1 -0
  14. package/dist/{chunk-LVJM4CTG.js → chunk-T7EWURTF.js} +34 -19
  15. package/dist/chunk-T7EWURTF.js.map +1 -0
  16. package/dist/{chunk-BMQINH6O.js → chunk-UHOFGWI7.js} +5 -4
  17. package/dist/chunk-UHOFGWI7.js.map +1 -0
  18. package/dist/{chunk-SIMLMQPT.js → chunk-WKN4U7QF.js} +3 -5
  19. package/dist/chunk-WKN4U7QF.js.map +1 -0
  20. package/dist/{chunk-AZE3NPSY.js → chunk-ZR6X3GSM.js} +3 -3
  21. package/dist/{chunk-AZE3NPSY.js.map → chunk-ZR6X3GSM.js.map} +1 -1
  22. package/dist/{client-CqE2dF9A.d.ts → client-NvqPpfQP.d.ts} +1 -1
  23. package/dist/consent/index.d.ts +3 -3
  24. package/dist/consent/index.js +2 -2
  25. package/dist/{controller-BQb62D0N.d.ts → controller-BLH3wnxl.d.ts} +1 -1
  26. package/dist/dynamic/index.d.ts +13 -5
  27. package/dist/dynamic/index.js +4 -3
  28. package/dist/form/index.d.ts +12 -4
  29. package/dist/form/index.js +2 -2
  30. package/dist/{index-Dqfa890U.d.ts → index-BMSbQGWP.d.ts} +23 -3
  31. package/dist/index.d.ts +8 -8
  32. package/dist/index.js +9 -8
  33. package/dist/{model-mnjAEtKe.d.ts → model-BI_I-D_V.d.ts} +16 -2
  34. package/dist/{model-ESLSEbi0.d.ts → model-CEwvHlWA.d.ts} +14 -1
  35. package/dist/render-EWQXF73I.js +5 -0
  36. package/dist/{render-DC7P2BGQ.js.map → render-EWQXF73I.js.map} +1 -1
  37. package/dist/{schema-BrXb9gnh.d.ts → schema-BrtjpXfL.d.ts} +0 -10
  38. package/dist/{signup-client-BL5DfarJ.d.ts → signup-client-CS95nkVO.d.ts} +4 -4
  39. package/dist/spec/index.d.ts +3 -3
  40. package/dist/spec/index.js +2 -2
  41. package/dist/submit/index.d.ts +5 -5
  42. package/dist/submit/index.js +3 -2
  43. package/dist/viewer/index.d.ts +3 -3
  44. package/dist/viewer/index.js +1 -1
  45. package/dist/widget/index.d.ts +61 -22
  46. package/dist/widget/index.js +9 -8
  47. package/package.json +1 -1
  48. package/dist/chunk-BMQINH6O.js.map +0 -1
  49. package/dist/chunk-F75ZJERM.js.map +0 -1
  50. package/dist/chunk-IPAKPLIF.js.map +0 -1
  51. package/dist/chunk-KQTS47FC.js.map +0 -1
  52. package/dist/chunk-LVJM4CTG.js.map +0 -1
  53. package/dist/chunk-SIMLMQPT.js.map +0 -1
  54. package/dist/chunk-U56GQITG.js.map +0 -1
  55. package/dist/chunk-U6AXKGYQ.js.map +0 -1
  56. package/dist/render-DC7P2BGQ.js +0 -5
@@ -340,7 +340,8 @@ declare const onboardingSpecSchema: z.ZodObject<{
340
340
  title: z.ZodString;
341
341
  versionNo: z.ZodNumber;
342
342
  contentHash: z.ZodString;
343
- downloadUrl: z.ZodString;
343
+ downloadUrl: z.ZodEffects<z.ZodString, string, string>;
344
+ contentType: z.ZodOptional<z.ZodString>;
344
345
  acceptanceLevel: z.ZodEnum<["checkbox", "scroll_then_accept", "open_then_accept"]>;
345
346
  required: z.ZodBoolean;
346
347
  }, "strict", z.ZodTypeAny, {
@@ -353,6 +354,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
353
354
  downloadUrl: string;
354
355
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
355
356
  required: boolean;
357
+ contentType?: string | undefined;
356
358
  }, {
357
359
  legalDocumentId: string;
358
360
  docType: "custom" | "tos" | "privacy" | "dpa";
@@ -363,6 +365,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
363
365
  downloadUrl: string;
364
366
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
365
367
  required: boolean;
368
+ contentType?: string | undefined;
366
369
  }>, {
367
370
  legalDocumentId: string;
368
371
  docType: "custom" | "tos" | "privacy" | "dpa";
@@ -373,6 +376,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
373
376
  downloadUrl: string;
374
377
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
375
378
  required: boolean;
379
+ contentType?: string | undefined;
376
380
  }, {
377
381
  legalDocumentId: string;
378
382
  docType: "custom" | "tos" | "privacy" | "dpa";
@@ -383,6 +387,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
383
387
  downloadUrl: string;
384
388
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
385
389
  required: boolean;
390
+ contentType?: string | undefined;
386
391
  }>, "many">;
387
392
  }, "strict", z.ZodTypeAny, {
388
393
  documents: {
@@ -395,6 +400,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
395
400
  downloadUrl: string;
396
401
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
397
402
  required: boolean;
403
+ contentType?: string | undefined;
398
404
  }[];
399
405
  }, {
400
406
  documents: {
@@ -407,6 +413,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
407
413
  downloadUrl: string;
408
414
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
409
415
  required: boolean;
416
+ contentType?: string | undefined;
410
417
  }[];
411
418
  }>, {
412
419
  documents: {
@@ -419,6 +426,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
419
426
  downloadUrl: string;
420
427
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
421
428
  required: boolean;
429
+ contentType?: string | undefined;
422
430
  }[];
423
431
  }, {
424
432
  documents: {
@@ -431,6 +439,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
431
439
  downloadUrl: string;
432
440
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
433
441
  required: boolean;
442
+ contentType?: string | undefined;
434
443
  }[];
435
444
  }>>;
436
445
  }, "strip", z.ZodTypeAny, {
@@ -480,6 +489,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
480
489
  downloadUrl: string;
481
490
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
482
491
  required: boolean;
492
+ contentType?: string | undefined;
483
493
  }[];
484
494
  } | undefined;
485
495
  }, {
@@ -529,6 +539,7 @@ declare const onboardingSpecSchema: z.ZodObject<{
529
539
  downloadUrl: string;
530
540
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
531
541
  required: boolean;
542
+ contentType?: string | undefined;
532
543
  }[];
533
544
  } | undefined;
534
545
  }>;
@@ -584,6 +595,7 @@ declare function validateOnboardingSpec(value: unknown): z.SafeParseReturnType<{
584
595
  downloadUrl: string;
585
596
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
586
597
  required: boolean;
598
+ contentType?: string | undefined;
587
599
  }[];
588
600
  } | undefined;
589
601
  }, {
@@ -633,6 +645,7 @@ declare function validateOnboardingSpec(value: unknown): z.SafeParseReturnType<{
633
645
  downloadUrl: string;
634
646
  acceptanceLevel: "checkbox" | "scroll_then_accept" | "open_then_accept";
635
647
  required: boolean;
648
+ contentType?: string | undefined;
636
649
  }[];
637
650
  } | undefined;
638
651
  }>;
@@ -0,0 +1,5 @@
1
+ export { _pickPdfWorkerSrc, _resetPdfWorkerState, renderConsentDocument } from './chunk-T7EWURTF.js';
2
+ import './chunk-SEKNVHD3.js';
3
+ import './chunk-XJTJPBJB.js';
4
+ //# sourceMappingURL=render-EWQXF73I.js.map
5
+ //# sourceMappingURL=render-EWQXF73I.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"render-DC7P2BGQ.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"render-EWQXF73I.js"}
@@ -51,7 +51,6 @@ declare const contactFormSchema: z.ZodObject<{
51
51
  privacyNoticeAcknowledged: z.ZodEffects<z.ZodBoolean, boolean, boolean>;
52
52
  marketingOptIn: z.ZodBoolean;
53
53
  intakeKind: z.ZodEnum<["contact", "enquiry"]>;
54
- website: z.ZodOptional<z.ZodString>;
55
54
  }, "strip", z.ZodTypeAny, {
56
55
  companyName: string;
57
56
  contactName: string;
@@ -70,7 +69,6 @@ declare const contactFormSchema: z.ZodObject<{
70
69
  landingPage?: string | undefined;
71
70
  button?: string | undefined;
72
71
  } | undefined;
73
- website?: string | undefined;
74
72
  }, {
75
73
  companyName: string;
76
74
  contactName: string;
@@ -89,7 +87,6 @@ declare const contactFormSchema: z.ZodObject<{
89
87
  landingPage?: string | undefined;
90
88
  button?: string | undefined;
91
89
  } | undefined;
92
- website?: string | undefined;
93
90
  }>;
94
91
  type ContactFormValues = z.infer<typeof contactFormSchema>;
95
92
  /**
@@ -128,7 +125,6 @@ declare const leadsWireSchema: z.ZodObject<{
128
125
  privacyNoticeAcknowledged: z.ZodEffects<z.ZodBoolean, boolean, boolean>;
129
126
  marketingOptIn: z.ZodBoolean;
130
127
  intakeKind: z.ZodEnum<["contact", "enquiry"]>;
131
- website: z.ZodOptional<z.ZodString>;
132
128
  } & {
133
129
  botToken: z.ZodOptional<z.ZodString>;
134
130
  }, "strict", z.ZodTypeAny, {
@@ -149,7 +145,6 @@ declare const leadsWireSchema: z.ZodObject<{
149
145
  landingPage?: string | undefined;
150
146
  button?: string | undefined;
151
147
  } | undefined;
152
- website?: string | undefined;
153
148
  botToken?: string | undefined;
154
149
  }, {
155
150
  companyName: string;
@@ -169,7 +164,6 @@ declare const leadsWireSchema: z.ZodObject<{
169
164
  landingPage?: string | undefined;
170
165
  button?: string | undefined;
171
166
  } | undefined;
172
- website?: string | undefined;
173
167
  botToken?: string | undefined;
174
168
  }>;
175
169
  type LeadsWirePayload = z.infer<typeof leadsWireSchema>;
@@ -195,7 +189,6 @@ declare function validateContactForm(values: unknown): z.SafeParseReturnType<{
195
189
  landingPage?: string | undefined;
196
190
  button?: string | undefined;
197
191
  } | undefined;
198
- website?: string | undefined;
199
192
  }, {
200
193
  companyName: string;
201
194
  contactName: string;
@@ -214,7 +207,6 @@ declare function validateContactForm(values: unknown): z.SafeParseReturnType<{
214
207
  landingPage?: string | undefined;
215
208
  button?: string | undefined;
216
209
  } | undefined;
217
- website?: string | undefined;
218
210
  }>;
219
211
  /**
220
212
  * Validate the full wire payload (including botToken) before submission.
@@ -240,7 +232,6 @@ declare function validateLeadsPayload(payload: unknown): z.SafeParseReturnType<{
240
232
  landingPage?: string | undefined;
241
233
  button?: string | undefined;
242
234
  } | undefined;
243
- website?: string | undefined;
244
235
  botToken?: string | undefined;
245
236
  }, {
246
237
  companyName: string;
@@ -260,7 +251,6 @@ declare function validateLeadsPayload(payload: unknown): z.SafeParseReturnType<{
260
251
  landingPage?: string | undefined;
261
252
  button?: string | undefined;
262
253
  } | undefined;
263
- website?: string | undefined;
264
254
  botToken?: string | undefined;
265
255
  }>;
266
256
 
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod/v3';
2
- import { T as TerminalStage } from './model-ESLSEbi0.js';
3
- import { e as ConsentSubmitDocument } from './controller-BQb62D0N.js';
4
- import { C as ContactFormValues } from './schema-BrXb9gnh.js';
2
+ import { T as TerminalStage } from './model-CEwvHlWA.js';
3
+ import { e as ConsentSubmitDocument } from './controller-BLH3wnxl.js';
4
+ import { C as ContactFormValues } from './schema-BrtjpXfL.js';
5
5
 
6
6
  /**
7
7
  * Submit client for POST /api/v1/public/onboarding/leads.
@@ -258,7 +258,7 @@ declare const signupWireSchema: z.ZodObject<{
258
258
  displayName?: string | undefined;
259
259
  }>;
260
260
  organisationName: z.ZodOptional<z.ZodString>;
261
- subdomain: z.ZodString;
261
+ subdomain: z.ZodEffects<z.ZodString, string, string>;
262
262
  }, "strict", z.ZodTypeAny, {
263
263
  specVersion: string;
264
264
  credentials: {
@@ -1,6 +1,6 @@
1
- export { F as FIELD_KEY_PATTERN, O as OnboarderTier, a as OnboardingSpec, P as PiiClass, S as SpecField, b as SpecStage, c as SpecStep, T as TerminalStage, o as onboardingSpecSchema, s as specFieldSchema, d as specStepSchema, v as validateOnboardingSpec } from '../model-ESLSEbi0.js';
2
- export { A as ACCEPTANCE_LEVELS, a as AcceptanceLevel, C as ConsentDocType, b as ConsentDocument, c as ConsentPack, D as DOC_TYPES, d as acceptanceLevelSchema, e as consentDocKey, f as consentDocumentSchema, g as consentPackSchema } from '../model-mnjAEtKe.js';
3
- export { O as OnboardingSpecCache, S as SpecFetchOptions, a as SpecFieldDiff, d as defaultSpecCache, b as diffSpecFields, f as fetchOnboardingSpec } from '../client-CqE2dF9A.js';
1
+ export { F as FIELD_KEY_PATTERN, O as OnboarderTier, a as OnboardingSpec, P as PiiClass, S as SpecField, b as SpecStage, c as SpecStep, T as TerminalStage, o as onboardingSpecSchema, s as specFieldSchema, d as specStepSchema, v as validateOnboardingSpec } from '../model-CEwvHlWA.js';
2
+ export { A as ACCEPTANCE_LEVELS, a as AcceptanceLevel, C as ConsentDocType, b as ConsentDocument, c as ConsentPack, D as DOC_TYPES, d as acceptanceLevelSchema, e as consentDocKey, f as consentDocumentSchema, g as consentPackSchema } from '../model-BI_I-D_V.js';
3
+ export { O as OnboardingSpecCache, S as SpecFetchOptions, a as SpecFieldDiff, d as defaultSpecCache, b as diffSpecFields, f as fetchOnboardingSpec } from '../client-NvqPpfQP.js';
4
4
  import 'zod/v3';
5
5
 
6
6
  interface PinStateResult {
@@ -1,5 +1,5 @@
1
- export { FIELD_KEY_PATTERN, OnboardingSpecCache, defaultSpecCache, diffSpecFields, fetchOnboardingSpec, fetchPinState, onboardingSpecSchema, specFieldSchema, specStepSchema, validateOnboardingSpec } from '../chunk-F75ZJERM.js';
2
- export { ACCEPTANCE_LEVELS, DOC_TYPES, acceptanceLevelSchema, consentDocKey, consentDocumentSchema, consentPackSchema } from '../chunk-U56GQITG.js';
1
+ export { FIELD_KEY_PATTERN, OnboardingSpecCache, defaultSpecCache, diffSpecFields, fetchOnboardingSpec, fetchPinState, onboardingSpecSchema, specFieldSchema, specStepSchema, validateOnboardingSpec } from '../chunk-KFHVZGX6.js';
2
+ export { ACCEPTANCE_LEVELS, DOC_TYPES, acceptanceLevelSchema, consentDocKey, consentDocumentSchema, consentPackSchema } from '../chunk-NDSBIB3N.js';
3
3
  import '../chunk-XJTJPBJB.js';
4
4
  //# sourceMappingURL=index.js.map
5
5
  //# sourceMappingURL=index.js.map
@@ -1,10 +1,10 @@
1
- export { B as BotTokenSource, L as LeadsSubmitOptions, a as LeadsSubmitResult, S as SignupSubmitBody, b as SignupSubmitOptions, c as SignupSubmitResult, d as SignupWirePayload, e as computePayloadFingerprint, f as computeSignupFingerprint, g as consentSubmitDocumentSchema, h as generateIdempotencyKey, r as resolveBotToken, s as signupWireSchema, i as submitLead, j as submitSignup } from '../signup-client-BL5DfarJ.js';
1
+ export { B as BotTokenSource, L as LeadsSubmitOptions, a as LeadsSubmitResult, S as SignupSubmitBody, b as SignupSubmitOptions, c as SignupSubmitResult, d as SignupWirePayload, e as computePayloadFingerprint, f as computeSignupFingerprint, g as consentSubmitDocumentSchema, h as generateIdempotencyKey, r as resolveBotToken, s as signupWireSchema, i as submitLead, j as submitSignup } from '../signup-client-CS95nkVO.js';
2
2
  export { O as OnboardingBotChallengeError, a as OnboardingConflictError, b as OnboardingConsentIncompleteError, c as OnboardingConsentStaleError, d as OnboardingError, e as OnboardingErrorKind, f as OnboardingNetworkError, g as OnboardingRateLimitError, h as OnboardingServerError, i as OnboardingSpecInvalidError, j as OnboardingSpecStaleError, k as OnboardingSubdomainTakenError, l as OnboardingValidationError, S as StaleConsentDocument } from '../errors-vJkye4a0.js';
3
3
  import 'zod/v3';
4
- import '../model-ESLSEbi0.js';
5
- import '../controller-BQb62D0N.js';
6
- import '../model-mnjAEtKe.js';
7
- import '../schema-BrXb9gnh.js';
4
+ import '../model-CEwvHlWA.js';
5
+ import '../controller-BLH3wnxl.js';
6
+ import '../model-BI_I-D_V.js';
7
+ import '../schema-BrtjpXfL.js';
8
8
 
9
9
  /**
10
10
  * Shared HTTP helper: fetch bounded by a timeout via AbortController.
@@ -1,6 +1,7 @@
1
1
  import '../chunk-Z4PA6C2Z.js';
2
- export { computePayloadFingerprint, computeSignupFingerprint, consentSubmitDocumentSchema, generateIdempotencyKey, resolveBotToken, signupWireSchema, submitLead, submitSignup } from '../chunk-BMQINH6O.js';
3
- import '../chunk-SIMLMQPT.js';
2
+ export { computePayloadFingerprint, computeSignupFingerprint, consentSubmitDocumentSchema, generateIdempotencyKey, resolveBotToken, signupWireSchema, submitLead, submitSignup } from '../chunk-UHOFGWI7.js';
3
+ import '../chunk-WKN4U7QF.js';
4
+ import '../chunk-OLWR5COK.js';
4
5
  export { DEFAULT_REQUEST_TIMEOUT_MS, OnboardingBotChallengeError, OnboardingConflictError, OnboardingConsentIncompleteError, OnboardingConsentStaleError, OnboardingError, OnboardingNetworkError, OnboardingRateLimitError, OnboardingServerError, OnboardingSpecInvalidError, OnboardingSpecStaleError, OnboardingSubdomainTakenError, OnboardingValidationError, fetchWithTimeout, withTimeout } from '../chunk-XJTJPBJB.js';
5
6
  //# sourceMappingURL=index.js.map
6
7
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,4 @@
1
- export { C as ConsentDocumentViewerOptions, a as ConsentDocumentViewerResult, b as _resetPdfWorkerState, r as renderConsentDocument } from '../index-Dqfa890U.js';
2
- import '../controller-BQb62D0N.js';
3
- import '../model-mnjAEtKe.js';
1
+ export { C as ConsentDocumentViewerOptions, a as ConsentDocumentViewerResult, b as _resetPdfWorkerState, r as renderConsentDocument } from '../index-BMSbQGWP.js';
2
+ import '../controller-BLH3wnxl.js';
3
+ import '../model-BI_I-D_V.js';
4
4
  import 'zod/v3';
@@ -1,4 +1,4 @@
1
- export { _resetPdfWorkerState, renderConsentDocument } from '../chunk-LVJM4CTG.js';
1
+ export { _resetPdfWorkerState, renderConsentDocument } from '../chunk-T7EWURTF.js';
2
2
  import '../chunk-SEKNVHD3.js';
3
3
  import '../chunk-XJTJPBJB.js';
4
4
  //# sourceMappingURL=index.js.map
@@ -1,25 +1,34 @@
1
- import { a as OnboardingSpec } from '../model-ESLSEbi0.js';
2
- import { O as OnboardingSpecCache } from '../client-CqE2dF9A.js';
3
- import { c as SignupSubmitResult } from '../signup-client-BL5DfarJ.js';
4
- import { _ as ___viewer_render_js } from '../index-Dqfa890U.js';
5
- import { C as ConsentController, c as ConsentState } from '../controller-BQb62D0N.js';
1
+ import { C as ContactFormValues } from '../schema-BrtjpXfL.js';
2
+ import { a as OnboardingSpec } from '../model-CEwvHlWA.js';
3
+ import { O as OnboardingSpecCache } from '../client-NvqPpfQP.js';
4
+ import { c as SignupSubmitResult } from '../signup-client-CS95nkVO.js';
5
+ import { _ as ___viewer_render_js } from '../index-BMSbQGWP.js';
6
+ import { C as ConsentController, c as ConsentState } from '../controller-BLH3wnxl.js';
6
7
  import 'zod/v3';
7
- import '../schema-BrXb9gnh.js';
8
- import '../model-mnjAEtKe.js';
8
+ import '../model-BI_I-D_V.js';
9
9
 
10
10
  /**
11
11
  * Default vanilla-TS contact form widget.
12
12
  *
13
13
  * Renders a form into a host element and handles the full lifecycle:
14
- * 1. Render fields (name, email, phone, message)
14
+ * 1. Render the D1165 lead fields: company, contact name, email, phone,
15
+ * message, the interest and team-size choices, the privacy-notice
16
+ * acknowledgement and the marketing opt-in
15
17
  * 2. Client-side validation on submit
16
- * 3. reCAPTCHA token acquisition (D537)
18
+ * 3. reCAPTCHA token acquisition (D537) — only while a challenge is in
19
+ * force (D1444: an onboarder can switch the bot check off)
17
20
  * 4. POST to /api/v1/public/onboarding/leads via submit client
18
21
  * 5. Retry on 5xx with SAME idempotency key
19
22
  * 6. Show success or error state
20
23
  *
24
+ * The field SET is fixed (it is the `/leads` wire contract); the choices
25
+ * inside it are the onboarder's. Pass the spec from `fetchOnboardingSpec`
26
+ * and the interests, team sizes, privacy-notice link and bot-challenge
27
+ * siteKey all come from it.
28
+ *
21
29
  * No React or framework required. CSS via injectWidgetStyles().
22
30
  */
31
+
23
32
  interface ContactFormMountOptions {
24
33
  /**
25
34
  * The onboarder identifier (slug or UUID).
@@ -28,10 +37,7 @@ interface ContactFormMountOptions {
28
37
  * trust/scoping is derived server-side from the request Host at the edge
29
38
  * ("onboarder derived from the bundle, never client-supplied", D540 §1);
30
39
  * a client-supplied identifier is never trusted and therefore never sent.
31
- *
32
- * This option is reserved for Phase 2, where it becomes the client-side
33
- * cache key for the spec-fetch (`(onboarder, specVersion)` per D540 §3).
34
- * In Phase 1 it has no runtime effect.
40
+ * It is the client-side spec-cache key (`fetchOnboardingSpec`).
35
41
  */
36
42
  onboarder: string;
37
43
  /**
@@ -40,15 +46,45 @@ interface ContactFormMountOptions {
40
46
  */
41
47
  baseUrl: string;
42
48
  /**
43
- * reCAPTCHA v3 site key. Obtained from the spec endpoint (D540 §1)
44
- * or passed directly in Phase 1 before spec-fetch is wired.
49
+ * The onboarder's spec, from `fetchOnboardingSpec`. Supplies the interest
50
+ * and team-size choices (`forms.contact`), the field labels, the
51
+ * privacy-notice link (the ConsentPack's privacy document) and the
52
+ * bot-challenge siteKey. Without it the widget renders the text fields and
53
+ * the two checkboxes only.
54
+ *
55
+ * The widget draws a fixed set of fields: company, contact name and email
56
+ * are always required, and the spec's `required` flag is honoured for the
57
+ * interest choice only. Phone, team size and message are always optional
58
+ * here (as the platform spec publishes them), and the address fields are
59
+ * not drawn. An onboarder that requires more builds its own form on the
60
+ * headless `@nodii/onboarding/form` and `/submit` modules.
45
61
  */
46
- siteKey?: string;
62
+ spec?: OnboardingSpec;
47
63
  /**
48
- * Which form to render. Phase 1 supports "contact" only.
49
- * Phase 2 will add "signup" (dynamic stepper from spec).
64
+ * reCAPTCHA v3 site key override. Normally taken from `spec.botChallenge`;
65
+ * with neither, no token is requested and Google is never loaded (D1444).
50
66
  */
67
+ siteKey?: string;
68
+ /** Which form to render. "signup" is `mountSignupForm`. */
51
69
  form: "contact";
70
+ /**
71
+ * Which door this form is (D1165 §1). Default "enquiry": what every lead
72
+ * from this widget carried before 0.2.0, and the headless form's default.
73
+ */
74
+ intakeKind?: ContactFormValues["intakeKind"];
75
+ /** Campaign tags, referring site, landing page, button — stamped on the lead. */
76
+ attribution?: ContactFormValues["attribution"];
77
+ /** Privacy-notice link override (defaults to the spec's privacy document). */
78
+ privacyUrl?: string;
79
+ /**
80
+ * How often a mounted form re-reads the spec to keep the privacy-notice link
81
+ * live, in ms (default 5 minutes). The spec's link is the ConsentPack's
82
+ * presigned download URL, which expires (D543 §4: ten minutes on dev), and
83
+ * a visitor can sit on a form longer than that. Applies only when the link
84
+ * comes from `spec`; 0 turns it off. Reads happen only while the form is in
85
+ * the document; call `unmount()` to stop the timer.
86
+ */
87
+ specRefreshMs?: number;
52
88
  /** Called after a successful lead submission. */
53
89
  onSuccess?: () => void;
54
90
  /** Called on non-retryable errors (bot challenge, rate limit). */
@@ -76,12 +112,15 @@ interface MountedWidget {
76
112
  *
77
113
  * @example
78
114
  * ```ts
115
+ * import { fetchOnboardingSpec } from "@nodii/onboarding/spec";
79
116
  * import { OnboardingWidget } from "@nodii/onboarding/widget";
80
- * const widget = OnboardingWidget.mount(document.getElementById("signup"), {
117
+ * const baseUrl = "https://api.acme.nucleus-cloud.in";
118
+ * const spec = await fetchOnboardingSpec({ baseUrl, onboarder: "acme-corp" });
119
+ * OnboardingWidget.mount(document.getElementById("contact")!, {
81
120
  * onboarder: "acme-corp",
82
121
  * form: "contact",
83
- * baseUrl: "https://api.acme.nucleus-cloud.in",
84
- * siteKey: "6Lc...",
122
+ * baseUrl,
123
+ * spec,
85
124
  * });
86
125
  * ```
87
126
  */
@@ -165,7 +204,7 @@ declare function mountSignupForm(host: HTMLElement, options: SignupFormMountOpti
165
204
  * The widget scopes all styles under `.nodii-onboarding-widget` to avoid
166
205
  * leaking into the host page.
167
206
  */
168
- declare const DEFAULT_WIDGET_CSS = "\n.nodii-onboarding-widget {\n --_font: var(--nodii-onboarding-font-family, system-ui, -apple-system, sans-serif);\n --_primary: var(--nodii-onboarding-primary-color, #2563eb);\n --_primary-hover: var(--nodii-onboarding-primary-hover, #1d4ed8);\n --_error: var(--nodii-onboarding-error-color, #dc2626);\n --_border: var(--nodii-onboarding-border-color, #d1d5db);\n --_input-bg: var(--nodii-onboarding-input-bg, #fff);\n --_label: var(--nodii-onboarding-label-color, #374151);\n --_radius: var(--nodii-onboarding-radius, 6px);\n --_spacing: var(--nodii-onboarding-spacing, 16px);\n\n font-family: var(--_font);\n box-sizing: border-box;\n}\n\n.nodii-onboarding-widget *,\n.nodii-onboarding-widget *::before,\n.nodii-onboarding-widget *::after {\n box-sizing: inherit;\n}\n\n.nodii-onboarding-field {\n display: flex;\n flex-direction: column;\n gap: calc(var(--_spacing) * 0.25);\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-label {\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--_label);\n}\n\n.nodii-onboarding-label.required::after {\n content: \" *\";\n color: var(--_error);\n}\n\n.nodii-onboarding-input,\n.nodii-onboarding-textarea {\n width: 100%;\n padding: calc(var(--_spacing) * 0.5) calc(var(--_spacing) * 0.75);\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_input-bg);\n font-family: var(--_font);\n font-size: 1rem;\n line-height: 1.5;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.nodii-onboarding-input:focus,\n.nodii-onboarding-textarea:focus {\n border-color: var(--_primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_primary) 20%, transparent);\n}\n\n.nodii-onboarding-input.has-error,\n.nodii-onboarding-textarea.has-error {\n border-color: var(--_error);\n}\n\n.nodii-onboarding-textarea {\n min-height: 100px;\n resize: vertical;\n}\n\n.nodii-onboarding-field-error {\n font-size: 0.75rem;\n color: var(--_error);\n margin-top: 2px;\n}\n\n.nodii-onboarding-server-error {\n padding: calc(var(--_spacing) * 0.75);\n border-radius: var(--_radius);\n background: color-mix(in srgb, var(--_error) 8%, transparent);\n border: 1px solid color-mix(in srgb, var(--_error) 30%, transparent);\n color: var(--_error);\n font-size: 0.875rem;\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-success {\n padding: var(--_spacing);\n border-radius: var(--_radius);\n background: #f0fdf4;\n border: 1px solid #bbf7d0;\n color: #15803d;\n font-size: 0.9375rem;\n text-align: center;\n}\n\n.nodii-onboarding-submit-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n padding: calc(var(--_spacing) * 0.625) var(--_spacing);\n background: var(--_primary);\n color: #fff;\n border: none;\n border-radius: var(--_radius);\n font-family: var(--_font);\n font-size: 1rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.nodii-onboarding-submit-btn:hover:not(:disabled) {\n background: var(--_primary-hover);\n}\n\n.nodii-onboarding-submit-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.nodii-onboarding-spinner {\n display: inline-block;\n width: 1em;\n height: 1em;\n border: 2px solid rgba(255,255,255,0.4);\n border-top-color: #fff;\n border-radius: 50%;\n animation: nodii-spin 0.6s linear infinite;\n margin-right: 0.5em;\n}\n\n@keyframes nodii-spin {\n to { transform: rotate(360deg); }\n}\n\n.nodii-onboarding-recaptcha-notice {\n font-size: 0.7rem;\n color: #6b7280;\n margin-top: calc(var(--_spacing) * 0.5);\n text-align: center;\n}\n\n.nodii-onboarding-recaptcha-notice a {\n color: inherit;\n}\n\n/* \u2500\u2500 dynamic stepper (signup form) \u2500\u2500 */\n\n.nodii-onboarding-steps {\n display: flex;\n gap: calc(var(--_spacing) * 0.5);\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-step-dot {\n flex: 1;\n height: 4px;\n border-radius: 2px;\n background: var(--_border);\n}\n\n.nodii-onboarding-step-dot.active {\n background: var(--_primary);\n}\n\n.nodii-onboarding-stage-title {\n font-size: 1.0625rem;\n font-weight: 600;\n color: var(--_label);\n margin: 0 0 var(--_spacing) 0;\n}\n\n.nodii-onboarding-nav {\n display: flex;\n gap: calc(var(--_spacing) * 0.5);\n margin-top: var(--_spacing);\n}\n\n.nodii-onboarding-back-btn {\n padding: calc(var(--_spacing) * 0.625) var(--_spacing);\n background: transparent;\n color: var(--_label);\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n font-family: var(--_font);\n font-size: 1rem;\n cursor: pointer;\n}\n\n.nodii-onboarding-stale-banner {\n padding: calc(var(--_spacing) * 0.75);\n border-radius: var(--_radius);\n background: #fffbeb;\n border: 1px solid #fcd34d;\n color: #92400e;\n font-size: 0.875rem;\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-consent-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--_spacing) * 0.5);\n margin-bottom: calc(var(--_spacing) * 0.5);\n font-size: 0.875rem;\n color: var(--_label);\n}\n\n.nodii-onboarding-consent-row input[type=\"checkbox\"] {\n margin-top: 3px;\n}\n\n.nodii-onboarding-account-section {\n border-top: 1px solid var(--_border);\n margin-top: var(--_spacing);\n padding-top: var(--_spacing);\n}\n\n.nodii-onboarding-loading {\n padding: var(--_spacing);\n text-align: center;\n color: var(--_label);\n}\n\n/* \u2500\u2500 D543 ConsentPack step (full-width, generous spacing) \u2500\u2500 */\n\n.nodii-onboarding-consent-stage {\n /* Full-bleed within the widget host \u2014 consent cards may span the page. */\n width: 100%;\n display: flex;\n flex-direction: column;\n gap: calc(var(--_spacing) * 2);\n margin: calc(var(--_spacing) * 1.5) 0;\n}\n\n.nodii-onboarding-consent-intro {\n font-size: 0.9375rem;\n color: var(--_label);\n margin: 0 0 calc(var(--_spacing) * 0.5) 0;\n line-height: 1.6;\n}\n\n.nodii-onboarding-consent-card {\n width: 100%;\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_input-bg);\n padding: calc(var(--_spacing) * 1.5);\n display: flex;\n flex-direction: column;\n gap: var(--_spacing);\n}\n\n.nodii-onboarding-consent-card.is-stale {\n border-color: #fcd34d;\n background: #fffbeb;\n}\n\n.nodii-onboarding-consent-card.has-error {\n border-color: var(--_error);\n}\n\n.nodii-onboarding-consent-title {\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--_label);\n margin: 0;\n}\n\n.nodii-onboarding-consent-stale-tag {\n font-size: 0.8125rem;\n font-weight: 500;\n color: #92400e;\n}\n\n.nodii-onboarding-consent-gate {\n width: 100%;\n}\n\n.nodii-onboarding-consent-scrollbox {\n width: 100%;\n height: 360px;\n overflow-y: auto;\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: #fff;\n position: relative;\n}\n\n.nodii-onboarding-consent-scrollbox:focus {\n outline: none;\n border-color: var(--_primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_primary) 20%, transparent);\n}\n\n/* Inline, same-origin document content (no iframe). Rendered by the default\n viewer: sanitized HTML/markdown, escaped plain text, or PDF.js canvases. */\n.nodii-onboarding-consent-doc-body {\n padding: var(--_spacing);\n font-size: 0.9375rem;\n line-height: 1.7;\n color: var(--_label);\n word-break: break-word;\n}\n\n.nodii-onboarding-consent-doc-body:not(.nodii-consent-pdf) {\n white-space: pre-wrap;\n}\n\n.nodii-onboarding-consent-doc-loading {\n padding: var(--_spacing);\n color: #6b7280;\n font-size: 0.875rem;\n}\n\n.nodii-consent-pdf {\n display: flex;\n flex-direction: column;\n gap: var(--_spacing);\n white-space: normal;\n}\n\n.nodii-consent-pdf-page {\n max-width: 100%;\n height: auto;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n}\n\n.nodii-onboarding-consent-note {\n font-size: 0.8125rem;\n color: #6b7280;\n margin: calc(var(--_spacing) * 0.5) 0 0 0;\n}\n\n/* A gated doc that could not be rendered inline: the gate stays unsatisfiable\n (accept disabled); we surface a clear error + Retry. NO click-to-satisfy. */\n.nodii-onboarding-consent-unrenderable {\n display: flex;\n flex-direction: column;\n gap: calc(var(--_spacing) * 0.5);\n padding: var(--_spacing);\n border: 1px solid color-mix(in srgb, var(--_error) 30%, transparent);\n border-radius: var(--_radius);\n background: color-mix(in srgb, var(--_error) 6%, transparent);\n}\n\n.nodii-onboarding-consent-retry {\n align-self: flex-start;\n padding: calc(var(--_spacing) * 0.5) var(--_spacing);\n background: transparent;\n color: var(--_primary);\n border: 1px solid var(--_primary);\n border-radius: var(--_radius);\n font-family: var(--_font);\n font-size: 0.875rem;\n cursor: pointer;\n}\n\n.nodii-onboarding-consent-open-row,\n.nodii-onboarding-consent-preview-row {\n display: flex;\n}\n\n.nodii-onboarding-consent-open {\n display: inline-flex;\n align-items: center;\n padding: calc(var(--_spacing) * 0.625) var(--_spacing);\n border: 1px solid var(--_primary);\n border-radius: var(--_radius);\n color: var(--_primary);\n text-decoration: none;\n font-size: 0.9375rem;\n font-weight: 500;\n}\n\n.nodii-onboarding-consent-open:hover {\n background: color-mix(in srgb, var(--_primary) 8%, transparent);\n}\n\n.nodii-onboarding-consent-preview {\n font-size: 0.875rem;\n color: var(--_primary);\n}\n\n.nodii-onboarding-consent-accept-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--_spacing) * 0.5);\n}\n\n.nodii-onboarding-consent-accept {\n margin-top: 3px;\n width: 1.125rem;\n height: 1.125rem;\n}\n\n.nodii-onboarding-consent-accept:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.nodii-onboarding-consent-accept-label {\n font-size: 0.9375rem;\n color: var(--_label);\n line-height: 1.5;\n}\n\n.nodii-onboarding-consent-required {\n color: var(--_error);\n}\n";
207
+ declare const DEFAULT_WIDGET_CSS = "\n.nodii-onboarding-widget {\n --_font: var(--nodii-onboarding-font-family, system-ui, -apple-system, sans-serif);\n --_primary: var(--nodii-onboarding-primary-color, #2563eb);\n --_primary-hover: var(--nodii-onboarding-primary-hover, #1d4ed8);\n --_error: var(--nodii-onboarding-error-color, #dc2626);\n --_border: var(--nodii-onboarding-border-color, #d1d5db);\n --_input-bg: var(--nodii-onboarding-input-bg, #fff);\n --_label: var(--nodii-onboarding-label-color, #374151);\n --_radius: var(--nodii-onboarding-radius, 6px);\n --_spacing: var(--nodii-onboarding-spacing, 16px);\n\n font-family: var(--_font);\n box-sizing: border-box;\n}\n\n.nodii-onboarding-widget *,\n.nodii-onboarding-widget *::before,\n.nodii-onboarding-widget *::after {\n box-sizing: inherit;\n}\n\n.nodii-onboarding-field {\n display: flex;\n flex-direction: column;\n gap: calc(var(--_spacing) * 0.25);\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-label {\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--_label);\n}\n\n.nodii-onboarding-label.required::after {\n content: \" *\";\n color: var(--_error);\n}\n\n.nodii-onboarding-input,\n.nodii-onboarding-textarea {\n width: 100%;\n padding: calc(var(--_spacing) * 0.5) calc(var(--_spacing) * 0.75);\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_input-bg);\n font-family: var(--_font);\n font-size: 1rem;\n line-height: 1.5;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.nodii-onboarding-input:focus,\n.nodii-onboarding-textarea:focus {\n border-color: var(--_primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_primary) 20%, transparent);\n}\n\n.nodii-onboarding-input.has-error,\n.nodii-onboarding-textarea.has-error {\n border-color: var(--_error);\n}\n\n.nodii-onboarding-textarea {\n min-height: 100px;\n resize: vertical;\n}\n\n.nodii-onboarding-field-error {\n font-size: 0.75rem;\n color: var(--_error);\n margin-top: 2px;\n}\n\n.nodii-onboarding-server-error {\n padding: calc(var(--_spacing) * 0.75);\n border-radius: var(--_radius);\n background: color-mix(in srgb, var(--_error) 8%, transparent);\n border: 1px solid color-mix(in srgb, var(--_error) 30%, transparent);\n color: var(--_error);\n font-size: 0.875rem;\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-success {\n padding: var(--_spacing);\n border-radius: var(--_radius);\n background: #f0fdf4;\n border: 1px solid #bbf7d0;\n color: #15803d;\n font-size: 0.9375rem;\n text-align: center;\n}\n\n.nodii-onboarding-submit-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n padding: calc(var(--_spacing) * 0.625) var(--_spacing);\n background: var(--_primary);\n color: #fff;\n border: none;\n border-radius: var(--_radius);\n font-family: var(--_font);\n font-size: 1rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.nodii-onboarding-submit-btn:hover:not(:disabled) {\n background: var(--_primary-hover);\n}\n\n.nodii-onboarding-submit-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.nodii-onboarding-spinner {\n display: inline-block;\n width: 1em;\n height: 1em;\n border: 2px solid rgba(255,255,255,0.4);\n border-top-color: #fff;\n border-radius: 50%;\n animation: nodii-spin 0.6s linear infinite;\n margin-right: 0.5em;\n}\n\n@keyframes nodii-spin {\n to { transform: rotate(360deg); }\n}\n\n.nodii-onboarding-recaptcha-notice {\n font-size: 0.7rem;\n color: #6b7280;\n margin-top: calc(var(--_spacing) * 0.5);\n text-align: center;\n}\n\n.nodii-onboarding-recaptcha-notice a {\n color: inherit;\n}\n\n/* \u2500\u2500 dynamic stepper (signup form) \u2500\u2500 */\n\n.nodii-onboarding-steps {\n display: flex;\n gap: calc(var(--_spacing) * 0.5);\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-step-dot {\n flex: 1;\n height: 4px;\n border-radius: 2px;\n background: var(--_border);\n}\n\n.nodii-onboarding-step-dot.active {\n background: var(--_primary);\n}\n\n.nodii-onboarding-stage-title {\n font-size: 1.0625rem;\n font-weight: 600;\n color: var(--_label);\n margin: 0 0 var(--_spacing) 0;\n}\n\n.nodii-onboarding-nav {\n display: flex;\n gap: calc(var(--_spacing) * 0.5);\n margin-top: var(--_spacing);\n}\n\n.nodii-onboarding-back-btn {\n padding: calc(var(--_spacing) * 0.625) var(--_spacing);\n background: transparent;\n color: var(--_label);\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n font-family: var(--_font);\n font-size: 1rem;\n cursor: pointer;\n}\n\n.nodii-onboarding-stale-banner {\n padding: calc(var(--_spacing) * 0.75);\n border-radius: var(--_radius);\n background: #fffbeb;\n border: 1px solid #fcd34d;\n color: #92400e;\n font-size: 0.875rem;\n margin-bottom: var(--_spacing);\n}\n\nfieldset.nodii-onboarding-field {\n border: none;\n padding: 0;\n margin-inline: 0;\n}\n\n.nodii-onboarding-choices {\n display: flex;\n flex-wrap: wrap;\n gap: calc(var(--_spacing) * 0.5);\n}\n\n.nodii-onboarding-choice {\n display: inline-flex;\n align-items: center;\n gap: calc(var(--_spacing) * 0.375);\n padding: calc(var(--_spacing) * 0.375) calc(var(--_spacing) * 0.75);\n border: 1px solid var(--_border);\n border-radius: 999px;\n background: var(--_input-bg);\n font-size: 0.875rem;\n color: var(--_label);\n cursor: pointer;\n}\n\nfieldset.nodii-onboarding-field.has-error .nodii-onboarding-choice {\n border-color: var(--_error);\n}\n\n.nodii-onboarding-consent-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--_spacing) * 0.5);\n margin-bottom: calc(var(--_spacing) * 0.5);\n font-size: 0.875rem;\n color: var(--_label);\n}\n\n.nodii-onboarding-consent-row input[type=\"checkbox\"] {\n margin-top: 3px;\n}\n\n.nodii-onboarding-account-section {\n border-top: 1px solid var(--_border);\n margin-top: var(--_spacing);\n padding-top: var(--_spacing);\n}\n\n.nodii-onboarding-loading {\n padding: var(--_spacing);\n text-align: center;\n color: var(--_label);\n}\n\n/* \u2500\u2500 D543 ConsentPack step (full-width, generous spacing) \u2500\u2500 */\n\n.nodii-onboarding-consent-stage {\n /* Full-bleed within the widget host \u2014 consent cards may span the page. */\n width: 100%;\n display: flex;\n flex-direction: column;\n gap: calc(var(--_spacing) * 2);\n margin: calc(var(--_spacing) * 1.5) 0;\n}\n\n.nodii-onboarding-consent-intro {\n font-size: 0.9375rem;\n color: var(--_label);\n margin: 0 0 calc(var(--_spacing) * 0.5) 0;\n line-height: 1.6;\n}\n\n.nodii-onboarding-consent-card {\n width: 100%;\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_input-bg);\n padding: calc(var(--_spacing) * 1.5);\n display: flex;\n flex-direction: column;\n gap: var(--_spacing);\n}\n\n.nodii-onboarding-consent-card.is-stale {\n border-color: #fcd34d;\n background: #fffbeb;\n}\n\n.nodii-onboarding-consent-card.has-error {\n border-color: var(--_error);\n}\n\n.nodii-onboarding-consent-title {\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--_label);\n margin: 0;\n}\n\n.nodii-onboarding-consent-stale-tag {\n font-size: 0.8125rem;\n font-weight: 500;\n color: #92400e;\n}\n\n.nodii-onboarding-consent-gate {\n width: 100%;\n}\n\n.nodii-onboarding-consent-scrollbox {\n width: 100%;\n height: 360px;\n overflow-y: auto;\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: #fff;\n position: relative;\n}\n\n.nodii-onboarding-consent-scrollbox:focus {\n outline: none;\n border-color: var(--_primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_primary) 20%, transparent);\n}\n\n/* Inline, same-origin document content (no iframe). Rendered by the default\n viewer: sanitized HTML/markdown, escaped plain text, or PDF.js canvases. */\n.nodii-onboarding-consent-doc-body {\n padding: var(--_spacing);\n font-size: 0.9375rem;\n line-height: 1.7;\n color: var(--_label);\n word-break: break-word;\n}\n\n.nodii-onboarding-consent-doc-body:not(.nodii-consent-pdf) {\n white-space: pre-wrap;\n}\n\n.nodii-onboarding-consent-doc-loading {\n padding: var(--_spacing);\n color: #6b7280;\n font-size: 0.875rem;\n}\n\n.nodii-consent-pdf {\n display: flex;\n flex-direction: column;\n gap: var(--_spacing);\n white-space: normal;\n}\n\n.nodii-consent-pdf-page {\n max-width: 100%;\n height: auto;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n}\n\n.nodii-onboarding-consent-note {\n font-size: 0.8125rem;\n color: #6b7280;\n margin: calc(var(--_spacing) * 0.5) 0 0 0;\n}\n\n/* A gated doc that could not be rendered inline: the gate stays unsatisfiable\n (accept disabled); we surface a clear error + Retry. NO click-to-satisfy. */\n.nodii-onboarding-consent-unrenderable {\n display: flex;\n flex-direction: column;\n gap: calc(var(--_spacing) * 0.5);\n padding: var(--_spacing);\n border: 1px solid color-mix(in srgb, var(--_error) 30%, transparent);\n border-radius: var(--_radius);\n background: color-mix(in srgb, var(--_error) 6%, transparent);\n}\n\n.nodii-onboarding-consent-retry {\n align-self: flex-start;\n padding: calc(var(--_spacing) * 0.5) var(--_spacing);\n background: transparent;\n color: var(--_primary);\n border: 1px solid var(--_primary);\n border-radius: var(--_radius);\n font-family: var(--_font);\n font-size: 0.875rem;\n cursor: pointer;\n}\n\n.nodii-onboarding-consent-open-row,\n.nodii-onboarding-consent-preview-row {\n display: flex;\n}\n\n.nodii-onboarding-consent-open {\n display: inline-flex;\n align-items: center;\n padding: calc(var(--_spacing) * 0.625) var(--_spacing);\n border: 1px solid var(--_primary);\n border-radius: var(--_radius);\n color: var(--_primary);\n text-decoration: none;\n font-size: 0.9375rem;\n font-weight: 500;\n}\n\n.nodii-onboarding-consent-open:hover {\n background: color-mix(in srgb, var(--_primary) 8%, transparent);\n}\n\n.nodii-onboarding-consent-preview {\n font-size: 0.875rem;\n color: var(--_primary);\n}\n\n.nodii-onboarding-consent-accept-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--_spacing) * 0.5);\n}\n\n.nodii-onboarding-consent-accept {\n margin-top: 3px;\n width: 1.125rem;\n height: 1.125rem;\n}\n\n.nodii-onboarding-consent-accept:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.nodii-onboarding-consent-accept-label {\n font-size: 0.9375rem;\n color: var(--_label);\n line-height: 1.5;\n}\n\n.nodii-onboarding-consent-required {\n color: var(--_error);\n}\n";
169
208
  /**
170
209
  * Inject the default widget CSS once into the document head.
171
210
  * Idempotent — will not add a duplicate style tag.
@@ -1,11 +1,12 @@
1
- export { DEFAULT_WIDGET_CSS, OnboardingWidget, consentDocElementId, cssEscape, idSelector, injectWidgetStyles, mountContactForm, mountSignupForm, renderConsentStep, updateConsentStep } from '../chunk-IPAKPLIF.js';
2
- import '../chunk-U6AXKGYQ.js';
3
- import '../chunk-F75ZJERM.js';
4
- import '../chunk-KQTS47FC.js';
5
- import '../chunk-AZE3NPSY.js';
6
- import '../chunk-U56GQITG.js';
7
- import '../chunk-BMQINH6O.js';
8
- import '../chunk-SIMLMQPT.js';
1
+ export { DEFAULT_WIDGET_CSS, OnboardingWidget, consentDocElementId, cssEscape, idSelector, injectWidgetStyles, mountContactForm, mountSignupForm, renderConsentStep, updateConsentStep } from '../chunk-OVULPWNZ.js';
2
+ import '../chunk-DWFZA5T6.js';
3
+ import '../chunk-KFHVZGX6.js';
4
+ import '../chunk-A3DE2IAH.js';
5
+ import '../chunk-ZR6X3GSM.js';
6
+ import '../chunk-NDSBIB3N.js';
7
+ import '../chunk-UHOFGWI7.js';
8
+ import '../chunk-WKN4U7QF.js';
9
+ import '../chunk-OLWR5COK.js';
9
10
  import '../chunk-XJTJPBJB.js';
10
11
  import '../chunk-7NS7ENJV.js';
11
12
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodii/onboarding",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Browser SDK for the Nodii self-signup onboarding flow. Headless form state, reCAPTCHA integration, and a default themeable vanilla-TS widget. D540/D537 contract client.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/submit/client.ts","../src/submit/signup-client.ts"],"names":["MAX_ATTEMPTS","RETRY_DELAYS_MS","buildUrl","parseRetryAfter","sleep"],"mappings":"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","file":"chunk-BMQINH6O.js","sourcesContent":["/**\n * Submit client for POST /api/v1/public/onboarding/leads.\n *\n * Contract per D540 + hub REST interface 8686dd33:\n * - Idempotency-Key header REQUIRED (uuid v4, generated ONCE per logical\n * submission, REUSED on retries — same key = exactly-once delivery).\n * - Outgoing payload validated through leadsWireSchema (STRICT — unknown\n * keys rejected, matching additionalProperties:false) BEFORE any fetch.\n * - botToken in request body (D537).\n * - Response: { accepted: true } on 200/202 (empty/non-JSON 2xx body is\n * treated as acceptance; a JSON body that does not confirm acceptance\n * throws OnboardingServerError — see parseSuccessBody).\n * - 403 bot_challenge_failed → throw OnboardingBotChallengeError (no retry)\n * - 409 spec_stale (D540) → throw OnboardingSpecStaleError (no retry)\n * - 422 → throw OnboardingValidationError (no retry)\n * - 429 → throw OnboardingRateLimitError (no auto-retry)\n * - 5xx → retry with SAME idempotency key, capped backoff\n * - network error → retry with SAME idempotency key, capped backoff\n *\n * Max retries: 3 attempts total (1 initial + 2 retries).\n * Backoff: 1s, 2s (capped at 2 retries).\n */\n\nimport { validateLeadsPayload } from \"../form/schema.js\";\nimport type { ContactFormValues } from \"../form/schema.js\";\nimport {\n OnboardingBotChallengeError,\n OnboardingConflictError,\n OnboardingNetworkError,\n OnboardingRateLimitError,\n OnboardingServerError,\n OnboardingValidationError,\n} from \"./errors.js\";\nimport { DEFAULT_REQUEST_TIMEOUT_MS, fetchWithTimeout } from \"./http.js\";\n\nconst LEADS_PATH = \"/api/v1/public/onboarding/leads\";\nconst MAX_ATTEMPTS = 3;\nconst RETRY_DELAYS_MS = [1000, 2000] as const;\n\n/**\n * Resolve a bot token inside the retry loop, mapping ANY provider failure to\n * a typed OnboardingBotChallengeError. The provider is usually\n * executeRecaptcha, which throws a RecaptchaError if reCAPTCHA is unreachable\n * — that raw error must NOT escape submitLead/submitSignup (the @throws\n * contract is Onboarding* only) and must not silently burn the retry budget.\n */\nexport async function resolveBotTokenGuarded(\n source: BotTokenSource,\n): Promise<string> {\n try {\n return await resolveBotToken(source);\n } catch (err) {\n throw new OnboardingBotChallengeError(\n err instanceof Error\n ? `Could not obtain a bot-challenge token: ${err.message}`\n : \"Could not obtain a bot-challenge token.\",\n );\n }\n}\n\n/**\n * A bot token, or a provider that mints a FRESH one per attempt.\n *\n * reCAPTCHA tokens are SINGLE-USE — the server consumes them at siteverify.\n * If a 5xx happens AFTER the token was consumed, resending the same token on\n * retry gets 403. Pass a provider function to mint a fresh token per attempt;\n * a bare string is back-compat but the caller then OWNS that risk.\n */\nexport type BotTokenSource = string | (() => string | Promise<string>);\n\nexport async function resolveBotToken(source: BotTokenSource): Promise<string> {\n return typeof source === \"function\" ? source() : source;\n}\n\nexport interface LeadsSubmitOptions {\n baseUrl: string;\n /**\n * reCAPTCHA token (D537), or a provider called ONCE PER ATTEMPT to mint a\n * fresh token (recommended — tokens are single-use; a fixed string on a\n * retried request risks 403 after the first attempt consumed it).\n */\n botToken?: BotTokenSource;\n /**\n * Stable idempotency key for this submission. Generate ONCE with\n * `generateIdempotencyKey()` and pass the same key for retries.\n * The server chains this key through to D538 for exactly-once delivery.\n */\n idempotencyKey: string;\n /** Optional custom fetch implementation (defaults to global fetch). */\n fetch?: typeof globalThis.fetch;\n /**\n * Per-attempt request timeout in ms (default 15s). A stalled intake aborts\n * with OnboardingNetworkError so the widget re-enables instead of hanging.\n */\n timeoutMs?: number;\n /** Optional onAttempt callback for observability (attempt number, 1-based). */\n onAttempt?: (attempt: number) => void;\n}\n\nexport interface LeadsSubmitResult {\n accepted: true;\n}\n\n/**\n * Generate a stable idempotency key for one logical submission.\n * Uses the Web Crypto API (available in all modern browsers + Bun/Node >= 19).\n * Call ONCE per user submission, pass the SAME key on retries.\n */\nexport function generateIdempotencyKey(): string {\n // crypto.randomUUID() is available in all modern browsers, Bun, and Node >= 14.17\n return globalThis.crypto.randomUUID();\n}\n\nfunction buildUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n return `${base}${path}`;\n}\n\nfunction parseRetryAfter(response: Response): number | null {\n const header = response.headers.get(\"retry-after\");\n if (!header) return null;\n const seconds = Number.parseInt(header, 10);\n return Number.isNaN(seconds) ? null : seconds;\n}\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Compute a stable fingerprint of the outgoing lead payload, EXCLUDING\n * botToken (which is fresh per attempt by design).\n *\n * The widget uses this to decide whether a resubmission is a RETRY of the\n * same logical submission (same fingerprint → reuse the Idempotency-Key)\n * or a NEW logical submission (fingerprint changed because the user edited\n * fields → mint a NEW key). This mirrors the server's D538 payload-match\n * guard: same key + different payload is INVALID_ARGUMENT server-side.\n *\n * FNV-1a 32-bit over a stable JSON serialization of [name, email, phone,\n * message]. Collision risk is irrelevant here — a collision only means an\n * unnecessary key reuse across two submissions from the same browser tab.\n */\nexport function computePayloadFingerprint(values: ContactFormValues): string {\n const canonical = JSON.stringify([\n values.companyName,\n values.contactName,\n values.email,\n values.phone ?? null,\n values.message ?? null,\n [...(values.interestModules ?? [])].sort(),\n values.sizeBand ?? null,\n values.attribution ?? null,\n values.privacyNoticeAcknowledged,\n values.marketingOptIn,\n values.intakeKind,\n ]);\n let hash = 0x811c9dc5;\n for (let i = 0; i < canonical.length; i++) {\n hash ^= canonical.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0).toString(16).padStart(8, \"0\");\n}\n\n/**\n * Parse a success-status (200/202) response body.\n *\n * Documented behavior (one rule, tested):\n * - EMPTY body or NON-JSON body → treated as acceptance. The locked\n * contract response is `{accepted:true}` and the status is already\n * 2xx; load balancers / CDNs occasionally strip or replace bodies on\n * 202, and a 2xx from the intake IS the acceptance signal.\n * - A PARSEABLE JSON body that is not `{accepted: true}` (e.g.\n * `{accepted:false}` or an unrelated shape) → OnboardingServerError.\n * A server-signaled soft-rejection on a success status must NOT be\n * masked as success. Not auto-retried (the status was 2xx; the server\n * state is ambiguous — the caller decides).\n */\nasync function parseSuccessBody(\n response: Response,\n): Promise<LeadsSubmitResult> {\n let text: string;\n try {\n text = await response.text();\n } catch {\n // Unreadable body on a 2xx — acceptance per the rule above.\n return { accepted: true };\n }\n if (text.trim() === \"\") {\n return { accepted: true };\n }\n let json: unknown;\n try {\n json = JSON.parse(text);\n } catch {\n // Non-JSON body on a 2xx (LB/CDN interstitial) — acceptance.\n return { accepted: true };\n }\n if (\n typeof json === \"object\" &&\n json !== null &&\n (json as { accepted?: unknown }).accepted === true\n ) {\n return { accepted: true };\n }\n throw new OnboardingServerError(\n response.status,\n `Success status ${response.status} but response body did not confirm acceptance.`,\n );\n}\n\n/**\n * Submit a contact-form lead to the onboarding intake.\n *\n * The outgoing payload is validated through leadsWireSchema in STRICT mode\n * before any network I/O — unknown keys are rejected client-side, exactly\n * mirroring the server's additionalProperties:false.\n *\n * Handles retry semantics: 5xx and network errors are retried up to\n * MAX_ATTEMPTS total, always with the SAME idempotencyKey to guarantee\n * exactly-once server-side processing (D538 idempotency chain).\n *\n * 403, 409, 422 and 429 are NOT retried — caller handles them.\n *\n * @throws {OnboardingValidationError} if the payload fails the strict wire\n * schema (unknown keys, constraint violations) — thrown BEFORE any fetch\n * @throws {OnboardingBotChallengeError} on 403, or if the bot-token provider\n * throws (reCAPTCHA unreachable) on any attempt\n * @throws {OnboardingConflictError} on 409 (leads has no spec_stale concept)\n * @throws {OnboardingRateLimitError} on 429\n * @throws {OnboardingServerError} on 5xx after all retries exhausted, or on\n * a 2xx whose JSON body does not confirm acceptance\n * @throws {OnboardingNetworkError} on network failure OR timeout after all\n * retries exhausted\n * @throws {OnboardingValidationError} on 422 (server-side re-validation)\n */\nexport async function submitLead(\n values: ContactFormValues,\n options: LeadsSubmitOptions,\n): Promise<LeadsSubmitResult> {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const url = buildUrl(options.baseUrl, LEADS_PATH);\n\n // Normalize non-token fields once (empty optionals → omitted). The token\n // is attached PER ATTEMPT below so a retry can carry a FRESH single-use\n // token; the body is therefore serialized inside the loop.\n const base: Record<string, unknown> = { ...values };\n if (base[\"phone\"] === \"\" || base[\"phone\"] === undefined) {\n delete base[\"phone\"];\n }\n if (base[\"message\"] === \"\" || base[\"message\"] === undefined) {\n delete base[\"message\"];\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": options.idempotencyKey,\n };\n\n let lastError: unknown = null;\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n options.onAttempt?.(attempt);\n\n // Mint a fresh token per attempt (single-use tokens; see BotTokenSource).\n // Guarded: a provider failure (reCAPTCHA down) becomes a typed\n // OnboardingBotChallengeError, never a raw RecaptchaError escaping.\n const botToken =\n options.botToken === undefined\n ? undefined\n : await resolveBotTokenGuarded(options.botToken);\n\n // Strict wire-schema validation BEFORE the network I/O of THIS attempt.\n // Extra keys are REJECTED (additionalProperties:false), not stripped.\n const parsed = validateLeadsPayload({ ...base, botToken });\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((i) => `${i.path.join(\".\") || \"(payload)\"}: ${i.message}`)\n .join(\"; \");\n throw new OnboardingValidationError(\n `Payload failed wire-schema validation: ${detail}`,\n );\n }\n const body = JSON.stringify(parsed.data);\n\n let response: Response;\n\n try {\n response = await fetchWithTimeout(url, {\n method: \"POST\",\n headers,\n body,\n timeoutMs,\n fetchImpl,\n });\n } catch (err) {\n // Timeout (abort) and network failures are both retryable transient.\n lastError = new OnboardingNetworkError(err);\n const delay = RETRY_DELAYS_MS[attempt - 1];\n if (attempt < MAX_ATTEMPTS && delay !== undefined) {\n await sleep(delay);\n continue;\n }\n throw lastError;\n }\n\n // 200/202 → success (body parse guarded; see parseSuccessBody)\n if (response.status === 200 || response.status === 202) {\n return parseSuccessBody(response);\n }\n\n // 403 → bot challenge failed — NOT retryable\n if (response.status === 403) {\n throw new OnboardingBotChallengeError();\n }\n\n // 409 on /leads is NOT spec_stale (the leads payload has no specVersion;\n // spec_stale is a /submit-only concept per D540). A leads 409 is a\n // generic conflict (e.g. a D538 same-key concurrent-processing race) —\n // surface OnboardingConflictError so headless consumers switching on\n // `kind` get the right diagnosis, not a \"form has been updated\" message.\n if (response.status === 409) {\n throw new OnboardingConflictError(409);\n }\n\n // 429 → rate limited — NOT auto-retried (caller decides when to retry)\n if (response.status === 429) {\n const retryAfter = parseRetryAfter(response);\n throw new OnboardingRateLimitError(retryAfter);\n }\n\n // 422 → validation error (client-side should have caught this)\n if (response.status === 422) {\n throw new OnboardingValidationError(\n \"Server rejected the submission (422). Please check your inputs.\",\n );\n }\n\n // 5xx → retryable server error\n if (response.status >= 500) {\n lastError = new OnboardingServerError(response.status);\n const delay = RETRY_DELAYS_MS[attempt - 1];\n if (attempt < MAX_ATTEMPTS && delay !== undefined) {\n await sleep(delay);\n continue;\n }\n throw lastError;\n }\n\n // Any other unexpected status\n throw new OnboardingServerError(\n response.status,\n `Unexpected response status ${response.status}`,\n );\n }\n\n // Should not be reachable; guard for type safety\n throw lastError ?? new OnboardingNetworkError();\n}\n","/**\n * Submit client for POST /api/v1/public/onboarding/submit (D540 §2).\n *\n * Contract per the LOCKED hub REST interface (PublicOnboardingSubmitRequest):\n * - Body: { specVersion, answers, selectedServices?, botToken,\n * consent:{tosAccepted:true, privacyAccepted:true},\n * credentials:{email, displayName?}, organisationName?, subdomain }\n * additionalProperties: false — validated STRICTLY client-side before\n * any fetch (unknown keys are rejected, not stripped).\n * - Idempotency-Key header REQUIRED (same D538 discipline as leads:\n * stable across retries of the SAME payload; the widget mints a new\n * key when the payload fingerprint changes).\n * - specVersion policy is reject-stale: 409 → OnboardingSpecStaleError\n * carrying the server's current version. The CALLER refetches the spec\n * and surfaces re-validation to the user — NEVER silent auto-resubmit.\n * - Response: { accepted:true, terminalStage, verificationEmailSent? }.\n *\n * Success-body rule for /submit (DIFFERENT from /leads, deliberately):\n * the response carries REQUIRED routing data (terminalStage decides what\n * the UI shows next), so a 2xx with an empty/unparseable/garbage body is\n * an OnboardingServerError — the SDK will not fabricate a terminal stage.\n * (/leads tolerates body-less 2xx because its only signal IS the status.)\n */\n\nimport { z } from \"zod/v3\";\nimport type { TerminalStage } from \"../spec/model.js\";\nimport type { ConsentSubmitDocument } from \"../consent/controller.js\";\nimport {\n OnboardingBotChallengeError,\n OnboardingConflictError,\n OnboardingConsentIncompleteError,\n OnboardingConsentStaleError,\n OnboardingNetworkError,\n OnboardingRateLimitError,\n OnboardingServerError,\n OnboardingSpecStaleError,\n OnboardingSubdomainTakenError,\n OnboardingValidationError,\n type StaleConsentDocument,\n} from \"./errors.js\";\nimport { type BotTokenSource, resolveBotTokenGuarded } from \"./client.js\";\nimport { DEFAULT_REQUEST_TIMEOUT_MS, fetchWithTimeout } from \"./http.js\";\n\nconst SUBMIT_PATH = \"/api/v1/public/onboarding/submit\";\nconst MAX_ATTEMPTS = 3;\nconst RETRY_DELAYS_MS = [1000, 2000] as const;\n\n/**\n * STRICT wire schema for ONE element of the D543 §4 consent.documents array.\n * `accepted` is const-true (an unaccepted doc is never sent — the consent\n * controller omits it), and `evidence` carries only the gate-relevant flags.\n */\nexport const consentSubmitDocumentSchema = z\n .object({\n legalDocumentId: z.string().min(1, \"legalDocumentId is required\"),\n versionNo: z.number().int().nonnegative(),\n accepted: z.literal(true, {\n errorMap: () => ({ message: \"a consent document must be accepted\" }),\n }),\n // evidence keys stay snake_case (the only snake bits in the wire shape).\n evidence: z\n .object({\n scrolled_to_end: z.boolean().optional(),\n opened: z.boolean().optional(),\n })\n .strict(),\n })\n .strict();\n\n/**\n * STRICT wire schema — mirrors PublicOnboardingSubmitRequest exactly\n * (additionalProperties: false; consent booleans are const-true; passwordless\n * per D1010, with the workspace subdomain required by D836).\n *\n * D543 §4: `consent` extends from booleans to a per-document array. The\n * `documents` array is AUTHORITATIVE (the server re-validates it); the\n * `tosAccepted`/`privacyAccepted` booleans are kept as a DERIVED convenience\n * (D543 \"kept as a derived convenience only\"). `documents` is OPTIONAL on the\n * wire for backward-compat — a pre-ConsentPack onboarder serves no consentPack\n * and submits the booleans alone (D543 \"pre-ConsentPack submits still parse\").\n */\nexport const signupWireSchema = z\n .object({\n specVersion: z.string().min(1, \"specVersion is required\"),\n answers: z.record(z.unknown()),\n selectedServices: z.array(z.string()).optional(),\n botToken: z.string().min(1, \"botToken is required\").optional(),\n consent: z\n .object({\n tosAccepted: z.literal(true, {\n errorMap: () => ({ message: \"Terms of Service must be accepted\" }),\n }),\n privacyAccepted: z.literal(true, {\n errorMap: () => ({ message: \"Privacy Policy must be accepted\" }),\n }),\n documents: z.array(consentSubmitDocumentSchema).optional(),\n })\n .strict(),\n credentials: z\n .object({\n email: z.string().email(\"A valid account email is required\"),\n displayName: z.string().trim().min(1).max(200).optional(),\n })\n .strict(),\n organisationName: z.string().trim().min(1).max(200).optional(),\n subdomain: z.string().trim().min(1).max(63),\n })\n .strict();\n\nexport type SignupWirePayload = z.infer<typeof signupWireSchema>;\n\n/** The payload the caller assembles (botToken attached by the transport). */\nexport interface SignupSubmitBody {\n specVersion: string;\n answers: Record<string, unknown>;\n selectedServices?: string[];\n /**\n * D543 §4 consent. `documents` (the per-doc array) is authoritative when\n * present; the booleans are the derived convenience. `documents` is omitted\n * for a pre-ConsentPack onboarder (backward-compat).\n */\n consent: {\n tosAccepted: true;\n privacyAccepted: true;\n documents?: ConsentSubmitDocument[];\n };\n credentials: { email: string; displayName?: string };\n organisationName?: string;\n subdomain: string;\n}\n\nexport interface SignupSubmitOptions {\n baseUrl: string;\n /**\n * reCAPTCHA token (D537), or a provider called ONCE PER ATTEMPT to mint a\n * fresh token. Tokens are single-use (consumed at siteverify), so a fixed\n * string on a retried request risks 403 after the first attempt consumed\n * it — pass a provider for retry-safe recovery.\n */\n botToken?: BotTokenSource;\n /** Stable idempotency key — same discipline as submitLead. */\n idempotencyKey: string;\n fetch?: typeof globalThis.fetch;\n /**\n * Per-attempt request timeout in ms (default 15s). A stalled intake aborts\n * with OnboardingNetworkError so the widget re-enables instead of hanging.\n */\n timeoutMs?: number;\n onAttempt?: (attempt: number) => void;\n}\n\nexport interface SignupSubmitResult {\n accepted: true;\n /** Stage the existing engine landed the applicant at (D540 §2). */\n terminalStage: TerminalStage;\n verificationEmailSent?: boolean;\n}\n\n/**\n * Stable fingerprint of a signup submission, EXCLUDING botToken (fresh per\n * attempt by design). Same role as computePayloadFingerprint for leads:\n * same fingerprint ⇒ retry (reuse the Idempotency-Key); changed\n * fingerprint ⇒ new logical submission (mint a new key) — mirroring the\n * server's D538 payload-match guard.\n */\nexport function computeSignupFingerprint(body: SignupSubmitBody): string {\n const canonical = JSON.stringify([\n body.specVersion,\n // Sort answer keys for stability regardless of insertion order.\n Object.keys(body.answers)\n .sort()\n .map((k) => [k, body.answers[k]]),\n [...(body.selectedServices ?? [])].sort(),\n body.credentials.email,\n body.credentials.displayName ?? null,\n body.organisationName ?? null,\n body.subdomain,\n body.consent.tosAccepted,\n body.consent.privacyAccepted,\n // Consent documents fold into the fingerprint (sorted by legalDocumentId\n // for order-independence) — re-accepting a changed doc after 409\n // consent_stale is a NEW logical submission and must rotate the key.\n [...(body.consent.documents ?? [])]\n .sort((a, b) => a.legalDocumentId.localeCompare(b.legalDocumentId))\n .map((d) => [\n d.legalDocumentId,\n d.versionNo,\n d.evidence.scrolled_to_end ?? false,\n d.evidence.opened ?? false,\n ]),\n ]);\n let hash = 0x811c9dc5;\n for (let i = 0; i < canonical.length; i++) {\n hash ^= canonical.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0).toString(16).padStart(8, \"0\");\n}\n\nfunction buildUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n return `${base}${path}`;\n}\n\nfunction parseRetryAfter(response: Response): number | null {\n const header = response.headers.get(\"retry-after\");\n if (!header) return null;\n const seconds = Number.parseInt(header, 10);\n return Number.isNaN(seconds) ? null : seconds;\n}\n\n/**\n * Parse the OPTIONAL `staleDocuments` array on a consent_stale 409 body\n * (D543 §4). CANONICAL camelCase ONLY — { legalDocumentId, docType?,\n * currentVersionNo? } — NO snake_case hedging (P2b), matching the sibling\n * spec_stale error's \"no field hedging\" discipline: hedging both cases lets a\n * mismatched server shape pass silently and defeats the canonical-camelCase\n * pin. The server may omit the array entirely (caller then refetches + diffs);\n * an entry lacking a string `legalDocumentId` is skipped. Never throws.\n */\nfunction parseStaleConsentDocuments(value: unknown): StaleConsentDocument[] {\n if (!Array.isArray(value)) return [];\n const out: StaleConsentDocument[] = [];\n for (const entry of value) {\n if (entry === null || typeof entry !== \"object\") continue;\n const e = entry as Record<string, unknown>;\n if (typeof e[\"legalDocumentId\"] !== \"string\") continue;\n const doc: StaleConsentDocument = { legalDocumentId: e[\"legalDocumentId\"] };\n if (typeof e[\"docType\"] === \"string\") doc.docType = e[\"docType\"];\n if (typeof e[\"currentVersionNo\"] === \"number\") {\n doc.currentVersionNo = e[\"currentVersionNo\"];\n }\n out.push(doc);\n }\n return out;\n}\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nconst TERMINAL_STAGES: readonly TerminalStage[] = [\n \"prospect\",\n \"inactive\",\n \"active\",\n];\n\n/** Parse the REQUIRED submit response shape; anything else → ServerError. */\nasync function parseSubmitSuccess(\n response: Response,\n): Promise<SignupSubmitResult> {\n let json: unknown;\n try {\n json = await response.json();\n } catch {\n throw new OnboardingServerError(\n response.status,\n `Success status ${response.status} but the submit response body was not JSON — terminalStage is unknown.`,\n );\n }\n if (\n typeof json === \"object\" &&\n json !== null &&\n (json as { accepted?: unknown }).accepted === true\n ) {\n const stage = (json as { terminalStage?: unknown }).terminalStage;\n if (\n typeof stage === \"string\" &&\n (TERMINAL_STAGES as readonly string[]).includes(stage)\n ) {\n const verificationEmailSent = (\n json as { verificationEmailSent?: unknown }\n ).verificationEmailSent;\n return {\n accepted: true,\n terminalStage: stage as TerminalStage,\n ...(typeof verificationEmailSent === \"boolean\"\n ? { verificationEmailSent }\n : {}),\n };\n }\n }\n throw new OnboardingServerError(\n response.status,\n `Success status ${response.status} but the submit response did not carry {accepted:true, terminalStage}.`,\n );\n}\n\n/**\n * Submit a full signup to the onboarding intake.\n *\n * @throws {OnboardingValidationError} strict wire-schema failure — BEFORE any fetch\n * @throws {OnboardingBotChallengeError} on 403, or if the bot-token provider\n * throws (reCAPTCHA unreachable) on any attempt\n * @throws {OnboardingSpecStaleError} on 409 with code:\"spec_stale\" (caller\n * refetches spec); carries the server's currentSpecVersion\n * @throws {OnboardingConsentStaleError} on 409 with code:\"consent_stale\"\n * (D543 §4 — a required legal doc changed; caller refetches spec + requires\n * re-acceptance of the changed docs, NEVER auto-accepts); carries\n * staleDocuments\n * @throws {OnboardingConsentIncompleteError} on 400 with\n * code:\"consent_incomplete\" (D543 §4 — server found a required doc missing/\n * unaccepted/evidence-insufficient)\n * @throws {OnboardingConflictError} on a 409 that is NOT spec_stale/consent_stale\n * @throws {OnboardingRateLimitError} on 429\n * @throws {OnboardingValidationError} on 422 (server re-validation) or a\n * generic 400 that is NOT consent_incomplete\n * @throws {OnboardingServerError} on 5xx after retries, or a 2xx without\n * the required {accepted:true, terminalStage} body\n * @throws {OnboardingNetworkError} on network failure OR timeout after retries\n */\nexport async function submitSignup(\n body: SignupSubmitBody,\n options: SignupSubmitOptions,\n): Promise<SignupSubmitResult> {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const url = buildUrl(options.baseUrl, SUBMIT_PATH);\n\n // Non-token fields normalized once; token attached PER ATTEMPT (single-use)\n // so a retry carries a fresh token — the body is serialized inside the loop.\n const base: Record<string, unknown> = { ...body };\n if (base[\"selectedServices\"] === undefined) {\n delete base[\"selectedServices\"];\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": options.idempotencyKey,\n };\n\n let lastError: unknown = null;\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n options.onAttempt?.(attempt);\n\n // Guarded: a token-provider failure (reCAPTCHA unreachable on a retry)\n // becomes a typed OnboardingBotChallengeError, never a raw RecaptchaError.\n const botToken =\n options.botToken === undefined\n ? undefined\n : await resolveBotTokenGuarded(options.botToken);\n const parsed = signupWireSchema.safeParse({ ...base, botToken });\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((i) => `${i.path.join(\".\") || \"(payload)\"}: ${i.message}`)\n .join(\"; \");\n throw new OnboardingValidationError(\n `Signup payload failed wire-schema validation: ${detail}`,\n );\n }\n const wireBody = JSON.stringify(parsed.data);\n\n let response: Response;\n try {\n response = await fetchWithTimeout(url, {\n method: \"POST\",\n headers,\n body: wireBody,\n timeoutMs,\n fetchImpl,\n });\n } catch (err) {\n lastError = new OnboardingNetworkError(err);\n const delay = RETRY_DELAYS_MS[attempt - 1];\n if (attempt < MAX_ATTEMPTS && delay !== undefined) {\n await sleep(delay);\n continue;\n }\n throw lastError;\n }\n\n if (response.status === 200 || response.status === 202) {\n return parseSubmitSuccess(response);\n }\n\n if (response.status === 403) {\n throw new OnboardingBotChallengeError();\n }\n\n // 400 — the D543 §4 PINNED consent_incomplete body is\n // { error: true, code: \"consent_incomplete\", ... }\n // The server's authoritative re-validation found a required live document\n // that was missing / unaccepted / evidence-insufficient. Any OTHER 400 is\n // a generic validation error (routes the user to review inputs).\n if (response.status === 400) {\n let code400: unknown;\n let message400: string | undefined;\n try {\n const body400 = (await response.json()) as Record<string, unknown>;\n code400 = body400[\"code\"];\n const m = body400[\"message\"];\n if (typeof m === \"string\" && m !== \"\") message400 = m;\n } catch {\n // keep defaults; a body-less 400 is a generic validation error\n }\n if (code400 === \"consent_incomplete\") {\n throw new OnboardingConsentIncompleteError(message400);\n }\n throw new OnboardingValidationError(\n message400 ??\n \"The server rejected the submission (400). Please review your inputs.\",\n );\n }\n\n // 409 — TWO pinned bodies (D540 spec_stale + D543 §4 consent_stale, its\n // sibling):\n // { error: true, code: \"spec_stale\", currentSpecVersion: string }\n // { error: true, code: \"consent_stale\", staleDocuments?: [...] }\n // ONLY a 409 carrying one of these codes maps to a typed stale error; any\n // other 409 is a generic conflict.\n if (response.status === 409) {\n let code: unknown;\n let currentSpecVersion = \"unknown\";\n let staleDocuments: StaleConsentDocument[] = [];\n try {\n const body409 = (await response.json()) as Record<string, unknown>;\n code = body409[\"code\"];\n const v = body409[\"currentSpecVersion\"];\n if (typeof v === \"string\" && v !== \"\") currentSpecVersion = v;\n staleDocuments = parseStaleConsentDocuments(body409[\"staleDocuments\"]);\n } catch {\n // keep defaults; a body-less 409 is treated as a generic conflict\n }\n if (code === \"spec_stale\") {\n throw new OnboardingSpecStaleError(currentSpecVersion);\n }\n if (code === \"consent_stale\") {\n throw new OnboardingConsentStaleError(staleDocuments);\n }\n if (code === \"subdomain_taken\") {\n throw new OnboardingSubdomainTakenError();\n }\n throw new OnboardingConflictError(409);\n }\n\n if (response.status === 429) {\n throw new OnboardingRateLimitError(parseRetryAfter(response));\n }\n\n if (response.status === 422) {\n throw new OnboardingValidationError(\n \"The server rejected the submission (422). Please review your inputs.\",\n );\n }\n\n if (response.status >= 500) {\n lastError = new OnboardingServerError(response.status);\n const delay = RETRY_DELAYS_MS[attempt - 1];\n if (attempt < MAX_ATTEMPTS && delay !== undefined) {\n await sleep(delay);\n continue;\n }\n throw lastError;\n }\n\n throw new OnboardingServerError(\n response.status,\n `Unexpected response status ${response.status}`,\n );\n }\n\n throw lastError ?? new OnboardingNetworkError();\n}\n"]}