@cxpinsight/survey-spec 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -15,8 +15,8 @@ export { evaluateTree, compareValues, MAX_TREE_DEPTH } from './evaluateTree';
15
15
  export type { ConditionNode, Operand, TreeContext } from './evaluateTree';
16
16
  export { resolveVariable } from './resolveVariable';
17
17
  export { splitIntoPages } from './pageFlow';
18
- export { validateAnswer, impliedChoices, impliedActions, impliedStepActions, impliedEqualProminence, safeActionHref, safeImageSrc, isDeferChoice, canSwipeToDismiss, autoDismissMs, deadlineCountdown, formatCountdown, outcomeFor, voiceUsable, rewardToShow, cardClickTarget, cardHoverEffect, resolveAction } from './validate';
19
- export type { ValidatableQuestion, OutcomeKind, OutcomeInput, ActionKind, RawAction, ResolvedAction } from './validate';
18
+ export { complianceGate, questionForValidation, validateAnswer, impliedChoices, impliedActions, impliedStepActions, impliedEqualProminence, safeActionHref, safeImageSrc, isDeferChoice, canSwipeToDismiss, autoDismissMs, deadlineCountdown, formatCountdown, outcomeFor, voiceUsable, rewardToShow, cardClickTarget, cardHoverEffect, resolveAction } from './validate';
19
+ export type { ComplianceState, EscapeHatch, ComplianceInput, ComplianceVerdict, ValidatableQuestion, OutcomeKind, OutcomeInput, ActionKind, RawAction, ResolvedAction } from './validate';
20
20
  export { validateAnswerCode } from './validate';
21
21
  export { wordMessage, EN_MESSAGES, MESSAGE_CODES } from './messages';
22
22
  export type { ValidationMessage } from './messages';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC7E,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnU,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACxH,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACrE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EACL,mBAAmB,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EACzE,kBAAkB,EAAE,SAAS,EAAE,iBAAiB,EAChD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EACjD,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EACtE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,GAC7D,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvF,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC7E,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1W,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,eAAe,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1L,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACrE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EACL,mBAAmB,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EACzE,kBAAkB,EAAE,SAAS,EAAE,iBAAiB,EAChD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EACjD,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EACtE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,GAC7D,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvF,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.optionLabelsOf = exports.voiceOptionsOf = exports.optionValueOf = exports.optionLabelOf = exports.shortCode = exports.buildPromptText = exports.scaffoldFor = exports.skipNoteText = exports.voiceDisclaimer = exports.auditVoiceQuestions = exports.voiceCanHandle = exports.voiceSupport = exports.distinctiveLabels = exports.matchDate = exports.matchNumberInRange = exports.rankOptions = exports.matchOptions = exports.matchOption = exports.matchBoolean = exports.normalizeTranscript = exports.MESSAGE_CODES = exports.EN_MESSAGES = exports.wordMessage = exports.validateAnswerCode = exports.resolveAction = exports.cardHoverEffect = exports.cardClickTarget = exports.rewardToShow = exports.voiceUsable = exports.outcomeFor = exports.formatCountdown = exports.deadlineCountdown = exports.autoDismissMs = exports.canSwipeToDismiss = exports.isDeferChoice = exports.safeImageSrc = exports.safeActionHref = exports.impliedEqualProminence = exports.impliedStepActions = exports.impliedActions = exports.impliedChoices = exports.validateAnswer = exports.splitIntoPages = exports.resolveVariable = exports.MAX_TREE_DEPTH = exports.compareValues = exports.evaluateTree = exports.isParseable = exports.evaluateCondition = exports.evaluateExpression = void 0;
3
+ exports.optionValueOf = exports.optionLabelOf = exports.shortCode = exports.buildPromptText = exports.scaffoldFor = exports.skipNoteText = exports.voiceDisclaimer = exports.auditVoiceQuestions = exports.voiceCanHandle = exports.voiceSupport = exports.distinctiveLabels = exports.matchDate = exports.matchNumberInRange = exports.rankOptions = exports.matchOptions = exports.matchOption = exports.matchBoolean = exports.normalizeTranscript = exports.MESSAGE_CODES = exports.EN_MESSAGES = exports.wordMessage = exports.validateAnswerCode = exports.resolveAction = exports.cardHoverEffect = exports.cardClickTarget = exports.rewardToShow = exports.voiceUsable = exports.outcomeFor = exports.formatCountdown = exports.deadlineCountdown = exports.autoDismissMs = exports.canSwipeToDismiss = exports.isDeferChoice = exports.safeImageSrc = exports.safeActionHref = exports.impliedEqualProminence = exports.impliedStepActions = exports.impliedActions = exports.impliedChoices = exports.validateAnswer = exports.questionForValidation = exports.complianceGate = exports.splitIntoPages = exports.resolveVariable = exports.MAX_TREE_DEPTH = exports.compareValues = exports.evaluateTree = exports.isParseable = exports.evaluateCondition = exports.evaluateExpression = void 0;
4
+ exports.optionLabelsOf = exports.voiceOptionsOf = void 0;
4
5
  /**
5
6
  * @cxpinsight/survey-spec
6
7
  *
@@ -26,6 +27,8 @@ Object.defineProperty(exports, "resolveVariable", { enumerable: true, get: funct
26
27
  var pageFlow_1 = require("./pageFlow");
27
28
  Object.defineProperty(exports, "splitIntoPages", { enumerable: true, get: function () { return pageFlow_1.splitIntoPages; } });
28
29
  var validate_1 = require("./validate");
30
+ Object.defineProperty(exports, "complianceGate", { enumerable: true, get: function () { return validate_1.complianceGate; } });
31
+ Object.defineProperty(exports, "questionForValidation", { enumerable: true, get: function () { return validate_1.questionForValidation; } });
29
32
  Object.defineProperty(exports, "validateAnswer", { enumerable: true, get: function () { return validate_1.validateAnswer; } });
30
33
  Object.defineProperty(exports, "impliedChoices", { enumerable: true, get: function () { return validate_1.impliedChoices; } });
31
34
  Object.defineProperty(exports, "impliedActions", { enumerable: true, get: function () { return validate_1.impliedActions; } });
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,4EAA4E;AAC5E,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CA4B9E,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAI5E;AAED,0EAA0E;AAC1E,eAAO,MAAM,aAAa,UAA2B,CAAC"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,4EAA4E;AAC5E,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAiC9E,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAI5E;AAED,0EAA0E;AAC1E,eAAO,MAAM,aAAa,UAA2B,CAAC"}
package/dist/messages.js CHANGED
@@ -25,6 +25,11 @@ exports.wordMessage = wordMessage;
25
25
  /** English wording, one entry per code. The only prose in the validator. */
26
26
  exports.EN_MESSAGES = {
27
27
  required: () => 'This field is required',
28
+ // Compliance. Written for someone who is being held, not for a developer:
29
+ // say what is happening and what happens next, never just "blocked".
30
+ compliance_under_review: () => 'Thanks — someone is reviewing this now. '
31
+ + 'You do not need to do anything else, and this will update when the review is done.',
32
+ compliance_blocked: () => 'This needs to be accepted before you can continue.',
28
33
  choose_option: () => 'Please choose an option',
29
34
  format_email: () => 'Please enter a valid email address (e.g., you@example.com)',
30
35
  format_url: () => 'Please enter a full URL, including https://',
@@ -259,6 +259,101 @@ export interface OutcomeInput {
259
259
  * surface you get past by saying no is not a compliance surface.
260
260
  */
261
261
  export declare function outcomeFor(input: OutcomeInput): OutcomeKind;
262
+ /**
263
+ * The question as it should be validated RIGHT NOW.
264
+ *
265
+ * `requiredWhen` — "required only if they said yes earlier" — was compiled by
266
+ * the server and read by no renderer at all. An author who set it got a question
267
+ * that was never required, on every channel, with nothing reporting it. The last
268
+ * of the four conditions to be inert everywhere rather than somewhere.
269
+ *
270
+ * ADDITIVE, deliberately: a question already marked required stays required. The
271
+ * alternative reading — that requiredWhen REPLACES required — would let a
272
+ * condition make an existing question optional, so a survey that collects an
273
+ * answer today could quietly stop collecting it. This direction can only ever
274
+ * ask for more, never less, which is the safe way to turn on a rule that has
275
+ * never run.
276
+ *
277
+ * The caller supplies the evaluator because each renderer has its own, already
278
+ * wired to its answers and its compiled trees. What lives here is the decision,
279
+ * which is the part that must not differ between them.
280
+ *
281
+ * @param evaluate must FAIL OPEN by returning false for a condition it cannot
282
+ * read — an unreadable requiredWhen leaves the question optional rather than
283
+ * blocking a respondent behind a rule nobody can see.
284
+ */
285
+ export declare function questionForValidation<T extends {
286
+ required?: unknown;
287
+ isRequired?: unknown;
288
+ requiredWhen?: unknown;
289
+ }>(question: T, evaluate: (expr: string) => boolean): T;
290
+ /** Where a compliance interaction has got to. */
291
+ export type ComplianceState = 'open' | 'blocked' | 'under_review' | 'cleared';
292
+ /** What a respondent is offered when they cannot simply proceed. */
293
+ export type EscapeHatch = 'none' | 'contact' | 'defer';
294
+ export interface ComplianceInput {
295
+ interactionType?: string | null;
296
+ /** The implied choice taken, or null before they have answered. */
297
+ action?: string | null;
298
+ /** True when the chosen action is the affirmative one. */
299
+ affirmative?: boolean;
300
+ compliance?: {
301
+ reviewRequired?: unknown;
302
+ onDecline?: unknown;
303
+ blockedMessage?: unknown;
304
+ reviewMessage?: unknown;
305
+ escapeHatch?: unknown;
306
+ contactHref?: unknown;
307
+ } | null;
308
+ }
309
+ export interface ComplianceVerdict {
310
+ state: ComplianceState;
311
+ /** May the respondent close the surface? */
312
+ dismissible: boolean;
313
+ escape: EscapeHatch;
314
+ /** Where `escape: 'contact'` sends them. Null when there is nowhere. */
315
+ contactHref: string | null;
316
+ /** A wording code, or null when there is nothing to say. */
317
+ messageCode: 'compliance_blocked' | 'compliance_under_review' | null;
318
+ /** The author's own prose, which travels verbatim and is never translated. */
319
+ authorMessage: string | null;
320
+ /** True when this state is worth recording — see the monitoring note. */
321
+ reportable: boolean;
322
+ }
323
+ /**
324
+ * Decide whether a compliance interaction lets the respondent go.
325
+ *
326
+ * This is the half of compliance that was never built. `outcomeFor` already
327
+ * returns `hold`, meaning "do not let them through yet", and all three renderers
328
+ * read it — to choose a closing message. Nothing anywhere connected it to
329
+ * whether the surface could be CLOSED, so a compliance check was dismissed
330
+ * exactly like a rating prompt. A compliance surface you get past by ignoring it
331
+ * is not a compliance surface.
332
+ *
333
+ * Three states hold, and they are not the same thing to the person looking at
334
+ * them:
335
+ *
336
+ * open they have not answered yet
337
+ * blocked they declined, and the author chose to hold them
338
+ * under_review they accepted, and a person has to look at it
339
+ *
340
+ * `under_review` matters most and is the easiest to get wrong. Letting someone
341
+ * through on submit tells them they are finished when they are not, and defeats
342
+ * the check; saying nothing leaves them staring at a surface that looks stuck.
343
+ * So it holds AND it says so.
344
+ *
345
+ * ── The escape hatch ──
346
+ * A surface that cannot be closed can trap someone whose answer will never be
347
+ * accepted — a failed document check at 2am with nobody to ask. The author
348
+ * chooses which way out exists, and the default is `contact` rather than `none`,
349
+ * because a route to a human costs the check nothing while a dead end costs a
350
+ * customer everything. `defer` is the only one that actually releases them; it
351
+ * is a postponement, and the interaction comes back.
352
+ *
353
+ * Anything unrecognised falls back to `contact` rather than to `none`: a
354
+ * mis-typed setting must not silently become a trap.
355
+ */
356
+ export declare function complianceGate(input: ComplianceInput): ComplianceVerdict;
262
357
  /**
263
358
  * Can voice mode do anything on this step?
264
359
  *
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
package/dist/validate.js CHANGED
@@ -14,6 +14,8 @@ exports.autoDismissMs = autoDismissMs;
14
14
  exports.deadlineCountdown = deadlineCountdown;
15
15
  exports.formatCountdown = formatCountdown;
16
16
  exports.outcomeFor = outcomeFor;
17
+ exports.questionForValidation = questionForValidation;
18
+ exports.complianceGate = complianceGate;
17
19
  exports.voiceUsable = voiceUsable;
18
20
  exports.rewardToShow = rewardToShow;
19
21
  exports.cardClickTarget = cardClickTarget;
@@ -738,6 +740,126 @@ function outcomeFor(input) {
738
740
  // for something we do not recognise: it acknowledges without promising.
739
741
  return 'thanks';
740
742
  }
743
+ /**
744
+ * The question as it should be validated RIGHT NOW.
745
+ *
746
+ * `requiredWhen` — "required only if they said yes earlier" — was compiled by
747
+ * the server and read by no renderer at all. An author who set it got a question
748
+ * that was never required, on every channel, with nothing reporting it. The last
749
+ * of the four conditions to be inert everywhere rather than somewhere.
750
+ *
751
+ * ADDITIVE, deliberately: a question already marked required stays required. The
752
+ * alternative reading — that requiredWhen REPLACES required — would let a
753
+ * condition make an existing question optional, so a survey that collects an
754
+ * answer today could quietly stop collecting it. This direction can only ever
755
+ * ask for more, never less, which is the safe way to turn on a rule that has
756
+ * never run.
757
+ *
758
+ * The caller supplies the evaluator because each renderer has its own, already
759
+ * wired to its answers and its compiled trees. What lives here is the decision,
760
+ * which is the part that must not differ between them.
761
+ *
762
+ * @param evaluate must FAIL OPEN by returning false for a condition it cannot
763
+ * read — an unreadable requiredWhen leaves the question optional rather than
764
+ * blocking a respondent behind a rule nobody can see.
765
+ */
766
+ function questionForValidation(question, evaluate) {
767
+ if (!question)
768
+ return question;
769
+ if (question.required === true || question.isRequired === true)
770
+ return question;
771
+ const expr = question.requiredWhen;
772
+ if (typeof expr !== 'string' || !expr.trim())
773
+ return question;
774
+ let now = false;
775
+ try {
776
+ now = evaluate(expr) === true;
777
+ }
778
+ catch {
779
+ now = false;
780
+ }
781
+ return now ? { ...question, required: true } : question;
782
+ }
783
+ const ESCAPES = ['none', 'contact', 'defer'];
784
+ /**
785
+ * Decide whether a compliance interaction lets the respondent go.
786
+ *
787
+ * This is the half of compliance that was never built. `outcomeFor` already
788
+ * returns `hold`, meaning "do not let them through yet", and all three renderers
789
+ * read it — to choose a closing message. Nothing anywhere connected it to
790
+ * whether the surface could be CLOSED, so a compliance check was dismissed
791
+ * exactly like a rating prompt. A compliance surface you get past by ignoring it
792
+ * is not a compliance surface.
793
+ *
794
+ * Three states hold, and they are not the same thing to the person looking at
795
+ * them:
796
+ *
797
+ * open they have not answered yet
798
+ * blocked they declined, and the author chose to hold them
799
+ * under_review they accepted, and a person has to look at it
800
+ *
801
+ * `under_review` matters most and is the easiest to get wrong. Letting someone
802
+ * through on submit tells them they are finished when they are not, and defeats
803
+ * the check; saying nothing leaves them staring at a surface that looks stuck.
804
+ * So it holds AND it says so.
805
+ *
806
+ * ── The escape hatch ──
807
+ * A surface that cannot be closed can trap someone whose answer will never be
808
+ * accepted — a failed document check at 2am with nobody to ask. The author
809
+ * chooses which way out exists, and the default is `contact` rather than `none`,
810
+ * because a route to a human costs the check nothing while a dead end costs a
811
+ * customer everything. `defer` is the only one that actually releases them; it
812
+ * is a postponement, and the interaction comes back.
813
+ *
814
+ * Anything unrecognised falls back to `contact` rather than to `none`: a
815
+ * mis-typed setting must not silently become a trap.
816
+ */
817
+ function complianceGate(input) {
818
+ const free = {
819
+ state: 'cleared', dismissible: true, escape: 'none',
820
+ contactHref: null, messageCode: null, authorMessage: null, reportable: false,
821
+ };
822
+ if (String(input?.interactionType || '').toLowerCase() !== 'comply')
823
+ return free;
824
+ const c = input.compliance || {};
825
+ const raw = String(c.escapeHatch ?? '').toLowerCase();
826
+ const escape = ESCAPES.includes(raw) ? raw : 'contact';
827
+ // Through safeActionHref, not raw: this is a destination the host page is told
828
+ // to follow, so `javascript:` and friends are refused the same way an action
829
+ // button's destination is. `mailto:` passes, which is the common case for a
830
+ // route to a human.
831
+ const contactHref = (escape === 'contact' ? safeActionHref({ type: 'url', value: c.contactHref }) : null) ?? null;
832
+ const authorPose = (v) => (typeof v === 'string' && v.trim() ? v : null);
833
+ // `defer` is the only hatch that releases them — closing IS the postponement.
834
+ const canLeave = escape === 'defer';
835
+ const answered = input.action !== null && input.action !== undefined && input.action !== '';
836
+ if (!answered) {
837
+ return {
838
+ state: 'open', dismissible: canLeave, escape, contactHref,
839
+ messageCode: null, authorMessage: null, reportable: false,
840
+ };
841
+ }
842
+ if (input.affirmative === false) {
843
+ // A decline the author chose to accept is simply the end of it.
844
+ if (String(c.onDecline || 'block') !== 'block')
845
+ return free;
846
+ return {
847
+ state: 'blocked', dismissible: canLeave, escape, contactHref,
848
+ messageCode: 'compliance_blocked',
849
+ authorMessage: authorPose(c.blockedMessage),
850
+ reportable: true,
851
+ };
852
+ }
853
+ if (c.reviewRequired === true) {
854
+ return {
855
+ state: 'under_review', dismissible: canLeave, escape, contactHref,
856
+ messageCode: 'compliance_under_review',
857
+ authorMessage: authorPose(c.reviewMessage),
858
+ reportable: true,
859
+ };
860
+ }
861
+ return free;
862
+ }
741
863
  /**
742
864
  * Can voice mode do anything on this step?
743
865
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cxpinsight/survey-spec",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "The survey specification the renderers share — expression evaluation first. Source of truth for behaviour that must be identical on web, links and mobile.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",