@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/aegis.min.js +1 -1
- package/dist/aegis.min.js.map +1 -1
- package/dist/{analytics-Cc4-QQBf.mjs → analytics-6PR9ERDS.mjs} +190 -4
- package/dist/analytics-6PR9ERDS.mjs.map +1 -0
- package/dist/core/analytics.d.ts +1 -0
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.d.ts.map +1 -1
- package/dist/governance/trait-governor.d.ts +63 -0
- package/dist/governance/trait-governor.d.ts.map +1 -0
- package/dist/index.js +234 -10
- package/dist/index.js.map +1 -1
- package/dist/react.js +1 -1
- package/dist/runtime/AegisMessageRuntime.d.ts +0 -1
- package/dist/runtime/AegisMessageRuntime.d.ts.map +1 -1
- package/dist/state/intent_ledger.d.ts +136 -0
- package/dist/state/intent_ledger.d.ts.map +1 -0
- package/dist/triggers/IntentRuleEvaluator.d.ts +167 -0
- package/dist/triggers/IntentRuleEvaluator.d.ts.map +1 -0
- package/dist/triggers/IntentSnapshotCollector.d.ts +83 -0
- package/dist/triggers/IntentSnapshotCollector.d.ts.map +1 -0
- package/dist/triggers/SelectorBinder.d.ts +118 -0
- package/dist/triggers/SelectorBinder.d.ts.map +1 -0
- package/dist/triggers/TriggerEngine.d.ts +177 -0
- package/dist/triggers/TriggerEngine.d.ts.map +1 -1
- package/dist/triggers/index.d.ts +7 -1
- package/dist/triggers/index.d.ts.map +1 -1
- package/dist/widgets/AegisWidgetManager.d.ts +6 -0
- package/dist/widgets/AegisWidgetManager.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/analytics-Cc4-QQBf.mjs.map +0 -1
package/dist/react.js
CHANGED
|
@@ -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;
|
|
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"}
|