feat(dev-tools): rename devTelemetry() to devToolbar() and add devToolbarDefaultOn flag (#47494)

## What

- Renames the `window.devTelemetry()` helper to `window.devToolbar()`.
- Adds a `devToolbarDefaultOn` ConfigCat flag. When enabled, the dev
toolbar shows automatically without the user having to run
`window.devToolbar()` in the console.
- The toolbar remains gated to `local` and `staging` environments only —
the flag has no effect in production.

## Notes

- The `localStorage` key (`dev-telemetry-toolbar-enabled`) is
intentionally left unchanged so anyone who already enabled the toolbar
keeps their setting.
- Updated unit tests: renamed all references and added coverage for the
`devToolbarDefaultOn` flag across local, staging, and prod.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a new default-on option for the dev toolbar in local or staging
environments when the feature flag is enabled.
* The toolbar can now be opened from a new global trigger when
available.

* **Bug Fixes**
* Improved toolbar enablement behavior across environments, including
production, to avoid showing the trigger when it shouldn’t appear.
* Updated cleanup behavior so the toolbar trigger is removed correctly
after unmounting.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jordi EnricandClaude Opus 4.8 authored and GitHub committed 2026-07-01 12:24:35 +02:00
1 parent 9f1ce56322
commit af324d4189
2 files changed
+60 -20

No files matched your search

+52 -14
View File
@@ -4,10 +4,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
declare global {
interface Window {
devTelemetry?: () => void
devToolbar?: () => void
}
}
const flags = vi.hoisted(() => ({ devToolbarDefaultOn: false }))
// Mock common package
vi.mock('common', async () => {
return {
@@ -16,6 +18,7 @@ vi.mock('common', async () => {
posthog: {},
configcat: {},
}),
useFlag: (name: string) => (name === 'devToolbarDefaultOn' ? flags.devToolbarDefaultOn : false),
posthogClient: {
subscribeToEvents: vi.fn(() => () => {}),
},
@@ -27,7 +30,7 @@ const originalEnv = process.env.NEXT_PUBLIC_ENVIRONMENT
/**
* Helper to render the full component tree as used in production.
* The Provider sets up window.devTelemetry and manages state.
* The Provider sets up window.devToolbar and manages state.
* The Trigger shows the activity icon in the header.
* The Toolbar is the actual panel/sheet.
*/
@@ -73,7 +76,8 @@ describe('DevToolbar', () => {
})
localStorage.clear()
delete window.devTelemetry
flags.devToolbarDefaultOn = false
delete window.devToolbar
vi.spyOn(console, 'warn').mockImplementation(() => {})
vi.spyOn(console, 'error').mockImplementation(() => {})
})
@@ -101,11 +105,11 @@ describe('DevToolbar', () => {
expect(container.querySelector('button')).toBeNull()
})
it('does not register window.devTelemetry', async () => {
it('does not register window.devToolbar', async () => {
vi.resetModules()
await renderFullToolbar()
expect(window.devTelemetry).toBeUndefined()
expect(window.devToolbar).toBeUndefined()
})
})
@@ -122,12 +126,12 @@ describe('DevToolbar', () => {
expect(container.querySelector('button')).toBeNull()
})
it('registers window.devTelemetry function', async () => {
it('registers window.devToolbar function', async () => {
vi.resetModules()
await renderFullToolbar()
expect(window.devTelemetry).toBeDefined()
expect(typeof window.devTelemetry).toBe('function')
expect(window.devToolbar).toBeDefined()
expect(typeof window.devToolbar).toBe('function')
})
})
@@ -218,7 +222,41 @@ describe('DevToolbar', () => {
})
})
describe('window.devTelemetry function', () => {
describe('when devToolbarDefaultOn flag is enabled', () => {
it('renders the trigger without calling window.devToolbar in local', async () => {
process.env.NEXT_PUBLIC_ENVIRONMENT = 'local'
flags.devToolbarDefaultOn = true
vi.resetModules()
await renderFullToolbar()
const triggerButton = screen.getByRole('button')
expect(triggerButton).toBeInTheDocument()
})
it('renders the trigger without calling window.devToolbar in staging', async () => {
process.env.NEXT_PUBLIC_ENVIRONMENT = 'staging'
flags.devToolbarDefaultOn = true
vi.resetModules()
await renderFullToolbar()
const triggerButton = screen.getByRole('button')
expect(triggerButton).toBeInTheDocument()
})
it('does not render the trigger in production even when flag is on', async () => {
process.env.NEXT_PUBLIC_ENVIRONMENT = 'prod'
flags.devToolbarDefaultOn = true
vi.resetModules()
const { container } = await renderFullToolbar()
expect(container.querySelector('button')).toBeNull()
})
})
describe('window.devToolbar function', () => {
beforeEach(() => {
process.env.NEXT_PUBLIC_ENVIRONMENT = 'local'
})
@@ -230,9 +268,9 @@ describe('DevToolbar', () => {
// Trigger should not be visible initially
expect(screen.queryByRole('button')).not.toBeInTheDocument()
// Call devTelemetry to enable
// Call devToolbar to enable
act(() => {
window.devTelemetry?.()
window.devToolbar?.()
})
// Re-import and rerender to pick up state change
@@ -256,17 +294,17 @@ describe('DevToolbar', () => {
})
describe('cleanup', () => {
it('removes window.devTelemetry on unmount', async () => {
it('removes window.devToolbar on unmount', async () => {
process.env.NEXT_PUBLIC_ENVIRONMENT = 'local'
vi.resetModules()
const result = await renderFullToolbar()
expect(window.devTelemetry).toBeDefined()
expect(window.devToolbar).toBeDefined()
result.unmount()
expect(window.devTelemetry).toBeUndefined()
expect(window.devToolbar).toBeUndefined()
})
})
+8 -6
View File
@@ -1,6 +1,6 @@
'use client'
import { ensurePlatformSuffix, posthogClient, type ClientTelemetryEvent } from 'common'
import { ensurePlatformSuffix, posthogClient, useFlag, type ClientTelemetryEvent } from 'common'
import {
createContext,
useCallback,
@@ -32,7 +32,7 @@ const SSE_BACKOFF_MULTIPLIER = 2
declare global {
interface Window {
devTelemetry?: () => void
devToolbar?: () => void
}
}
@@ -48,6 +48,8 @@ export function DevToolbarProvider({ children, apiUrl }: DevToolbarProviderProps
const [isOpen, setIsOpen] = useState(false)
const [events, setEvents] = useState<DevTelemetryEvent[]>([])
const isDefaultOn = useFlag('devToolbarDefaultOn')
const sseRetryDelayRef = useRef(SSE_INITIAL_RETRY_MS)
const sseRetryTimeoutRef = useRef<NodeJS.Timeout | null>(null)
@@ -66,11 +68,11 @@ export function DevToolbarProvider({ children, apiUrl }: DevToolbarProviderProps
try {
stored = localStorage.getItem(STORAGE_KEY)
} catch {}
if (stored === 'true') {
if (stored === 'true' || isDefaultOn) {
setIsEnabled(true)
}
window.devTelemetry = () => {
window.devToolbar = () => {
try {
localStorage.setItem(STORAGE_KEY, 'true')
} catch {}
@@ -78,9 +80,9 @@ export function DevToolbarProvider({ children, apiUrl }: DevToolbarProviderProps
}
return () => {
delete window.devTelemetry
delete window.devToolbar
}
}, [])
}, [isDefaultOn])
const appendEvent = useCallback((event: DevTelemetryEvent) => {
setEvents((prev) => {