@cxpinsight/survey-spec 0.6.0 → 0.8.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, cardIsClickable, 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,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3X,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.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.cardIsClickable = 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 = exports.optionValueOf = 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; } });
@@ -42,6 +45,7 @@ Object.defineProperty(exports, "outcomeFor", { enumerable: true, get: function (
42
45
  Object.defineProperty(exports, "voiceUsable", { enumerable: true, get: function () { return validate_1.voiceUsable; } });
43
46
  Object.defineProperty(exports, "rewardToShow", { enumerable: true, get: function () { return validate_1.rewardToShow; } });
44
47
  Object.defineProperty(exports, "cardClickTarget", { enumerable: true, get: function () { return validate_1.cardClickTarget; } });
48
+ Object.defineProperty(exports, "cardIsClickable", { enumerable: true, get: function () { return validate_1.cardIsClickable; } });
45
49
  Object.defineProperty(exports, "cardHoverEffect", { enumerable: true, get: function () { return validate_1.cardHoverEffect; } });
46
50
  Object.defineProperty(exports, "resolveAction", { enumerable: true, get: function () { return validate_1.resolveAction; } });
47
51
  var validate_2 = require("./validate");
@@ -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
  *
@@ -305,6 +400,31 @@ export declare function cardClickTarget(display: {
305
400
  } | null | undefined, actions: Array<{
306
401
  href?: string;
307
402
  }> | null | undefined): string | null;
403
+ /**
404
+ * May the whole card be pressed?
405
+ *
406
+ * SEPARATE FROM WHERE IT GOES, and that separation is the fix. cardClickTarget
407
+ * answers "what destination does the card carry", and returns null when there
408
+ * is none — but a notification whose action is "tell your app" or "call an
409
+ * endpoint" has no destination and is still entirely pressable: pressing it
410
+ * records the answer and runs the action, which is the whole point of those
411
+ * two kinds.
412
+ *
413
+ * Reading the target as the permission meant that on any non-URL action the
414
+ * card lost its pointer cursor, its hover treatment and its click handler at
415
+ * once — so "What the card does under the cursor" did nothing, with no clue
416
+ * why.
417
+ *
418
+ * The safety argument is unchanged and lives here: exactly ONE action. That is
419
+ * the notification shape, and it excludes every consent choice — on a
420
+ * two-action compliance takeover a stray click anywhere would otherwise record
421
+ * agreement, which a consent record must never manufacture.
422
+ */
423
+ export declare function cardIsClickable(display: {
424
+ cardClickable?: unknown;
425
+ } | null | undefined, actions: Array<{
426
+ href?: string;
427
+ }> | null | undefined): boolean;
308
428
  /** Hover treatment for a clickable card. Pointer devices only. */
309
429
  export declare function cardHoverEffect(display: {
310
430
  hoverEffect?: unknown;
@@ -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,9 +14,12 @@ 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;
22
+ exports.cardIsClickable = cardIsClickable;
20
23
  exports.cardHoverEffect = cardHoverEffect;
21
24
  exports.resolveAction = resolveAction;
22
25
  const messages_1 = require("./messages");
@@ -738,6 +741,126 @@ function outcomeFor(input) {
738
741
  // for something we do not recognise: it acknowledges without promising.
739
742
  return 'thanks';
740
743
  }
744
+ /**
745
+ * The question as it should be validated RIGHT NOW.
746
+ *
747
+ * `requiredWhen` — "required only if they said yes earlier" — was compiled by
748
+ * the server and read by no renderer at all. An author who set it got a question
749
+ * that was never required, on every channel, with nothing reporting it. The last
750
+ * of the four conditions to be inert everywhere rather than somewhere.
751
+ *
752
+ * ADDITIVE, deliberately: a question already marked required stays required. The
753
+ * alternative reading — that requiredWhen REPLACES required — would let a
754
+ * condition make an existing question optional, so a survey that collects an
755
+ * answer today could quietly stop collecting it. This direction can only ever
756
+ * ask for more, never less, which is the safe way to turn on a rule that has
757
+ * never run.
758
+ *
759
+ * The caller supplies the evaluator because each renderer has its own, already
760
+ * wired to its answers and its compiled trees. What lives here is the decision,
761
+ * which is the part that must not differ between them.
762
+ *
763
+ * @param evaluate must FAIL OPEN by returning false for a condition it cannot
764
+ * read — an unreadable requiredWhen leaves the question optional rather than
765
+ * blocking a respondent behind a rule nobody can see.
766
+ */
767
+ function questionForValidation(question, evaluate) {
768
+ if (!question)
769
+ return question;
770
+ if (question.required === true || question.isRequired === true)
771
+ return question;
772
+ const expr = question.requiredWhen;
773
+ if (typeof expr !== 'string' || !expr.trim())
774
+ return question;
775
+ let now = false;
776
+ try {
777
+ now = evaluate(expr) === true;
778
+ }
779
+ catch {
780
+ now = false;
781
+ }
782
+ return now ? { ...question, required: true } : question;
783
+ }
784
+ const ESCAPES = ['none', 'contact', 'defer'];
785
+ /**
786
+ * Decide whether a compliance interaction lets the respondent go.
787
+ *
788
+ * This is the half of compliance that was never built. `outcomeFor` already
789
+ * returns `hold`, meaning "do not let them through yet", and all three renderers
790
+ * read it — to choose a closing message. Nothing anywhere connected it to
791
+ * whether the surface could be CLOSED, so a compliance check was dismissed
792
+ * exactly like a rating prompt. A compliance surface you get past by ignoring it
793
+ * is not a compliance surface.
794
+ *
795
+ * Three states hold, and they are not the same thing to the person looking at
796
+ * them:
797
+ *
798
+ * open they have not answered yet
799
+ * blocked they declined, and the author chose to hold them
800
+ * under_review they accepted, and a person has to look at it
801
+ *
802
+ * `under_review` matters most and is the easiest to get wrong. Letting someone
803
+ * through on submit tells them they are finished when they are not, and defeats
804
+ * the check; saying nothing leaves them staring at a surface that looks stuck.
805
+ * So it holds AND it says so.
806
+ *
807
+ * ── The escape hatch ──
808
+ * A surface that cannot be closed can trap someone whose answer will never be
809
+ * accepted — a failed document check at 2am with nobody to ask. The author
810
+ * chooses which way out exists, and the default is `contact` rather than `none`,
811
+ * because a route to a human costs the check nothing while a dead end costs a
812
+ * customer everything. `defer` is the only one that actually releases them; it
813
+ * is a postponement, and the interaction comes back.
814
+ *
815
+ * Anything unrecognised falls back to `contact` rather than to `none`: a
816
+ * mis-typed setting must not silently become a trap.
817
+ */
818
+ function complianceGate(input) {
819
+ const free = {
820
+ state: 'cleared', dismissible: true, escape: 'none',
821
+ contactHref: null, messageCode: null, authorMessage: null, reportable: false,
822
+ };
823
+ if (String(input?.interactionType || '').toLowerCase() !== 'comply')
824
+ return free;
825
+ const c = input.compliance || {};
826
+ const raw = String(c.escapeHatch ?? '').toLowerCase();
827
+ const escape = ESCAPES.includes(raw) ? raw : 'contact';
828
+ // Through safeActionHref, not raw: this is a destination the host page is told
829
+ // to follow, so `javascript:` and friends are refused the same way an action
830
+ // button's destination is. `mailto:` passes, which is the common case for a
831
+ // route to a human.
832
+ const contactHref = (escape === 'contact' ? safeActionHref({ type: 'url', value: c.contactHref }) : null) ?? null;
833
+ const authorPose = (v) => (typeof v === 'string' && v.trim() ? v : null);
834
+ // `defer` is the only hatch that releases them — closing IS the postponement.
835
+ const canLeave = escape === 'defer';
836
+ const answered = input.action !== null && input.action !== undefined && input.action !== '';
837
+ if (!answered) {
838
+ return {
839
+ state: 'open', dismissible: canLeave, escape, contactHref,
840
+ messageCode: null, authorMessage: null, reportable: false,
841
+ };
842
+ }
843
+ if (input.affirmative === false) {
844
+ // A decline the author chose to accept is simply the end of it.
845
+ if (String(c.onDecline || 'block') !== 'block')
846
+ return free;
847
+ return {
848
+ state: 'blocked', dismissible: canLeave, escape, contactHref,
849
+ messageCode: 'compliance_blocked',
850
+ authorMessage: authorPose(c.blockedMessage),
851
+ reportable: true,
852
+ };
853
+ }
854
+ if (c.reviewRequired === true) {
855
+ return {
856
+ state: 'under_review', dismissible: canLeave, escape, contactHref,
857
+ messageCode: 'compliance_under_review',
858
+ authorMessage: authorPose(c.reviewMessage),
859
+ reportable: true,
860
+ };
861
+ }
862
+ return free;
863
+ }
741
864
  /**
742
865
  * Can voice mode do anything on this step?
743
866
  *
@@ -806,6 +929,32 @@ function cardClickTarget(display, actions) {
806
929
  const own = safeActionHref({ type: 'url', value: display?.cardHref });
807
930
  return own || acts[0].href || null;
808
931
  }
932
+ /**
933
+ * May the whole card be pressed?
934
+ *
935
+ * SEPARATE FROM WHERE IT GOES, and that separation is the fix. cardClickTarget
936
+ * answers "what destination does the card carry", and returns null when there
937
+ * is none — but a notification whose action is "tell your app" or "call an
938
+ * endpoint" has no destination and is still entirely pressable: pressing it
939
+ * records the answer and runs the action, which is the whole point of those
940
+ * two kinds.
941
+ *
942
+ * Reading the target as the permission meant that on any non-URL action the
943
+ * card lost its pointer cursor, its hover treatment and its click handler at
944
+ * once — so "What the card does under the cursor" did nothing, with no clue
945
+ * why.
946
+ *
947
+ * The safety argument is unchanged and lives here: exactly ONE action. That is
948
+ * the notification shape, and it excludes every consent choice — on a
949
+ * two-action compliance takeover a stray click anywhere would otherwise record
950
+ * agreement, which a consent record must never manufacture.
951
+ */
952
+ function cardIsClickable(display, actions) {
953
+ const acts = Array.isArray(actions) ? actions : [];
954
+ if (acts.length !== 1)
955
+ return false;
956
+ return display?.cardClickable !== false;
957
+ }
809
958
  /** Hover treatment for a clickable card. Pointer devices only. */
810
959
  function cardHoverEffect(display) {
811
960
  const v = String(display?.hoverEffect || '').toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cxpinsight/survey-spec",
3
- "version": "0.6.0",
3
+ "version": "0.8.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",