@active-reach/web-sdk 1.10.0 → 1.11.1

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/react.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { createContext, useState, useEffect, useContext } from "react";
2
- import { A as Aegis } from "./analytics-Cc4-QQBf.mjs";
2
+ import { A as Aegis } from "./analytics-6PR9ERDS.mjs";
3
3
  const AegisContext = createContext({
4
4
  aegis: null,
5
5
  isReady: false
@@ -46,7 +46,6 @@ export declare class AegisMessageRuntime {
46
46
  readonly inApp: AegisInAppManager;
47
47
  readonly widgets: AegisWidgetManager;
48
48
  private initialized;
49
- private ownedTriggerEngine;
50
49
  constructor(config: AegisMessageRuntimeConfig);
51
50
  /**
52
51
  * Boots both managers in parallel. Safe to call multiple times — the
@@ -1 +1 @@
1
- {"version":3,"file":"AegisMessageRuntime.d.ts","sourceRoot":"","sources":["../../src/runtime/AegisMessageRuntime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGxE,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE;;kEAE8D;IAC9D,aAAa,CAAC,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAC;IACnD;;sDAEkD;IAClD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;0EACsE;IACtE,cAAc,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;CACtD;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAA8B;gBAE5C,MAAM,EAAE,yBAAyB;IAoC7C;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC;;;;;;;OAOG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD;;;;;;OAMG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI;IAI/E;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIxC;;;;;OAKG;IACH,OAAO,IAAI,IAAI;IAOf;;;;;OAKG;IACH,YAAY,IAAI,aAAa,EAAE;CAMhC"}
1
+ {"version":3,"file":"AegisMessageRuntime.d.ts","sourceRoot":"","sources":["../../src/runtime/AegisMessageRuntime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGxE,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE;;kEAE8D;IAC9D,aAAa,CAAC,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAC;IACnD;;sDAEkD;IAClD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;0EACsE;IACtE,cAAc,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;CACtD;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,yBAAyB;IAsC7C;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC;;;;;;;OAOG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD;;;;;;OAMG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI;IAI/E;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIxC;;;;;OAKG;IACH,OAAO,IAAI,IAAI;IAMf;;;;;OAKG;IACH,YAAY,IAAI,aAAa,EAAE;CAMhC"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Micro-Intent Engine — anonymous-visitor cross-session ledger
3
+ * (P1 Task 7 + P2 Task 4 full implementation).
4
+ *
5
+ * Stores `cum_dwell_sku` (rolling cumulative time on PDP per SKU) and
6
+ * `session_count_for_sku` for anonymous visitors so the Returning
7
+ * Hover-Browser pattern (catalog ID 4) can fire when an unknown
8
+ * visitor has accumulated meaningful engagement across multiple sessions.
9
+ *
10
+ * ─────────────────────────────────────────────────────────────────────
11
+ * SAFARI / ITP ACKNOWLEDGEMENT (Robustness #2 — load-bearing)
12
+ * ─────────────────────────────────────────────────────────────────────
13
+ *
14
+ * Apple's Intelligent Tracking Prevention (ITP) caps client-side
15
+ * `localStorage` to **7 days** for non-interactive third-party browsing
16
+ * on iOS Safari + macOS Safari.
17
+ *
18
+ * The `aegis_intent_ledger` key persisted here decays for anonymous
19
+ * Safari users faster than for Chrome / Firefox users. Concrete
20
+ * downstream impact:
21
+ *
22
+ * - Pattern 4 "Returning Hover-Browser" is the ONLY pattern whose
23
+ * trigger depends on cross-session anonymous accumulation. On Safari
24
+ * it is reliable only within a 7-day window. After that, an
25
+ * anonymous returning visitor looks like a new visitor.
26
+ *
27
+ * - All other patterns are session-scoped (Layer-2 only, in-tab) or
28
+ * identified-contact-scoped (Layer-1 fetched per identify()), so
29
+ * they are unaffected by ITP storage decay.
30
+ *
31
+ * DOCUMENTATION REQUIREMENT — when P3a ships the Pattern Detail screen
32
+ * for Returning Hover-Browser, its UI must carry the footnote:
33
+ *
34
+ * "On Safari, this pattern is reliable for visitors who return within
35
+ * 7 days. For longer windows, consider identifying customers via
36
+ * signed-in checkout."
37
+ *
38
+ * SERVER-SIDE FALLBACK (deferred to P5) — when a contact eventually
39
+ * identifies (sign-in / OTP / checkout), we promote this localStorage
40
+ * ledger to a server-side `contact_intent_ledger` row. That bypasses
41
+ * ITP entirely for identified users. Implementation tracked in
42
+ * MICRO_INTENT_ENGINE.md §"Robustness 2" → "Server-side fallback".
43
+ *
44
+ * ─────────────────────────────────────────────────────────────────────
45
+ * Implementation (P2 Task 4)
46
+ * ─────────────────────────────────────────────────────────────────────
47
+ *
48
+ * The IntentLedger class implements IIntentLedger with localStorage as
49
+ * the backing store. Capped-LRU at INTENT_LEDGER_MAX_SKUS, rolling
50
+ * INTENT_LEDGER_WINDOW_DAYS window, hydrate-on-construct, debounced
51
+ * persistence (250ms) to avoid write-amplification on rapid dwell
52
+ * ticks. In-memory state is always fresh for synchronous reads.
53
+ *
54
+ * The constructor accepts an optional Storage-shaped object for
55
+ * testability; in production it defaults to `globalThis.localStorage`.
56
+ * The P2 Task 4 forward-marker below remains so the drift-anchor
57
+ * test (test_intent_ledger_itp_doc.py) keeps tracking the right
58
+ * scope reference even after the class lands.
59
+ *
60
+ * P2 Task 4 implementation complete (2026-05-11). The acknowledgement
61
+ * comment above is retained so future engineers reading a Safari bug
62
+ * report can find the source-of-truth explanation here, not in a plan
63
+ * doc that may have rotted.
64
+ */
65
+ /** localStorage key the ledger persists under. Same key in stub and
66
+ * P2 Task 4 implementation so the stub-to-full upgrade is non-
67
+ * breaking (no key migration). */
68
+ export declare const INTENT_LEDGER_STORAGE_KEY = "aegis_intent_ledger";
69
+ /** Max number of SKU entries persisted. Beyond this, the LRU evicts
70
+ * least-recently-touched. Picked to stay well under typical Safari /
71
+ * Chrome localStorage caps (~5MB origin quota; we use <2KB). */
72
+ export declare const INTENT_LEDGER_MAX_SKUS = 50;
73
+ /** Rolling window for `cum_dwell_sku` accumulation. Aligns with the
74
+ * Safari ITP 7-day cap above — values older than this are dropped on
75
+ * every read so the data set doesn't outlive the storage's own
76
+ * reliability window. */
77
+ export declare const INTENT_LEDGER_WINDOW_DAYS = 7;
78
+ export interface SkuLedgerEntry {
79
+ /** Rolling cumulative dwell on this SKU's PDP, in milliseconds. */
80
+ cumDwellMs: number;
81
+ /** Distinct session count for this SKU. */
82
+ sessionCount: number;
83
+ /** Epoch ms of last update; used for LRU eviction + window pruning. */
84
+ lastSeenAt: number;
85
+ }
86
+ /** Storage interface — matches the surface of `window.localStorage` we
87
+ * actually use. Allows injection in tests without depending on the
88
+ * full DOM Storage type. */
89
+ export interface LedgerStorage {
90
+ getItem(key: string): string | null;
91
+ setItem(key: string, value: string): void;
92
+ removeItem(key: string): void;
93
+ }
94
+ /** P1 Task 7 stub. P2 Task 4 fills in the methods. Defining the shape
95
+ * here means TriggerEngine + IntentRuleEvaluator can begin importing
96
+ * the type without waiting on the storage code. */
97
+ export interface IIntentLedger {
98
+ /** Add dwell time on the current SKU. No-op on Safari past day 7 due
99
+ * to ITP — see module docstring. */
100
+ addDwell(skuId: string, deltaMs: number): void;
101
+ /** Increment session count for a SKU. Called once per session per SKU. */
102
+ noteSession(skuId: string): void;
103
+ /** Read current accumulated values for a SKU. Returns null for
104
+ * unknown / evicted / decayed-out SKUs. */
105
+ getEntry(skuId: string): SkuLedgerEntry | null;
106
+ }
107
+ export declare class IntentLedger implements IIntentLedger {
108
+ private readonly storage;
109
+ private readonly entries;
110
+ /** Now provider — injectable so tests can simulate time progression
111
+ * without faking globalThis.Date.now. */
112
+ private readonly now;
113
+ private pendingFlush;
114
+ constructor(opts?: {
115
+ storage?: LedgerStorage | null;
116
+ now?: () => number;
117
+ });
118
+ addDwell(skuId: string, deltaMs: number): void;
119
+ noteSession(skuId: string): void;
120
+ getEntry(skuId: string): SkuLedgerEntry | null;
121
+ /** Force a synchronous persistence flush. Caller invokes from the
122
+ * SDK's pagehide / beforeunload hook so in-flight dwell isn't lost
123
+ * when the tab closes inside the debounce window. */
124
+ flush(): void;
125
+ /** Forget every entry — used by `aegis.reset()` and similar. Wipes
126
+ * the localStorage row too. */
127
+ clear(): void;
128
+ /** Snapshot of all currently-known SKU ids. For tests + the SDK's
129
+ * debug surface. */
130
+ knownSkus(): string[];
131
+ private hydrate;
132
+ private evictIfOverCap;
133
+ private schedulePersist;
134
+ private persistNow;
135
+ }
136
+ //# sourceMappingURL=intent_ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent_ledger.d.ts","sourceRoot":"","sources":["../../src/state/intent_ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH;;mCAEmC;AACnC,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D;;iEAEiE;AACjE,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;0BAG0B;AAC1B,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;6BAE6B;AAC7B,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;oDAEoD;AACpD,MAAM,WAAW,aAAa;IAC5B;yCACqC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,0EAA0E;IAC1E,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;gDAC4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;CAChD;AAsBD,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D;8CAC0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,YAAY,CAA8C;gBAEtD,IAAI,GAAE;QAChB,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;QAC/B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;KACf;IAaN,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAsB9C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAoBhC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAe9C;;0DAEsD;IACtD,KAAK,IAAI,IAAI;IAQb;oCACgC;IAChC,KAAK,IAAI,IAAI;IAeb;yBACqB;IACrB,SAAS,IAAI,MAAM,EAAE;IAMrB,OAAO,CAAC,OAAO;IAwCf,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,UAAU;CAanB"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Micro-Intent Engine — compound rule evaluator (P1 Task 4).
3
+ *
4
+ * Pure, side-effect-free evaluation of an IntentRule predicate tree against
5
+ * a current snapshot of Layer-1 (identity) + Layer-2 (session) signal values.
6
+ *
7
+ * Why a separate class from TriggerEngine
8
+ * ----------------------------------------
9
+ * TriggerEngine.ts owns signal CAPTURE (scroll, time-on-page, exit-intent,
10
+ * etc.) and emits named events like `scroll_depth_50`. IntentRuleEvaluator
11
+ * owns rule EVALUATION — given a compound predicate and the current state
12
+ * of all known signals, return whether the rule fires now. The two
13
+ * concerns separate cleanly: capture is global per-page (one engine
14
+ * instance), evaluation is per-rule (the manager re-runs every armed
15
+ * rule when a `fire_on` signal fires).
16
+ *
17
+ * Type strategy
18
+ * -------------
19
+ * Types are declared LOCALLY in this file rather than imported from
20
+ * `@aegis/shared-types`. The SDK is published standalone on npm and
21
+ * cannot pull in workspace packages at runtime. A drift-check test
22
+ * (`libs/web-sdk/tests/intent-evaluator-shared-types-drift.test.ts`)
23
+ * parses both files and asserts the local types and the canonical
24
+ * `libs/shared-types/src/intent.ts` declare the same set of signal /
25
+ * comparator values.
26
+ *
27
+ * Plan reference: docs/architecture/MICRO_INTENT_ENGINE.md §"Schemas"
28
+ */
29
+ export type IntentSignal = 'scroll_depth' | 'time_on_page' | 'exit_intent' | 'back_button' | 'scroll_velocity' | 'inactivity' | 'visibility_change' | 'rage_click' | 'price_hover_ms' | 'cta_hover_ms' | 'cum_dwell_sku' | 'session_count_sku' | 'cart_value' | 'converted_in_session' | 'rfm_segment' | 'rfm_score' | 'lifecycle_stage' | 'purchase_intent_score' | 'purchase_intent_tier' | 'churn_risk_score' | 'churn_risk_tier' | 'predicted_ltv' | 'uninstall_risk_tier' | 'engagement_level' | 'price_tier' | 'price_sensitivity_score' | 'top_category' | 'avg_cart_value' | 'scoring_tier';
30
+ export type Comparator = 'gte' | 'lte' | 'gt' | 'lt' | 'eq' | 'neq' | 'in' | 'nin';
31
+ export type IntentLeafValue = number | string | boolean | ReadonlyArray<number | string | boolean>;
32
+ export interface IntentLeaf {
33
+ signal: IntentSignal;
34
+ op: Comparator;
35
+ value: IntentLeafValue;
36
+ }
37
+ export interface IntentNode {
38
+ op: 'AND' | 'OR' | 'NOT';
39
+ operands: ReadonlyArray<IntentExpr>;
40
+ }
41
+ export type IntentExpr = IntentNode | IntentLeaf;
42
+ export interface IntentRule {
43
+ fire_on: ReadonlyArray<IntentSignal>;
44
+ when: IntentExpr;
45
+ priority: number;
46
+ suppress_competing: boolean;
47
+ }
48
+ /** A scalar a signal can hold in the snapshot. null = signal not (yet)
49
+ * populated — happens for Layer-1 scores on anonymous sessions and for
50
+ * Layer-2 signals before the user triggers them. */
51
+ export type SignalValue = number | string | boolean | null;
52
+ /** Current state of every known signal. Missing keys are treated the
53
+ * same as `null`. */
54
+ export type IntentSnapshot = Partial<Record<IntentSignal, SignalValue>>;
55
+ export declare function isIntentLeaf(expr: IntentExpr): expr is IntentLeaf;
56
+ export declare function isIntentNode(expr: IntentExpr): expr is IntentNode;
57
+ /**
58
+ * Evaluate a single leaf comparison against a snapshot.
59
+ *
60
+ * Missing-signal semantics (per the plan's "Graceful degradation rules"):
61
+ * - The CALLER (evaluateExpr) decides AND/OR semantics on missing
62
+ * operands; this function answers strictly for THIS leaf.
63
+ * - When the signal is missing from the snapshot, the leaf is
64
+ * "unknown" → return null so the parent node can interpret it.
65
+ *
66
+ * Comparator semantics:
67
+ * - gte/lte/gt/lt: numeric only; comparing strings/bools returns false.
68
+ * - eq/neq: any type; strict equality.
69
+ * - in/nin: leaf value must be a list; membership test on the snapshot
70
+ * value. `nin` returns true for missing snapshot values (the
71
+ * contact's RFM segment is "not in" the forbidden list if we don't
72
+ * know it yet — conservative).
73
+ */
74
+ export declare function evaluateLeaf(leaf: IntentLeaf, snapshot: IntentSnapshot): boolean | null;
75
+ /**
76
+ * Evaluate the full predicate tree.
77
+ *
78
+ * AND/OR/NOT semantics with missing-value handling (per plan §"Graceful
79
+ * degradation rules"):
80
+ *
81
+ * AND(a, b, c) where any operand is null → false (conservative; the
82
+ * rule doesn't fire when we can't prove all conditions).
83
+ * OR(a, b, c) where some operands are null → if any non-null operand
84
+ * is true, returns true. If all operands are null OR all are
85
+ * false, returns false.
86
+ * NOT(a) where a is null → null (parent decides). When this propagates
87
+ * to the top-level evaluateExpr, the rule does NOT fire.
88
+ *
89
+ * The "AND treats missing as unmet" + "OR treats missing as inert" rule
90
+ * from the plan maps exactly onto this implementation: AND short-circuits
91
+ * to false on any null operand; OR ignores nulls and looks at the rest.
92
+ *
93
+ * Top-level: a null result from the root expression means the rule
94
+ * cannot be evaluated yet — do NOT fire.
95
+ */
96
+ export declare function evaluateExpr(expr: IntentExpr, snapshot: IntentSnapshot): boolean | null;
97
+ /** Top-level "does the rule fire right now?" check. Treats a null
98
+ * evaluation result as "do not fire" (we can't prove the rule's
99
+ * conditions are met). */
100
+ export declare function evaluateRule(rule: IntentRule, snapshot: IntentSnapshot): boolean;
101
+ /** A campaign carrying a compound rule, in the SDK's runtime shape.
102
+ * Mirrors the cell-plane payload at /v1/in-app/active when
103
+ * client_trigger.type === 'micro_intent'. */
104
+ export interface ArmedCampaign {
105
+ id: string;
106
+ rule: IntentRule;
107
+ }
108
+ /** The decision the evaluator returns when a signal fires.
109
+ *
110
+ * fire : caller should display this campaign now.
111
+ * suppress : caller should silence the listed competing campaign IDs
112
+ * for the rest of the session (the bouncing-researcher
113
+ * pattern — a rule whose only job is to silence others).
114
+ * none : no rule matched, do nothing.
115
+ */
116
+ export interface EvaluationDecision {
117
+ kind: 'fire' | 'suppress' | 'none';
118
+ campaignId?: string;
119
+ suppressIds?: ReadonlyArray<string>;
120
+ }
121
+ export declare class IntentRuleEvaluator {
122
+ /** All campaigns currently armed on this page. Caller (the
123
+ * AegisInAppManager) replaces this on every refresh. */
124
+ private armed;
125
+ /** Latest signal values. Caller pushes updates via updateSignal /
126
+ * updateSnapshot whenever TriggerEngine emits a state change or
127
+ * contact-scores fetch completes. */
128
+ private snapshot;
129
+ /** Campaigns that have already fired this session — never re-fire. */
130
+ private firedThisSession;
131
+ /** Campaigns silenced by a prior `suppress_competing` win. The set
132
+ * resets on session boundary (new tab / new pageview after timeout).
133
+ * The AegisInAppManager owns durability semantics; this set is
134
+ * in-memory only. */
135
+ private silencedThisSession;
136
+ updateSignal(signal: IntentSignal, value: SignalValue): void;
137
+ updateSnapshot(partial: IntentSnapshot): void;
138
+ getSnapshot(): Readonly<IntentSnapshot>;
139
+ setArmed(campaigns: ArmedCampaign[]): void;
140
+ getArmed(): ReadonlyArray<ArmedCampaign>;
141
+ markFired(campaignId: string): void;
142
+ /** Clear all per-session state. Caller invokes on logout or explicit
143
+ * session reset. */
144
+ reset(): void;
145
+ /**
146
+ * Called every time a signal value changes meaningfully (e.g. a
147
+ * TriggerEngine `exit_intent` event, or a `scroll_depth_50` crossing
148
+ * threshold, or the Layer-1 fetch finishing). Returns the decision the
149
+ * caller should act on.
150
+ *
151
+ * Algorithm:
152
+ * 1. Filter armed campaigns to those whose `fire_on` includes the
153
+ * signal that just changed.
154
+ * 2. Drop already-fired and silenced campaigns.
155
+ * 3. Sort the remainder by priority DESC, tie-broken by campaign id
156
+ * lexicographic (per plan §"Pattern ordering rules").
157
+ * 4. Walk the sorted list:
158
+ * - If a suppress_competing rule matches, emit a 'suppress'
159
+ * decision listing every lower-priority armed campaign id.
160
+ * Mark those as silenced internally too.
161
+ * - Otherwise the first matching rule emits 'fire'.
162
+ * 5. If nothing matches, return 'none'.
163
+ */
164
+ onSignalChanged(signal: IntentSignal): EvaluationDecision;
165
+ }
166
+ export declare const MAX_RULE_DEPTH: 3;
167
+ //# sourceMappingURL=IntentRuleEvaluator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IntentRuleEvaluator.d.ts","sourceRoot":"","sources":["../../src/triggers/IntentRuleEvaluator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Micro-Intent Engine — pipeline integration (P2 Task 5).
3
+ *
4
+ * Wires the three signal-capture surfaces (SelectorBinder, TriggerEngine,
5
+ * IntentLedger) together and projects their current state onto the
6
+ * IntentRuleEvaluator's `IntentSnapshot`. The collector is the single
7
+ * place every Layer-2 signal flows through on its way into rule
8
+ * evaluation — adding a new signal means touching exactly one file.
9
+ *
10
+ * Wiring summary
11
+ * --------------
12
+ * SelectorBinder.hover_start → TriggerEngine.noteHoverStart
13
+ * → evaluator.updateSignal(price/cta_hover_ms, 0)
14
+ * SelectorBinder.hover_end → TriggerEngine.noteHoverEnd
15
+ * → evaluator.updateSignal(price/cta_hover_ms, getHoverMs)
16
+ * SelectorBinder.click → TriggerEngine.noteClick
17
+ * → evaluator.updateSignal(rage_click, getRageClickCount)
18
+ * SelectorBinder.sku_change → IntentLedger.noteSession
19
+ * → evaluator.updateSignal(cum_dwell_sku, session_count_sku)
20
+ *
21
+ * window.mousemove (≤100ms) → TriggerEngine.noteMousePosition
22
+ * → evaluator.updateSignal(mouse_velocity_to_top)
23
+ *
24
+ * pagehide / beforeunload → ledger.flush()
25
+ *
26
+ * Why a separate class instead of putting this in AegisInAppManager:
27
+ * the manager already owns delivery + display rules + suppression.
28
+ * Adding "snapshot wrangling" on top would make it the SDK's
29
+ * everything-shaped object. The collector keeps signal flow isolated
30
+ * and unit-testable without spinning up the full manager.
31
+ *
32
+ * The collector is signal-name-agnostic in its core flow — the only
33
+ * place IntentSignal names appear is the `signalForIntent()` helper
34
+ * that maps the `price` intent to the `price_hover_ms` signal etc.
35
+ * If we add a new intent (e.g. `coupon_hover_ms`) the helper grows;
36
+ * everything else is reused.
37
+ */
38
+ import type { SelectorBinder } from './SelectorBinder';
39
+ import type { IntentRuleEvaluator, IntentSignal, EvaluationDecision } from './IntentRuleEvaluator';
40
+ import type { IntentLedger } from '../state/intent_ledger';
41
+ import { TriggerEngine } from './TriggerEngine';
42
+ export interface IntentSnapshotCollectorConfig {
43
+ triggerEngine: TriggerEngine;
44
+ selectorBinder: SelectorBinder;
45
+ intentRuleEvaluator: IntentRuleEvaluator;
46
+ intentLedger?: IntentLedger;
47
+ /** Throttle floor for mousemove sampling, in ms. Plan default = 100. */
48
+ mouseSampleMs?: number;
49
+ /** Forwarded decisions from `evaluator.onSignalChanged` — the
50
+ * AegisInAppManager subscribes here to drive the actual campaign
51
+ * display / suppress side-effects. */
52
+ onDecision?: (decision: EvaluationDecision) => void;
53
+ }
54
+ export declare class IntentSnapshotCollector {
55
+ private readonly triggerEngine;
56
+ private readonly selectorBinder;
57
+ private readonly evaluator;
58
+ private readonly ledger;
59
+ private readonly mouseSampleMs;
60
+ private onDecision?;
61
+ private started;
62
+ private unsubscribeBinder?;
63
+ private lastMouseSampleAt;
64
+ private mouseMoveHandler?;
65
+ private flushHandler?;
66
+ constructor(cfg: IntentSnapshotCollectorConfig);
67
+ start(): void;
68
+ stop(): void;
69
+ /** Override the decision callback after construction — used by the
70
+ * AegisInAppManager when it lazily wires up. */
71
+ setDecisionHandler(handler: (d: EvaluationDecision) => void): void;
72
+ /**
73
+ * Caller signals that a known trigger event happened (exit_intent,
74
+ * back_button, scroll_depth_50, etc.). The collector forwards to the
75
+ * evaluator and pushes any resulting decision through `onDecision`.
76
+ *
77
+ * The signal-name here must be a real IntentSignal — anything else is
78
+ * silently ignored (no-throw, the SDK can't crash a host page).
79
+ */
80
+ notifyTrigger(signal: IntentSignal): void;
81
+ private handleBinderEvent;
82
+ }
83
+ //# sourceMappingURL=IntentSnapshotCollector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IntentSnapshotCollector.d.ts","sourceRoot":"","sources":["../../src/triggers/IntentSnapshotCollector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQhD,MAAM,WAAW,6BAA6B;IAC5C,aAAa,EAAE,aAAa,CAAC;IAC7B,cAAc,EAAE,cAAc,CAAC;IAC/B,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;2CAEuC;IACvC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACrD;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,UAAU,CAAC,CAAkC;IAErD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,iBAAiB,CAAC,CAAa;IACvC,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,gBAAgB,CAAC,CAA0B;IACnD,OAAO,CAAC,YAAY,CAAC,CAAa;gBAEtB,GAAG,EAAE,6BAA6B;IAe9C,KAAK,IAAI,IAAI;IAyCb,IAAI,IAAI,IAAI;IAkBZ;qDACiD;IACjD,kBAAkB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI;IAMlE;;;;;;;OAOG;IACH,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAOzC,OAAO,CAAC,iBAAiB;CA6C1B"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Micro-Intent Engine — delegated event listeners (P1 Task 5, robustness #1).
3
+ *
4
+ * The problem
5
+ * -----------
6
+ * Static `document.querySelector('.price').addEventListener(...)` on SDK
7
+ * init detaches the moment Shopify themes, React/Next.js storefronts, or
8
+ * any SPA re-renders the price node. Element-scoped signals
9
+ * (price_hover_ms, cta_hover_ms, rage-click-on-coupon) then fail
10
+ * silently — the worst possible failure mode because nothing logs.
11
+ *
12
+ * The fix
13
+ * -------
14
+ * Bind ONCE to `document.body` (capture phase) and dispatch via
15
+ * `event.target.closest(selector)`. The handler runs globally; the
16
+ * configured intent selectors are evaluated per-event. Survives any
17
+ * framework re-render — there's never a stale reference because we
18
+ * never held one.
19
+ *
20
+ * document.body.addEventListener('mouseenter', this.handleHover, true);
21
+ * document.body.addEventListener('mouseleave', this.handleHover, true);
22
+ * document.body.addEventListener('pointerdown', this.handleClick, true);
23
+ *
24
+ * Plus a `MutationObserver` that watches for changes to the configured
25
+ * SKU selector so a SPA navigating between PDPs without a full page
26
+ * load still updates the engine's `cum_dwell_sku` / `session_count_sku`
27
+ * context. The observer is NOT used to re-attach event listeners
28
+ * (delegation makes that unnecessary).
29
+ *
30
+ * What this file does NOT do
31
+ * --------------------------
32
+ * Compute `price_hover_ms` / `cta_hover_ms` dwell totals, or count
33
+ * rage-clicks. That arithmetic lives in TriggerEngine.ts (P2 Tasks
34
+ * 1-3) which subscribes to this binder via `onIntent(handler)` and
35
+ * accumulates into snapshot values. SelectorBinder is the I/O layer;
36
+ * the timing logic is downstream.
37
+ *
38
+ * Plan reference: docs/architecture/MICRO_INTENT_ENGINE.md §"Robustness
39
+ * 1 — DOM brittleness".
40
+ */
41
+ /** Named element role the storefront marks. The selector config maps
42
+ * each name to one or more CSS selectors; on every match, the binder
43
+ * emits an IntentDomEvent with this name as `intent`. */
44
+ export type IntentElementName = 'price' | 'cta' | 'coupon' | 'checkout' | 'sku';
45
+ /** Event types the binder forwards. Keep narrow on purpose — every
46
+ * type carried downstream is a real signal someone derives from. */
47
+ export type IntentDomEventType = 'hover_start' | 'hover_end' | 'click' | 'sku_change';
48
+ export interface IntentDomEvent {
49
+ type: IntentDomEventType;
50
+ intent: IntentElementName;
51
+ /** Best-effort: the matched element. Consumers shouldn't hold strong
52
+ * references — the SPA may unmount it before the next event fires. */
53
+ target: Element | null;
54
+ /** For `sku_change`: the new SKU id (read from `data-aegis-sku` or the
55
+ * matched element's `data-product-id` / `data-sku` attribute). For
56
+ * other events: undefined. */
57
+ sku?: string;
58
+ timestamp: number;
59
+ }
60
+ export type IntentDomHandler = (evt: IntentDomEvent) => void;
61
+ /** Map from intent name to a list of CSS selectors that mark elements
62
+ * playing that role on the storefront. Multiple selectors per intent
63
+ * so a single plugin config can cover all the themes / DOM variants
64
+ * it knows about. */
65
+ export type IntentSelectors = Partial<Record<IntentElementName, ReadonlyArray<string>>>;
66
+ export interface SelectorBinderConfig {
67
+ /** Caller-supplied selectors, highest priority. */
68
+ intentSelectors?: IntentSelectors;
69
+ /** Server-pushed selectors from /v1/sdk/config (dashboard-managed).
70
+ * Falls behind explicit init but ahead of platform defaults. */
71
+ serverSelectors?: IntentSelectors;
72
+ /** Platform-default map bundled with the plugin (Shopify / Woo /
73
+ * Magento). Lowest non-fallback priority. */
74
+ platformDefaults?: IntentSelectors;
75
+ }
76
+ export declare class SelectorBinder {
77
+ private readonly selectors;
78
+ private readonly listeners;
79
+ private observer;
80
+ private boundRoot;
81
+ private currentSku;
82
+ /** Element-keyed memory of which intent is currently hovered, so we
83
+ * can fire `hover_end` even if the matched DOM node unmounts in
84
+ * between (the unmount path is `hover_start` → unmount → no
85
+ * `hover_end`; we rely on `mouseleave` capture which fires on the
86
+ * original target before unmount in most browsers). */
87
+ private hoverActive;
88
+ constructor(config?: SelectorBinderConfig);
89
+ /** Subscribe to delegated DOM events. Returns an unsubscribe fn. */
90
+ onIntent(handler: IntentDomHandler): () => void;
91
+ /** Attach listeners + MutationObserver. Idempotent — calling twice
92
+ * is a no-op so a manager that re-initializes doesn't double-bind. */
93
+ start(doc?: Document): void;
94
+ /** Detach listeners and observer. Idempotent. The manager calls this
95
+ * on `destroy()`. */
96
+ stop(): void;
97
+ /** Snapshot of the currently-active SKU on the page. Used by
98
+ * TriggerEngine to attribute hover/click events to a product context.
99
+ * Undefined when the page has no SKU element or it's been unmounted. */
100
+ getCurrentSku(): string | undefined;
101
+ /** Current selector resolution — useful for tests + editor "preview"
102
+ * to show which selectors would match. */
103
+ getResolvedSelectors(): Readonly<Required<IntentSelectors>>;
104
+ /** Find the intent name a given event target belongs to (if any).
105
+ * Walks the configured selectors in order; first match wins. Returns
106
+ * null if none match (the event is for unrelated DOM and should be
107
+ * ignored). */
108
+ private matchIntent;
109
+ private emit;
110
+ private handleHoverStart;
111
+ private handleHoverEnd;
112
+ private handleClick;
113
+ private handleMutation;
114
+ /** Re-resolve the current SKU from the page. Emits `sku_change` if
115
+ * the value differs from the prior snapshot. */
116
+ private refreshCurrentSku;
117
+ }
118
+ //# sourceMappingURL=SelectorBinder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectorBinder.d.ts","sourceRoot":"","sources":["../../src/triggers/SelectorBinder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;0DAE0D;AAC1D,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAC;AAEhF;qEACqE;AACrE,MAAM,MAAM,kBAAkB,GAC1B,aAAa,GACb,WAAW,GACX,OAAO,GACP,YAAY,CAAC;AAEjB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,iBAAiB,CAAC;IAC1B;2EACuE;IACvE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB;;mCAE+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;AAE7D;;;sBAGsB;AACtB,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAmDxF,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;qEACiE;IACjE,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;kDAC8C;IAC9C,gBAAgB,CAAC,EAAE,eAAe,CAAC;CACpC;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,UAAU,CAAqB;IACvC;;;;4DAIwD;IACxD,OAAO,CAAC,WAAW,CAA6C;gBAEpD,MAAM,GAAE,oBAAyB;IAS7C,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAO/C;2EACuE;IACvE,KAAK,CAAC,GAAG,GAAE,QAAqF,GAAG,IAAI;IAgCvG;0BACsB;IACtB,IAAI,IAAI,IAAI;IAcZ;;6EAEyE;IACzE,aAAa,IAAI,MAAM,GAAG,SAAS;IAInC;+CAC2C;IAC3C,oBAAoB,IAAI,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IAM3D;;;oBAGgB;IAChB,OAAO,CAAC,WAAW;IAmBnB,OAAO,CAAC,IAAI;IAYZ,OAAO,CAAC,gBAAgB,CActB;IAEF,OAAO,CAAC,cAAc,CAapB;IAEF,OAAO,CAAC,WAAW,CAWjB;IAEF,OAAO,CAAC,cAAc,CAMpB;IAEF;qDACiD;IACjD,OAAO,CAAC,iBAAiB;CAyB1B"}