sample 03/in production
ChecklistQA
An extension that had to move out of Google Meet to work in corporate environments.
- MV3
- JS
- Panel
01
Context
On a sales call it's easy to miss a step of the script, and taking notes elsewhere pulls attention away from the conversation. ChecklistQA keeps the script and notes right beside the meeting.
The rule from day one was to collect nothing: the meeting content is never read, and nothing leaves the browser.
02
Starting point
The first version injected the checklist straight into the Google Meet page.
10
items in the original script
3
call stages: start, middle and end
0
data collected or sent
03
Timeline
12 commits in the project history. These are the milestones.
Aug 29, 2026
First version
A Manifest V3 extension showing the checklist inside Meet, isolated in Shadow DOM, with a privacy policy and store listing.
Aug 31, 2026
Blocked in corporate environments
On machines with corporate security policies, a strict CSP and Trusted Types kept the panel from being built inside Meet. The extension was re-architected to run on its own page, in Chrome's side panel.
Sep 07, 2026
Call topics
Version 1.2.0 with an agenda block at the top of the checklist that grows with the text.
04
Decision map
Reconstructed from the commit history, the README and the project documentation.
01
Zero collection as a requirement
Nothing is stored or sent. State lives only in the panel's memory and disappears on reload, which simplifies privacy and store approval.
02
Moving out of Meet
Injecting into Meet worked at home and failed on corporate machines. Instead of working around the security policy, the panel moved to the extension's own page, which has its own CSP.
03
Detecting isn't opening
Chrome only opens the side panel on a user gesture. So the detector just lights a badge on the icon, and the panel opens with a click or Alt+Shift+C.
04
Read-only detector
The script running on Meet only looks at the tab's address. It doesn't touch the page, doesn't read the meeting and makes no requests.
05
User text never becomes HTML
The list is built from fixed items in the code itself. Whatever the user types is handled only as text, so a note has no way to execute anything.
06
A panel that stays open
Once open, the side panel follows tab and call changes. Opening it once at the start of the day is enough.
05
Architecture
The same five layers as this page, applied to the project.
000 m/interface
- Native Chrome side panel
- Three-stage checklist and agenda
- Notes with copy
030 m/route
- Icon click opens the panel
- Alt+Shift+C shortcut
060 m/service
- Service worker
- Per-tab badge when joining a call
- Read-only detector on Meet
120 m/persistence
- None: state lives only in memory
06
Code
Real excerpts from the public repository.
A detector that only reads the address
This is all the code that runs inside Meet. It matches the tab's address against a meeting room pattern and notifies the service worker. No page element is read or changed.
(function () {
'use strict';
// Código de call do Meet: 3-4-3 letras (ex.: abc-defg-hij)
const CALL_CODE_RE = /^\/[a-z]{3}-[a-z]{4}-[a-z]{3}$/i;
let lastHref = null;
let lastInCall = null;
function report() {
const inCall = CALL_CODE_RE.test(location.pathname);
if (inCall === lastInCall) return;
lastInCall = inCall;
// sendMessage pode falhar se o service worker estiver dormindo; ignoramos.
try {
chrome.runtime.sendMessage({ type: 'meet-call-state', inCall });
} catch (_) { /* no-op */ }
}
report();
// O Meet troca de URL sem recarregar (SPA); poll leve pega entrada/saída da call.
setInterval(() => {
if (location.href !== lastHref) {
lastHref = location.href;
report();
}
}, 1000);
})();Open on a gesture, signal without one
A key press counts as a user gesture, so the shortcut can open the panel. Joining a call doesn't count, so it only lights the badge on that tab's icon.
// (2) Atalho de teclado abre o painel na janela em foco.
chrome.commands.onCommand.addListener(async (command) => {
if (command !== 'open-checklist') return;
try {
const win = await chrome.windows.getLastFocused();
await chrome.sidePanel.open({ windowId: win.id });
} catch (err) {
console.error('Falha ao abrir painel pelo atalho:', err);
}
});
// (3) Badge de "entrou na call", por aba.
chrome.runtime.onMessage.addListener((msg, sender) => {
if (!msg || msg.type !== 'meet-call-state') return;
const tabId = sender.tab && sender.tab.id;
if (tabId == null) return;
if (msg.inCall) {
chrome.action.setBadgeBackgroundColor({ tabId, color: BADGE_COLOR });
chrome.action.setBadgeText({ tabId, text: BADGE_TEXT });
} else {
chrome.action.setBadgeText({ tabId, text: '' });
}
});Minimal permissions
Manifest excerpt: one API permission, the side panel, and a script restricted to Meet pages. No access to tabs, history, storage or network.
{
"manifest_version": 3,
"permissions": ["sidePanel"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["https://meet.google.com/*"],
"js": ["meet-detect.js"],
"run_at": "document_idle"
}
],
"side_panel": {
"default_path": "panel.html"
}
}07
Outcome
1
API permission: the side panel
0
data collected or sent
0
external dependencies
12
items in the current script
Current status
Published on the Chrome Web Store.