@nodii/onboarding 0.1.0-dev.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 (68) hide show
  1. package/README.md +124 -0
  2. package/dist/{chunk-4SWOVHZ2.js → chunk-A3DE2IAH.js} +155 -18
  3. package/dist/chunk-A3DE2IAH.js.map +1 -0
  4. package/dist/{chunk-AFCJLUPK.js → chunk-DWFZA5T6.js} +19 -8
  5. package/dist/chunk-DWFZA5T6.js.map +1 -0
  6. package/dist/chunk-FIRSA7EH.js +3 -0
  7. package/dist/{chunk-ZDY2I6KY.js.map → chunk-FIRSA7EH.js.map} +1 -1
  8. package/dist/chunk-JFDQZ3AC.js +3 -0
  9. package/dist/chunk-JFDQZ3AC.js.map +1 -0
  10. package/dist/{chunk-34BQX2KR.js → chunk-KFHVZGX6.js} +61 -6
  11. package/dist/chunk-KFHVZGX6.js.map +1 -0
  12. package/dist/chunk-NDSBIB3N.js +99 -0
  13. package/dist/chunk-NDSBIB3N.js.map +1 -0
  14. package/dist/chunk-OLWR5COK.js +10 -0
  15. package/dist/chunk-OLWR5COK.js.map +1 -0
  16. package/dist/{chunk-SWMIIUXR.js → chunk-OVULPWNZ.js} +917 -158
  17. package/dist/chunk-OVULPWNZ.js.map +1 -0
  18. package/dist/chunk-SEKNVHD3.js +59 -0
  19. package/dist/chunk-SEKNVHD3.js.map +1 -0
  20. package/dist/chunk-T7EWURTF.js +321 -0
  21. package/dist/chunk-T7EWURTF.js.map +1 -0
  22. package/dist/{chunk-BQCTDU5V.js → chunk-UHOFGWI7.js} +90 -15
  23. package/dist/chunk-UHOFGWI7.js.map +1 -0
  24. package/dist/chunk-WKN4U7QF.js +35 -0
  25. package/dist/chunk-WKN4U7QF.js.map +1 -0
  26. package/dist/{chunk-AW7W3HAC.js → chunk-XJTJPBJB.js} +40 -3
  27. package/dist/chunk-XJTJPBJB.js.map +1 -0
  28. package/dist/chunk-ZR6X3GSM.js +186 -0
  29. package/dist/chunk-ZR6X3GSM.js.map +1 -0
  30. package/dist/client-NvqPpfQP.d.ts +86 -0
  31. package/dist/consent/index.d.ts +59 -0
  32. package/dist/consent/index.js +7 -0
  33. package/dist/consent/index.js.map +1 -0
  34. package/dist/controller-BLH3wnxl.d.ts +177 -0
  35. package/dist/dynamic/index.d.ts +65 -6
  36. package/dist/dynamic/index.js +5 -2
  37. package/dist/errors-vJkye4a0.d.ts +110 -0
  38. package/dist/form/index.d.ts +12 -4
  39. package/dist/form/index.js +2 -2
  40. package/dist/index-BMSbQGWP.d.ts +103 -0
  41. package/dist/index.d.ts +13 -7
  42. package/dist/index.js +13 -8
  43. package/dist/model-BI_I-D_V.d.ts +267 -0
  44. package/dist/{model-IEyDI54w.d.ts → model-CEwvHlWA.d.ts} +212 -46
  45. package/dist/render-EWQXF73I.js +5 -0
  46. package/dist/render-EWQXF73I.js.map +1 -0
  47. package/dist/schema-BrtjpXfL.d.ts +257 -0
  48. package/dist/{signup-client-Bl6eUrxR.d.ts → signup-client-CS95nkVO.d.ts} +152 -19
  49. package/dist/spec/index.d.ts +10 -81
  50. package/dist/spec/index.js +3 -2
  51. package/dist/submit/index.d.ts +7 -71
  52. package/dist/submit/index.js +4 -3
  53. package/dist/viewer/index.d.ts +4 -0
  54. package/dist/viewer/index.js +5 -0
  55. package/dist/viewer/index.js.map +1 -0
  56. package/dist/widget/index.d.ts +102 -20
  57. package/dist/widget/index.js +10 -7
  58. package/package.json +21 -2
  59. package/dist/chunk-34BQX2KR.js.map +0 -1
  60. package/dist/chunk-4SWOVHZ2.js.map +0 -1
  61. package/dist/chunk-AFCJLUPK.js.map +0 -1
  62. package/dist/chunk-AW7W3HAC.js.map +0 -1
  63. package/dist/chunk-BIFANV7M.js +0 -22
  64. package/dist/chunk-BIFANV7M.js.map +0 -1
  65. package/dist/chunk-BQCTDU5V.js.map +0 -1
  66. package/dist/chunk-SWMIIUXR.js.map +0 -1
  67. package/dist/chunk-ZDY2I6KY.js +0 -3
  68. package/dist/schema-TfyBU6JW.d.ts +0 -107
@@ -0,0 +1,59 @@
1
+ import { b as ConsentDocument } from '../model-BI_I-D_V.js';
2
+ export { A as ACCEPTANCE_LEVELS, a as AcceptanceLevel, C as ConsentDocType, 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 { C as ConsentController, a as ConsentDocState, b as ConsentEvidence, c as ConsentState, d as ConsentStateListener, e as ConsentSubmitDocument, S as SCROLL_END_THRESHOLD, f as createConsentController, i as initialGateSatisfied } from '../controller-BLH3wnxl.js';
4
+ import { d as OnboardingError } from '../errors-vJkye4a0.js';
5
+ import 'zod/v3';
6
+
7
+ /**
8
+ * Headless document-content fetch helper (D543 §5).
9
+ *
10
+ * The consent controller is DOM-free; document RENDERING is the consumer's
11
+ * responsibility (TanStack model — you render the row, we own the state). For
12
+ * `scroll_then_accept` gating to work the doc must be rendered SAME-ORIGIN and
13
+ * inline (a cross-origin iframe cannot report scroll position), so consumers
14
+ * fetch the presigned artifact and render its bytes/text themselves.
15
+ *
16
+ * This helper does the fetch + classification so a consumer doesn't wrestle
17
+ * CORS/content-type detection. It returns the artifact's content-type plus the
18
+ * body as text (for HTML/plain) or a Blob (for PDF/binary). It NEVER renders
19
+ * or sanitizes — the consumer (or the default component) decides how to display
20
+ * it (e.g. sanitize+inject HTML, or PDF.js a blob, or degrade to open-in-tab).
21
+ *
22
+ * A short-TTL presigned GET may fail (expired URL, CORS, offline); the caller
23
+ * gets a typed OnboardingDocumentFetchError so a failed preview degrades
24
+ * gracefully (e.g. fall back to an "open in a new tab" affordance) without
25
+ * breaking the consent gate.
26
+ */
27
+
28
+ /** How the fetched artifact should be treated by a renderer. */
29
+ type ConsentDocumentKind = "html" | "text" | "pdf" | "binary";
30
+ interface FetchedConsentDocument {
31
+ /** The raw `Content-Type` header (lower-cased, params stripped). */
32
+ contentType: string;
33
+ /** Classification for the renderer. */
34
+ kind: ConsentDocumentKind;
35
+ /** Present for kind "html" | "text". */
36
+ text?: string;
37
+ /** Present for kind "pdf" | "binary". */
38
+ blob?: Blob;
39
+ }
40
+ interface FetchDocumentContentOptions {
41
+ fetch?: typeof globalThis.fetch;
42
+ /** Per-request timeout (default 15s) — a stalled CDN never hangs the UI. */
43
+ timeoutMs?: number;
44
+ }
45
+ /** A failed presigned artifact fetch (expired URL, CORS, offline, non-2xx). */
46
+ declare class OnboardingDocumentFetchError extends OnboardingError {
47
+ readonly url: string;
48
+ constructor(url: string, detail: string);
49
+ }
50
+ /**
51
+ * Fetch a consent document's presigned artifact and return its content-type +
52
+ * body (text for HTML/plain, Blob for PDF/binary).
53
+ *
54
+ * @throws {OnboardingDocumentFetchError} on network failure, timeout, CORS,
55
+ * or a non-2xx response — the caller degrades gracefully (open-in-tab).
56
+ */
57
+ declare function fetchDocumentContent(doc: Pick<ConsentDocument, "downloadUrl">, options?: FetchDocumentContentOptions): Promise<FetchedConsentDocument>;
58
+
59
+ export { ConsentDocument, type ConsentDocumentKind, type FetchDocumentContentOptions, type FetchedConsentDocument, OnboardingDocumentFetchError, fetchDocumentContent };
@@ -0,0 +1,7 @@
1
+ import '../chunk-FIRSA7EH.js';
2
+ export { SCROLL_END_THRESHOLD, createConsentController, initialGateSatisfied } from '../chunk-ZR6X3GSM.js';
3
+ export { ACCEPTANCE_LEVELS, DOC_TYPES, acceptanceLevelSchema, consentDocKey, consentDocumentSchema, consentPackSchema } from '../chunk-NDSBIB3N.js';
4
+ export { OnboardingDocumentFetchError, fetchDocumentContent } from '../chunk-SEKNVHD3.js';
5
+ import '../chunk-XJTJPBJB.js';
6
+ //# sourceMappingURL=index.js.map
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,177 @@
1
+ import { b as ConsentDocument, a as AcceptanceLevel, c as ConsentPack } from './model-BI_I-D_V.js';
2
+
3
+ /**
4
+ * Headless ConsentPack controller (D543 §2/§5).
5
+ *
6
+ * Framework-agnostic consent state for the signup flow's consent stage.
7
+ * Exposes, per document: { doc, accepted, gateSatisfied, evidence } and
8
+ * enforces the per-document ACCEPTANCE-LEVEL GATE (D543 §2):
9
+ *
10
+ * - checkbox — accept is enable-able immediately (basic clickwrap).
11
+ * - scroll_then_accept — accept is DISABLED until the rendered doc is
12
+ * scrolled to the end (evidence.scrolled_to_end).
13
+ * - open_then_accept — accept is DISABLED until the doc is opened/viewed
14
+ * (evidence.opened).
15
+ *
16
+ * The gate is a UX guard (D543 §5: "Client evidence is UX-only; the server
17
+ * re-validates"). But the controller ENFORCES it locally so a UI cannot
18
+ * present an accept affordance the gate hasn't unlocked, and so a headless
19
+ * caller cannot assemble a payload that claims an unsatisfied gate.
20
+ *
21
+ * COMPLIANCE-CRITICAL (same discipline as the stepper's consent-fabrication
22
+ * fix): `accept(key)` is REFUSED when the gate is not yet satisfied, and
23
+ * `buildConsentPayload()` THROWS if ANY required doc is not accepted at its
24
+ * gate. The controller NEVER fabricates acceptance or evidence — it only
25
+ * records what the user genuinely did. `isComplete()` is true only when every
26
+ * REQUIRED doc is accepted with its gate satisfied.
27
+ *
28
+ * Same subscribe/mutate contract as the dynamic stepper so the default
29
+ * component layer renders it and tenants can build their own UI against the
30
+ * exported control surface (D543 §5 headless flexibility).
31
+ */
32
+
33
+ /**
34
+ * Evidence for a document's gate. Only the field relevant to the doc's
35
+ * acceptanceLevel is ever set true — a checkbox doc carries no evidence.
36
+ * Mirrors the D543 §4 submit `evidence:{scrolled_to_end?, opened?}` — these
37
+ * two keys stay snake_case (the ONLY snake bits in the consent wire shape).
38
+ */
39
+ interface ConsentEvidence {
40
+ scrolled_to_end?: boolean;
41
+ opened?: boolean;
42
+ }
43
+ /**
44
+ * Per-document view of the consent state — the framework-agnostic "row model"
45
+ * a consumer binds their own rendering to (TanStack-style). NO DOM here.
46
+ */
47
+ interface ConsentDocState {
48
+ /** The document descriptor (from the spec's consentPack). */
49
+ doc: ConsentDocument;
50
+ /** Stable client-side key (docType | custom:<customKey>). */
51
+ key: string;
52
+ /** The document's acceptance level, surfaced for convenience. */
53
+ acceptanceLevel: AcceptanceLevel;
54
+ /** Whether the document is required, surfaced for convenience. */
55
+ required: boolean;
56
+ /**
57
+ * Whether the acceptance-level GATE is satisfied — i.e. accept is
58
+ * enable-able. checkbox ⇒ always true; scroll ⇒ scrolled-to-end;
59
+ * open ⇒ opened.
60
+ */
61
+ gateSatisfied: boolean;
62
+ /** Whether the user has genuinely accepted (only possible past the gate). */
63
+ accepted: boolean;
64
+ /** The evidence collected so far (drives gateSatisfied for gated levels). */
65
+ evidence: ConsentEvidence;
66
+ /**
67
+ * Scroll progress in [0,1] for a `scroll_then_accept` doc (0 for other
68
+ * levels). A consumer's scroll handler feeds this via reportScrollProgress;
69
+ * reaching ~1 flips gateSatisfied. Purely a UX-progress read (e.g. a
70
+ * progress bar); the gate itself keys on scrolledToEnd evidence.
71
+ */
72
+ scrollProgress: number;
73
+ }
74
+ interface ConsentState {
75
+ /** All documents in spec order. */
76
+ documents: ConsentDocState[];
77
+ /** True iff every REQUIRED doc is accepted with its gate satisfied. */
78
+ complete: boolean;
79
+ }
80
+ type ConsentStateListener = (state: ConsentState) => void;
81
+ /**
82
+ * One element of the D543 §4 submit `consent.documents` array — CANONICAL
83
+ * camelCase (legalDocumentId, versionNo, accepted); `evidence` keys stay
84
+ * snake_case (scrolled_to_end / opened).
85
+ */
86
+ interface ConsentSubmitDocument {
87
+ legalDocumentId: string;
88
+ versionNo: number;
89
+ accepted: true;
90
+ evidence: ConsentEvidence;
91
+ }
92
+ interface ConsentController {
93
+ getState(): ConsentState;
94
+ /** True iff there are no documents to consent to (stage is skippable). */
95
+ isEmpty(): boolean;
96
+ /** True iff every required doc is accepted with its gate satisfied. */
97
+ isComplete(): boolean;
98
+ /**
99
+ * Alias of isComplete() — TanStack-style ergonomic read: "can the consumer
100
+ * submit the consent step?" (all required docs accepted at their gate).
101
+ */
102
+ canSubmit(): boolean;
103
+ /**
104
+ * Whether the given document's gate is satisfied (accept is enable-able).
105
+ * Unknown key ⇒ false.
106
+ */
107
+ isGateSatisfied(key: string): boolean;
108
+ /**
109
+ * Report scroll progress in [0,1] for a `scroll_then_accept` doc — the
110
+ * consumer's OWN scroll handler feeds this (e.g. on its same-origin scroll
111
+ * container). Ratio ≥ SCROLL_END_THRESHOLD marks scrolled-to-end (opens the
112
+ * gate); otherwise it just updates scrollProgress for UX. Clamped to [0,1].
113
+ * No-op for other levels and unknown keys.
114
+ */
115
+ reportScrollProgress(key: string, ratio: number): void;
116
+ /**
117
+ * Mark a `scroll_then_accept` doc as scrolled to the end (evidence). No-op
118
+ * for other levels (their gate isn't scroll-driven) and unknown keys.
119
+ * Equivalent to reportScrollProgress(key, 1).
120
+ */
121
+ markScrolledToEnd(key: string): void;
122
+ /**
123
+ * Mark an `open_then_accept` doc as opened/viewed (evidence). No-op for
124
+ * other levels and unknown keys.
125
+ */
126
+ markOpened(key: string): void;
127
+ /**
128
+ * Set a document's accepted flag. Accepting is REFUSED (accepted stays
129
+ * false) when the gate is not satisfied — a UI cannot record acceptance a
130
+ * user could not legitimately give. Unaccepting is always allowed.
131
+ */
132
+ setAccepted(key: string, accepted: boolean): void;
133
+ /**
134
+ * Build the D543 §4 submit array from the ACTUAL consent state.
135
+ *
136
+ * @throws {Error} if any REQUIRED doc is not accepted with its gate
137
+ * satisfied — never fabricates acceptance/evidence (compliance-critical).
138
+ * Optional docs that were accepted ARE included; optional docs left
139
+ * unaccepted are omitted.
140
+ */
141
+ buildConsentPayload(): ConsentSubmitDocument[];
142
+ /**
143
+ * Swap in a refetched consentPack after 409 consent_stale. For every doc
144
+ * whose (versionNo, contentHash) CHANGED, acceptance + evidence are
145
+ * RESET (re-acceptance required — NEVER auto-accept a changed doc, D543 §4
146
+ * / task §4). Unchanged docs keep their state. Returns the keys whose
147
+ * consent was reset so the UI can highlight what changed.
148
+ */
149
+ applyNewPack(pack: ConsentPack | undefined): string[];
150
+ subscribe(listener: ConsentStateListener): () => void;
151
+ }
152
+ /**
153
+ * Scroll ratio at/above which a `scroll_then_accept` doc counts as
154
+ * scrolled-to-end. Slightly below 1 so sub-pixel rounding on the consumer's
155
+ * scroll math (scrollTop+clientHeight vs scrollHeight) still opens the gate.
156
+ */
157
+ declare const SCROLL_END_THRESHOLD = 0.99;
158
+ /** checkbox docs have no gate; gated levels start unsatisfied. */
159
+ declare function initialGateSatisfied(level: AcceptanceLevel): boolean;
160
+ /**
161
+ * Create a headless consent controller for a spec's consentPack.
162
+ *
163
+ * @param pack the spec's `consentPack` (or undefined ⇒ an empty controller;
164
+ * the stage is skipped and `isComplete()` / `isEmpty()` are true).
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * const consent = createConsentController(spec.consentPack);
169
+ * consent.subscribe(render);
170
+ * consent.markScrolledToEnd("tos"); // scroll_then_accept gate
171
+ * consent.setAccepted("tos", true); // now allowed
172
+ * const docs = consent.buildConsentPayload(); // throws if any required unmet
173
+ * ```
174
+ */
175
+ declare function createConsentController(pack: ConsentPack | undefined): ConsentController;
176
+
177
+ export { type ConsentController as C, SCROLL_END_THRESHOLD as S, type ConsentDocState as a, type ConsentEvidence as b, type ConsentState as c, type ConsentStateListener as d, type ConsentSubmitDocument as e, createConsentController as f, initialGateSatisfied as i };
@@ -1,7 +1,10 @@
1
1
  import { z } from 'zod/v3';
2
- import { c as SpecStep, S as SpecField, T as TerminalStage, a as OnboardingSpec } from '../model-IEyDI54w.js';
2
+ import { c as SpecStep, S as SpecField, T as TerminalStage, a as OnboardingSpec } from '../model-CEwvHlWA.js';
3
+ import { c as ConsentState, C as ConsentController, e as ConsentSubmitDocument } from '../controller-BLH3wnxl.js';
4
+ export { a as ConsentDocState, b as ConsentEvidence, d as ConsentStateListener, S as SCROLL_END_THRESHOLD, f as createConsentController, i as initialGateSatisfied } from '../controller-BLH3wnxl.js';
3
5
  import { FieldError } from '../form/index.js';
4
- import '../schema-TfyBU6JW.js';
6
+ import '../model-BI_I-D_V.js';
7
+ import '../schema-BrtjpXfL.js';
5
8
 
6
9
  /**
7
10
  * Spec → Zod validator derivation (D540 §3).
@@ -88,7 +91,9 @@ interface SignupConsent {
88
91
  }
89
92
  interface SignupCredentials {
90
93
  email: string;
91
- password: string;
94
+ displayName: string;
95
+ organisationName: string;
96
+ subdomain: string;
92
97
  }
93
98
  interface SignupStepperState {
94
99
  /** Zero-based index of the current step. */
@@ -113,6 +118,22 @@ interface SignupStepperState {
113
118
  specVersion: string;
114
119
  /** Set on success from the submit response. */
115
120
  terminalStage: TerminalStage | null;
121
+ /**
122
+ * D543 ConsentPack state (per-document acceptance + gates). Empty
123
+ * (documents:[]) when the spec serves no consentPack — the consent stage is
124
+ * then skipped and does not gate the submit.
125
+ */
126
+ consentState: ConsentState;
127
+ /**
128
+ * True after a 409 consent_stale swap until the user re-accepts the changed
129
+ * docs. Distinct from specStale — a doc-version bump does not necessarily
130
+ * change the form fields, and the banner copy differs.
131
+ */
132
+ consentStale: boolean;
133
+ /** Keys of the consent docs whose acceptance was reset by the last swap. */
134
+ staleConsentKeys: string[];
135
+ /** Consent-stage errors (submit-time; e.g. a required doc not accepted). */
136
+ consentErrors: FieldError[];
116
137
  }
117
138
  type SignupStepperListener = (state: SignupStepperState) => void;
118
139
  interface SignupStepperControl {
@@ -120,6 +141,14 @@ interface SignupStepperControl {
120
141
  setAnswer(key: string, value: unknown): void;
121
142
  setConsent(consent: Partial<SignupConsent>): void;
122
143
  setCredentials(credentials: Partial<SignupCredentials>): void;
144
+ /**
145
+ * The headless ConsentPack controller (D543 §5). Exposed so the default
146
+ * component layer AND fully custom tenant UIs drive the SAME state: mark
147
+ * scroll/open evidence, set acceptance, read per-doc gate status. The
148
+ * stepper subscribes to it internally so consent mutations re-emit stepper
149
+ * state (consentState/consentStale) to the widget.
150
+ */
151
+ readonly consent: ConsentController;
123
152
  /** Validate ONLY the current step. Returns true when clean. */
124
153
  validateStep(): boolean;
125
154
  /** Validate current step; advance when clean. Returns true if advanced. */
@@ -135,6 +164,11 @@ interface SignupStepperControl {
135
164
  /**
136
165
  * The submit payload body (minus botToken, which the transport attaches).
137
166
  * Call ONLY after validateAll() returns true.
167
+ *
168
+ * D543 §4: `consent.documents` is the per-doc consent array built from the
169
+ * ACTUAL consent state (omitted when the spec serves no consentPack). The
170
+ * booleans stay as the derived convenience. THROWS if any required consent
171
+ * doc is not accepted at its gate (never fabricates acceptance).
138
172
  */
139
173
  buildSubmitPayload(): {
140
174
  specVersion: string;
@@ -143,20 +177,45 @@ interface SignupStepperControl {
143
177
  consent: {
144
178
  tosAccepted: true;
145
179
  privacyAccepted: true;
180
+ documents?: ConsentSubmitDocument[];
146
181
  };
147
- credentials: SignupCredentials;
182
+ credentials: {
183
+ email: string;
184
+ displayName?: string;
185
+ };
186
+ organisationName?: string;
187
+ subdomain: string;
148
188
  };
149
189
  /**
150
190
  * Swap in a refetched spec after 409 spec_stale. Drops answers for
151
191
  * removed fields, re-validates the rest, marks specStale, repositions
152
- * to the first step with errors. NEVER auto-resubmits.
192
+ * to the first step with errors. ALSO swaps the consentPack (a spec bump
193
+ * may carry new doc versions) — changed docs require re-acceptance. NEVER
194
+ * auto-resubmits.
153
195
  */
154
196
  applyNewSpec(spec: OnboardingSpec): void;
155
197
  /** Clear the specStale flag once the user has re-confirmed. */
156
198
  acknowledgeSpecStale(): void;
199
+ /**
200
+ * Swap in the refetched spec's consentPack after 409 consent_stale (D543
201
+ * §4). Resets acceptance for docs whose version/hash changed (re-acceptance
202
+ * required — NEVER auto-accept a changed doc), marks consentStale, records
203
+ * which keys were reset. NEVER auto-resubmits.
204
+ */
205
+ applyNewConsentPack(spec: OnboardingSpec): void;
206
+ /** Clear the consentStale flag once the user has re-accepted. */
207
+ acknowledgeConsentStale(): void;
157
208
  setSubmitting(): void;
158
209
  setSuccess(terminalStage: TerminalStage): void;
159
210
  setError(message: string): void;
211
+ /**
212
+ * Put a server's refusal on the one account field it is about (409
213
+ * subdomain_taken → `subdomain`), so the user is told which input to change.
214
+ * Returns the form to "editing" with no form-level error: the field carries
215
+ * the message. Replaces any error already on that field. Editing the field
216
+ * clears it; the next validateAll() recomputes account errors as usual.
217
+ */
218
+ setAccountError(path: keyof SignupCredentials, message: string): void;
160
219
  /**
161
220
  * Reset to the pristine initial state against the CURRENT spec: step 0,
162
221
  * status "editing", answers/credentials/consent/errors cleared. This is
@@ -181,4 +240,4 @@ interface SignupStepperControl {
181
240
  */
182
241
  declare function createSignupStepper(spec: OnboardingSpec): SignupStepperControl;
183
242
 
184
- export { type SignupConsent, type SignupCredentials, type SignupStepperControl, type SignupStepperListener, type SignupStepperState, type StepperStatus, createSignupStepper, deriveAnswersSchema, deriveFieldSchema, deriveStepSchema, optionValue };
243
+ export { ConsentController, ConsentState, ConsentSubmitDocument, type SignupConsent, type SignupCredentials, type SignupStepperControl, type SignupStepperListener, type SignupStepperState, type StepperStatus, createSignupStepper, deriveAnswersSchema, deriveFieldSchema, deriveStepSchema, optionValue };
@@ -1,4 +1,7 @@
1
- import '../chunk-ZDY2I6KY.js';
2
- export { createSignupStepper, deriveAnswersSchema, deriveFieldSchema, deriveStepSchema, optionValue } from '../chunk-4SWOVHZ2.js';
1
+ import '../chunk-JFDQZ3AC.js';
2
+ export { createSignupStepper, deriveAnswersSchema, deriveFieldSchema, deriveStepSchema, optionValue } from '../chunk-A3DE2IAH.js';
3
+ export { SCROLL_END_THRESHOLD, createConsentController, initialGateSatisfied } from '../chunk-ZR6X3GSM.js';
4
+ import '../chunk-NDSBIB3N.js';
5
+ import '../chunk-OLWR5COK.js';
3
6
  //# sourceMappingURL=index.js.map
4
7
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Error taxonomy for the onboarding submit client.
3
+ *
4
+ * Maps HTTP status codes and server error bodies to typed errors:
5
+ * 403 bot_challenge_failed → OnboardingBotChallengeError
6
+ * 409 spec_stale (SUBMIT) → OnboardingSpecStaleError (submit endpoint only,
7
+ * body { error:true, code:"spec_stale",
8
+ * currentSpecVersion })
9
+ * 409 (other / LEADS) → OnboardingConflictError (generic conflict)
10
+ * 429 → OnboardingRateLimitError
11
+ * 5xx → OnboardingServerError (retryable)
12
+ * network / timeout → OnboardingNetworkError
13
+ */
14
+ type OnboardingErrorKind = "bot_challenge_failed" | "spec_stale" | "spec_invalid" | "consent_stale" | "consent_incomplete" | "subdomain_taken" | "conflict" | "rate_limited" | "server_error" | "network_error" | "validation_error";
15
+ declare class OnboardingError extends Error {
16
+ readonly kind: OnboardingErrorKind;
17
+ readonly statusCode: number | null;
18
+ constructor(kind: OnboardingErrorKind, message: string, statusCode?: number | null);
19
+ }
20
+ declare class OnboardingBotChallengeError extends OnboardingError {
21
+ constructor(message?: string);
22
+ }
23
+ /**
24
+ * A 409 that is NOT spec_stale. On the /leads path there is no spec_stale
25
+ * concept (the payload carries no specVersion); on /submit, a 409 that
26
+ * lacks `code:"spec_stale"` also lands here. Distinct from
27
+ * OnboardingSpecStaleError so headless consumers switching on `kind` get the
28
+ * correct diagnosis (a generic conflict, e.g. a D538 same-key concurrent
29
+ * race) rather than a "form has been updated, refresh" recovery message.
30
+ */
31
+ declare class OnboardingConflictError extends OnboardingError {
32
+ constructor(statusCode?: number, message?: string);
33
+ }
34
+ declare class OnboardingSubdomainTakenError extends OnboardingError {
35
+ constructor();
36
+ }
37
+ declare class OnboardingRateLimitError extends OnboardingError {
38
+ readonly retryAfterSeconds: number | null;
39
+ constructor(retryAfterSeconds?: number | null);
40
+ }
41
+ /**
42
+ * A /submit 409 whose body carries `code:"spec_stale"` (D540 reject-stale).
43
+ * The PINNED body shape (confirmed against the crm intake) is exactly:
44
+ * { error: true, code: "spec_stale", currentSpecVersion: string }
45
+ * The version is read ONLY from `currentSpecVersion` — no field hedging.
46
+ * Submit-endpoint only; /leads 409s are OnboardingConflictError.
47
+ */
48
+ declare class OnboardingSpecStaleError extends OnboardingError {
49
+ readonly currentSpecVersion: string;
50
+ constructor(currentSpecVersion: string);
51
+ }
52
+ /**
53
+ * A /submit 409 whose body carries `code:"consent_stale"` (D543 §4 —
54
+ * SIBLING to spec_stale). Raised when a required legal document's live
55
+ * version/content_hash changed between spec-fetch and submit, so the echoed
56
+ * consent no longer matches the live version.
57
+ *
58
+ * PINNED body shape (D543 §4, mirroring spec_stale):
59
+ * { error: true, code: "consent_stale", staleDocuments?: [...] }
60
+ * where each staleDocuments entry identifies a changed doc so the caller can
61
+ * tell the user WHICH docs changed. The caller refetches the spec and
62
+ * REQUIRES re-acceptance of the changed docs — the SDK NEVER auto-accepts a
63
+ * changed doc (D543 §4). Submit-endpoint only.
64
+ */
65
+ interface StaleConsentDocument {
66
+ /** The server registry id of the changed doc (D543 §3 legal_documents.id). */
67
+ legalDocumentId?: string;
68
+ /** doc_type of the changed doc, when the server includes it. */
69
+ docType?: string;
70
+ /** The new live version_no, when the server includes it. */
71
+ currentVersionNo?: number;
72
+ }
73
+ declare class OnboardingConsentStaleError extends OnboardingError {
74
+ /**
75
+ * The documents the server reports as changed (may be empty when the
76
+ * server does not enumerate them — the caller then refetches + diffs).
77
+ */
78
+ readonly staleDocuments: StaleConsentDocument[];
79
+ constructor(staleDocuments?: StaleConsentDocument[]);
80
+ }
81
+ /**
82
+ * A /submit 400 whose body carries `code:"consent_incomplete"` (D543 §4).
83
+ * The server's authoritative re-validation found a required live document
84
+ * that was missing, not accepted, or whose evidence did not satisfy its
85
+ * acceptance_level gate. Distinct from a generic 422 so headless consumers
86
+ * switching on `kind` route the user back to the consent step specifically.
87
+ */
88
+ declare class OnboardingConsentIncompleteError extends OnboardingError {
89
+ constructor(message?: string);
90
+ }
91
+ declare class OnboardingServerError extends OnboardingError {
92
+ constructor(statusCode: number, message?: string);
93
+ }
94
+ declare class OnboardingNetworkError extends OnboardingError {
95
+ constructor(cause?: unknown);
96
+ }
97
+ declare class OnboardingValidationError extends OnboardingError {
98
+ constructor(message: string);
99
+ }
100
+ /**
101
+ * The spec endpoint returned a body that does not conform to the LOCKED
102
+ * D540 §1 response shape. This is a server-side contract violation — the
103
+ * SDK refuses to render a form from a malformed spec (a half-parsed spec
104
+ * could silently drop required fields).
105
+ */
106
+ declare class OnboardingSpecInvalidError extends OnboardingError {
107
+ constructor(detail: string);
108
+ }
109
+
110
+ export { OnboardingBotChallengeError as O, type StaleConsentDocument as S, OnboardingConflictError as a, OnboardingConsentIncompleteError as b, OnboardingConsentStaleError as c, OnboardingError as d, type OnboardingErrorKind as e, OnboardingNetworkError as f, OnboardingRateLimitError as g, OnboardingServerError as h, OnboardingSpecInvalidError as i, OnboardingSpecStaleError as j, OnboardingSubdomainTakenError as k, OnboardingValidationError as l };
@@ -1,5 +1,5 @@
1
- import { C as ContactFormValues } from '../schema-TfyBU6JW.js';
2
- export { L as LeadsWirePayload, c as contactFormSchema, l as leadsWireSchema, v as validateContactForm, a as validateLeadsPayload } from '../schema-TfyBU6JW.js';
1
+ import { C as ContactFormValues } from '../schema-BrtjpXfL.js';
2
+ export { L as LeadsWirePayload, c as contactFormSchema, l as leadsWireSchema, v as validateContactForm, a as validateLeadsPayload } from '../schema-BrtjpXfL.js';
3
3
  import 'zod/v3';
4
4
 
5
5
  /**
@@ -26,6 +26,14 @@ interface ContactFormState {
26
26
  dirty: boolean;
27
27
  }
28
28
  type ContactFormStateListener = (state: ContactFormState) => void;
29
+ interface ContactFormStateOptions {
30
+ /**
31
+ * The onboarder publishes the interest question as required (its spec's
32
+ * `forms.contact` field `interestModules`), so an empty choice is a field
33
+ * error rather than a lead that says nothing about what was asked.
34
+ */
35
+ requireInterest?: boolean;
36
+ }
29
37
  interface ContactFormControl {
30
38
  /** Current snapshot of form state. */
31
39
  getState(): ContactFormState;
@@ -59,6 +67,6 @@ interface ContactFormControl {
59
67
  * if (valid) await submitContact(form.getState().values, botToken);
60
68
  * ```
61
69
  */
62
- declare function createContactFormState(): ContactFormControl;
70
+ declare function createContactFormState(options?: ContactFormStateOptions): ContactFormControl;
63
71
 
64
- export { type ContactFormControl, type ContactFormState, type ContactFormStateListener, ContactFormValues, type FieldError, type FormStatus, createContactFormState };
72
+ export { type ContactFormControl, type ContactFormState, type ContactFormStateListener, type ContactFormStateOptions, ContactFormValues, type FieldError, type FormStatus, createContactFormState };
@@ -1,5 +1,5 @@
1
1
  import '../chunk-JFIONONR.js';
2
- export { createContactFormState } from '../chunk-AFCJLUPK.js';
3
- export { contactFormSchema, leadsWireSchema, validateContactForm, validateLeadsPayload } from '../chunk-BIFANV7M.js';
2
+ export { createContactFormState } from '../chunk-DWFZA5T6.js';
3
+ export { contactFormSchema, leadsWireSchema, validateContactForm, validateLeadsPayload } from '../chunk-WKN4U7QF.js';
4
4
  //# sourceMappingURL=index.js.map
5
5
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,103 @@
1
+ import { C as ConsentController } from './controller-BLH3wnxl.js';
2
+ import { b as ConsentDocument } from './model-BI_I-D_V.js';
3
+
4
+ /**
5
+ * Default ConsentPack document viewer (D543 §5) — @nodii/onboarding/viewer.
6
+ *
7
+ * A SEPARATE, opt-in module built on STANDARD libraries so the base SDK stays
8
+ * lean: consumers only pay for the viewer if they render our default consent
9
+ * components. The headless controller (@nodii/onboarding/consent) knows nothing
10
+ * about this — it just receives reportScrollProgress / markScrolledToEnd /
11
+ * markOpened from whatever renders the doc.
12
+ *
13
+ * Renderers (per fetched artifact — NO IFRAME anywhere; everything is inline,
14
+ * same-origin, so `scroll_then_accept` scroll tracking is REAL):
15
+ * - text/html → sanitize with DOMPurify, inject into a scroll <div>.
16
+ * - markdown → marked → DOMPurify → inject into a scroll <div>.
17
+ * - text/plain → escaped text (textContent) in a scroll <div>.
18
+ * - application/pdf → PDF.js (pdfjs-dist), rendered to <canvas> + a text
19
+ * layer, page by page, in a scroll <div>. PDF.js is LAZY-loaded via a
20
+ * dynamic import() ONLY when a PDF doc is actually present, so it never
21
+ * bloats the base bundle.
22
+ *
23
+ * The scroll container's scroll-to-end (and the PDF/open fallbacks) drive the
24
+ * consent controller so the gate works for real. A failed fetch/render
25
+ * degrades to an "open in a new tab" affordance (marks the doc opened /
26
+ * scrolled) so every doc stays consentable.
27
+ *
28
+ * Bundle discipline: DOMPurify + marked are imported at module top (this is
29
+ * the VIEWER entry, not the base SDK — the base `@nodii/onboarding` never
30
+ * statically imports this file). pdfjs-dist is imported ONLY via dynamic
31
+ * import() inside renderPdf, so even the viewer entry doesn't eagerly load it.
32
+ */
33
+
34
+ interface ConsentDocumentViewerOptions {
35
+ /** Injectable fetch for the presigned artifact GET (defaults to global). */
36
+ fetch?: typeof globalThis.fetch;
37
+ /** Per-request timeout in ms (default from fetchDocumentContent). */
38
+ timeoutMs?: number;
39
+ /**
40
+ * The `key` the controller identifies this document by (doc_type |
41
+ * custom:<key>). The viewer reports scroll/open evidence under this key.
42
+ */
43
+ consentKey: string;
44
+ }
45
+ /** Result of rendering — lets the caller know how the doc was displayed. */
46
+ interface ConsentDocumentViewerResult {
47
+ /**
48
+ * How the artifact was rendered.
49
+ *
50
+ * - html | markdown | text | pdf → rendered inline (gate is driven by real
51
+ * scroll/open).
52
+ * - open-affordance → an `open_then_accept` doc that opens in a new tab (the
53
+ * real-open IS its gate; satisfies ONLY `opened`).
54
+ * - unrenderable → a GATED doc (scroll_then_accept) that could NOT be
55
+ * rendered inline. The gate stays UNSATISFIABLE — accept is disabled and a
56
+ * clear error is shown. NO evidence is ever fabricated (COMPLIANCE, D543).
57
+ */
58
+ renderedAs: "html" | "markdown" | "text" | "pdf" | "open-affordance" | "unrenderable";
59
+ }
60
+ /**
61
+ * The worker URL to use: the packaged candidate when it really serves a
62
+ * script, else the CDN build of the exact API version that is loaded (so the
63
+ * API and the worker can never disagree on version).
64
+ *
65
+ * An http(s) candidate is fetched and judged the way the browser will judge a
66
+ * module worker: a 2xx with a JavaScript MIME type. A host's HTML fallback page
67
+ * or a 404 fails, so a bundled page no longer ends up with a worker that cannot
68
+ * load and every scroll-to-accept PDF unrenderable. A file: or blob: candidate
69
+ * comes from the runtime's own module resolution and is used as is.
70
+ *
71
+ * The probe is bounded like every other request the SDK makes: its answer is
72
+ * kept for the session and awaited before every PDF, so a host that accepts
73
+ * the connection and then stalls must cost one timeout, not every PDF on the
74
+ * page. A probe that times out counts as "not a script".
75
+ *
76
+ * Exported for tests only.
77
+ */
78
+ declare function _pickPdfWorkerSrc(packagedUrl: string | null, apiVersion: string, fetchImpl?: typeof globalThis.fetch, timeoutMs?: number): Promise<string>;
79
+ /**
80
+ * Fetch + render a consent document INLINE into `container`, wiring its scroll
81
+ * container (or the legitimate open-affordance) to the consent controller so
82
+ * `scroll_then_accept` / `open_then_accept` gates work for real. NO iframe.
83
+ *
84
+ * On ANY inline-render failure for a `scroll_then_accept` doc the gate stays
85
+ * UNSATISFIABLE (never fabricates scrolled_to_end, D543 §5 COMPLIANCE) — the
86
+ * caller's Retry re-invokes this.
87
+ *
88
+ * @returns how the doc was rendered (for the caller's telemetry/UX).
89
+ */
90
+ declare function renderConsentDocument(container: HTMLElement, doc: ConsentDocument, control: ConsentController, options: ConsentDocumentViewerOptions): Promise<ConsentDocumentViewerResult>;
91
+ /** Reset the module-level pdf-worker latch (tests). */
92
+ declare function _resetPdfWorkerState(): void;
93
+
94
+ type ___viewer_render_js_ConsentDocumentViewerOptions = ConsentDocumentViewerOptions;
95
+ type ___viewer_render_js_ConsentDocumentViewerResult = ConsentDocumentViewerResult;
96
+ declare const ___viewer_render_js__pickPdfWorkerSrc: typeof _pickPdfWorkerSrc;
97
+ declare const ___viewer_render_js__resetPdfWorkerState: typeof _resetPdfWorkerState;
98
+ declare const ___viewer_render_js_renderConsentDocument: typeof renderConsentDocument;
99
+ declare namespace ___viewer_render_js {
100
+ export { type ___viewer_render_js_ConsentDocumentViewerOptions as ConsentDocumentViewerOptions, type ___viewer_render_js_ConsentDocumentViewerResult as ConsentDocumentViewerResult, ___viewer_render_js__pickPdfWorkerSrc as _pickPdfWorkerSrc, ___viewer_render_js__resetPdfWorkerState as _resetPdfWorkerState, ___viewer_render_js_renderConsentDocument as renderConsentDocument };
101
+ }
102
+
103
+ export { type ConsentDocumentViewerOptions as C, ___viewer_render_js as _, type ConsentDocumentViewerResult as a, _resetPdfWorkerState as b, renderConsentDocument as r };