Files
pbakaus_impeccable/extension/devtools/sidebar.js
T
Paul BakausandClaude Opus 4.6 13b2f763d9 Polish Chrome extension for Web Store submission
Refactors the extension for on-demand injection (no static content_scripts
entry — content script and detector are loaded only when the user actively
opens the Impeccable panel, sidebar pane, or popup). Adds a new "Auto-scan"
preference (default: scan when the Impeccable panel opens, opt-in: scan
when DevTools opens) plus configurable line length (strict/lax) and
highlight blur on/off settings. Adds an Elements panel sidebar that shows
findings for the currently selected element.

Includes substantial overlay UX work: page-pixel-perfect spotlight mask
via clip-path, refined hover/dim states, instant transitions for snappier
feel, copy buttons for findings, hover-from-panel highlighting, and a
brand-aware exception list so the font check no longer flags Roboto on
Google's own properties.

Robustness fixes for the MV3 service worker lifecycle: heartbeat keepalive
plus auto-reconnecting ports across panel/sidebar/devtools so transient
SW restarts don't break the panel UI, and immediate teardown on DevTools
close (replacing an unreliable setTimeout-based defer that didn't survive
SW termination).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 21:06:09 -07:00

104 lines
3.0 KiB
JavaScript

/**
* Impeccable DevTools Extension - Elements Sidebar Pane
*
* Shows Impeccable findings for the currently selected element ($0) in the Elements panel.
*/
if (chrome.devtools.panels.themeName === 'dark') {
document.documentElement.classList.add('theme-dark');
}
const tabId = chrome.devtools.inspectedWindow.tabId;
const content = document.getElementById('sidebar-content');
let currentFindings = [];
// Auto-reconnecting port (service worker may restart in MV3)
let port = null;
function getPort() {
if (port) return port;
port = chrome.runtime.connect({ name: `impeccable-sidebar-${tabId}` });
port.onMessage.addListener((msg) => {
if (msg.action === 'findings' || msg.action === 'state') {
currentFindings = msg.findings || [];
refreshForCurrentSelection();
}
});
port.onDisconnect.addListener(() => { port = null; });
return port;
}
getPort();
chrome.devtools.panels.elements.onSelectionChanged.addListener(refreshForCurrentSelection);
function refreshForCurrentSelection() {
if (!currentFindings.length) {
renderEmpty('No findings on this page yet.');
return;
}
// Collect non-page-level selectors and ask the inspected window which one matches $0
const selectors = [];
for (const item of currentFindings) {
if (item.isPageLevel || item.isHidden) continue;
selectors.push(item.selector);
}
if (!selectors.length) {
renderEmpty('No element-level findings on this page.');
return;
}
const code = `(function() {
var sels = ${JSON.stringify(selectors)};
var matched = [];
for (var i = 0; i < sels.length; i++) {
try { if (document.querySelector(sels[i]) === $0) matched.push(sels[i]); } catch (e) {}
}
return matched;
})()`;
chrome.devtools.inspectedWindow.eval(code, (matched) => {
if (!matched || !matched.length) {
renderNoFindings();
return;
}
const items = currentFindings.filter(item => matched.includes(item.selector));
render(items);
});
}
function renderEmpty(text) {
content.innerHTML = `<div class="state">${escapeHtml(text)}</div>`;
}
function renderNoFindings() {
content.innerHTML = `<div class="state"><strong>Clean.</strong> No anti-patterns on this element.</div>`;
}
function render(items) {
const html = [];
for (const item of items) {
for (const f of item.findings) {
const isSlop = f.category === 'slop';
const marker = isSlop ? '<span class="marker">\u2726</span>' : '';
const kind = isSlop ? 'AI tell' : 'Quality';
html.push(`
<div class="finding">
<div class="finding-header">
<span class="finding-name">${marker}${escapeHtml(f.name)}</span>
<span class="finding-kind">${kind}</span>
</div>
<div class="finding-detail">${escapeHtml(f.detail)}</div>
<div class="finding-description">${escapeHtml(f.description)}</div>
</div>
`);
}
}
content.innerHTML = html.join('');
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}