diff --git a/.agents/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.agents/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.agents/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.agents/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.agents/skills/impeccable/scripts/live-browser.js b/.agents/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.agents/skills/impeccable/scripts/live-browser.js +++ b/.agents/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.claude/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.claude/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.claude/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.claude/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.claude/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.claude/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.claude/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.claude/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.claude/skills/impeccable/scripts/live-browser.js b/.claude/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.claude/skills/impeccable/scripts/live-browser.js +++ b/.claude/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.cursor/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.cursor/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.cursor/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.cursor/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.cursor/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.cursor/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.cursor/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.cursor/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.cursor/skills/impeccable/scripts/live-browser.js b/.cursor/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.cursor/skills/impeccable/scripts/live-browser.js +++ b/.cursor/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.gemini/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.gemini/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.gemini/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.gemini/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.gemini/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.gemini/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.gemini/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.gemini/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.gemini/skills/impeccable/scripts/live-browser.js b/.gemini/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.gemini/skills/impeccable/scripts/live-browser.js +++ b/.gemini/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.github/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.github/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.github/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.github/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.github/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.github/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.github/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.github/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.github/skills/impeccable/scripts/live-browser.js b/.github/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.github/skills/impeccable/scripts/live-browser.js +++ b/.github/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.kiro/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.kiro/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.kiro/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.kiro/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.kiro/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.kiro/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.kiro/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.kiro/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.kiro/skills/impeccable/scripts/live-browser.js b/.kiro/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.kiro/skills/impeccable/scripts/live-browser.js +++ b/.kiro/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.opencode/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.opencode/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.opencode/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.opencode/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.opencode/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.opencode/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.opencode/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.opencode/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.opencode/skills/impeccable/scripts/live-browser.js b/.opencode/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.opencode/skills/impeccable/scripts/live-browser.js +++ b/.opencode/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.pi/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.pi/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.pi/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.pi/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.pi/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.pi/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.pi/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.pi/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.pi/skills/impeccable/scripts/live-browser.js b/.pi/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.pi/skills/impeccable/scripts/live-browser.js +++ b/.pi/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.qoder/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.qoder/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.qoder/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.qoder/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.qoder/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.qoder/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.qoder/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.qoder/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.qoder/skills/impeccable/scripts/live-browser.js b/.qoder/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.qoder/skills/impeccable/scripts/live-browser.js +++ b/.qoder/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.rovodev/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.rovodev/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.rovodev/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.rovodev/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.rovodev/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.rovodev/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.rovodev/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.rovodev/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.rovodev/skills/impeccable/scripts/live-browser.js b/.rovodev/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.rovodev/skills/impeccable/scripts/live-browser.js +++ b/.rovodev/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.trae-cn/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.trae-cn/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.trae-cn/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.trae-cn/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.trae-cn/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.trae-cn/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.trae-cn/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.trae-cn/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.trae-cn/skills/impeccable/scripts/live-browser.js b/.trae-cn/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.trae-cn/skills/impeccable/scripts/live-browser.js +++ b/.trae-cn/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/.trae/skills/impeccable/scripts/detector/browser/injected/index.mjs b/.trae/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/.trae/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/.trae/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.trae/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.trae/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/.trae/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.trae/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/.trae/skills/impeccable/scripts/live-browser.js b/.trae/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/.trae/skills/impeccable/scripts/live-browser.js +++ b/.trae/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/cli/engine/browser/injected/index.mjs b/cli/engine/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/cli/engine/browser/injected/index.mjs +++ b/cli/engine/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/cli/engine/detect-antipatterns-browser.js b/cli/engine/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/cli/engine/detect-antipatterns-browser.js +++ b/cli/engine/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/plugin/skills/impeccable/scripts/detector/browser/injected/index.mjs b/plugin/skills/impeccable/scripts/detector/browser/injected/index.mjs index ceff16737..29b20f5ee 100644 --- a/plugin/skills/impeccable/scripts/detector/browser/injected/index.mjs +++ b/plugin/skills/impeccable/scripts/detector/browser/injected/index.mjs @@ -1437,13 +1437,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1497,7 +1504,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -1565,7 +1572,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -1585,6 +1592,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -1599,11 +1607,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -1618,10 +1626,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/plugin/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/plugin/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 88439a5c3..f1535b97a 100644 --- a/plugin/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/plugin/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -4253,13 +4253,20 @@ if (IS_BROWSER) { return true; } - function postSerializedFindings(groupMap) { + function scanResultMeta(options = {}) { + const scanId = options.scanId; + if (typeof scanId !== 'string' && typeof scanId !== 'number') return {}; + return { scanId: String(scanId) }; + } + + function postSerializedFindings(groupMap, options = {}) { if (!EXTENSION_MODE) return; const allFindings = browserFindingsFromMap(groupMap); window.postMessage({ source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4313,7 +4320,7 @@ if (IS_BROWSER) { rememberVisualContrastAnalysis(result); const added = addVisualContrastResult(groupMap, result, { decorate: true }); if (added) { - postSerializedFindings(groupMap); + postSerializedFindings(groupMap, options); window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-resolved', { detail: { selector: result.selector, @@ -4381,7 +4388,7 @@ if (IS_BROWSER) { overlayIndex = 0; } - function renderBrowserFindings(collected) { + function renderBrowserFindings(collected, options = {}) { const { allFindings, pageLevelFindings } = collected; for (const { el, findings } of allFindings) { @@ -4401,6 +4408,7 @@ if (IS_BROWSER) { source: 'impeccable-results', findings: serializeFindings(allFindings), count: allFindings.length, + ...scanResultMeta(options), }, '*'); } @@ -4415,11 +4423,11 @@ if (IS_BROWSER) { clearOverlays(); const generation = scanGeneration; const collected = collectBrowserFindings(); - const allFindings = renderBrowserFindings(collected); + const allFindings = renderBrowserFindings(collected, options); if (shouldRunVisualContrast(options)) { addVisualContrastFindings(collected.groupMap, options, { decorate: true, generation }) .then(() => { - if (generation === scanGeneration) postSerializedFindings(collected.groupMap); + if (generation === scanGeneration) postSerializedFindings(collected.groupMap, options); }) .catch(err => { reportVisualContrastError(err); @@ -4434,10 +4442,10 @@ if (IS_BROWSER) { if (shouldRunVisualContrast(options)) { const collected = await collectBrowserFindingsAsync(options, { generation, scheduleLazy: true }); if (generation !== scanGeneration) return []; - return renderBrowserFindings(collected); + return renderBrowserFindings(collected, options); } lastVisualContrastAnalyses = []; - return renderBrowserFindings(collectBrowserFindings()); + return renderBrowserFindings(collectBrowserFindings(), options); }; const detect = function(options = {}) { diff --git a/plugin/skills/impeccable/scripts/live-browser.js b/plugin/skills/impeccable/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/plugin/skills/impeccable/scripts/live-browser.js +++ b/plugin/skills/impeccable/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/skill/scripts/live-browser.js b/skill/scripts/live-browser.js index 9b2d24f83..f609ac847 100644 --- a/skill/scripts/live-browser.js +++ b/skill/scripts/live-browser.js @@ -6088,6 +6088,10 @@ void main() { let steerFocusRecoverTimer = null; const STEER_PAGE_FOCUS_PAUSE_MS = 500; let detectActive = false; + let detectScanSeq = 0; + let activeDetectScanId = null; + let pendingDetectScanId = null; + const DETECT_EMPTY_MESSAGE = 'No detector issues found.'; const PICK_PREFS_KEY = 'impeccable-live-pick'; const INTERACTION_PREFS_KEY = 'impeccable-live-interaction'; const PLACEHOLDER_DEFAULT_HEIGHT = 80; @@ -7601,6 +7605,17 @@ void main() { let detectReady = false; // true once detect script posts 'impeccable-ready' let detectPendingScan = false; // scan requested before script was ready + function requestDetectScan() { + const scanId = String(++detectScanSeq); + activeDetectScanId = scanId; + pendingDetectScanId = scanId; + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId }, + }, '*'); + } + function toggleDetect() { if (pendingApplyInFlight) { showManualApplyBusyToast(); return; } detectActive = !detectActive; @@ -7611,12 +7626,14 @@ void main() { detectPendingScan = true; loadDetectScript(); } else if (detectReady) { - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } else { detectPendingScan = true; } } else { window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*'); + activeDetectScanId = null; + pendingDetectScanId = null; detectCount = 0; updateGlobalBarState(); } @@ -7686,12 +7703,18 @@ void main() { detectReady = true; if (detectPendingScan && detectActive) { detectPendingScan = false; - window.postMessage({ source: 'impeccable-command', action: 'scan' }, '*'); + requestDetectScan(); } } // Scan results arrived if (e.data.source === 'impeccable-results') { + if (!detectActive) return; + if (activeDetectScanId && e.data.scanId !== activeDetectScanId) return; detectCount = e.data.count || 0; + if (detectActive && pendingDetectScanId && detectCount === 0) { + showToast(DETECT_EMPTY_MESSAGE, 3200); + } + pendingDetectScanId = null; updateGlobalBarState(); } } diff --git a/tests/detect-antipatterns-browser.test.mjs b/tests/detect-antipatterns-browser.test.mjs index bc44898fa..9388de8d2 100644 --- a/tests/detect-antipatterns-browser.test.mjs +++ b/tests/detect-antipatterns-browser.test.mjs @@ -577,6 +577,61 @@ describe('detectUrl — browser-only fixtures', () => { } }); + it('extension mode echoes scan ids on result messages', async () => { + const puppeteer = await import('puppeteer'); + const browser = await puppeteer.default.launch({ + headless: true, + args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [], + }); + try { + const page = await browser.newPage(); + await page.setViewport({ width: 1280, height: 800 }); + await page.goto(`${baseUrl}/fixtures/antipatterns/should-pass.html`, { waitUntil: 'load' }); + const browserScript = fs.readFileSync(path.join(ROOT, 'cli/engine/detect-antipatterns-browser.js'), 'utf-8'); + await page.evaluate(() => { + document.documentElement.dataset.impeccableExtension = 'true'; + window.__impeccableMessages = []; + window.addEventListener('message', event => { + if (event.source !== window || !event.data?.source?.startsWith('impeccable-')) return; + window.__impeccableMessages.push(event.data); + }); + }); + await page.evaluate(browserScript); + const result = await page.evaluate(async () => { + window.postMessage({ + source: 'impeccable-command', + action: 'scan', + config: { scanId: 'scan-2' }, + }, '*'); + const deadline = Date.now() + 1000; + while ( + Date.now() < deadline && + !window.__impeccableMessages.some(message => + message.source === 'impeccable-results' && + message.scanId === 'scan-2' + ) + ) { + await new Promise(resolve => setTimeout(resolve, 25)); + } + const resultMessage = window.__impeccableMessages.find(message => + message.source === 'impeccable-results' && + message.scanId === 'scan-2' + ); + return { + ready: window.__impeccableMessages.some(message => message.source === 'impeccable-ready'), + scanId: resultMessage?.scanId || null, + count: resultMessage?.count ?? null, + }; + }); + assert.equal(result.ready, true, `expected extension ready message, got: ${JSON.stringify(result)}`); + assert.equal(result.scanId, 'scan-2', `expected scan id echo in results, got: ${JSON.stringify(result)}`); + assert.equal(result.count, 0, `expected clean fixture to have no findings, got: ${JSON.stringify(result)}`); + await page.close(); + } finally { + await browser.close().catch(() => {}); + } + }); + it('browser API: impeccableDetect is pure, impeccableScan decorates', async () => { const puppeteer = await import('puppeteer'); const browser = await puppeteer.default.launch({ diff --git a/tests/live-browser-regression.test.mjs b/tests/live-browser-regression.test.mjs index cabe0bf3c..3cd986fdf 100644 --- a/tests/live-browser-regression.test.mjs +++ b/tests/live-browser-regression.test.mjs @@ -292,6 +292,34 @@ describe('live-browser.js regression guards', () => { ); }); + it('detect mode shows an empty result toast once per requested scan', () => { + assert.match( + SOURCE, + /const DETECT_EMPTY_MESSAGE = 'No detector issues found\.';/, + 'live detector zero result copy should live in one named constant', + ); + assert.match( + SOURCE, + /function requestDetectScan\(\)[\s\S]{0,240}?const scanId = String\(\+\+detectScanSeq\);[\s\S]{0,80}?activeDetectScanId = scanId;[\s\S]{0,160}?config: \{ scanId \}/, + 'Detect scans must send a fresh scan id to the detector', + ); + assert.match( + SOURCE, + /if \(!detectActive\) return;[\s\S]{0,80}?if \(activeDetectScanId && e\.data\.scanId !== activeDetectScanId\) return;/, + 'live detector results must ignore inactive and stale scan ids', + ); + assert.match( + SOURCE, + /if \(detectActive && pendingDetectScanId && detectCount === 0\) \{[\s\S]{0,80}?showToast\(DETECT_EMPTY_MESSAGE, 3200\);[\s\S]{0,120}?pendingDetectScanId = null;/, + 'a matching zero result scan must use the existing toast UI and clear the pending scan id', + ); + assert.match( + SOURCE, + /window\.postMessage\(\{ source: 'impeccable-command', action: 'remove' \}, '\*'\);[\s\S]{0,80}?activeDetectScanId = null;[\s\S]{0,80}?pendingDetectScanId = null;/, + 'turning Detect off must clear scan ids', + ); + }); + it('insert mode UI and generate payload guards', () => { assert.match(SOURCE, /function toggleInsert\(\)/, 'global bar must expose insert toggle'); assert.match(SOURCE, /PREFIX \+ '-insert-toggle'/, 'insert toggle needs stable id');