From 422c977ca9d15580e7fd5ab287306b9c4b51f0a6 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Sat, 7 Nov 2026 09:12:50 +0900 Subject: [PATCH 1/3] fix(analytics): count searches repeated after browser clear button --- apps.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps.html b/apps.html index 78cb72f..d84a5ce 100644 --- a/apps.html +++ b/apps.html @@ -1473,7 +1473,11 @@

Chaos - Stranger Missions

}); lastReportedSearch = query; }; - input.addEventListener('input', render); + input.addEventListener('input', () => { + // The browser search clear (×) must allow an identical query next time. + if (!clean(input.value)) lastReportedSearch = ''; + render(); + }); input.addEventListener('blur', reportSearch); input.addEventListener('keydown', event => { if (event.key === 'Enter') reportSearch(); From e8ed9667c7e40d69329a36ff08e05935dc207807 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Sat, 7 Nov 2026 09:14:31 +0900 Subject: [PATCH 2/3] test(catalog): run browser search event handlers with Node VM regressions --- scripts/test_catalog_search_events.cjs | 93 ++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 scripts/test_catalog_search_events.cjs diff --git a/scripts/test_catalog_search_events.cjs b/scripts/test_catalog_search_events.cjs new file mode 100644 index 0000000..7b66ad2 --- /dev/null +++ b/scripts/test_catalog_search_events.cjs @@ -0,0 +1,93 @@ +'use strict'; +// Run from repository root: node --test scripts/test_catalog_search_events.cjs +// Execute the actual catalog's input/reporting handlers in an isolated VM. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const vm = require('node:vm'); + +const html = fs.readFileSync(path.join(__dirname, '..', 'apps.html'), 'utf8'); +const start = html.indexOf(' const reportSearch = () => {'); +const after = html.indexOf(" input.addEventListener('keydown', event => {", start); +const end = html.indexOf(' });', after) + ' });'.length; +assert.ok(start >= 0 && after > start && end > after, 'search handlers are present'); +const handlers = html.slice(start, end); + +function setup() { + const events = []; + const listeners = {}; + const input = { + value: '', + addEventListener(type, callback) { listeners[type] = callback; }, + }; + const context = { + input, + clean: v => v.normalize('NFKC').toLocaleLowerCase().trim(), + render: () => 4, + fire: (name, payload) => events.push({ name, ...payload }), + }; + vm.runInNewContext("let lastReportedSearch = '';\n" + handlers + "\nglobalThis.commitSearch = reportSearch;", context); + assert.equal(typeof listeners.input, 'function'); + assert.equal(typeof listeners.blur, 'function'); + assert.equal(typeof listeners.keydown, 'function'); + return { input, events, listeners, commit: context.commitSearch }; +} + +test('typing does not send an unfinished search', () => { + const h = setup(); + h.input.value = 'do'; + h.listeners.input(); + assert.equal(h.events.length, 0); +}); + +test('Enter emits exactly one committed search', () => { + const h = setup(); + h.input.value = 'dotchi'; + h.listeners.input(); + h.listeners.keydown({ key: 'Enter' }); + h.listeners.blur(); + assert.equal(h.events.length, 1); + assert.equal(h.events[0].name, 'catalog_search_used'); + assert.equal(h.events[0].results, 4); + assert.equal(h.events[0].search_length_band, 'long'); +}); + +test('browser search clear allows the same query to count again', () => { + const h = setup(); + h.input.value = 'dotchi'; + h.listeners.input(); + h.listeners.blur(); + h.input.value = ''; + h.listeners.input(); + h.input.value = 'dotchi'; + h.listeners.input(); + h.listeners.blur(); + assert.equal(h.events.length, 2); +}); + +test('clearing a search does not emit a blank query', () => { + const h = setup(); + h.input.value = ' '; + h.listeners.input(); + h.listeners.blur(); + assert.equal(h.events.length, 0); +}); + +test('repeated blur without a cleared search is deduplicated', () => { + const h = setup(); + h.input.value = 'sleep'; + h.listeners.input(); + h.listeners.blur(); + h.listeners.blur(); + assert.equal(h.events.length, 1); +}); + +test('committed searches never send raw query text', () => { + const h = setup(); + h.input.value = 'sensitive private query'; + h.listeners.input(); + h.listeners.blur(); + assert.equal(h.events.length, 1); + assert.equal(JSON.stringify(h.events).includes('sensitive'), false); +}); From 54315c4ff56fcbf81314c1bfbccf17c5ea1347f5 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Sat, 7 Nov 2026 09:15:29 +0900 Subject: [PATCH 3/3] docs(qa): include real catalog search handler regression tests --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a23cfa2..a62aee1 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ From a clean checkout of the intended production commit: ```bash python3 -m unittest discover -s scripts -p 'test_*.py' -v +node --test scripts/test_catalog_search_events.cjs python3 scripts/diagnose_growth_deploy.py --root . --output growth-deploy-diagnosis.json python3 scripts/diagnose_growth_routes.py --output growth-route-diagnosis.json python3 scripts/check_growth_live.py > growth-live-smoke.json