fix(ui): provider-aware context rail branding for GitLab

This commit is contained in:
2026-08-16 16:24:40 +00:00
parent 06a5dd63bb
commit 7bf07f0dd9
17 changed files with 87 additions and 16 deletions
@@ -24,7 +24,7 @@ import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
import { useBrowserFaviconStore } from '@/stores/useBrowserFaviconStore';
import { useGitProvider } from '@/lib/gitProvider';
import { useGitProvider, type GitProvider } from '@/lib/gitProvider';
import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore';
import { useUIStore, type ContextPanelMode, type PendingDiffScope } from '@/stores/useUIStore';
import { markSessionViewed } from '@/sync/notification-store';
@@ -110,7 +110,8 @@ const getRelativePathLabel = (filePath: string | null, directory: string): strin
const getModeLabel = (
mode: ContextPanelMode,
t: TranslateFn
t: TranslateFn,
gitProvider: GitProvider | null,
): string => {
if (mode === 'chat') return t('contextPanel.mode.chat');
if (mode === 'file') return t('contextPanel.mode.files');
@@ -119,7 +120,7 @@ const getModeLabel = (
if (mode === 'plan') return t('contextPanel.mode.plan');
if (mode === 'browser') return t('contextPanel.mode.browser');
if (mode === 'git') return t('layout.rightSidebar.git');
if (mode === 'pr') return t('contextPanel.mode.pr');
if (mode === 'pr') return gitProvider === 'gitlab' ? t('contextPanel.mode.mr') : t('contextPanel.mode.pr');
if (mode === 'notes') return t('contextRail.surface.notes');
if (mode === 'terminal') return t('layout.mainTab.terminal');
return t('contextPanel.mode.context');
@@ -146,7 +147,8 @@ const getFileNameFromPath = (path: string | null): string | null => {
const getTabLabel = (
tab: { mode: ContextPanelMode; label: string | null; targetPath: string | null; dedupeKey?: string; sessionTitleFallback?: string | null; stagedDiff?: boolean },
sessionTitleById: ReadonlyMap<string, string>,
t: TranslateFn
t: TranslateFn,
gitProvider: GitProvider | null,
): string => {
if (tab.mode === 'chat') {
const sessionID = getSessionIDFromDedupeKey(tab.dedupeKey);
@@ -185,7 +187,7 @@ const getTabLabel = (
return t('contextPanel.mode.diff');
}
return getModeLabel(tab.mode, t);
return getModeLabel(tab.mode, t, gitProvider);
};
const getTabIcon = (
@@ -922,7 +924,7 @@ export const ContextPanel: React.FC = () => {
);
const tabItems = React.useMemo(() => activeModeTabs.map((tab) => {
const rawLabel = getTabLabel(tab, sessionTitleById, t);
const rawLabel = getTabLabel(tab, sessionTitleById, t, gitProvider);
const label = truncateTabLabel(rawLabel, CONTEXT_TAB_LABEL_MAX_CHARS);
const tabPathLabel = getRelativePathLabel(tab.targetPath, effectiveDirectory);
return {
@@ -932,7 +934,7 @@ export const ContextPanel: React.FC = () => {
title: tabPathLabel ? `${rawLabel}: ${tabPathLabel}` : rawLabel,
closeLabel: t('contextPanel.tab.closeTabAria', { label }),
};
}), [activeModeTabs, effectiveDirectory, faviconByOrigin, sessionTitleById, t]);
}), [activeModeTabs, effectiveDirectory, faviconByOrigin, gitProvider, sessionTitleById, t]);
const activeNonChatContent = activeTab?.mode === 'context'
? <ContextPanelContent />
@@ -1006,7 +1008,7 @@ export const ContextPanel: React.FC = () => {
<div className="flex min-w-0 flex-1 items-center gap-1.5 px-3">
{activeTab ? getTabIcon(activeTab, faviconByOrigin) : null}
<span className="truncate typography-ui-label text-foreground">
{activeTab ? getModeLabel(activeTab.mode, t) : null}
{activeTab ? getModeLabel(activeTab.mode, t, gitProvider) : null}
</span>
</div>
)}
@@ -17,11 +17,13 @@ import {
import { CSS } from '@dnd-kit/utilities';
import { Icon } from '@/components/icon/Icon';
import type { IconName } from '@/components/icon/icons';
import { DiffViewIcon } from '@/components/icons/DiffIcon';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
import { useDeviceInfo } from '@/lib/device';
import { isVSCodeRuntime } from '@/lib/desktop';
import { useGitProvider } from '@/lib/gitProvider';
import { useI18n } from '@/lib/i18n';
import {
getVisibleContextRailSurfaces,
@@ -167,6 +169,9 @@ export const ContextPanelRail: React.FC = () => {
const planModeEnabled = useFeatureFlagsStore((state) => state.planModeEnabled);
const { screenWidth } = useDeviceInfo();
const gitStatus = useGitStatus(directoryKey || null);
// Provider-aware 'pr' branding: GitLab repositories get the MR descriptor
// (icon + labels) on the rail instead of the GitHub pull-request branding.
const gitProvider = useGitProvider(directoryKey);
const surfaceSwitchPrefix = React.useMemo(
() => getEffectiveShortcutPrefix('switch_context_surface', shortcutOverrides),
@@ -260,8 +265,9 @@ export const ContextPanelRail: React.FC = () => {
isVSCode: isVSCodeRuntime(),
screenWidth,
tabs,
gitProvider,
});
}, [contextRailOrder, planModeEnabled, screenWidth, tabs]);
}, [contextRailOrder, gitProvider, planModeEnabled, screenWidth, tabs]);
const handleDragEnd = React.useCallback((event: DragEndEvent) => {
const { active, over } = event;
@@ -291,7 +297,17 @@ export const ContextPanelRail: React.FC = () => {
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={surfaces.map((surface) => surface.id)} strategy={verticalListSortingStrategy}>
{surfaces.map((surface, index) => {
const label = t(surface.labelKey);
// The 'pr' surface renders the GitLab MR view in GitLab repos, so
// it borrows GitLab's merge-request branding instead of GitHub's.
const gitlabMrSurface: ContextSurfaceDescriptor = surface.id === 'pr' && gitProvider === 'gitlab'
? {
...surface,
icon: 'git-merge' as IconName,
labelKey: 'contextPanel.mode.mr',
descriptionKey: 'contextRail.surface.mr.description',
}
: surface;
const label = t(gitlabMrSurface.labelKey);
// Git shows a numeric badge instead of the old activity dot.
// Other surfaces never inherit git's changed-files signal.
// The work-status panel reports the same count in words a few
@@ -301,11 +317,11 @@ export const ContextPanelRail: React.FC = () => {
return (
<ContextPanelRailItem
key={surface.id}
surface={surface}
surface={gitlabMrSurface}
isActive={activeMode === surface.mode}
showActivityDot={false}
label={label}
description={t(surface.descriptionKey)}
description={t(gitlabMrSurface.descriptionKey)}
badgeCount={badgeCount}
badgeAriaLabel={badgeCount !== null
? t(