Skip to content
KW
Back to projects

sample 03/in production

ChecklistQA

An extension that had to move out of Google Meet to work in corporate environments.

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.

  1. 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.

  2. 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.

  3. 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.

  1. 000 m/interface

    • Native Chrome side panel
    • Three-stage checklist and agenda
    • Notes with copy
  2. 030 m/route

    • Icon click opens the panel
    • Alt+Shift+C shortcut
  3. 060 m/service

    • Service worker
    • Per-tab badge when joining a call
    • Read-only detector on Meet
  4. 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.

meet-detect.jsjs
(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.

background.jsjs
// (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.jsonjson
{
  "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.