Late Meet is a Manifest V3 Chrome Extension built with TypeScript and Vite 5. It captures meeting audio directly from the browser tab, transcribes it using AI, and presents real-time intelligence through a side panel dashboard — all without adding bots to your call.
| Component | File(s) | Responsibility |
|---|---|---|
| Background Service Worker | background.ts |
Central state machine. Detects Meet tabs, routes audio to STT APIs, coordinates LLM summarization, manages session lifecycle, and handles participant tracking. |
| Offscreen Audio Engine | offscreen.ts, offscreen.html |
Runs a hidden offscreen document for chrome.tabCapture. Captures audio via MediaRecorder, processes chunks, and forwards blobs to the background worker. |
| Content Script | content.ts, content.css |
Injects floating UI elements into Google Meet pages. Handles the "Start Copilot" button, late-joiner briefing overlays, and chat automation for welcome messages. |
| Side Panel Dashboard | dashboard.ts, dashboard.html, dashboard.css |
Real-time intelligence display. Shows live summary, topics, decisions, action items, sentiment analysis, and meeting timeline. |
| Popup | popup.ts, popup.html, popup.css |
Quick-access extension controls. Start/stop copilot, view meeting status, navigate to dashboard. |
| Options Page | options.ts, options.html, options.css |
API key configuration and local preferences. Users enter their ElevenLabs and OpenAI keys (BYOK model). |
| Storage Utilities | sessionStorage.ts, utils/storageUtils.ts |
Session persistence, storage metrics, cleanup functions, legacy support, and local storage quotas. |
| AI Prompts + Processing | utils/prompts.ts, utils/api.ts |
Prompt construction and provider wrappers for OpenAI / ElevenLabs validation and transcription. |
| Type Definitions | types.ts |
TypeScript interfaces for meeting state, participants, timeline entries, transcript chunks, and analytics. |
Every meeting session follows this end-to-end pipeline — from the moment you join a call to the final dashboard output.
| Step | Component | Action |
|---|---|---|
| 1 | 🌐 Google Meet tab | User joins a meeting |
| 2 | 📄 Content script | Detects meeting → injects Start Copilot button |
| 3 | 👆 User | Clicks Start → message sent to background worker |
| 4 | ⚙️ Background | Creates offscreen document → begins tabCapture stream |
| 5 | 🎙️ Offscreen document | Captures audio chunks via MediaRecorder → produces blobs |
| 6 | 🔊 ElevenLabs Scribe | Receives audio blobs → returns transcribed text (Whisper fallback if needed) |
| 7 | 📝 Background | Appends transcript → maintains rolling context window |
| 8 | 🤖 OpenAI GPT | Processes transcript → generates structured intelligence |
| 9 | 💾 chrome.storage.local | Structured results saved locally |
| 10 | 📊 Side panel dashboard | Polls storage → renders live updates |
| 11 | ✅ User | Meeting ends → chooses Save or Discard |
The codebase is organized around extension layers and domain concerns.
-
src/— extension source code.background.ts— MV3 service worker orchestrating state, audio capture, and AI work.offscreen.ts/offscreen.html— hidden document used forchrome.tabCaptureandMediaRecorderaudio processing.content.ts/content.css— Google Meet page integration, floating UI injection, and meeting detection.dashboard.ts/dashboard.html/dashboard.css— side panel dashboard rendering, state consumption, and dashboard interactions.popup.ts/popup.html/popup.css— quick extension controls and status display.options.ts/options.html/options.css— user settings, API credential entry and validation.utils/— reusable helpers for API calls, credentials encryption, prompts, and storage metrics.types.ts— shared TypeScript interfaces used across components.
-
docs/— contributor-facing architecture, workflow, and troubleshooting documentation. -
manifest.json— Chrome extension metadata, permissions, commands, and side panel configuration. -
package.json/vite.config.ts— build tooling and extension packaging configuration.
Late Meet separates audio capture into two layers to satisfy MV3 constraints.
-
Meet Detection (
content.ts)- Detects a Google Meet page using URL pattern matching (
meet.google.com/*). - Injects the Start Copilot UI overlay and sends meeting state updates to
background.ts.
- Detects a Google Meet page using URL pattern matching (
-
Capture Initialization (
background.ts)- Receives a user command to start Copilot from the popup or content script.
- Creates or reactivates an offscreen document via
chrome.offscreen.createDocument. - Uses
chrome.tabCapture.getMediaStreamIdwith the Meet tab ID, then forwards that stream ID tooffscreen.ts.
-
Offscreen Processing (
offscreen.ts)- Opens a hidden page that can access DOM and media APIs.
- Starts
MediaRecorderon the captured audio stream. - Emits recorded audio chunks back to
background.tsvia runtime messages.
-
Speech-to-Text Routing (
background.ts)- Receives audio chunks and decides whether to use ElevenLabs or OpenAI Whisper.
- Sends audio blobs to the configured provider and awaits transcript results.
- Appends transcript entries to the in-memory state and persists them if needed.
flowchart LR
A[Google Meet tab] --> B[content.ts detect meeting]
B --> C[background.ts start Copilot]
C --> D[offscreen.ts create audio stream]
D --> E[MediaRecorder captures chunks]
E --> F[background.ts receives audio blobs]
F --> G[STT provider (ElevenLabs/Whisper)]
G --> H[Transcript state update]
Once transcript text arrives, the background worker coordinates analysis and summary generation.
- Each transcript chunk is added to the meeting state along with metadata like
chunkId,timestamp, andspeaker. summarizeTranscriptIfNeeded()wakes periodically based on user settings and the latest transcript activity.- The prompt builder in
utils/prompts.tsconstructs a JSON-focused request for summary, topics, decisions, and actions. - OpenAI GPT processes the transcript window and returns a structured JSON payload.
- Background logic merges new decisions, actions, and summary points into persistent state while avoiding duplicates.
Late Meet expects the AI response to contain fields such as:
summarysummaryItemstopicsdecisionsactionItemssentimentkeyInsights
This keeps the dashboard rendering code independent from raw AI responses.
Late Meet uses chrome.storage.local as its single source of truth for saved sessions and settings.
- Active meeting state is stored under
activeMeetingStateand broadcast to UI components. - Saved sessions are stored individually as
savedSession:<id>keys with a separate index keysavedSessionIndex. - Legacy fallback support uses
savedSessionsfor older releases. utils/storageUtils.tscomputes storage usage, session sizes, and quota metrics.- Bulk cleanup APIs in
sessionStorage.tsremove saved payloads and keep the session index in sync.
flowchart LR
A[background.ts state] --> B[chrome.storage.local activeMeetingState]
B --> C[dashboard.ts reads state]
C --> D[render UI]
A --> E[savedSession:<id> payloads]
E --> F[sessionStorage.ts delete / cleanup]
dashboard.ts is responsible for reading stored state and updating the UI.
- On load, it hydrates the latest meeting state from
background.tsandchrome.storage.local. - It only renders data; it does not perform core business logic such as transcription or summarization.
- Components like summary, decisions, and action items are updated when
background.tsbroadcasts state changes. - The dashboard also exposes export functions and transcript navigation.
The Late Meet lifecycle follows these phases:
-
Install / first run
background.tsregisters commands and context menus.- If onboarding is not complete, first-run setup may be launched.
-
Initialization
- The service worker hydrates state from
chrome.storage.local. - UI components connect to the background with message listeners.
- The service worker hydrates state from
-
Meeting detection
content.tsdetects Google Meet pages and reports status.- The user can start Copilot from the injected overlay or popup.
-
Active session
- The background worker creates an offscreen document and begins audio capture.
- Transcript, summary, and intelligence are generated live.
-
Session completion
- The user may save, export, or discard the session.
- Saved session state is persisted and available in storage dashboards.
Late Meet is intentionally local-first.
- No project-managed databases or external session storage.
- Users provide their own OpenAI and ElevenLabs API keys.
- Persistent meeting data is stored locally on the user's device (
chrome.storage.local). - Audio/transcript content is transmitted only to user-configured providers (BYOK) for transcription/summarization.
chrome.storage.localis the only persistent storage mechanism.
| Output | Description |
|---|---|
| 📋 Summary | Concise overview of what was discussed |
| 🏷️ Topics | Key subjects and themes extracted from the meeting |
| ✅ Action items | Tasks identified with implicit or explicit ownership |
| 🎯 Decisions | Conclusions and commitments made during the meeting |
| 💬 Sentiment | Tone and confidence analysis across the conversation |
💡 For contributors: The separation of responsibilities must be preserved — audio work belongs in
offscreen.ts, coordination inbackground.ts, and display logic indashboard.ts. See Contributor Guidelines before making pipeline changes.
Late Meet components never call each other directly — they communicate exclusively through Chrome runtime messages and local storage updates. This keeps each component decoupled and testable in isolation.
| Sender | Receiver | Message Type | Trigger |
|---|---|---|---|
content.ts |
background.ts |
Page status | Meeting detected / page changed |
popup.ts |
background.ts |
User command | Start / Stop copilot clicked |
background.ts |
offscreen.ts |
Capture control | Create / destroy audio stream |
offscreen.ts |
background.ts |
Status update | Audio chunks ready / capture stopped |
background.ts |
dashboard.ts |
Intelligence update | New transcript / summary ready |
options.ts |
chrome.storage.local |
Settings write | API keys saved |
dashboard.ts |
chrome.storage.local |
Settings read | Dashboard loads / refreshes |
Important
Never call component functions directly. All cross-component communication must go through chrome.runtime.sendMessage or chrome.storage updates. Direct imports between components break the MV3 architecture.
- background.ts is the only hub — all messages route through it, not directly between components
- content.ts only sends, never receives complex state — it detects and reports, background.ts decides
- dashboard.ts only reads — it should never write business logic back to background.ts
- offscreen.ts is isolated — it only talks to background.ts, never directly to content.ts or dashboard.ts
- options.ts writes to storage directly — it does not need to go through background.ts for settings saves
- BYOK: Users supply their own API keys. No shared credentials.
- Local-only storage: All data lives in
chrome.storage.local. Zero cloud sync. - No telemetry: No analytics, no tracking, no usage data collection.
- Invisible: No bot participant added to the meeting. Audio captured via Chrome's native
tabCaptureAPI. - User-controlled lifecycle: Data can be discarded at any time.
| Category | Technology |
|---|---|
| Extension Platform | Chrome Manifest V3 |
| Language | TypeScript |
| Build Tool | Vite 5 with @crxjs/vite-plugin |
| Styling | Vanilla CSS (monochrome design system) |
| Storage | chrome.storage.local |
| Transcription | ElevenLabs Scribe v2 (primary), OpenAI Whisper (fallback) |
| Summarization | OpenAI GPT models (configurable) |
| Audio Capture | Chrome tabCapture + Offscreen Documents API |
Late Meet is built exclusively for Manifest V3 — the current and future standard for Chrome Extensions. MV3 introduces important architectural constraints that directly shape how Late Meet is designed.
MV3 replaces the old persistent background page with an event-driven service worker. This means background.ts can be stopped by Chrome at any time when idle — it is not a permanently running process.
What this means for contributors: Never rely on in-memory state surviving between events. Always persist important state to
chrome.storage.localand re-read it when needed. Design message handlers to be stateless and self-contained.
Chrome's MV3 service workers cannot access media APIs like MediaRecorder or chrome.tabCapture directly — they require a document context. Late Meet solves this by spinning up a hidden Offscreen Document (offscreen.ts) whenever audio capture is needed.
What this means for contributors: All audio capture and media processing must stay inside
offscreen.ts. Do not attempt to move media work into the service worker — it will not work in MV3.
Late Meet relies on Chrome-only APIs including chrome.tabCapture, chrome.storage.local, and chrome.sidePanel. These are intentional choices for the current Google Meet + Chrome implementation.
What this means for contributors: Always verify that any new browser API you use is available in MV3 and check whether it requires new permissions in
manifest.json. Document any new permission additions clearly in your PR.
📖 New to contributing? Start with the Contributor Guide first for the full contribution workflow, then come back here for architecture-specific rules.
Before making any architecture-level change, read this section carefully. Late Meet follows a strict separation of concerns — each component owns a specific layer and should not bleed into others.
| Component | Owns |
|---|---|
content.ts |
Page detection, UI injection, Meet page state |
background.ts |
Orchestration, message routing, session lifecycle |
offscreen.ts |
Audio capture, MediaRecorder, stream processing |
dashboard.ts |
Display rendering, storage reads, live updates |
popup.ts |
Quick user actions, status display |
options.ts |
User configuration, API key management |
chrome.storage.local |
All persistent session state |
- Long-running media work in the service worker — use the Offscreen Document instead
- Server-side storage for meeting data — Late Meet is local-first; never add cloud persistence without an explicit project decision
- Dashboard rendering logic in background.ts — keep display concerns in
dashboard.ts - Calling AI providers from multiple components — all provider calls should be coordinated through
background.ts - Adding new permissions silently — every new
manifest.jsonpermission must be justified and documented in the PR
- When in doubt about where logic belongs, follow the existing message-passing pattern
- Keep components focused — if a file is growing too large, that's a signal to extract a utility
- Test message-passing changes on a real Google Meet tab, not just in isolation
Use Chrome's built-in extension tools when debugging architecture-level issues.
- Open
chrome://extensions - Enable Developer mode (toggle in top-right)
- Click Load unpacked → select the
dist/folder - Use the Inspect links to open DevTools for each component
📽️ For a visual walkthrough of the full setup process, see docs/DEMO.md
| Component | How to inspect |
|---|---|
background.ts |
Click Service Worker link on the extension card in chrome://extensions |
popup.ts |
Right-click the extension icon → Inspect popup |
dashboard.ts |
Open the side panel → right-click → Inspect |
options.ts |
Open the options page → right-click → Inspect |
content.ts |
Open DevTools on the Google Meet tab → Console |
offscreen.ts |
Appears as a separate target in chrome://inspect/#other |
- Message-passing issues — log the message
type,sender, and intended destination at both ends - Storage issues — use
chrome.storage.local.get(null, console.log)in the service worker console to dump all stored keys - Audio capture issues — always test on a real Google Meet tab; mock environments won't trigger
tabCapturecorrectly - Service worker stopped — if background logs aren't appearing, click Service Worker in
chrome://extensionsto wake it up