diff --git a/packages/ui/src/features/browser-tabs/AGENTS.md b/packages/ui/src/features/browser-tabs/AGENTS.md index 46e3681401..ebedf7f4e8 100644 --- a/packages/ui/src/features/browser-tabs/AGENTS.md +++ b/packages/ui/src/features/browser-tabs/AGENTS.md @@ -70,8 +70,12 @@ differ. Desktop ships first. ## UX ### The strip -- Lives in the Channels title bar, after a `#title-bar-left` section sized to the - Channels sidebar width so the strip starts flush with the content pane. +- The browser-style strip is no longer rendered. The title bar uses a centered, + Slack-style search trigger that opens the existing command menu for commands, + channels, and tasks. +- `BrowserTabStrip` remains mounted in reconciliation-only mode so persisted tab + state and restored routes continue to settle safely while tab-only controls + and keyboard shortcuts stay disabled. - Each tab is a quill `Button` (variant `default`). The active tab is elevated; inactive tabs are muted. Tabs **shrink to fit** — the strip never scrolls (`overflow-hidden`, pills `flex-1 basis-[200px]` capped at `max-w-[200px]`). diff --git a/packages/ui/src/features/browser-tabs/BrowserTabStrip.tsx b/packages/ui/src/features/browser-tabs/BrowserTabStrip.tsx index f803282b2e..841b5cf68f 100644 --- a/packages/ui/src/features/browser-tabs/BrowserTabStrip.tsx +++ b/packages/ui/src/features/browser-tabs/BrowserTabStrip.tsx @@ -152,7 +152,11 @@ function isAppView(value: string): value is AppView { return value in APP_VIEW_META; } -export function BrowserTabStrip() { +interface BrowserTabStripProps { + showTabs?: boolean; +} + +export function BrowserTabStrip({ showTabs = true }: BrowserTabStripProps) { const logger = useService(ROOT_LOGGER); const snapshot = useTabsSnapshot(); const navigate = useNavigate(); @@ -804,7 +808,7 @@ export function BrowserTabStrip() { { enableOnFormTags: true, enableOnContentEditable: true, - enabled: !isCloudRun, + enabled: showTabs && !isCloudRun, }, ); @@ -818,7 +822,11 @@ export function BrowserTabStrip() { if (taskHasCloseableEditorTab(params.taskId)) return; if (activeTabId) handleClose(activeTabId); }, - { enableOnFormTags: true, enableOnContentEditable: true }, + { + enableOnFormTags: true, + enableOnContentEditable: true, + enabled: showTabs, + }, ); // With channels on, Cmd/Ctrl+1-9 switches to the Nth browser tab (in the @@ -839,12 +847,12 @@ export function BrowserTabStrip() { enableOnFormTags: true, enableOnContentEditable: true, preventDefault: true, - enabled: channelsEnabled, + enabled: showTabs && channelsEnabled, }, [tabs, handleSelect], ); - return ( + return showTabs ? ( - ); + ) : null; } diff --git a/packages/ui/src/features/command/TitleBarSearch.test.tsx b/packages/ui/src/features/command/TitleBarSearch.test.tsx new file mode 100644 index 0000000000..5ce369fd28 --- /dev/null +++ b/packages/ui/src/features/command/TitleBarSearch.test.tsx @@ -0,0 +1,17 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { TitleBarSearch } from "./TitleBarSearch"; + +describe("TitleBarSearch", () => { + it("opens global search when activated", async () => { + const onClick = vi.fn(); + render(); + + await userEvent.click( + screen.getByRole("button", { name: "Search PostHog Code" }), + ); + + expect(onClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/ui/src/features/command/TitleBarSearch.tsx b/packages/ui/src/features/command/TitleBarSearch.tsx new file mode 100644 index 0000000000..6c9e193ec9 --- /dev/null +++ b/packages/ui/src/features/command/TitleBarSearch.tsx @@ -0,0 +1,29 @@ +import { MagnifyingGlass } from "@phosphor-icons/react"; +import { Button, Kbd } from "@posthog/quill"; +import { formatHotkeyParts, SHORTCUTS } from "./keyboard-shortcuts"; + +interface TitleBarSearchProps { + onClick: () => void; +} + +export function TitleBarSearch({ onClick }: TitleBarSearchProps) { + return ( +
+ +
+ ); +} diff --git a/packages/ui/src/router/routes/__root.tsx b/packages/ui/src/router/routes/__root.tsx index d5544ddc46..5c2a56bd60 100644 --- a/packages/ui/src/router/routes/__root.tsx +++ b/packages/ui/src/router/routes/__root.tsx @@ -34,6 +34,7 @@ import { useChannelDeepLink } from "@posthog/ui/features/canvas/hooks/useChannel import { CommandMenu } from "@posthog/ui/features/command/CommandMenu"; import { GlobalFilePicker } from "@posthog/ui/features/command/GlobalFilePicker"; import { KeyboardShortcutsSheet } from "@posthog/ui/features/command/KeyboardShortcutsSheet"; +import { TitleBarSearch } from "@posthog/ui/features/command/TitleBarSearch"; import { ConnectivityBanner } from "@posthog/ui/features/connectivity/ConnectivityBanner"; import { useNewTaskDeepLink } from "@posthog/ui/features/deep-links/useNewTaskDeepLink"; import { useOpenTargetDeepLink } from "@posthog/ui/features/deep-links/useOpenTargetDeepLink"; @@ -418,11 +419,11 @@ function RootLayout() { )} - {/* Tabs work in both spaces: channel tabs under /website and plain - task tabs in the Code experience. The strip's route→tab effect - noops on param-less routes (inbox, agents, new-task), so it's safe - to mount everywhere. */} - + setCommandMenuOpen(true)} /> + {/* Keep route/tab reconciliation mounted while the old browser-tab + chrome is hidden. This preserves restored routes and persisted + state without exposing tab-specific controls or shortcuts. */} + {/* Gated so an empty right-side group can't claim a no-drag rect in the title bar for nothing — every pixel without controls should drag the window. */}