feat(settings): add CDP settings UI and DevTools toggle#1374
Merged
Conversation
added 3 commits
March 17, 2026 19:47
- Add CDP (Chrome DevTools Protocol) configuration panel in system settings with enable/disable switch, port display, and MCP config copy functionality (only visible in dev mode) - Add DevTools open/close toggle button in system settings - Pin chrome-devtools-mcp version from @latest to @0.16.0
The default MCP server config is for end users (WebUI), so it should use @latest to always get the newest version.
Cover CdpSettings component (render in dev mode, toggle enable, error handling, restart alert, disabled hint, MCP config display, open URL) and DevTools toggle button (render, click, event listener).
IceyLiu
approved these changes
Mar 17, 2026
There was a problem hiding this comment.
Pull request overview
Restores the developer-facing CDP settings panel in System Settings and adds a DevTools open/close toggle, plus pins the documented/logged chrome-devtools-mcp version to avoid upstream breakage.
Changes:
- Add CDP settings UI (dev-mode only) with enable/disable, port display, URL open/copy, MCP config copy, and restart-required alert.
- Add DevTools open/close toggle button in System Settings and subscribe to main-process state updates.
- Pin
chrome-devtools-mcpto@0.16.0in docs and CDP log output; add unit tests for the new UI.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| tests/unit/SystemModalContent.dom.test.tsx | Adds DOM tests for DevTools toggle + CDP settings UI behavior. |
| src/renderer/components/SettingsModal/contents/SystemModalContent.tsx | Implements CDP settings panel and DevTools toggle UI in System Settings. |
| src/index.ts | Pins CDP log output MCP command to [email protected]. |
| docs/cdp.md | Pins documented MCP config to [email protected]. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const result = await ipcBridge.application.updateCdpConfig.invoke({ enabled: checked }); | ||
| if (result.success) { | ||
| Message.success(t('settings.cdp.configSaved')); | ||
| await mutate('cdp.status'); |
Comment on lines
+163
to
+165
| void navigator.clipboard.writeText(config).then(() => { | ||
| Message.success(t('common.copySuccess')); | ||
| }); |
Comment on lines
+109
to
+152
| let swrCache: Record<string, any> = {}; | ||
| let swrMutateCallback: ((key: string) => void) | null = null; | ||
|
|
||
| vi.mock('swr', () => { | ||
| const useSWR = (key: string, fetcher: () => Promise<any>) => { | ||
| const [data, setData] = React.useState<any>(undefined); | ||
| const [isLoading, setIsLoading] = React.useState(true); | ||
|
|
||
| React.useEffect(() => { | ||
| if (swrCache[key] !== undefined) { | ||
| setData(swrCache[key]); | ||
| setIsLoading(false); | ||
| return; | ||
| } | ||
| fetcher().then((result) => { | ||
| swrCache[key] = result; | ||
| setData(result); | ||
| setIsLoading(false); | ||
| }); | ||
| }, [key]); | ||
|
|
||
| // Register mutate listener | ||
| React.useEffect(() => { | ||
| swrMutateCallback = (mutateKey: string) => { | ||
| if (mutateKey === key) { | ||
| fetcher().then((result) => { | ||
| swrCache[key] = result; | ||
| setData(result); | ||
| }); | ||
| } | ||
| }; | ||
| }, [key]); | ||
|
|
||
| return { data, isLoading, error: undefined }; | ||
| }; | ||
|
|
||
| const mutate = (key: string) => { | ||
| if (swrMutateCallback) swrMutateCallback(key); | ||
| return Promise.resolve(); | ||
| }; | ||
|
|
||
| useSWR.default = useSWR; | ||
| return { default: useSWR, mutate }; | ||
| }); |
Comment on lines
+100
to
+176
| const { data: cdpStatus, isLoading } = useSWR('cdp.status', () => ipcBridge.application.getCdpStatus.invoke()); | ||
| const [switchLoading, setSwitchLoading] = useState(false); | ||
|
|
||
| const status = cdpStatus?.data; | ||
|
|
||
| // Track the pending state (config saved but not yet applied) | ||
| const hasPendingChange = status?.startupEnabled !== status?.enabled; | ||
|
|
||
| const handleToggle = async (checked: boolean) => { | ||
| setSwitchLoading(true); | ||
| try { | ||
| const result = await ipcBridge.application.updateCdpConfig.invoke({ enabled: checked }); | ||
| if (result.success) { | ||
| Message.success(t('settings.cdp.configSaved')); | ||
| await mutate('cdp.status'); | ||
| } else { | ||
| Message.error(result.msg || t('settings.cdp.configFailed')); | ||
| } | ||
| } catch { | ||
| Message.error(t('settings.cdp.configFailed')); | ||
| } finally { | ||
| setSwitchLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| const handleRestart = async () => { | ||
| try { | ||
| await ipcBridge.application.restart.invoke(); | ||
| } catch { | ||
| Message.error(t('common.error')); | ||
| } | ||
| }; | ||
|
|
||
| const openCdpUrl = () => { | ||
| if (status?.port) { | ||
| const url = `http://127.0.0.1:${status.port}/json`; | ||
| ipcBridge.shell.openExternal.invoke(url).catch(console.error); | ||
| } | ||
| }; | ||
|
|
||
| const copyCdpUrl = () => { | ||
| if (status?.port) { | ||
| const url = `http://127.0.0.1:${status.port}`; | ||
| void navigator.clipboard.writeText(url).then(() => { | ||
| Message.success(t('common.copySuccess')); | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| const copyMcpConfig = () => { | ||
| if (status?.port) { | ||
| const config = `{ | ||
| "mcpServers": { | ||
| "chrome-devtools": { | ||
| "command": "npx", | ||
| "args": [ | ||
| "-y", | ||
| "[email protected]", | ||
| "--browser-url=http://127.0.0.1:${status.port}" | ||
| ] | ||
| } | ||
| } | ||
| }`; | ||
| void navigator.clipboard.writeText(config).then(() => { | ||
| Message.success(t('common.copySuccess')); | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| // Only show CDP settings in development mode | ||
| if (!isLoading && status?.isDevMode === false) { | ||
| return null; | ||
| } | ||
|
|
||
| if (isLoading) { | ||
| return null; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Restore the CDP Settings UI that was accidentally removed in #1095, and add a DevTools toggle button.
chrome-devtools-mcpto@0.16.0in docs and log output to avoid breakage from upstream changes (keep@latestingetDefaultMcpServersfor end-user WebUI)Closes #1375
Test plan
bun run test -- tests/unit/SystemModalContent.dom.test.tsx)