Skip to content

Instantly share code, notes, and snippets.

@assada
Created April 8, 2026 17:32
Show Gist options
  • Select an option

  • Save assada/3b7c44b42b25db01556b4071a4783b7b to your computer and use it in GitHub Desktop.

Select an option

Save assada/3b7c44b42b25db01556b4071a4783b7b to your computer and use it in GitHub Desktop.
TIG Rule Engine Tester
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TIG Rule Engine Tester</title>
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/lib/codemirror.min.css">
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/lib/codemirror.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/javascript/javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/yaml/yaml.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #1a1a1a; height: 100vh; display: flex; flex-direction: column; }
header { background: #fff; border-bottom: 1px solid #ddd; padding: 10px 20px; display: flex; align-items: center; gap: 16px; flex-shrink: 0; }
header h1 { font-size: 16px; font-weight: 600; }
header button { padding: 6px 14px; font-size: 13px; border: 1px solid #ccc; border-radius: 4px; background: #fff; cursor: pointer; }
header button:hover { background: #f0f0f0; }
header button.primary { background: #2563eb; color: #fff; border-color: #2563eb; }
header button.primary:hover { background: #1d4ed8; }
select.preset { padding: 5px 8px; font-size: 12px; border: 1px solid #ccc; border-radius: 4px; background: #fff; max-width: 200px; }
.main { display: flex; flex: 1; overflow: hidden; }
.col { display: flex; flex-direction: column; border-right: 1px solid #ddd; overflow: hidden; }
.col:last-child { border-right: none; }
.col-input { flex: 1; min-width: 0; }
.col-rules { flex: 1; min-width: 0; }
.col-results { flex: 1.2; min-width: 0; background: #fff; }
.col-header { padding: 8px 12px; background: #fafafa; border-bottom: 1px solid #ddd; font-size: 13px; font-weight: 600; color: #555; flex-shrink: 0; display: flex; justify-content: space-between; align-items: center; }
.col-header .badge { font-size: 11px; background: #e5e7eb; padding: 2px 8px; border-radius: 10px; font-weight: 500; }
textarea { flex: 1; width: 100%; border: none; outline: none; resize: none; padding: 12px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 12.5px; line-height: 1.5; background: #fff; tab-size: 2; }
textarea:focus { background: #fefefe; }
.CodeMirror { flex: 1; height: auto !important; font-size: 12.5px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; border: none; }
.cm-wrap { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.results-body { flex: 1; overflow-y: auto; padding: 12px; }
.stats-bar { display: flex; gap: 12px; font-size: 12px; color: #666; padding: 8px 12px; background: #fafafa; border-top: 1px solid #ddd; flex-shrink: 0; }
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot-green { background: #22c55e; }
.dot-yellow { background: #f59e0b; }
.dot-gray { background: #d1d5db; }
</style>
</head>
<body>
<header>
<h1>TIG Rule Engine Tester</h1>
<button class="primary" id="btn-eval">Evaluate (Ctrl+Enter)</button>
<select class="preset" id="preset-select">
<option value="">-- Event Presets --</option>
<option value="editor_web_rich">1. Editor open (Web) rich attrs -- fanout amp+dwh, dedup</option>
<option value="editor_android">2. Editor save (Android) -- cross: amp-prod + amp-android + dwh</option>
<option value="ios_document_signed">3. Document signed (iOS) -- amp-ios + amp-prod + dwh</option>
<option value="legacy_upload">4. Legacy: upload_document -- DWH only, minimal event</option>
<option value="legacy_send_to_sign">5. Legacy: send_to_sign -- DWH with many dbParams</option>
<option value="amplitude_with_dbparams">6. Amplitude track() with dbParams -- cross-route test</option>
<option value="backend_qless">7. Backend Qless event -- sn_api_*, low priority filter test</option>
<option value="guest_no_userid">8. Guest user (no user_id) -- edge case, device_id only</option>
</select>
<button id="btn-clear">Clear</button>
</header>
<div class="main">
<div class="col col-input">
<div class="col-header">Canonical Event (JSON)<span class="badge" id="input-status">waiting</span></div>
<textarea id="input" placeholder='Paste canonical event JSON here...'></textarea>
</div>
<div class="col col-rules">
<div class="col-header">Routing Config (YAML)<span class="badge" id="rules-status">waiting</span></div>
<textarea id="rules" placeholder="Paste routing config JSON here..."></textarea>
</div>
<div class="col col-results">
<div class="col-header">Evaluation Results<span class="badge" id="result-count">-</span></div>
<div class="results-body" id="results"></div>
<div class="stats-bar" id="stats-bar" style="display:none">
<span><span class="dot dot-green"></span> <span id="stat-send">0</span> destinations</span>
<span><span class="dot dot-yellow"></span> <span id="stat-filtered">0</span> priority-filtered</span>
<span><span class="dot dot-gray"></span> <span id="stat-rules-matched">0</span>/<span id="stat-rules-total">0</span> rules matched</span>
</div>
</div>
</div>
<script>
// =========== RULE ENGINE (exact TIG-05 spec) ===========
function resolveField(obj, path) {
const parts = path.split('.');
let val = obj;
for (const p of parts) {
if (val == null || typeof val !== 'object') return undefined;
val = val[p];
}
return val;
}
function checkCondition(event, cond) {
const val = cond.by === 'event_name' ? event.event_name : resolveField(event, cond.field || '');
const fieldLabel = cond.by === 'field' ? cond.field : 'event_name';
if (val === undefined || val === null) return { matched: false, reason: fieldLabel + ' is null/undefined' };
const strVal = String(val);
if (cond.type === 'regex') {
try {
const m = new RegExp(cond.pattern).test(strVal);
return { matched: m, reason: '"' + strVal + '" ' + (m ? 'matches' : 'does not match') + ' /' + cond.pattern + '/' };
} catch (e) { return { matched: false, reason: 'invalid regex: ' + e.message }; }
}
if (cond.type === 'match') {
const expected = String(cond.value);
const m = strVal === expected;
return { matched: m, reason: '"' + strVal + '" ' + (m ? '===' : '!==') + ' "' + expected + '"' };
}
return { matched: false, reason: 'unknown type: ' + cond.type };
}
function evaluateRules(event, config) {
const destConfigs = config.config || {};
const rules = config.rules || {};
const destinations = {};
const ruleTraces = [];
for (const [name, rule] of Object.entries(rules)) {
const conditions = rule.conditions || [];
const condResults = conditions.map(function(c) {
var r = checkCondition(event, c);
r.condition = c;
return r;
});
const allMatched = condResults.every(function(c) { return c.matched; });
ruleTraces.push({ name: name, priority: rule.priority || 0, matched: allMatched, conditions: condResults, destinations: rule.destinations || [] });
if (allMatched) {
(rule.destinations || []).forEach(function(dest) {
const target = dest.target;
const dc = destConfigs[target] || {};
const minP = dc.minimum_priority || 0;
const ruleP = rule.priority || 0;
if (!destinations[target]) destinations[target] = { maxPriority: 0, matchedRules: [], sent: false, filteredBy: null, config: dc };
var d = destinations[target];
d.matchedRules.push({ rule: name, priority: ruleP });
if (ruleP >= minP) { d.sent = true; if (ruleP > d.maxPriority) d.maxPriority = ruleP; }
else if (!d.sent) { d.filteredBy = { rule: name, rulePriority: ruleP, minPriority: minP }; }
});
}
}
// Build output payloads
for (const [target, d] of Object.entries(destinations)) {
d.outputPayload = buildOutput(event, d.config);
}
return { destinations: destinations, ruleTraces: ruleTraces };
}
function buildOutput(event, dc) {
var out = { event_name: event.event_name, event_properties: Object.assign({}, event.event_properties || {}) };
var allowed = dc.allowedUserProps;
var up = event.user_properties || {};
if (allowed && Array.isArray(allowed)) {
var filtered = {};
allowed.forEach(function(k) { if (up[k] !== undefined) filtered[k] = up[k]; });
out.user_properties = filtered;
out._filtered = true;
out._original = Object.keys(up).length;
out._kept = Object.keys(filtered).length;
} else {
out.user_properties = Object.assign({}, up);
out._filtered = false;
}
if (event.identity) out.identity = Object.assign({}, event.identity);
if (event.source) out.source = Object.assign({}, event.source);
return out;
}
// =========== RENDER (safe, no innerHTML with user data) ===========
function clearElement(el) { while (el.firstChild) el.removeChild(el.firstChild); }
function el(tag, attrs, children) {
var e = document.createElement(tag);
if (attrs) for (var k in attrs) {
if (k === 'style' && typeof attrs[k] === 'object') Object.assign(e.style, attrs[k]);
else if (k === 'textContent') e.textContent = attrs[k];
else if (k === 'className') e.className = attrs[k];
else e.setAttribute(k, attrs[k]);
}
if (children) children.forEach(function(c) { if (typeof c === 'string') e.appendChild(document.createTextNode(c)); else if (c) e.appendChild(c); });
return e;
}
function renderResults(results, event) {
var container = document.getElementById('results');
clearElement(container);
var allDests = Object.keys(results.destinations);
var sentDests = allDests.filter(function(d) { return results.destinations[d].sent; });
var filteredDests = allDests.filter(function(d) { return !results.destinations[d].sent; });
var matchedRules = results.ruleTraces.filter(function(r) { return r.matched; }).length;
// Destinations section
var destSection = el('div', { className: 'result-section' }, [el('h3', { textContent: 'Destinations', style: { fontSize: '12px', fontWeight: '600', textTransform: 'uppercase', color: '#888', marginBottom: '6px', letterSpacing: '0.5px' } })]);
if (sentDests.length === 0 && filteredDests.length === 0) {
destSection.appendChild(el('div', { style: { background: '#f9fafb', border: '1px solid #e5e7eb', borderRadius: '6px', padding: '10px 12px', opacity: '0.6' } }, [el('b', { textContent: 'No rules matched' }), document.createTextNode(' \u2014 default fallback applies')]));
}
sentDests.forEach(function(target) {
var d = results.destinations[target];
var card = el('div', { style: { background: '#f0fdf4', border: '1px solid #22c55e', borderRadius: '6px', padding: '10px 12px', marginBottom: '8px' } });
var nameRow = el('div', { style: { fontWeight: '600', fontSize: '13px' } }, [
document.createTextNode(target + ' '),
el('span', { textContent: 'SEND', style: { fontSize: '10px', padding: '1px 6px', borderRadius: '3px', background: '#dcfce7', color: '#166534' } }),
el('span', { textContent: ' priority ' + d.maxPriority, style: { fontSize: '11px', color: '#888' } })
]);
card.appendChild(nameRow);
var matchedBy = el('div', { style: { fontSize: '12px', color: '#666', marginTop: '4px' } }, [document.createTextNode('Matched by: ')]);
d.matchedRules.forEach(function(r, i) {
if (i > 0) matchedBy.appendChild(document.createTextNode(', '));
matchedBy.appendChild(el('b', { textContent: r.rule }));
matchedBy.appendChild(document.createTextNode('(p' + r.priority + ')'));
});
card.appendChild(matchedBy);
// Output payload
var p = d.outputPayload;
var details = el('details', { style: { marginTop: '6px' } });
var summaryText = 'Output payload';
if (p._filtered) summaryText += ' (user_properties: ' + p._kept + '/' + p._original + ' after allowedUserProps filter)';
details.appendChild(el('summary', { textContent: summaryText, style: { fontSize: '11px', color: '#888', cursor: 'pointer' } }));
var display = Object.assign({}, p);
delete display._filtered; delete display._original; delete display._kept;
details.appendChild(el('pre', { textContent: JSON.stringify(display, null, 2), style: { fontSize: '11px', background: '#fff', border: '1px solid #eee', borderRadius: '3px', padding: '6px 8px', marginTop: '4px', maxHeight: '200px', overflow: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-all' } }));
card.appendChild(details);
destSection.appendChild(card);
});
filteredDests.forEach(function(target) {
var d = results.destinations[target];
var f = d.filteredBy;
var card = el('div', { style: { background: '#fffbeb', border: '1px solid #f59e0b', borderRadius: '6px', padding: '10px 12px', marginBottom: '8px' } });
card.appendChild(el('div', { style: { fontWeight: '600', fontSize: '13px' } }, [
document.createTextNode(target + ' '),
el('span', { textContent: 'PRIORITY FILTERED', style: { fontSize: '10px', padding: '1px 6px', borderRadius: '3px', background: '#fef3c7', color: '#92400e' } })
]));
card.appendChild(el('div', { textContent: 'Rule "' + f.rule + '" matched (priority ' + f.rulePriority + ') but destination requires minimum_priority ' + f.minPriority, style: { fontSize: '12px', color: '#666', marginTop: '4px' } }));
destSection.appendChild(card);
});
container.appendChild(destSection);
// Rule traces section
var traceSection = el('div', { className: 'result-section' }, [el('h3', { textContent: 'Rule Evaluation Trace', style: { fontSize: '12px', fontWeight: '600', textTransform: 'uppercase', color: '#888', marginBottom: '6px', letterSpacing: '0.5px' } })]);
results.ruleTraces.forEach(function(trace) {
var traceDiv = el('div', { style: { fontSize: '12px', marginTop: '4px', marginBottom: '8px' } });
var icon = trace.matched ? '\u2705' : '\u274c';
var color = trace.matched ? '#16a34a' : '#9ca3af';
var header = el('span', { style: { color: color } }, [document.createTextNode(icon + ' '), el('b', { textContent: trace.name }), document.createTextNode(' (priority ' + trace.priority + ')')]);
traceDiv.appendChild(header);
trace.conditions.forEach(function(c) {
var cIcon = c.matched ? '\u2713' : '\u2717';
var cColor = c.matched ? '#16a34a' : '#dc2626';
var condLabel = c.condition.by === 'field' ? c.condition.field : 'event_name';
var line = el('div', { style: { marginLeft: '16px', color: cColor } }, [
document.createTextNode(cIcon + ' '),
el('code', { textContent: c.condition.type }),
document.createTextNode(' by '),
el('code', { textContent: condLabel }),
document.createTextNode(': ' + c.reason)
]);
traceDiv.appendChild(line);
});
if (trace.matched) {
var destLine = el('div', { style: { marginLeft: '16px', color: '#555' } }, [document.createTextNode('\u2192 destinations: ')]);
trace.destinations.forEach(function(d, i) {
if (i > 0) destLine.appendChild(document.createTextNode(', '));
destLine.appendChild(el('b', { textContent: d.target }));
});
traceDiv.appendChild(destLine);
}
traceSection.appendChild(traceDiv);
});
container.appendChild(traceSection);
// Stats bar
document.getElementById('stats-bar').style.display = 'flex';
document.getElementById('stat-send').textContent = sentDests.length;
document.getElementById('stat-filtered').textContent = filteredDests.length;
document.getElementById('stat-rules-matched').textContent = matchedRules;
document.getElementById('stat-rules-total').textContent = results.ruleTraces.length;
document.getElementById('result-count').textContent = sentDests.length + ' dest';
}
// =========== ACTIONS ===========
function doEvaluate() {
var inputEl = document.getElementById('input');
var rulesEl = document.getElementById('rules');
var resultsEl = document.getElementById('results');
var event, config;
try {
event = JSON.parse(inputEl.value);
document.getElementById('input-status').textContent = 'valid';
document.getElementById('input-status').style.color = '#16a34a';
} catch (e) {
clearElement(resultsEl);
resultsEl.appendChild(el('div', { style: { background: '#fef2f2', border: '1px solid #fecaca', borderRadius: '6px', padding: '10px 12px', color: '#991b1b', fontSize: '12px' } }, [el('b', { textContent: 'Input JSON error: ' }), document.createTextNode(e.message)]));
document.getElementById('input-status').textContent = 'error';
document.getElementById('input-status').style.color = '#dc2626';
return;
}
try {
config = jsyaml.load(rulesEl.value);
document.getElementById('rules-status').textContent = 'valid';
document.getElementById('rules-status').style.color = '#16a34a';
} catch (e) {
clearElement(resultsEl);
resultsEl.appendChild(el('div', { style: { background: '#fef2f2', border: '1px solid #fecaca', borderRadius: '6px', padding: '10px 12px', color: '#991b1b', fontSize: '12px' } }, [el('b', { textContent: 'YAML parse error: ' }), document.createTextNode(e.message)]));
document.getElementById('rules-status').textContent = 'error';
document.getElementById('rules-status').style.color = '#dc2626';
return;
}
renderResults(evaluateRules(event, config), event);
}
// =========== SHARED CONFIG (YAML, covers all 8 presets) ===========
var SHARED_CONFIG_YAML = [
'config:',
' amplitude-prod:',
' minimum_priority: 50',
' amplitude-android:',
' minimum_priority: 50',
' amplitude-ios:',
' minimum_priority: 50',
' dwh:',
' minimum_priority: 0',
' allowedUserProps:',
' - subscription',
' - is_premium',
' - plan_name',
'',
'rules:',
' # High-priority editor events to Amplitude + DWH',
' editor_to_prod_and_dwh:',
' priority: 80',
' conditions:',
' - type: regex',
' by: event_name',
' pattern: "sn_editor_(.+)"',
' destinations:',
' - target: amplitude-prod',
' - target: dwh',
'',
' # All sn_ events to Amplitude prod',
' all_sn_to_amplitude_prod:',
' priority: 60',
' conditions:',
' - type: regex',
' by: event_name',
' pattern: "^sn_(.+)"',
' destinations:',
' - target: amplitude-prod',
'',
' # Editor events from Android also to Android Amplitude project',
' editor_android:',
' priority: 80',
' conditions:',
' - type: regex',
' by: event_name',
' pattern: "sn_editor_(.+)"',
' - type: match',
' by: field',
' field: source.platform',
' value: Android',
' destinations:',
' - target: amplitude-android',
'',
' # All iOS events to iOS Amplitude project',
' ios_all_events:',
' priority: 70',
' conditions:',
' - type: match',
' by: field',
' field: source.platform',
' value: iOS',
' destinations:',
' - target: amplitude-ios',
'',
' # Low-priority catch-all to Amplitude (tests priority filtering)',
' low_priority_catch_all:',
' priority: 15',
' conditions:',
' - type: regex',
' by: event_name',
' pattern: ".*"',
' destinations:',
' - target: amplitude-prod',
'',
' # Default: everything to DWH',
' default_dwh:',
' priority: 10',
' conditions:',
' - type: regex',
' by: event_name',
' pattern: ".*"',
' destinations:',
' - target: dwh',
].join('\n');
var PRESETS = {
editor_web_rich: {
event: {
"event_name": "sn_editor_open",
"identity": { "user_id": "user_abc123", "device_id": "dev_web_001", "session_id": 1712570400000 },
"source": { "platform": "Web", "os_name": "Chrome", "os_version": "124.0", "device_model": "Macintosh", "device_brand": "Apple", "language": "en", "library": "amplitude-ts/2.11.0", "ip": "1.2.3.4", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", "insert_id": "ins_001", "app_version": "3.45.0" },
"event_properties": { "document_id": "doc_555", "rows_amount": "42", "file_type": "pdf", "has_template": "true", "page_count": "12", "has_fields": "true" },
"user_properties": { "subscription": "premium", "is_premium": true, "plan_name": "business", "country": "US", "city": "San Francisco", "dma": "807", "region": "California", "device_type": "Desktop", "browser": "Chrome", "browser_version": "124.0" }
}
},
editor_android: {
event: {
"event_name": "sn_editor_save",
"identity": { "user_id": "user_mob_1", "device_id": "android_xyz_999", "session_id": 1712571000000 },
"source": { "platform": "Android", "os_name": "Android", "os_version": "14", "language": "uk", "library": "amplitude-android/1.5.0", "ip": "5.6.7.8", "insert_id": "ins_002", "device_brand": "Samsung", "device_manufacturer": "Samsung", "device_model": "Galaxy S24", "carrier": "Kyivstar", "app_version": "2.12.1" },
"event_properties": { "document_id": "doc_777", "page_count": "3", "save_type": "auto" },
"user_properties": { "subscription": "free", "is_premium": false, "country": "UA", "city": "Kyiv", "language": "uk" }
}
},
ios_document_signed: {
event: {
"event_name": "sn_document_signed",
"identity": { "user_id": "user_ios_5", "device_id": "iphone_abc_555", "session_id": 1712572000000 },
"source": { "platform": "iOS", "os_name": "iOS", "os_version": "17.4", "language": "en", "library": "amplitude-swift/1.8.0", "ip": "10.0.0.1", "insert_id": "ins_003", "device_model": "iPhone 15 Pro", "device_brand": "Apple", "idfv": "idfv_abc123", "app_version": "2.11.0" },
"event_properties": { "document_id": "doc_999", "signer_role": "primary", "signature_type": "drawn", "is_last_signer": "true" },
"user_properties": { "subscription": "business", "is_premium": true, "plan_name": "enterprise", "country": "GB", "city": "London", "region": "England" }
}
},
legacy_upload: {
event: {
"event_name": "upload_document",
"identity": { "user_id": "user_legacy_42" },
"source": { "platform": "Web", "input_type": "legacy" },
"event_properties": { "document_id": "doc_100", "file_type": "docx" },
"user_properties": {}
}
},
legacy_send_to_sign: {
event: {
"event_name": "sn_send_to_sign",
"identity": { "user_id": "user_pm_007" },
"source": { "platform": "Web", "input_type": "legacy" },
"event_properties": {
"document_id": "doc_200",
"rows_amount": "5",
"signers_count": "3",
"has_payment": "false",
"has_attachments": "true",
"signing_order": "sequential",
"template_id": "tmpl_50",
"is_bulk_send": "false",
"expiration_days": "30",
"reminder_enabled": "true",
"has_cc_recipients": "true",
"cc_count": "2"
},
"user_properties": { "subscription": "premium", "is_premium": true, "plan_name": "business" }
}
},
amplitude_with_dbparams: {
event: {
"event_name": "sn_editor_field_added",
"identity": { "user_id": "user_web_88", "device_id": "dev_web_088", "session_id": 1712574000000 },
"source": { "platform": "Web", "os_name": "Firefox", "os_version": "125.0", "language": "de", "library": "amplitude-ts/2.11.0", "ip": "85.10.20.30", "insert_id": "ins_007", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0)" },
"event_properties": {
"document_id": "doc_350",
"field_type": "signature",
"page_number": "2",
"has_convert_to_template_button": "true",
"ui_session_id": "sess_xyz_999"
},
"user_properties": { "subscription": "starter", "is_premium": false, "plan_name": "starter", "country": "DE", "city": "Berlin", "device_type": "Desktop", "browser": "Firefox", "browser_version": "125.0", "dma": "0" }
}
},
backend_qless: {
event: {
"event_name": "sn_api_dev_mode_limit_created",
"identity": { "user_id": "user_api_integrator_5" },
"source": { "platform": "backend", "library": "guzzle/7.0", "application_id": "app_zapier_prod" },
"event_properties": { "endpoint": "/v2/documents", "method": "POST", "limit_type": "rate", "current_count": "101", "max_allowed": "100" },
"user_properties": { "subscription": "api_basic", "is_premium": false, "plan_name": "api_basic" }
}
},
guest_no_userid: {
event: {
"event_name": "sn_editor_view",
"identity": { "device_id": "anon_dev_xyz_777", "session_id": 1712575000000 },
"source": { "platform": "Web", "os_name": "Safari", "os_version": "17.4", "language": "fr", "library": "amplitude-ts/2.11.0", "ip": "88.99.10.20", "insert_id": "ins_anon_001", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15" },
"event_properties": { "document_id": "doc_public_link_800", "access_type": "signing_link", "is_guest": "true" },
"user_properties": {}
}
}
};
function loadPreset(key) {
if (!key || !PRESETS[key]) return;
document.getElementById('input').value = JSON.stringify(PRESETS[key].event, null, 2);
document.getElementById('rules').value = SHARED_CONFIG_YAML;
doEvaluate();
}
function clearAll() {
document.getElementById('input').value = '';
document.getElementById('rules').value = '';
clearElement(document.getElementById('results'));
document.getElementById('stats-bar').style.display = 'none';
document.getElementById('input-status').textContent = 'waiting';
document.getElementById('input-status').style.color = '';
document.getElementById('rules-status').textContent = 'waiting';
document.getElementById('rules-status').style.color = '';
document.getElementById('result-count').textContent = '-';
}
// =========== CODEMIRROR SETUP ===========
var cmInput = CodeMirror.fromTextArea(document.getElementById('input'), {
mode: { name: 'javascript', json: true },
lineNumbers: true, tabSize: 2, matchBrackets: true, lineWrapping: true
});
var cmRules = CodeMirror.fromTextArea(document.getElementById('rules'), {
mode: 'yaml',
lineNumbers: true, tabSize: 2, lineWrapping: true
});
// Override getters to use CodeMirror
var origEval = doEvaluate;
doEvaluate = function() {
document.getElementById('input').value = cmInput.getValue();
document.getElementById('rules').value = cmRules.getValue();
origEval();
};
var origLoadPreset = loadPreset;
loadPreset = function(key) {
if (!key || !PRESETS[key]) return;
cmInput.setValue(JSON.stringify(PRESETS[key].event, null, 2));
cmRules.setValue(SHARED_CONFIG_YAML);
doEvaluate();
};
var origClear = clearAll;
clearAll = function() {
cmInput.setValue('');
cmRules.setValue('');
clearElement(document.getElementById('results'));
document.getElementById('stats-bar').style.display = 'none';
document.getElementById('input-status').textContent = 'waiting';
document.getElementById('input-status').style.color = '';
document.getElementById('rules-status').textContent = 'waiting';
document.getElementById('rules-status').style.color = '';
document.getElementById('result-count').textContent = '-';
};
document.getElementById('btn-eval').addEventListener('click', doEvaluate);
document.getElementById('preset-select').addEventListener('change', function() { loadPreset(this.value); });
document.getElementById('btn-clear').addEventListener('click', clearAll);
document.addEventListener('keydown', function(e) { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); doEvaluate(); } });
// Load first preset on page open
document.getElementById('preset-select').value = 'editor_web_rich';
loadPreset('editor_web_rich');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment