From af324d41890fad111f167caeda75addd5494c487 Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:24:35 +0200 Subject: [PATCH] feat(dev-tools): rename devTelemetry() to devToolbar() and add devToolbarDefaultOn flag (#47494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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) ## 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. Co-authored-by: Claude Opus 4.8 --- packages/dev-tools/DevToolbar.test.tsx | 66 +++++++++++++++++++----- packages/dev-tools/DevToolbarContext.tsx | 14 ++--- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/packages/dev-tools/DevToolbar.test.tsx b/packages/dev-tools/DevToolbar.test.tsx index 7e17c155569..57af25eef60 100644 --- a/packages/dev-tools/DevToolbar.test.tsx +++ b/packages/dev-tools/DevToolbar.test.tsx @@ -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() }) }) diff --git a/packages/dev-tools/DevToolbarContext.tsx b/packages/dev-tools/DevToolbarContext.tsx index 0c46a1d0323..6ba2c7cf643 100644 --- a/packages/dev-tools/DevToolbarContext.tsx +++ b/packages/dev-tools/DevToolbarContext.tsx @@ -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([]) + const isDefaultOn = useFlag('devToolbarDefaultOn') + const sseRetryDelayRef = useRef(SSE_INITIAL_RETRY_MS) const sseRetryTimeoutRef = useRef(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) => {