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(
@@ -18,6 +18,7 @@ import {
normalizeCombo,
} from '@/lib/shortcuts';
import { getVisibleContextRailSurfaces } from '@/lib/surfaces/registry';
import { useGitProvider } from '@/lib/gitProvider';
import { readEmbeddedThemeSearchParams } from '@/contexts/theme-embedded-bootstrap';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
@@ -39,6 +40,9 @@ export const useKeyboardShortcuts = () => {
const toggleSidebar = useUIStore((s) => s.toggleSidebar);
const currentShortcutDirectory = useDirectoryStore((s) => s.currentDirectory);
const effectiveDirectory = useEffectiveDirectory();
// Mirrors the rail's provider-aware 'pr' surface: the digit-shortcut list
// must agree with the rail on whether the PR/MR surface is visible.
const gitProvider = useGitProvider(effectiveDirectory);
// The terminal lives in the context panel; these mirror the rail behavior.
const toggleTerminalSurface = React.useCallback(() => {
@@ -505,6 +509,7 @@ export const useKeyboardShortcuts = () => {
isVSCode: isVSCodeRuntime(),
screenWidth: window.innerWidth,
tabs: panelState?.tabs ?? [],
gitProvider,
});
const target = visibleSurfaces[switchSurfaceDigit - 1];
if (!target) {
@@ -707,6 +712,7 @@ export const useKeyboardShortcuts = () => {
currentSessionId,
currentDirectory,
effectiveDirectory,
gitProvider,
activeProject?.id,
activeProject?.path,
shortcutOverrides,
+2
View File
@@ -2979,6 +2979,7 @@ export const dict = {
'gitView.pr.segment.comments': 'Kommentare',
'gitView.pr.comments.addAll': 'Alle hinzufügen',
'contextPanel.mode.pr': 'PR',
'contextPanel.mode.mr': 'Merge-Request',
'contextRail.aria.rail': 'Kontextleiste',
'contextPanel.editorEmpty.title': 'Kein Kontext ausgewählt',
'contextPanel.editorEmpty.description': 'Wählen Sie etwas aus der Seitenleiste aus, um Kontext anzuzeigen.',
@@ -3093,6 +3094,7 @@ export const dict = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': 'Das kleine Modell unterstützt die strukturierten Antworten nicht, die ein Walkthrough benötigt.',
'contextRail.surface.plan.description': 'Plankontext',
'contextRail.surface.pr.description': 'PR-Kontext',
'contextRail.surface.mr.description': 'MR-Kontext',
'contextRail.surface.notes.description': 'Notizkontext',
'contextRail.surface.context.description': 'Allgemeiner Kontext',
'contextRail.surface.browser.description': 'Browserkontext',
+2
View File
@@ -1113,6 +1113,7 @@ export const dict = {
'contextPanel.mode.workingDiff': 'Working Diff',
'contextPanel.mode.plan': 'Plan',
'contextPanel.mode.pr': 'Pull Request',
'contextPanel.mode.mr': 'Merge request',
'contextPanel.mode.context': 'Context',
'contextPanel.mode.preview': 'Preview',
'contextPanel.mode.browser': 'Browser',
@@ -1230,6 +1231,7 @@ export const dict = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': 'The small model does not support the structured responses a walkthrough needs.',
'contextRail.surface.plan.description': 'View the current plan',
'contextRail.surface.pr.description': 'Create, review, and merge the pull request for the current branch',
'contextRail.surface.mr.description': 'Create, review, and merge the merge request for the current branch',
'contextRail.surface.notes.description': 'Notes, todos, and plans for the project',
'contextRail.surface.context.description': 'Session context and token usage',
'contextRail.surface.browser.description': 'Built-in web browser',
+2
View File
@@ -1114,6 +1114,7 @@ export const dict: Record<I18nKey, string> = {
"contextPanel.mode.workingDiff": "Working Diff",
"contextPanel.mode.plan": "Plan",
"contextPanel.mode.pr": "Pull Request",
"contextPanel.mode.mr": "Solicitud de fusión",
"contextPanel.mode.context": "Contexto",
"contextPanel.mode.preview": "Vista previa",
"contextPanel.mode.browser": "Navegador",
@@ -1231,6 +1232,7 @@ export const dict: Record<I18nKey, string> = {
"walkthrough.blocked.structuredOutput.descriptionUnknownModel": "El modelo pequeño no admite las respuestas estructuradas que necesita un recorrido.",
"contextRail.surface.plan.description": "Ver el plan actual",
"contextRail.surface.pr.description": "Crea, revisa y fusiona el pull request de la rama actual",
"contextRail.surface.mr.description": "Crea, revisa y fusiona la solicitud de fusión de la rama actual",
"contextRail.surface.notes.description": "Notas, tareas y planes del proyecto",
"contextRail.surface.context.description": "Contexto de la sesión y uso de tokens",
"contextRail.surface.browser.description": "Navegador web integrado",
+2
View File
@@ -933,6 +933,7 @@ export const dict = {
'contextPanel.mode.workingDiff': 'Différentiel de travail',
'contextPanel.mode.plan': 'Plan',
'contextPanel.mode.pr': 'Pull Request',
'contextPanel.mode.mr': 'Demande de fusion',
'contextPanel.mode.context': 'Contexte',
'contextPanel.mode.preview': 'Aperçu',
'contextPanel.mode.browser': 'Navigateur',
@@ -1050,6 +1051,7 @@ export const dict = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': 'Le petit modèle ne prend pas en charge les réponses structurées nécessaires à un parcours.',
'contextRail.surface.plan.description': 'Voir le plan actuel',
'contextRail.surface.pr.description': 'Créer, relire et fusionner la pull request de la branche actuelle',
'contextRail.surface.mr.description': 'Créer, relire et fusionner la demande de fusion de la branche actuelle',
'contextRail.surface.notes.description': 'Notes, tâches et plans du projet',
'contextRail.surface.context.description': 'Contexte de session et utilisation des tokens',
'contextRail.surface.browser.description': 'Navigateur web intégré',
+2
View File
@@ -1110,6 +1110,7 @@ export const dict: Record<I18nKey, string> = {
'contextPanel.mode.workingDiff': '作業中差分',
'contextPanel.mode.plan': '計画',
'contextPanel.mode.pr': 'プルリクエスト',
'contextPanel.mode.mr': 'マージリクエスト',
'contextPanel.mode.context': 'コンテキスト',
'contextPanel.mode.preview': 'プレビュー',
'contextPanel.mode.browser': 'ブラウザ',
@@ -1227,6 +1228,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': 'スモールモデルはウォークスルーに必要な構造化応答をサポートしていません。',
'contextRail.surface.plan.description': '現在のプランを表示',
'contextRail.surface.pr.description': '現在のブランチのプルリクエストを作成・確認・マージ',
'contextRail.surface.mr.description': '現在のブランチのマージリクエストを作成・確認・マージ',
'contextRail.surface.notes.description': 'プロジェクトのノート・ToDo・プラン',
'contextRail.surface.context.description': 'セッションのコンテキストとトークン使用量',
'contextRail.surface.browser.description': '内蔵ウェブブラウザ',
+2
View File
@@ -1114,6 +1114,7 @@ export const dict: Record<I18nKey, string> = {
'contextPanel.mode.workingDiff': 'Working Diff',
'contextPanel.mode.plan': '계획',
'contextPanel.mode.pr': '풀 리퀘스트',
'contextPanel.mode.mr': '병합 요청',
'contextPanel.mode.context': '컨텍스트',
'contextPanel.mode.preview': '미리보기',
'contextPanel.mode.browser': '브라우저',
@@ -1231,6 +1232,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': '스몰 모델은 워크스루에 필요한 구조화된 응답을 지원하지 않습니다.',
'contextRail.surface.plan.description': '현재 계획 보기',
'contextRail.surface.pr.description': '현재 브랜치의 풀 리퀘스트를 생성, 검토, 병합',
'contextRail.surface.mr.description': '현재 브랜치의 병합 요청을 생성, 검토, 병합',
'contextRail.surface.notes.description': '프로젝트의 노트, 할 일, 계획',
'contextRail.surface.context.description': '세션 컨텍스트 및 토큰 사용량',
'contextRail.surface.browser.description': '내장 웹 브라우저',
+2
View File
@@ -1452,6 +1452,7 @@ export const dict: Record<I18nKey, string> = {
'contextPanel.mode.files': 'Pliki',
'contextPanel.mode.plan': 'Plan',
'contextPanel.mode.pr': 'Pull Request',
'contextPanel.mode.mr': 'Żądanie scalenia',
'contextPanel.mode.preview': 'Podgląd',
'contextPanel.mode.browser': 'Przeglądarka',
'contextRail.aria.rail': 'Powierzchnie panelu',
@@ -1568,6 +1569,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': 'Mały model nie obsługuje ustrukturyzowanych odpowiedzi wymaganych przez przewodnik.',
'contextRail.surface.plan.description': 'Zobacz bieżący plan',
'contextRail.surface.pr.description': 'Twórz, przeglądaj i scalaj pull request bieżącej gałęzi',
'contextRail.surface.mr.description': 'Twórz, przeglądaj i scalaj żądanie scalenia bieżącej gałęzi',
'contextRail.surface.notes.description': 'Notatki, zadania i plany projektu',
'contextRail.surface.context.description': 'Kontekst sesji i zużycie tokenów',
'contextRail.surface.browser.description': 'Wbudowana przeglądarka',
@@ -1114,6 +1114,7 @@ export const dict: Record<I18nKey, string> = {
"contextPanel.mode.workingDiff": "Working Diff",
"contextPanel.mode.plan": "Plano",
"contextPanel.mode.pr": "Pull Request",
"contextPanel.mode.mr": "Solicitação de merge",
"contextPanel.mode.context": "Contexto",
"contextPanel.mode.preview": "Prévia",
"contextPanel.mode.browser": "Navegador",
@@ -1231,6 +1232,7 @@ export const dict: Record<I18nKey, string> = {
"walkthrough.blocked.structuredOutput.descriptionUnknownModel": "O modelo pequeno não suporta as respostas estruturadas que um percurso exige.",
"contextRail.surface.plan.description": "Ver o plano atual",
"contextRail.surface.pr.description": "Crie, revise e faça merge do pull request do branch atual",
"contextRail.surface.mr.description": "Crie, revise e faça merge da solicitação de merge do branch atual",
"contextRail.surface.notes.description": "Notas, tarefas e planos do projeto",
"contextRail.surface.context.description": "Contexto da sessão e uso de tokens",
"contextRail.surface.browser.description": "Navegador web integrado",
+2
View File
@@ -1114,6 +1114,7 @@ export const dict: Record<I18nKey, string> = {
"contextPanel.mode.workingDiff": "Working Diff",
"contextPanel.mode.plan": "План",
"contextPanel.mode.pr": "Pull Request",
"contextPanel.mode.mr": "Запит на злиття",
"contextPanel.mode.context": "Контекст",
"contextPanel.mode.preview": "Перегляд",
"contextPanel.mode.browser": "Браузер",
@@ -1231,6 +1232,7 @@ export const dict: Record<I18nKey, string> = {
"walkthrough.blocked.structuredOutput.descriptionUnknownModel": "Small model не підтримує структуровані відповіді, потрібні для розбору.",
"contextRail.surface.plan.description": "Перегляд поточного плану",
"contextRail.surface.pr.description": "Створюйте, переглядайте та зливайте pull request поточної гілки",
"contextRail.surface.mr.description": "Створюйте, переглядайте та зливайте запит на злиття поточної гілки",
"contextRail.surface.notes.description": "Нотатки, задачі та плани проєкту",
"contextRail.surface.context.description": "Контекст сесії та використання токенів",
"contextRail.surface.browser.description": "Вбудований браузер",
@@ -1114,6 +1114,7 @@ export const dict: Record<I18nKey, string> = {
'contextPanel.mode.workingDiff': 'Working Diff',
'contextPanel.mode.plan': '计划',
'contextPanel.mode.pr': '拉取请求',
'contextPanel.mode.mr': '合并请求',
'contextPanel.mode.context': '上下文',
'contextPanel.mode.preview': '预览',
'contextPanel.mode.browser': '浏览器',
@@ -1231,6 +1232,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': '小模型不支持导读所需的结构化响应。',
'contextRail.surface.plan.description': '查看当前计划',
'contextRail.surface.pr.description': '创建、审查并合并当前分支的拉取请求',
'contextRail.surface.mr.description': '创建、审查并合并当前分支的合并请求',
'contextRail.surface.notes.description': '项目的笔记、待办和计划',
'contextRail.surface.context.description': '会话上下文与令牌用量',
'contextRail.surface.browser.description': '内置网页浏览器',
@@ -1126,6 +1126,7 @@ export const dict: Record<I18nKey, string> = {
'contextPanel.mode.workingDiff': 'Working Diff',
'contextPanel.mode.plan': '計畫',
'contextPanel.mode.pr': '提取請求',
'contextPanel.mode.mr': '合併請求',
'contextPanel.mode.context': '上下文',
'contextPanel.mode.preview': '預覽',
'contextPanel.mode.browser': '瀏覽器',
@@ -1243,6 +1244,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': '小模型不支援導讀所需的結構化回應。',
'contextRail.surface.plan.description': '檢視目前計畫',
'contextRail.surface.pr.description': '建立、審查並合併目前分支的提取請求',
'contextRail.surface.mr.description': '建立、審查並合併目前分支的合併請求',
'contextRail.surface.notes.description': '專案的筆記、待辦與計畫',
'contextRail.surface.context.description': '工作階段情境與權杖用量',
'contextRail.surface.browser.description': '內建網頁瀏覽器',
@@ -23,10 +23,11 @@ edge (`components/layout/ContextPanelRail.tsx`) and rendered by
- `getVisibleContextRailSurfaces` is the single visibility filter shared by the
rail and the global surface-switch shortcut (`switch_context_surface` in
`lib/shortcuts.ts`): it drops the plan surface unless plan mode is enabled,
drops the walkthrough on VS Code and below `WALKTHROUGH_MIN_WIDTH`, and hides
`has-content` surfaces until a tab of their mode exists. Both consumers use
it so the digit shown on a rail badge always maps to the same surface the
shortcut opens.
drops the walkthrough on VS Code and below `WALKTHROUGH_MIN_WIDTH`, hides
`has-content` surfaces until a tab of their mode exists, and drops the `pr`
surface when the repository is on a git provider other than GitHub or GitLab
(`gitProvider: 'other'`). Both consumers use it so the digit shown on a rail
badge always maps to the same surface the shortcut opens.
## Adding a surface
@@ -59,6 +59,17 @@ describe('getVisibleContextRailSurfaces', () => {
expect(getVisibleContextRailSurfaces(baseOptions).some((s) => s.id === 'browser')).toBe(true);
});
test('hides the pr surface for other git providers but keeps it for github/gitlab', () => {
expect(getVisibleContextRailSurfaces({ ...baseOptions, gitProvider: 'other' }).some((s) => s.id === 'pr')).toBe(false);
expect(getVisibleContextRailSurfaces({ ...baseOptions, gitProvider: 'gitlab' }).some((s) => s.id === 'pr')).toBe(true);
expect(getVisibleContextRailSurfaces({ ...baseOptions, gitProvider: 'github' }).some((s) => s.id === 'pr')).toBe(true);
});
test('keeps the pr surface while the git provider is unknown', () => {
expect(getVisibleContextRailSurfaces({ ...baseOptions, gitProvider: null }).some((s) => s.id === 'pr')).toBe(true);
expect(getVisibleContextRailSurfaces(baseOptions).some((s) => s.id === 'pr')).toBe(true);
});
test('respects the persisted user rail order', () => {
const surfaces = getVisibleContextRailSurfaces({ ...baseOptions, railOrder: ['git', 'context'] });
expect(surfaces.slice(0, 2).map((surface) => surface.id)).toEqual(['git', 'context']);
+13
View File
@@ -188,6 +188,12 @@ type VisibleRailSurfacesOptions = {
isVSCode: boolean;
screenWidth: number;
tabs: readonly { mode: ContextPanelMode }[];
/**
* The repository's git provider. The 'pr' surface renders the GitHub pull
* request / GitLab merge request view; it is hidden for repositories on
* any other provider. null (unknown, still resolving) keeps it visible.
*/
gitProvider?: 'github' | 'gitlab' | 'other' | null;
};
/**
@@ -203,6 +209,13 @@ export const getVisibleContextRailSurfaces = (options: VisibleRailSurfacesOption
if (surface.id === 'plan' && !options.planModeEnabled) {
return false;
}
// The 'pr' surface hosts the GitHub PR / GitLab MR view. Neither applies
// to repositories on other providers, so the rail button (and its shortcut
// digit) must not appear. Unknown (null) keeps it while the provider is
// still resolving — it is not a reason to hide the surface.
if (surface.id === 'pr' && options.gitProvider === 'other') {
return false;
}
// The walkthrough needs room for a stop list beside real code, and its
// diffs come from OpenChamber's Git routes, which VS Code does not serve.
if (surface.id === 'walkthrough' && (options.isVSCode || options.screenWidth < WALKTHROUGH_MIN_WIDTH)) {