Skip to content

Commit fe1bc48

Browse files
feat(web): show which profile a browser tab is running in
A tab's profile was only discoverable by opening the three-dot menu, which is a poor place for something that changes what you are logged into. The chrome row gains a leading slot before the URL bar, and the preview names the tab's profile there. Only when it differs from the default: labelling every tab "Default" would be noise on the common case, while a tab running in another profile is exactly what needs calling out. Also gives the three-dot menu's profile heading a `MenuGroup` ancestor — `MenuGroupLabel` reads Base UI's group context and throws without one, which took the app to its error boundary as soon as the menu opened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ab36f78 commit fe1bc48

3 files changed

Lines changed: 45 additions & 20 deletions

File tree

apps/web/src/components/preview/PreviewChromeRow.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ interface Props {
5858
* to mount the three-dot menu (hard reload, devtools, zoom, clear data).
5959
*/
6060
trailingActions?: ReactNode;
61+
/**
62+
* Slot between the nav buttons and the URL input. The preview view uses it
63+
* to name the tab's browser profile, which is otherwise invisible.
64+
*/
65+
leadingActions?: ReactNode;
6166
}
6267

6368
const NOOP = () => {};
@@ -87,6 +92,7 @@ export function PreviewChromeRow({
8792
pickDisabled,
8893
pickDisabledReason,
8994
trailingActions,
95+
leadingActions,
9096
}: Props) {
9197
const inputRef = useRef<HTMLInputElement | null>(null);
9298
const [draft, setDraft] = useState(url);
@@ -168,6 +174,8 @@ export function PreviewChromeRow({
168174
</Tooltip>
169175
</div>
170176

177+
{leadingActions}
178+
171179
<InputGroup variant="ghost" className="group/address h-7 flex-1">
172180
<Tooltip>
173181
<TooltipTrigger

apps/web/src/components/preview/PreviewMoreMenu.tsx

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
MenuPopup,
1111
MenuRadioGroup,
1212
MenuRadioItem,
13+
MenuGroup,
1314
MenuGroupLabel,
1415
MenuSeparator,
1516
MenuSub,
@@ -187,21 +188,27 @@ export function PreviewMoreMenu({
187188
</span>
188189
</MenuItem>
189190
<MenuSeparator />
190-
{profileName ? (
191-
// Otherwise the tab's profile is invisible: it is fixed at open, and
192-
// nothing else in the chrome says which one you are browsing in.
193-
<MenuGroupLabel>Profile: {profileName}</MenuGroupLabel>
194-
) : null}
195-
<MenuItem
196-
onClick={() => void bridge.clearCookies(environmentId, profileId).catch(() => undefined)}
197-
>
198-
{profileName ? `Clear cookies (${profileName})` : "Clear cookies"}
199-
</MenuItem>
200-
<MenuItem
201-
onClick={() => void bridge.clearCache(environmentId, profileId).catch(() => undefined)}
202-
>
203-
{profileName ? `Clear cache (${profileName})` : "Clear cache"}
204-
</MenuItem>
191+
{/*
192+
Grouped so the heading has a `MenuGroup` ancestor — `MenuGroupLabel`
193+
reads its context and throws without one. The heading also answers
194+
which profile the tab is in, which is otherwise invisible: it is fixed
195+
at open and nothing else in the chrome shows it.
196+
*/}
197+
<MenuGroup>
198+
{profileName ? <MenuGroupLabel>Profile: {profileName}</MenuGroupLabel> : null}
199+
<MenuItem
200+
onClick={() =>
201+
void bridge.clearCookies(environmentId, profileId).catch(() => undefined)
202+
}
203+
>
204+
{profileName ? `Clear cookies (${profileName})` : "Clear cookies"}
205+
</MenuItem>
206+
<MenuItem
207+
onClick={() => void bridge.clearCache(environmentId, profileId).catch(() => undefined)}
208+
>
209+
{profileName ? `Clear cache (${profileName})` : "Clear cache"}
210+
</MenuItem>
211+
</MenuGroup>
205212
</MenuPopup>
206213
</Menu>
207214
);

apps/web/src/components/preview/PreviewView.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { previewRuntimeTabId } from "~/browser/previewRuntimeTabId";
4848
import { PreviewUnreachable } from "./PreviewUnreachable";
4949
import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import { shouldShowPreviewEmptyState } from "./previewEmptyStateLogic";
51+
import { Badge } from "~/components/ui/badge";
5152
import { BrowserSurfaceSlot } from "~/browser/BrowserSurfaceSlot";
5253
import { useBrowserSurfaceStore } from "~/browser/browserSurfaceStore";
5354
import { useLoadingProgress } from "./useLoadingProgress";
@@ -145,6 +146,9 @@ export function PreviewView({
145146
const loadProgress = useLoadingProgress(loading);
146147
const viewport = snapshot?.viewport ?? FILL_PREVIEW_VIEWPORT;
147148
const browserDefaults = useBrowserDefaults();
149+
const activeProfile = browserDefaults.profiles.find(
150+
(profile) => profile.id === (snapshot?.profileId ?? browserDefaults.profileId),
151+
);
148152
const panelRect = useBrowserSurfaceStore((state) =>
149153
runtimeTabId ? (state.byTabId[runtimeTabId]?.rect ?? null) : null,
150154
);
@@ -686,16 +690,22 @@ export function PreviewView({
686690
pickDisabledReason={
687691
isUnreachable ? "Page didn't load — pick unavailable until the page renders" : undefined
688692
}
693+
leadingActions={
694+
// Only when it differs from the default: labelling every tab
695+
// "Default" would be noise on the common case, while a tab in
696+
// another profile is exactly what needs calling out.
697+
activeProfile && activeProfile.id !== browserDefaults.profileId ? (
698+
<Badge variant="outline" className="shrink-0">
699+
{activeProfile.name}
700+
</Badge>
701+
) : null
702+
}
689703
trailingActions={
690704
previewBridge ? (
691705
<PreviewMoreMenu
692706
environmentId={threadRef.environmentId}
693707
profileId={snapshot?.profileId}
694-
profileName={
695-
browserDefaults.profiles.find(
696-
(profile) => profile.id === (snapshot?.profileId ?? browserDefaults.profileId),
697-
)?.name
698-
}
708+
profileName={activeProfile?.name}
699709
tabId={runtimeTabId}
700710
hasWebContents={desktopOverlay?.hasWebContents ?? false}
701711
zoomFactor={desktopOverlay?.zoomFactor ?? 1}

0 commit comments

Comments
 (0)