/** * Text matching scoring helpers for `electron_click_by_text`. * * Why a string-source export? * The scoring runs **inside the Electron renderer** (sent over CDP as a code * string and evaluated). To unit-test the same algorithm in vitest we'd need * either a parallel TS implementation (drift risk) or a way to load the JS * source into a sandbox. Exporting the helpers as a JS source string lets us: * - Inline them into the IIFE that goes over CDP (see `generateClickByTextCommand`) * - Load them into a `new Function(...)` sandbox in tests (see `tests/unit/text-matching.test.ts`) * Single source of truth, both sides exercise the exact same characters. * * Webpack ships in `mode: 'production'` with `minimize: false` (see * `webpack.config.ts`), so this template string is preserved verbatim. */ export declare const TEXT_MATCH_HELPERS_JS = "\nfunction isWordChar(ch) {\n if (!ch) return false;\n var code = ch.charCodeAt(0);\n // ASCII alphanumeric: 0-9, A-Z, a-z. UI text matching does not need full\n // Unicode word semantics \u2014 the CJK-text use case goes through exact-match\n // (no word splitting), and Latin labels are well-served by ASCII bounds.\n return (code >= 48 && code <= 57)\n || (code >= 65 && code <= 90)\n || (code >= 97 && code <= 122);\n}\n\nfunction containsAsWord(haystack, needle) {\n if (!needle || !haystack) return false;\n var idx = haystack.indexOf(needle);\n while (idx !== -1) {\n var beforeChar = idx === 0 ? '' : haystack.charAt(idx - 1);\n var afterPos = idx + needle.length;\n var afterChar = afterPos >= haystack.length ? '' : haystack.charAt(afterPos);\n if (!isWordChar(beforeChar) && !isWordChar(afterChar)) return true;\n idx = haystack.indexOf(needle, idx + 1);\n }\n return false;\n}\n\nfunction tokenizeTarget(target) {\n // Split on space character only. Empty tokens dropped.\n var raw = target.split(' ');\n var out = [];\n for (var i = 0; i < raw.length; i++) {\n var token = raw[i];\n if (token && token.length > 0) out.push(token);\n }\n return out;\n}\n\n/**\n * Score how strongly target matches the given fields. Returns 0 when no\n * meaningful textual relation exists \u2014 this gates the candidate list and\n * is what fixes the \"Heavy Math \u2192 Fetch Data\" false-positive (#3): the\n * old positional similarity gave 4/10 = 0.4 \u2192 +8 from coincidental\n * character alignment plus visibility/interactivity bonuses, summing to 38.\n *\n * Score levels:\n * 100 \u2014 full string equality on any field\n * 70 \u2014 target appears as a contiguous phrase at word boundaries\n * 50 \u2014 every word in target appears at word boundaries (any order)\n * 10-19 \u2014 partial: \u226550% of target words present (only for multi-word targets)\n * 0 \u2014 no textual relation; element is filtered out\n *\n * Visibility/interactivity bonuses are applied by the caller on top of this.\n */\nfunction scoreTextMatch(text, label, title, target) {\n var targetLower = (target || '').toLowerCase().trim();\n if (!targetLower) return 0;\n\n var fields = [];\n var rawFields = [text, label, title];\n for (var rfi = 0; rfi < rawFields.length; rfi++) {\n var raw = (rawFields[rfi] || '').toLowerCase().trim();\n if (raw.length > 0) fields.push(raw);\n }\n if (fields.length === 0) return 0;\n\n var targetWords = tokenizeTarget(targetLower);\n\n var best = 0;\n for (var fi = 0; fi < fields.length; fi++) {\n var field = fields[fi];\n var fieldScore = 0;\n if (field === targetLower) {\n fieldScore = 100;\n } else if (containsAsWord(field, targetLower)) {\n fieldScore = 70;\n } else if (targetWords.length >= 2) {\n var matched = 0;\n for (var wi = 0; wi < targetWords.length; wi++) {\n if (containsAsWord(field, targetWords[wi])) matched++;\n }\n if (matched === targetWords.length) {\n fieldScore = 50;\n } else if (matched > 0 && (matched / targetWords.length) >= 0.5) {\n // Partial multi-word match: low score, will only win if nothing\n // better exists and the accept threshold is satisfied.\n fieldScore = Math.round(20 * (matched / targetWords.length));\n }\n }\n if (fieldScore > best) best = fieldScore;\n }\n return best;\n}\n";