From 7bf07f0dd98b0d9481890d5f11d44895f86afb0d Mon Sep 17 00:00:00 2001 From: bot-hermes Date: Thu, 13 Aug 2026 19:37:02 +0000 Subject: [PATCH] fix(ui): provider-aware context rail branding for GitLab --- .../ui/src/components/layout/ContextPanel.tsx | 18 +++++++------- .../components/layout/ContextPanelRail.tsx | 24 +++++++++++++++---- packages/ui/src/hooks/useKeyboardShortcuts.ts | 6 +++++ packages/ui/src/lib/i18n/messages/de.ts | 2 ++ packages/ui/src/lib/i18n/messages/en.ts | 2 ++ packages/ui/src/lib/i18n/messages/es.ts | 2 ++ packages/ui/src/lib/i18n/messages/fr.ts | 2 ++ packages/ui/src/lib/i18n/messages/ja.ts | 2 ++ packages/ui/src/lib/i18n/messages/ko.ts | 2 ++ packages/ui/src/lib/i18n/messages/pl.ts | 2 ++ packages/ui/src/lib/i18n/messages/pt-BR.ts | 2 ++ packages/ui/src/lib/i18n/messages/uk.ts | 2 ++ packages/ui/src/lib/i18n/messages/zh-CN.ts | 2 ++ packages/ui/src/lib/i18n/messages/zh-TW.ts | 2 ++ packages/ui/src/lib/surfaces/DOCUMENTATION.md | 9 +++---- packages/ui/src/lib/surfaces/registry.test.ts | 11 +++++++++ packages/ui/src/lib/surfaces/registry.ts | 13 ++++++++++ 17 files changed, 87 insertions(+), 16 deletions(-) diff --git a/packages/ui/src/components/layout/ContextPanel.tsx b/packages/ui/src/components/layout/ContextPanel.tsx index f5d2676f..40366e9a 100644 --- a/packages/ui/src/components/layout/ContextPanel.tsx +++ b/packages/ui/src/components/layout/ContextPanel.tsx @@ -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, - 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' ? @@ -1006,7 +1008,7 @@ export const ContextPanel: React.FC = () => {
{activeTab ? getTabIcon(activeTab, faviconByOrigin) : null} - {activeTab ? getModeLabel(activeTab.mode, t) : null} + {activeTab ? getModeLabel(activeTab.mode, t, gitProvider) : null}
)} diff --git a/packages/ui/src/components/layout/ContextPanelRail.tsx b/packages/ui/src/components/layout/ContextPanelRail.tsx index 6b3c147a..b0fae957 100644 --- a/packages/ui/src/components/layout/ContextPanelRail.tsx +++ b/packages/ui/src/components/layout/ContextPanelRail.tsx @@ -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 = () => { 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 ( { 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, diff --git a/packages/ui/src/lib/i18n/messages/de.ts b/packages/ui/src/lib/i18n/messages/de.ts index 69034345..e7fe8676 100644 --- a/packages/ui/src/lib/i18n/messages/de.ts +++ b/packages/ui/src/lib/i18n/messages/de.ts @@ -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', diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index e1e4b3aa..3ec23d5e 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -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', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index 64587627..aa543927 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -1114,6 +1114,7 @@ export const dict: Record = { "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 = { "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", diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts index 8c5bc358..d497c64f 100644 --- a/packages/ui/src/lib/i18n/messages/fr.ts +++ b/packages/ui/src/lib/i18n/messages/fr.ts @@ -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é', diff --git a/packages/ui/src/lib/i18n/messages/ja.ts b/packages/ui/src/lib/i18n/messages/ja.ts index 09d49f5a..4ebcab8a 100644 --- a/packages/ui/src/lib/i18n/messages/ja.ts +++ b/packages/ui/src/lib/i18n/messages/ja.ts @@ -1110,6 +1110,7 @@ export const dict: Record = { '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 = { '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': '内蔵ウェブブラウザ', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 30e9e6c8..2fe4715c 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -1114,6 +1114,7 @@ export const dict: Record = { '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 = { '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': '내장 웹 브라우저', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index 07723e4c..d5dd5023 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -1452,6 +1452,7 @@ export const dict: Record = { '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 = { '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', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index 1bf80567..2de2a277 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -1114,6 +1114,7 @@ export const dict: Record = { "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 = { "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", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index 2999a35a..f2a234e3 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -1114,6 +1114,7 @@ export const dict: Record = { "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 = { "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": "Вбудований браузер", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index 35f74dfe..74880a24 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -1114,6 +1114,7 @@ export const dict: Record = { '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 = { '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': '内置网页浏览器', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts index 5eb25b49..572556d7 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts @@ -1126,6 +1126,7 @@ export const dict: Record = { '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 = { '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': '內建網頁瀏覽器', diff --git a/packages/ui/src/lib/surfaces/DOCUMENTATION.md b/packages/ui/src/lib/surfaces/DOCUMENTATION.md index 9a584ce5..c290cb26 100644 --- a/packages/ui/src/lib/surfaces/DOCUMENTATION.md +++ b/packages/ui/src/lib/surfaces/DOCUMENTATION.md @@ -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 diff --git a/packages/ui/src/lib/surfaces/registry.test.ts b/packages/ui/src/lib/surfaces/registry.test.ts index 73f93acf..5d658299 100644 --- a/packages/ui/src/lib/surfaces/registry.test.ts +++ b/packages/ui/src/lib/surfaces/registry.test.ts @@ -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']); diff --git a/packages/ui/src/lib/surfaces/registry.ts b/packages/ui/src/lib/surfaces/registry.ts index 9224748c..ce2f4026 100644 --- a/packages/ui/src/lib/surfaces/registry.ts +++ b/packages/ui/src/lib/surfaces/registry.ts @@ -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)) {