feat(ui): show GitLab merge request status in walkthrough, git view and work status

This commit is contained in:
2026-08-16 16:23:38 +00:00
parent 2c4ba78ae5
commit 89db325168
17 changed files with 373 additions and 6 deletions
@@ -4,6 +4,8 @@ import { useGitStore } from '@/stores/useGitStore';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { runBackgroundNetworkTask } from '@/lib/background-network';
import { getGitHubPrStatusKey, usePrVisualSummary } from '@/stores/useGitHubPrStatusStore';
import { useGitLabMrForBranch } from '@/lib/gitlabMrStatus';
import { useGitProvider } from '@/lib/gitProvider';
import { useSession, useSessionMessages } from '@/sync/sync-context';
import { useConfigStore } from '@/stores/useConfigStore';
import { useUIStore } from '@/stores/useUIStore';
@@ -113,6 +115,12 @@ export const WorkStatusPrimaryGroup: React.FC<Props> = ({ sessionId, directory,
);
const prSummary = usePrVisualSummary(prKey);
// GitLab merge requests ride the same shared TTL cache as the git view and
// the walkthrough, so every surface that reports the branch's request stays
// consistent without extra requests.
const gitProvider = useGitProvider(directory);
const { mr: gitLabMr } = useGitLabMrForBranch(directory, branch);
// `getCurrentModel` is an imperative getter: its reference never changes, so
// calling it in render subscribes to nothing. Subscribe to the selected model
// ids and recompute the limits from those.
@@ -201,7 +209,17 @@ export const WorkStatusPrimaryGroup: React.FC<Props> = ({ sessionId, directory,
const cost = typeof session?.cost === 'number' && session.cost > 0 ? session.cost : null;
const hasSession = showSession && (usagePercent !== null || cost !== null || Boolean(goalRow));
const hasRepository = showRepository && Boolean(branch || changed || prSummary || attentionLabel);
const hasGitLabMr = gitProvider === 'gitlab' && gitLabMr !== null;
const gitLabMrVisualState = gitLabMr
? gitLabMr.state === 'merged'
? 'merged'
: gitLabMr.state === 'closed'
? 'closed'
: gitLabMr.draft
? 'draft'
: 'open'
: null;
const hasRepository = showRepository && Boolean(branch || changed || prSummary || attentionLabel || hasGitLabMr);
useReportWorkStatusPresence('session-repository', hasSession || hasRepository);
@@ -284,6 +302,24 @@ export const WorkStatusPrimaryGroup: React.FC<Props> = ({ sessionId, directory,
/>
) : null}
{hasGitLabMr && gitLabMr ? (
<WorkStatusRow
icon="git-merge"
onClick={directory ? () => openSurface('pr') : undefined}
ariaLabel={t('chat.workStatus.action.openMr')}
iconColor={`var(--pr-${gitLabMrVisualState})`}
label={gitLabMr.title || t('chat.workStatus.mr.untitled')}
value={(
<WorkStatusPill
color={`var(--pr-${gitLabMrVisualState})`}
background={`color-mix(in srgb, var(--pr-${gitLabMrVisualState}) 18%, transparent)`}
>
{gitLabMr.draft ? t('chat.workStatus.pr.draft') : `!${gitLabMr.number}`}
</WorkStatusPill>
)}
/>
) : null}
{prSummary ? (
<>
<WorkStatusRow
@@ -59,6 +59,7 @@ import { InProgressOperationBanner } from './git/InProgressOperationBanner';
import { BranchIntegrationSection, type OperationLogEntry } from './git/BranchIntegrationSection';
import { deriveBaseBranch } from './git/baseBranch';
import { getFreshestPrStatusForBranch, useGitHubPrStatusStore } from '@/stores/useGitHubPrStatusStore';
import { useGitLabMrForBranch } from '@/lib/gitlabMrStatus';
import { createGitIndexMutationQueue, type GitIndexMutationDirection, type GitIndexMutationQueue } from './git/gitIndexMutationQueue';
import type { GitRemote } from '@/lib/gitApi';
import { getRootBranch } from '@/lib/worktrees/worktreeStatus';
@@ -304,6 +305,7 @@ export const GitView: React.FC<GitViewProps> = ({ isActive }) => {
const openContextSurface = useUIStore((state) => state.openContextSurface);
const prStatusBranch = status?.current ?? null;
const { mr: gitLabMr } = useGitLabMrForBranch(currentDirectory, prStatusBranch);
const prChipStatus = useGitHubPrStatusStore((state) => {
if (!currentDirectory || !prStatusBranch) {
return null;
@@ -2361,6 +2363,10 @@ export const GitView: React.FC<GitViewProps> = ({ isActive }) => {
onOpenPullRequest={
currentDirectory ? () => openContextSurface(currentDirectory, 'pr') : undefined
}
gitLabMr={gitLabMr}
onOpenGitLabMr={
currentDirectory ? () => openContextSurface(currentDirectory, 'pr') : undefined
}
/>
{/* In-progress operation banner */}
@@ -19,6 +19,7 @@ import type {
GitRemoteComparison,
GitHubPullRequest,
GitHubChecksSummary,
GitLabMergeRequestSummary,
} from '@/lib/api/types';
import { useI18n } from '@/lib/i18n';
@@ -51,6 +52,8 @@ interface GitHeaderProps {
pullRequest?: GitHubPullRequest | null;
prChecks?: GitHubChecksSummary | null;
onOpenPullRequest?: () => void;
gitLabMr?: GitLabMergeRequestSummary | null;
onOpenGitLabMr?: () => void;
}
const IDENTITY_ICON_MAP: Record<string, IconName> = {
@@ -258,6 +261,8 @@ export const GitHeader: React.FC<GitHeaderProps> = ({
pullRequest,
prChecks,
onOpenPullRequest,
gitLabMr,
onOpenGitLabMr,
}) => {
const { t } = useI18n();
if (!status) {
@@ -371,6 +376,40 @@ export const GitHeader: React.FC<GitHeaderProps> = ({
</Tooltip>
) : null;
// GitLab merge request chip, mirroring the GitHub PR chip above. GitLab
// states are surfaced with the same PR state palette so merged/closed/open
// read identically across providers.
const gitLabMrVisualState = gitLabMr
? gitLabMr.state === 'merged'
? 'merged'
: gitLabMr.state === 'closed'
? 'closed'
: gitLabMr.draft
? 'draft'
: 'open'
: null;
const gitLabMrChip = gitLabMr && onOpenGitLabMr ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
onClick={onOpenGitLabMr}
className="h-8 gap-1.5 px-2 typography-micro"
>
<Icon
name="git-merge"
className="size-3.5"
style={{ color: `var(--pr-${gitLabMrVisualState})` }}
/>
<span className="tabular-nums text-foreground/80">!{gitLabMr.number}</span>
</Button>
</TooltipTrigger>
<TooltipContent sideOffset={8}>{t('gitView.header.openMergeRequest')}</TooltipContent>
</Tooltip>
) : null;
const syncButtons = (
<SyncActions
syncAction={syncAction}
@@ -435,6 +474,7 @@ export const GitHeader: React.FC<GitHeaderProps> = ({
<div className="mt-3 flex h-8 min-w-0 items-center gap-2">
{prChip ? <div className="shrink-0">{prChip}</div> : null}
{gitLabMrChip ? <div className="shrink-0">{gitLabMrChip}</div> : null}
<div className="min-w-0 flex-1" />
{upstreamStatusPill ? (
<div className="min-w-0 shrink">{upstreamStatusPill}</div>
@@ -14,6 +14,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { useI18n, type Locale } from '@/lib/i18n';
import { openExternalUrl } from '@/lib/url';
import { useGitProvider } from '@/lib/gitProvider';
import { useGitLabMrForBranch } from '@/lib/gitlabMrStatus';
import { buildWalkthroughView } from '@/lib/walkthrough/model';
import type { WalkthroughSource, WalkthroughWorkingTreeScope } from '@/lib/walkthrough/types';
import { ModelSelector } from '@/components/sections/agents/ModelSelector';
@@ -210,6 +211,7 @@ export const WalkthroughView = ({ directory }: WalkthroughViewProps) => {
const setPrStatusParams = useGitHubPrStatusStore((state) => state.setParams);
const refreshPrStatusTargets = useGitHubPrStatusStore((state) => state.refreshTargets);
const gitProvider = useGitProvider(directory);
const gitLabMr = useGitLabMrForBranch(directory, currentBranch);
useEffect(() => {
if (!directory || !currentBranch || !githubAuthChecked || !githubConnected || gitProvider !== 'github') return;
@@ -250,12 +252,18 @@ export const WalkthroughView = ({ directory }: WalkthroughViewProps) => {
[requestedSource, scope]
);
// Offer whichever pull request we know about: the one already selected, or
// the one this branch has.
// Offer whichever pull request or merge request we know about: the one
// already selected, or the one this branch has. GitLab repos get their MR
// number from the branch lookup; everything else falls back to the GitHub PR
// status store, which the polling effect above only fills for GitHub repos.
const prSource = useMemo<Extract<WalkthroughSource, { kind: 'pr' }> | null>(() => {
if (source.kind === 'pr') return source;
if (gitProvider === 'gitlab') {
const number = gitLabMr.mr?.number;
return number ? { kind: 'pr', number } : null;
}
return branchPrNumber ? { kind: 'pr', number: branchPrNumber } : null;
}, [branchPrNumber, source]);
}, [branchPrNumber, gitLabMr.mr, gitProvider, source]);
const selectWorkingTree = useCallback(
(value: WalkthroughWorkingTreeScope) => {
@@ -341,7 +349,9 @@ export const WalkthroughView = ({ directory }: WalkthroughViewProps) => {
const sourceLabel = source.kind === 'branch'
? t('walkthrough.scope.branch')
: source.kind === 'pr'
? t('walkthrough.scope.pullRequest', { number: source.number })
? gitProvider === 'gitlab'
? t('walkthrough.scope.mergeRequest', { number: source.number })
: t('walkthrough.scope.pullRequest', { number: source.number })
: scope === 'all'
? t('walkthrough.scope.all')
: scope === 'staged'
@@ -547,7 +557,9 @@ export const WalkthroughView = ({ directory }: WalkthroughViewProps) => {
)}
{prSource && (
<DropdownMenuRadioItem value="pr">
{t('walkthrough.scope.pullRequest', { number: prSource.number })}
{gitProvider === 'gitlab'
? t('walkthrough.scope.mergeRequest', { number: prSource.number })
: t('walkthrough.scope.pullRequest', { number: prSource.number })}
</DropdownMenuRadioItem>
)}
</DropdownMenuRadioGroup>
+119
View File
@@ -0,0 +1,119 @@
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
import type { GitLabMergeRequestSummary } from '@/lib/api/types';
const mrsListCalls: Array<{ directory: string; options?: { sourceBranch?: string } }> = [];
let mrsListResult: GitLabMergeRequestSummary[] = [];
let mrsListFailure: Error | null = null;
let registryHasGitlab = true;
mock.module('@/contexts/runtimeAPIRegistry', () => ({
getRegisteredRuntimeAPIs: () => {
if (!registryHasGitlab) return null;
return {
gitlab: {
mrsList: (directory: string, options?: { sourceBranch?: string }) => {
mrsListCalls.push({ directory, options });
if (mrsListFailure) {
return Promise.reject(mrsListFailure);
}
const all = mrsListResult;
const filtered = options?.sourceBranch
? all.filter((item) => item.sourceBranch === options.sourceBranch)
: all;
return Promise.resolve({ mrs: filtered });
},
},
};
},
}));
const { resolveGitLabMrForBranch } = await import('./gitlabMrStatus');
const mr = (number: number, state: string, sourceBranch: string): GitLabMergeRequestSummary => ({
number,
title: `MR ${number}`,
url: `https://gitlab.example/${number}`,
state,
draft: false,
author: { id: 1, username: 'user', name: 'User' },
sourceBranch,
targetBranch: 'main',
});
describe('resolveGitLabMrForBranch', () => {
beforeEach(() => {
mrsListCalls.length = 0;
mrsListResult = [];
mrsListFailure = null;
registryHasGitlab = true;
});
afterEach(() => {
mrsListCalls.length = 0;
mrsListResult = [];
mrsListFailure = null;
registryHasGitlab = true;
});
test('prefers the opened MR over a merged one for the branch', async () => {
mrsListResult = [mr(3, 'merged', 'feat/a'), mr(7, 'opened', 'feat/a')];
const result = await resolveGitLabMrForBranch('/repo', 'feat/a');
expect(result?.number).toBe(7);
expect(mrsListCalls).toEqual([
{ directory: '/repo', options: { sourceBranch: 'feat/a' } },
]);
});
test('falls back to a merged MR when no opened one exists', async () => {
mrsListResult = [mr(5, 'merged', 'feat/b'), mr(9, 'closed', 'feat/b')];
const result = await resolveGitLabMrForBranch('/repo', 'feat/b');
expect(result?.number).toBe(5);
});
test('returns null when no MR matches the branch', async () => {
mrsListResult = [mr(5, 'merged', 'feat/a')];
const result = await resolveGitLabMrForBranch('/repo', 'feat/c');
expect(result).toBeNull();
expect(mrsListCalls).toHaveLength(1);
});
test('returns null without calling the API when the runtime has no GitLab client', async () => {
registryHasGitlab = false;
const result = await resolveGitLabMrForBranch('/repo', 'feat/a');
expect(result).toBeNull();
expect(mrsListCalls).toHaveLength(0);
});
test('returns null and caches when the request fails', async () => {
mrsListFailure = new Error('boom');
const first = await resolveGitLabMrForBranch('/repo', 'fail-branch');
expect(first).toBeNull();
mrsListFailure = null;
mrsListResult = [mr(1, 'opened', 'fail-branch')];
// Same directory+branch within TTL must not re-request.
const second = await resolveGitLabMrForBranch('/repo', 'fail-branch');
expect(second).toBeNull();
expect(mrsListCalls).toHaveLength(1);
});
test('serves the second call from cache within the TTL window', async () => {
mrsListResult = [mr(7, 'opened', 'cache-branch')];
const first = await resolveGitLabMrForBranch('/repo', 'cache-branch');
const second = await resolveGitLabMrForBranch('/repo', 'cache-branch');
expect(first?.number).toBe(7);
expect(second?.number).toBe(7);
expect(mrsListCalls).toHaveLength(1);
});
});
+110
View File
@@ -0,0 +1,110 @@
import { useState, useEffect } from 'react';
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
import type { GitLabMergeRequestSummary } from '@/lib/api/types';
// Branch MR lookups are cheap to re-request but visible to the user on every
// mount of the surfaces that display them (walkthrough header, git view, work
// status). A shared TTL cache keeps those surfaces consistent with each other
// and stops repeated GitLab calls while a branch is in view.
const CACHE_TTL_MS = 90_000;
const mrCache = new Map<string, { at: number; mr: GitLabMergeRequestSummary | null }>();
const cacheKeyFor = (directory: string, branch: string): string => `${directory}\n${branch}`;
const readCachedMr = (directory: string, branch: string): GitLabMergeRequestSummary | null | undefined => {
const entry = mrCache.get(cacheKeyFor(directory, branch));
return entry?.mr;
};
/**
* Resolve the merge request targeting `branch` in `directory`, preferring the
* opened request and falling back to a merged one so a just-merged branch still
* surfaces its request instead of nothing.
*
* Returns null when the runtime has no GitLab API, the request fails, or no MR
* matches — callers only use the result to show or hide an additive chip, so a
* null answer simply means "nothing to show". Results are cached per
* directory+branch for CACHE_TTL_MS, including the null case.
*/
export const resolveGitLabMrForBranch = async (
directory: string,
branch: string,
): Promise<GitLabMergeRequestSummary | null> => {
const gitlab = getRegisteredRuntimeAPIs()?.gitlab;
if (!gitlab?.mrsList || !directory || !branch) {
return null;
}
const key = cacheKeyFor(directory, branch);
const cached = mrCache.get(key);
if (cached && Date.now() - cached.at < CACHE_TTL_MS) {
return cached.mr;
}
let mr: GitLabMergeRequestSummary | null = null;
try {
const result = await gitlab.mrsList(directory, { sourceBranch: branch });
const candidates = result.mrs ?? [];
mr = candidates.find((item) => item.state === 'opened')
?? candidates.find((item) => item.state === 'merged')
?? null;
} catch {
mr = null;
}
mrCache.set(key, { at: Date.now(), mr });
return mr;
};
/**
* Subscribe to the branch's merge request. Reads the TTL cache synchronously
* for the initial render so an already-resolved MR never flashes away while a
* refresh runs; a cache miss shows a loading state instead of a stale result
* from another branch.
*/
export const useGitLabMrForBranch = (
directory: string | null | undefined,
branch: string | null | undefined,
): { mr: GitLabMergeRequestSummary | null; isLoading: boolean } => {
const [mr, setMr] = useState<GitLabMergeRequestSummary | null>(() =>
directory && branch ? (readCachedMr(directory, branch) ?? null) : null
);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!directory || !branch) {
setMr(null);
setIsLoading(false);
return;
}
let mounted = true;
const cached = readCachedMr(directory, branch);
const cacheEntry = mrCache.get(cacheKeyFor(directory, branch));
const fresh = cacheEntry !== undefined && Date.now() - cacheEntry.at < CACHE_TTL_MS;
if (fresh) {
setMr(cached ?? null);
setIsLoading(false);
return;
}
// A stale entry stays on screen while it refreshes; a missing one shows
// the loading state rather than a result from a previous branch.
setMr(cached ?? null);
setIsLoading(true);
void resolveGitLabMrForBranch(directory, branch).then((resolved) => {
if (mounted) {
setMr(resolved);
setIsLoading(false);
}
});
return () => {
mounted = false;
};
}, [directory, branch]);
return { mr, isLoading };
};
+4
View File
@@ -2968,6 +2968,7 @@ export const dict = {
'sessions.sidebar.session.status.movingToWorktree': 'Sitzung wird in einen neuen Worktree verschoben',
'gitView.header.updateBranch': 'Branch aktualisieren',
'gitView.header.openPullRequest': 'Pull Request öffnen',
'gitView.header.openMergeRequest': 'Merge Request öffnen',
'gitView.history.refresh': 'Verlauf aktualisieren',
'gitView.operation.inProgressTitleManyConflicts': 'Operation {operation} läuft: {count} Konflikte',
'gitView.operation.inProgressTitleOneConflict': 'Operation {operation} läuft: {count} Konflikt',
@@ -3022,6 +3023,7 @@ export const dict = {
'walkthrough.missing.languageAndModel': 'Noch kein Walkthrough auf dieser Sprache von diesem Modell vorhanden — zeige den zuletzt hier erstellten.',
'walkthrough.language.selectorAria': 'Sprache auswählen',
'walkthrough.scope.pullRequest': 'PR #{number}',
'walkthrough.scope.mergeRequest': 'MR !{number}',
'walkthrough.action.generate': 'Generieren',
'walkthrough.action.regenerate': 'Erneut generieren',
'walkthrough.action.cancel': 'Abbrechen',
@@ -3109,6 +3111,7 @@ export const dict = {
'chat.workStatus.git.changedFileSingle': '{count} Datei geändert',
'chat.workStatus.git.changedFilePlural': '{count} Dateien geändert',
'chat.workStatus.pr.untitled': 'Pull Request ohne Titel',
'chat.workStatus.mr.untitled': 'Merge Request ohne Titel',
'chat.workStatus.pr.draft': 'Entwurf',
'chat.workStatus.pr.checks': 'Prüfungen',
'chat.workStatus.pr.checksFailed': '{count} fehlgeschlagen',
@@ -3140,6 +3143,7 @@ export const dict = {
'chat.workStatus.action.openChanges': 'Änderungen öffnen',
'chat.workStatus.action.openGit': 'Git-Panel öffnen',
'chat.workStatus.action.openPr': 'Pull Request öffnen',
'chat.workStatus.action.openMr': 'Merge Request öffnen',
'chat.workStatus.action.openSubagent': '{name} öffnen',
'chat.workStatus.section.usage': 'Nutzung',
'chat.workStatus.goal.open': 'Ziel verwalten',
+4
View File
@@ -770,6 +770,7 @@ export const dict = {
'gitView.header.repositoryViews': 'Repository views',
'gitView.header.updateBranch': 'Update branch',
'gitView.header.openPullRequest': 'Open pull request',
'gitView.header.openMergeRequest': 'Open merge request',
'gitView.header.removeRemoteAria': 'Remove remote {name}',
'gitView.header.removeRemoteTitle': 'Remove remote {name}',
'gitView.header.upstreamSynced': 'synced',
@@ -1160,6 +1161,7 @@ export const dict = {
'walkthrough.missing.languageAndModel': 'No walkthrough in this language from this model yet — showing the last one generated here.',
'walkthrough.language.selectorAria': 'Select the walkthrough language',
'walkthrough.scope.pullRequest': 'PR #{number}',
'walkthrough.scope.mergeRequest': 'MR !{number}',
'walkthrough.action.generate': 'Generate walkthrough',
'walkthrough.action.regenerate': 'Regenerate',
'walkthrough.action.cancel': 'Cancel',
@@ -3111,6 +3113,7 @@ export const dict = {
'chat.workStatus.git.changedFileSingle': '{count} file changed',
'chat.workStatus.git.changedFilePlural': '{count} files changed',
'chat.workStatus.pr.untitled': 'Untitled pull request',
'chat.workStatus.mr.untitled': 'Untitled merge request',
'chat.workStatus.pr.draft': 'Draft',
'chat.workStatus.pr.checks': 'Checks',
'chat.workStatus.pr.checksFailed': '{count} failed',
@@ -3142,6 +3145,7 @@ export const dict = {
'chat.workStatus.action.openChanges': 'Open changes',
'chat.workStatus.action.openGit': 'Open Git panel',
'chat.workStatus.action.openPr': 'Open pull request',
'chat.workStatus.action.openMr': 'Open merge request',
'chat.workStatus.action.openSubagent': 'Open {name}',
'chat.workStatus.section.usage': 'Usage',
'chat.workStatus.goal.open': 'Manage goal',
+4
View File
@@ -771,6 +771,7 @@ export const dict: Record<I18nKey, string> = {
"gitView.header.repositoryViews": "Vistas del repositorio",
"gitView.header.updateBranch": "Actualizar rama",
"gitView.header.openPullRequest": "Abrir pull request",
"gitView.header.openMergeRequest": "Abrir solicitud de fusión",
"gitView.header.removeRemoteAria": "Eliminar remoto",
"gitView.header.removeRemoteTitle": "Eliminar remoto",
"gitView.header.upstreamSynced": "sincronizado",
@@ -1161,6 +1162,7 @@ export const dict: Record<I18nKey, string> = {
"walkthrough.missing.languageAndModel": "Aún no hay un recorrido en este idioma con este modelo: se muestra el último generado aquí.",
"walkthrough.language.selectorAria": "Elegir el idioma del recorrido",
"walkthrough.scope.pullRequest": "PR n.º {number}",
"walkthrough.scope.mergeRequest": "MR !{number}",
"walkthrough.action.generate": "Generar recorrido",
"walkthrough.action.regenerate": "Regenerar",
"walkthrough.action.cancel": "Cancelar",
@@ -3112,6 +3114,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.git.changedFileSingle': '{count} archivo modificado',
'chat.workStatus.git.changedFilePlural': '{count} archivos modificados',
'chat.workStatus.pr.untitled': 'Pull request sin título',
'chat.workStatus.mr.untitled': 'Solicitud de fusión sin título',
'chat.workStatus.pr.draft': 'Borrador',
'chat.workStatus.pr.checks': 'Comprobaciones',
'chat.workStatus.pr.checksFailed': '{count} fallaron',
@@ -3143,6 +3146,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.action.openChanges': 'Abrir cambios',
'chat.workStatus.action.openGit': 'Abrir panel de Git',
'chat.workStatus.action.openPr': 'Abrir pull request',
'chat.workStatus.action.openMr': 'Abrir solicitud de fusión',
'chat.workStatus.action.openSubagent': 'Abrir {name}',
'chat.workStatus.section.usage': 'Uso',
'chat.workStatus.goal.open': 'Gestionar objetivo',
+4
View File
@@ -594,6 +594,7 @@ export const dict = {
'gitView.header.repositoryViews': 'Vues du dépôt',
'gitView.header.updateBranch': 'Mettre à jour la branche',
'gitView.header.openPullRequest': 'Ouvrir la pull request',
'gitView.header.openMergeRequest': 'Ouvrir la demande de fusion',
'gitView.header.removeRemoteAria': 'Supprimer le remote',
'gitView.header.removeRemoteTitle': 'Supprimer le remote',
'gitView.header.upstreamSynced': 'synchronisé',
@@ -980,6 +981,7 @@ export const dict = {
'walkthrough.missing.languageAndModel': 'Pas encore de parcours dans cette langue avec ce modèle — voici le dernier généré ici.',
'walkthrough.language.selectorAria': 'Choisir la langue du parcours',
'walkthrough.scope.pullRequest': 'PR n° {number}',
'walkthrough.scope.mergeRequest': 'MR !{number}',
'walkthrough.action.generate': 'Générer le parcours',
'walkthrough.action.regenerate': 'Régénérer',
'walkthrough.action.cancel': 'Annuler',
@@ -3109,6 +3111,7 @@ export const dict = {
'chat.workStatus.git.changedFileSingle': '{count} fichier modifié',
'chat.workStatus.git.changedFilePlural': '{count} fichiers modifiés',
'chat.workStatus.pr.untitled': 'Pull request sans titre',
'chat.workStatus.mr.untitled': 'Demande de fusion sans titre',
'chat.workStatus.pr.draft': 'Brouillon',
'chat.workStatus.pr.checks': 'Vérifications',
'chat.workStatus.pr.checksFailed': '{count} en échec',
@@ -3140,6 +3143,7 @@ export const dict = {
'chat.workStatus.action.openChanges': 'Ouvrir les modifications',
'chat.workStatus.action.openGit': 'Ouvrir le panneau Git',
'chat.workStatus.action.openPr': 'Ouvrir la pull request',
'chat.workStatus.action.openMr': 'Ouvrir la demande de fusion',
'chat.workStatus.action.openSubagent': 'Ouvrir {name}',
'chat.workStatus.section.usage': 'Utilisation',
'chat.workStatus.goal.open': 'Gérer lobjectif',
+4
View File
@@ -768,6 +768,7 @@ export const dict: Record<I18nKey, string> = {
'gitView.header.repositoryViews': 'リポジトリビュー',
'gitView.header.updateBranch': 'ブランチを更新',
'gitView.header.openPullRequest': 'プルリクエストを開く',
'gitView.header.openMergeRequest': 'マージリクエストを開く',
'gitView.header.removeRemoteAria': 'リモート{name}を削除',
'gitView.header.removeRemoteTitle': 'リモート{name}を削除',
'gitView.header.upstreamSynced': '同期済み',
@@ -1157,6 +1158,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.missing.languageAndModel': 'この言語・このモデルのウォークスルーはまだありません。ここで最後に生成されたものを表示しています。',
'walkthrough.language.selectorAria': 'ウォークスルーの言語を選択',
'walkthrough.scope.pullRequest': 'PR #{number}',
'walkthrough.scope.mergeRequest': 'MR !{number}',
'walkthrough.action.generate': 'ウォークスルーを生成',
'walkthrough.action.regenerate': '再生成',
'walkthrough.action.cancel': 'キャンセル',
@@ -3111,6 +3113,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.git.changedFileSingle': '{count} 件のファイルを変更',
'chat.workStatus.git.changedFilePlural': '{count} 件のファイルを変更',
'chat.workStatus.pr.untitled': 'タイトルなしのプルリクエスト',
'chat.workStatus.mr.untitled': 'タイトルなしのマージリクエスト',
'chat.workStatus.pr.draft': 'ドラフト',
'chat.workStatus.pr.checks': 'チェック',
'chat.workStatus.pr.checksFailed': '{count} 件失敗',
@@ -3142,6 +3145,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.action.openChanges': '変更を開く',
'chat.workStatus.action.openGit': 'Git パネルを開く',
'chat.workStatus.action.openPr': 'プルリクエストを開く',
'chat.workStatus.action.openMr': 'マージリクエストを開く',
'chat.workStatus.action.openSubagent': '{name} を開く',
'chat.workStatus.section.usage': '使用量',
'chat.workStatus.goal.open': '目標を管理',
+4
View File
@@ -771,6 +771,7 @@ export const dict: Record<I18nKey, string> = {
'gitView.header.repositoryViews': '저장소 보기',
'gitView.header.updateBranch': '브랜치 업데이트',
'gitView.header.openPullRequest': '풀 리퀘스트 열기',
'gitView.header.openMergeRequest': '머지 리퀘스트 열기',
'gitView.header.removeRemoteAria': '리모트 제거',
'gitView.header.removeRemoteTitle': '리모트 제거',
'gitView.header.upstreamSynced': '동기화됨',
@@ -1161,6 +1162,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.missing.languageAndModel': '이 언어와 이 모델로 생성한 워크스루가 아직 없어 마지막으로 생성된 것을 표시합니다.',
'walkthrough.language.selectorAria': '워크스루 언어 선택',
'walkthrough.scope.pullRequest': 'PR #{number}',
'walkthrough.scope.mergeRequest': 'MR !{number}',
'walkthrough.action.generate': '워크스루 생성',
'walkthrough.action.regenerate': '다시 생성',
'walkthrough.action.cancel': '취소',
@@ -3111,6 +3113,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.git.changedFileSingle': '파일 {count}개 변경됨',
'chat.workStatus.git.changedFilePlural': '파일 {count}개 변경됨',
'chat.workStatus.pr.untitled': '제목 없는 풀 리퀘스트',
'chat.workStatus.mr.untitled': '제목 없는 머지 리퀘스트',
'chat.workStatus.pr.draft': '초안',
'chat.workStatus.pr.checks': '검사',
'chat.workStatus.pr.checksFailed': '{count}개 실패',
@@ -3142,6 +3145,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.action.openChanges': '변경 사항 열기',
'chat.workStatus.action.openGit': 'Git 패널 열기',
'chat.workStatus.action.openPr': '풀 리퀘스트 열기',
'chat.workStatus.action.openMr': '머지 리퀘스트 열기',
'chat.workStatus.action.openSubagent': '{name} 열기',
'chat.workStatus.section.usage': '사용량',
'chat.workStatus.goal.open': '목표 관리',
+4
View File
@@ -1498,6 +1498,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.missing.languageAndModel': 'Nie ma jeszcze przewodnika w tym języku od tego modelu — pokazujemy ostatni wygenerowany tutaj.',
'walkthrough.language.selectorAria': 'Wybierz język przewodnika',
'walkthrough.scope.pullRequest': 'PR #{number}',
'walkthrough.scope.mergeRequest': 'MR !{number}',
'walkthrough.action.generate': 'Wygeneruj przewodnik',
'walkthrough.action.regenerate': 'Wygeneruj ponownie',
'walkthrough.action.cancel': 'Anuluj',
@@ -2074,6 +2075,7 @@ export const dict: Record<I18nKey, string> = {
'gitView.header.repositoryViews': 'Widoki repozytorium',
'gitView.header.updateBranch': 'Zaktualizuj gałąź',
'gitView.header.openPullRequest': 'Otwórz pull request',
'gitView.header.openMergeRequest': 'Otwórz żądanie scalenia',
'gitView.header.removeRemoteAria': 'Usuń remote {name}',
'gitView.header.removeRemoteTitle': 'Usuń remote {name}',
'gitView.header.upstreamSynced': 'zsynchronizowano',
@@ -3128,6 +3130,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.git.changedFileSingle': 'Zmieniono {count} plik',
'chat.workStatus.git.changedFilePlural': 'Zmieniono {count} plików',
'chat.workStatus.pr.untitled': 'Pull request bez tytułu',
'chat.workStatus.mr.untitled': 'Żądanie scalenia bez tytułu',
'chat.workStatus.pr.draft': 'Szkic',
'chat.workStatus.pr.checks': 'Sprawdzenia',
'chat.workStatus.pr.checksFailed': '{count} nieudanych',
@@ -3159,6 +3162,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.action.openChanges': 'Otwórz zmiany',
'chat.workStatus.action.openGit': 'Otwórz panel Git',
'chat.workStatus.action.openPr': 'Otwórz pull request',
'chat.workStatus.action.openMr': 'Otwórz żądanie scalenia',
'chat.workStatus.action.openSubagent': 'Otwórz {name}',
'chat.workStatus.section.usage': 'Zużycie',
'chat.workStatus.goal.open': 'Zarządzaj celem',
@@ -771,6 +771,7 @@ export const dict: Record<I18nKey, string> = {
"gitView.header.repositoryViews": "Visualizações do repositório",
"gitView.header.updateBranch": "Atualizar branch",
"gitView.header.openPullRequest": "Abrir pull request",
"gitView.header.openMergeRequest": "Abrir solicitação de merge",
"gitView.header.removeRemoteAria": "Excluir remoto",
"gitView.header.removeRemoteTitle": "Excluir remoto",
"gitView.header.upstreamSynced": "sincronizado",
@@ -1161,6 +1162,7 @@ export const dict: Record<I18nKey, string> = {
"walkthrough.missing.languageAndModel": "Ainda não há um percurso neste idioma com este modelo — exibindo o último gerado aqui.",
"walkthrough.language.selectorAria": "Escolher o idioma do percurso",
"walkthrough.scope.pullRequest": "PR nº {number}",
"walkthrough.scope.mergeRequest": "MR !{number}",
"walkthrough.action.generate": "Gerar percurso",
"walkthrough.action.regenerate": "Gerar novamente",
"walkthrough.action.cancel": "Cancelar",
@@ -3112,6 +3114,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.git.changedFileSingle': '{count} arquivo alterado',
'chat.workStatus.git.changedFilePlural': '{count} arquivos alterados',
'chat.workStatus.pr.untitled': 'Pull request sem título',
'chat.workStatus.mr.untitled': 'Solicitação de merge sem título',
'chat.workStatus.pr.draft': 'Rascunho',
'chat.workStatus.pr.checks': 'Verificações',
'chat.workStatus.pr.checksFailed': '{count} falharam',
@@ -3143,6 +3146,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.action.openChanges': 'Abrir alterações',
'chat.workStatus.action.openGit': 'Abrir painel do Git',
'chat.workStatus.action.openPr': 'Abrir pull request',
'chat.workStatus.action.openMr': 'Abrir solicitação de merge',
'chat.workStatus.action.openSubagent': 'Abrir {name}',
'chat.workStatus.section.usage': 'Uso',
'chat.workStatus.goal.open': 'Gerenciar objetivo',
+4
View File
@@ -771,6 +771,7 @@ export const dict: Record<I18nKey, string> = {
"gitView.header.repositoryViews": "Перегляди репозиторію",
"gitView.header.updateBranch": "Оновити гілку",
"gitView.header.openPullRequest": "Відкрити pull request",
"gitView.header.openMergeRequest": "Відкрити запит на злиття",
"gitView.header.removeRemoteAria": "Видалити remote",
"gitView.header.removeRemoteTitle": "Видалити remote",
"gitView.header.upstreamSynced": "синхронізовано",
@@ -1161,6 +1162,7 @@ export const dict: Record<I18nKey, string> = {
"walkthrough.missing.languageAndModel": "Розбору цією мовою від цієї моделі ще немає — показано останній згенерований тут.",
"walkthrough.language.selectorAria": "Обрати мову розбору",
"walkthrough.scope.pullRequest": "PR #{number}",
"walkthrough.scope.mergeRequest": "MR !{number}",
"walkthrough.action.generate": "Створити розбір",
"walkthrough.action.regenerate": "Створити заново",
"walkthrough.action.cancel": "Скасувати",
@@ -3112,6 +3114,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.git.changedFileSingle': 'Змінено {count} файл',
'chat.workStatus.git.changedFilePlural': 'Змінено {count} файлів',
'chat.workStatus.pr.untitled': 'Pull request без назви',
'chat.workStatus.mr.untitled': 'Запит на злиття без назви',
'chat.workStatus.pr.draft': 'Чернетка',
'chat.workStatus.pr.checks': 'Перевірки',
'chat.workStatus.pr.checksFailed': '{count} впало',
@@ -3143,6 +3146,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.action.openChanges': 'Відкрити зміни',
'chat.workStatus.action.openGit': 'Відкрити панель Git',
'chat.workStatus.action.openPr': 'Відкрити pull request',
'chat.workStatus.action.openMr': 'Відкрити запит на злиття',
'chat.workStatus.action.openSubagent': 'Відкрити {name}',
'chat.workStatus.section.usage': 'Використання',
'chat.workStatus.goal.open': 'Керувати ціллю',
@@ -771,6 +771,7 @@ export const dict: Record<I18nKey, string> = {
'gitView.header.repositoryViews': '仓库视图',
'gitView.header.updateBranch': '更新分支',
'gitView.header.openPullRequest': '打开拉取请求',
'gitView.header.openMergeRequest': '打开合并请求',
'gitView.header.removeRemoteAria': '移除远程 {name}',
'gitView.header.removeRemoteTitle': '移除 {name}',
'gitView.header.upstreamSynced': '已同步',
@@ -1161,6 +1162,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.missing.languageAndModel': '尚无使用该语言和该模型生成的导读,当前显示最近一次生成的版本。',
'walkthrough.language.selectorAria': '选择导读语言',
'walkthrough.scope.pullRequest': 'PR #{number}',
'walkthrough.scope.mergeRequest': 'MR !{number}',
'walkthrough.action.generate': '生成导读',
'walkthrough.action.regenerate': '重新生成',
'walkthrough.action.cancel': '取消',
@@ -3112,6 +3114,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.git.changedFileSingle': '已更改 {count} 个文件',
'chat.workStatus.git.changedFilePlural': '已更改 {count} 个文件',
'chat.workStatus.pr.untitled': '未命名的拉取请求',
'chat.workStatus.mr.untitled': '未命名的合并请求',
'chat.workStatus.pr.draft': '草稿',
'chat.workStatus.pr.checks': '检查',
'chat.workStatus.pr.checksFailed': '{count} 项失败',
@@ -3143,6 +3146,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.action.openChanges': '打开更改',
'chat.workStatus.action.openGit': '打开 Git 面板',
'chat.workStatus.action.openPr': '打开拉取请求',
'chat.workStatus.action.openMr': '打开合并请求',
'chat.workStatus.action.openSubagent': '打开 {name}',
'chat.workStatus.section.usage': '用量',
'chat.workStatus.goal.open': '管理目标',
@@ -784,6 +784,7 @@ export const dict: Record<I18nKey, string> = {
'gitView.header.repositoryViews': '儲存庫檢視',
'gitView.header.updateBranch': '更新分支',
'gitView.header.openPullRequest': '開啟提取請求',
'gitView.header.openMergeRequest': '開啟合併請求',
'gitView.header.removeRemoteAria': '移除遠端 {name}',
'gitView.header.removeRemoteTitle': '移除 {name}',
'gitView.header.upstreamSynced': '已同步',
@@ -1173,6 +1174,7 @@ export const dict: Record<I18nKey, string> = {
'walkthrough.missing.languageAndModel': '尚無使用該語言與該模型產生的導讀,目前顯示最近一次產生的版本。',
'walkthrough.language.selectorAria': '選擇導讀語言',
'walkthrough.scope.pullRequest': 'PR #{number}',
'walkthrough.scope.mergeRequest': 'MR !{number}',
'walkthrough.action.generate': '產生導讀',
'walkthrough.action.regenerate': '重新產生',
'walkthrough.action.cancel': '取消',
@@ -3111,6 +3113,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.git.changedFileSingle': '已變更 {count} 個檔案',
'chat.workStatus.git.changedFilePlural': '已變更 {count} 個檔案',
'chat.workStatus.pr.untitled': '未命名的提取請求',
'chat.workStatus.mr.untitled': '未命名的合併請求',
'chat.workStatus.pr.draft': '草稿',
'chat.workStatus.pr.checks': '檢查',
'chat.workStatus.pr.checksFailed': '{count} 項失敗',
@@ -3142,6 +3145,7 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.action.openChanges': '開啟變更',
'chat.workStatus.action.openGit': '開啟 Git 面板',
'chat.workStatus.action.openPr': '開啟提取請求',
'chat.workStatus.action.openMr': '開啟合併請求',
'chat.workStatus.action.openSubagent': '開啟 {name}',
'chat.workStatus.section.usage': '用量',
'chat.workStatus.goal.open': '管理目標',