feat: custom tab group names and colors per session#64
Conversation
Sessions can now specify a custom Chrome tab group name and color via `playwriter session new --name myproject --color red`. Different sessions with different names get separate Chrome tab groups. The metadata flows through: CLI options → relay HTTP → CDP URL query params → relay state (PlaywrightClient) → forwardCDPCommand params → extension TabInfo → chrome.tabGroups API. Key changes: **Extension (0.0.74 → 0.0.77)** - Add groupName/groupColor fields to TabInfo - Rewrite syncTabGroup to manage multiple named groups independently - Add updateTabGroupWithRetry to handle Chrome's race condition where tabGroups.update() silently fails right after tabs.group() - Fix tab group flapping bug: only apply group metadata on Target.createTarget, not on every CDP command. Broadcast commands like Target.setAutoAttach were overwriting unrelated tabs' group metadata - Add tabsBeingMoved guard set so programmatic ungroup/regroup during syncTabGroup doesn't trigger disconnect via onUpdated handler - Narrow store subscriber to only trigger syncTabGroup on group-relevant field changes (state, groupName, groupColor) **Playwriter MCP/CLI (0.0.85 → 0.0.87)** - Add --name and --color options to `session new` CLI command - Forward groupName/groupColor through getCdpUrl query params - Store group metadata on PlaywrightClient in relay state - Only inject group fields into forwardCDPCommand on Target.createTarget to prevent the flapping bug - Pass clientId through handleCDPCommand so relay can look up group metadata **Website** - Add markdown.tsx route using safe-mdx for markdown-driven editorial pages Backward compatible: older extensions ignore the new fields, omitting options preserves the default 'playwriter' green group.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds per-session Chrome tab group naming/color support end-to-end (CLI → relay → extension) and introduces a new website route that renders the editorial page from markdown via safe-mdx.
Changes:
- Propagate
groupName/groupColorfromplaywriter session new --name/--colorthrough relay state and CDP URL params to the extension. - Update extension tab-group synchronization to support multiple managed groups, reduce “group flapping”, and add retry logic for
chrome.tabGroups.update()race conditions. - Add
website/src/routes/markdown.tsxrendered viasafe-mdxand include the new dependency.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| website/src/routes/markdown.tsx | New markdown-driven editorial page rendered via safe-mdx with component overrides. |
| website/package.json | Adds safe-mdx dependency for the new markdown route. |
| pnpm-lock.yaml | Lockfile updates for safe-mdx and related dependency graph changes. |
| playwriter/src/utils.ts | Adds groupName/groupColor query params to generated CDP URLs. |
| playwriter/src/skill.md | Documents new --name/--color options for sessions. |
| playwriter/src/relay-state.ts | Stores groupName/groupColor on PlaywrightClient in relay state. |
| playwriter/src/protocol.ts | Extends forwardCDPCommand params to optionally include tab group metadata. |
| playwriter/src/executor.ts | Threads session group metadata into cdpConfig for CDP URL generation. |
| playwriter/src/cli.ts | Adds session new --name/--color options with color validation and forwards to relay. |
| playwriter/src/cdp-relay.ts | Reads group metadata from CDP URL params, stores per-client metadata, and injects it only on Target.createTarget. |
| playwriter/package.json | Bumps Playwriter version to 0.0.87. |
| playwriter/CHANGELOG.md | Documents new session grouping feature and flapping bug fix behavior. |
| extension/src/types.ts | Extends TabInfo with optional groupName/groupColor. |
| extension/src/background.ts | Implements multi-group syncTabGroup, retry logic for group updates, and guards against programmatic ungroup triggering disconnects. |
| extension/package.json | Bumps extension package version to 0.0.76. |
| extension/manifest.json | Bumps extension manifest version to 0.0.77. |
| extension/CHANGELOG.md | Documents the session grouping feature, race retry, and flapping/disconnect fixes. |
| AGENTS.md | Adds guidance about opensrc/ dependency source reference workflow. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const tabsInDupe = await chrome.tabs.query({ groupId: dupe.id }) | ||
| const idsToUngroup = tabsInDupe.map((t) => t.id).filter((id): id is number => id !== undefined) | ||
| if (idsToUngroup.length > 0) { | ||
| await chrome.tabs.ungroup(idsToUngroup) |
There was a problem hiding this comment.
When consolidating duplicate Chrome tab groups, tabs are ungrouped without marking them in tabsBeingMoved. This can trigger chrome.tabs.onUpdated with groupId=-1 and be misinterpreted as a manual removal, causing disconnectTab() to run (the exact flapping/disconnect loop this PR is trying to prevent). Wrap this chrome.tabs.ungroup(idsToUngroup) call with the same tabsBeingMoved add/remove guard used elsewhere in syncTabGroup (and ideally a try/finally).
| await chrome.tabs.ungroup(idsToUngroup) | |
| try { | |
| // Mark tabs as being moved so onUpdated doesn't disconnect them when | |
| // chrome.tabs.ungroup fires groupId=-1 | |
| for (const id of idsToUngroup) { | |
| tabsBeingMoved.add(id) | |
| } | |
| await chrome.tabs.ungroup(idsToUngroup) | |
| } catch (e: any) { | |
| logger.debug('Failed to ungroup tabs while removing duplicate group:', idsToUngroup, e.message) | |
| } finally { | |
| for (const id of idsToUngroup) { | |
| tabsBeingMoved.delete(id) | |
| } | |
| } |
| export function addPlaywrightClient( | ||
| state: RelayState, | ||
| { id, extensionId, ws }: { id: string; extensionId: string | null; ws: WSContext }, | ||
| { id, extensionId, ws, groupName, groupColor }: { id: string; extensionId: string | null; ws: WSContext; groupName?: string; groupColor?: string }, | ||
| ): RelayState { | ||
| const newClients = new Map(state.playwrightClients) | ||
| newClients.set(id, { id, extensionId, ws }) | ||
| newClients.set(id, { id, extensionId, ws, groupName, groupColor }) | ||
| return { ...state, playwrightClients: newClients } |
There was a problem hiding this comment.
addPlaywrightClient now stores groupName/groupColor, but existing unit tests only assert extensionId/ws. Since these fields are part of the new session grouping feature, please extend the existing addPlaywrightClient test coverage to assert that provided groupName/groupColor are persisted in state (and remain optional when omitted).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96575c1b69
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const groupFields = method === 'Target.createTarget' && (client?.groupName || client?.groupColor) | ||
| ? { groupName: client.groupName, groupColor: client.groupColor } | ||
| : {} |
There was a problem hiding this comment.
Forward group metadata for auto-created initial tabs
When PLAYWRITER_AUTO_ENABLE is enabled and no targets exist, the first page is created via the Target.setAutoAttach path (maybeAutoCreateInitialTab) rather than a Target.createTarget command. Because groupFields are currently gated to method === 'Target.createTarget', that initial tab never receives groupName/groupColor and is grouped under the default "playwriter" group, so sessions started with different --name/--color can still collide until they open another page.
Useful? React with 👍 / 👎.
Sessions can now specify a custom Chrome tab group name and color via
playwriter session new --name myproject --color red. Different sessions with different names get separate Chrome tab groups.Extension (0.0.74 → 0.0.77)
groupName/groupColoronTabInfo, multi-groupsyncTabGroup, retry logic fortabGroups.update()race conditionTarget.createTarget, not every CDP commandtabsBeingMovedguard prevents programmatic ungroup from triggering disconnectsPlaywriter MCP/CLI (0.0.85 → 0.0.87)
--nameand--coloroptions onsession newTarget.createTargetto prevent flappingWebsite
markdown.tsxroute with safe-mdxBackward compatible — older extensions ignore new fields, omitting options keeps default "playwriter" green group.