feat(ui): gate the pull-request surface on GitHub, move the account into it, and GitHub sign-in into Integrations
The pull-request rail icon now appears only while GitHub is connected (OAuth or gh CLI), like Linear; Linear sits after the walkthrough in the default rail order. The GitHub account avatar and switcher leave the header for the pull-request panel, where the walkthrough, refresh, and account controls share one row and one height, and the account stays visible on the panel's empty state. A manual refresh keeps its spinner on screen long enough to read as work done. GitHub sign-in moves from Settings → Git to Settings → Integrations → Built-in integrations as a card before Linear; search and the connect buttons follow it.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
/** Linear first-party integration settings strings — merged into each locale's settings dictionary. */
|
||||
/** Built-in integration (GitHub, Linear) settings strings — merged into each locale's settings dictionary. */
|
||||
export const linearIntegrationI18n = {
|
||||
en: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': 'Connect a GitHub account for pull requests and issues.',
|
||||
'settings.integrations.github.status.notConnected': 'Not connected',
|
||||
'settings.integrations.firstParty.title': 'Built-in integrations',
|
||||
'settings.integrations.firstParty.info': 'Sign-ins for services that ship with OpenChamber. The login stays on this computer so web, desktop, and a paired phone share it.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -48,6 +51,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Prompts used when starting a session from a Linear issue: visible user message + hidden instructions.',
|
||||
},
|
||||
de: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': 'GitHub-Konto für Pull Requests und Issues verbinden.',
|
||||
'settings.integrations.github.status.notConnected': 'Nicht verbunden',
|
||||
'settings.integrations.firstParty.title': 'Eingebaute Integrationen',
|
||||
'settings.integrations.firstParty.info': 'Anmeldungen für Dienste, die mit OpenChamber mitgeliefert werden. Die Anmeldung bleibt auf diesem Computer, damit Web, Desktop und ein gekoppeltes Telefon sie teilen.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -95,6 +101,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Eingabeaufforderungen beim Start einer Sitzung aus einem Linear-Issue: sichtbare Benutzernachricht + versteckte Anweisungen.',
|
||||
},
|
||||
fr: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': 'Connecter un compte GitHub pour les pull requests et les issues.',
|
||||
'settings.integrations.github.status.notConnected': 'Non connecté',
|
||||
'settings.integrations.firstParty.title': 'Intégrations natives',
|
||||
'settings.integrations.firstParty.info': 'Connexions aux services fournis avec OpenChamber. La connexion reste sur cet ordinateur pour que le web, le bureau et un téléphone apparié la partagent.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -142,6 +151,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Prompts utilisés au démarrage d’une session depuis un ticket Linear : message utilisateur visible + instructions masquées.',
|
||||
},
|
||||
es: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': 'Conecta una cuenta de GitHub para pull requests e issues.',
|
||||
'settings.integrations.github.status.notConnected': 'No conectado',
|
||||
'settings.integrations.firstParty.title': 'Integraciones nativas',
|
||||
'settings.integrations.firstParty.info': 'Inicios de sesión de los servicios incluidos en OpenChamber. El inicio de sesión se guarda en este ordenador para que la web, el escritorio y un teléfono emparejado lo compartan.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -189,6 +201,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Prompts usados al iniciar una sesión desde un issue de Linear: mensaje visible del usuario e instrucciones ocultas.',
|
||||
},
|
||||
ja: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': 'プルリクエストと Issue のために GitHub アカウントを接続します。',
|
||||
'settings.integrations.github.status.notConnected': '未接続',
|
||||
'settings.integrations.firstParty.title': '標準連携',
|
||||
'settings.integrations.firstParty.info': 'OpenChamber に同梱されているサービスのログインです。このコンピュータに保存され、Web、デスクトップ、ペアリングしたスマホで共有されます。',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -236,6 +251,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Linear の Issue からセッションを開始するときに使うプロンプト: 表示ユーザーメッセージ + 非表示の指示。',
|
||||
},
|
||||
ko: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': '풀 리퀘스트와 이슈를 위해 GitHub 계정을 연결합니다.',
|
||||
'settings.integrations.github.status.notConnected': '연결되지 않음',
|
||||
'settings.integrations.firstParty.title': '기본 제공 통합',
|
||||
'settings.integrations.firstParty.info': 'OpenChamber에 포함된 서비스 로그인입니다. 이 컴퓨터에 저장되며 웹, 데스크톱, 페어링된 휴대폰이 공유합니다.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -283,6 +301,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Linear 이슈로 세션을 시작할 때 쓰는 프롬프트입니다: 표시 사용자 메시지 + 숨겨진 지침.',
|
||||
},
|
||||
pl: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': 'Połącz konto GitHub dla pull requestów i issues.',
|
||||
'settings.integrations.github.status.notConnected': 'Nie połączono',
|
||||
'settings.integrations.firstParty.title': 'Wbudowane integracje',
|
||||
'settings.integrations.firstParty.info': 'Logowania do usług dostarczanych z OpenChamber. Zapisujemy je na tym komputerze, żeby przeglądarka, aplikacja desktopowa i sparowany telefon z nich korzystały.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -330,6 +351,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Prompty używane przy starcie sesji ze zgłoszenia Linear: widoczna wiadomość użytkownika i ukryte instrukcje.',
|
||||
},
|
||||
'pt-BR': {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': 'Conecte uma conta do GitHub para pull requests e issues.',
|
||||
'settings.integrations.github.status.notConnected': 'Não conectado',
|
||||
'settings.integrations.firstParty.title': 'Integrações nativas',
|
||||
'settings.integrations.firstParty.info': 'Logins dos serviços inclusos no OpenChamber. O login fica neste computador para que a web, o app desktop e um celular emparelhado o compartilhem.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -377,6 +401,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Prompts usados ao iniciar uma sessão a partir de uma issue do Linear: mensagem visível do usuário e instruções ocultas.',
|
||||
},
|
||||
uk: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': 'Підключіть акаунт GitHub для pull request-ів та issues.',
|
||||
'settings.integrations.github.status.notConnected': 'Не підключено',
|
||||
'settings.integrations.firstParty.title': 'Вбудовані інтеграції',
|
||||
'settings.integrations.firstParty.info': 'Входи до сервісів, що входять до OpenChamber. Логін лишається на цьому комп’ютері, тож веб, десктоп і спарений телефон користуються одним обліковим записом.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -424,6 +451,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': 'Промпти для старту сесії з Linear issue: видиме повідомлення користувача та приховані інструкції.',
|
||||
},
|
||||
'zh-CN': {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': '连接 GitHub 账号以处理拉取请求和议题。',
|
||||
'settings.integrations.github.status.notConnected': '未连接',
|
||||
'settings.integrations.firstParty.title': '内置集成',
|
||||
'settings.integrations.firstParty.info': 'OpenChamber 自带服务的登录。登录保存在这台电脑上,网页、桌面应用和已配对的手机会共用它。',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -471,6 +501,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': '从 Linear Issue 开始会话时使用的提示词:可见用户消息 + 隐藏指令。',
|
||||
},
|
||||
'zh-TW': {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': '連接 GitHub 帳號以處理拉取請求與議題。',
|
||||
'settings.integrations.github.status.notConnected': '未連接',
|
||||
'settings.integrations.firstParty.title': '內建整合',
|
||||
'settings.integrations.firstParty.info': 'OpenChamber 內建服務的登入。登入保存在這台電腦上,網頁、桌面應用程式和已配對的手機會共用它。',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
@@ -518,6 +551,9 @@ export const linearIntegrationI18n = {
|
||||
'settings.magicPrompts.page.group.linearIssueReview.description': '從 Linear Issue 開始會話時使用的提示詞:可見使用者訊息 + 隱藏指令。',
|
||||
},
|
||||
tr: {
|
||||
'settings.integrations.github.title': 'GitHub',
|
||||
'settings.integrations.github.description': "Pull request ve issue'lar için bir GitHub hesabı bağlayın.",
|
||||
'settings.integrations.github.status.notConnected': 'Bağlı değil',
|
||||
'settings.integrations.firstParty.title': 'Yerleşik entegrasyonlar',
|
||||
'settings.integrations.firstParty.info': 'OpenChamber ile gelen hizmetlerin oturumları. Giriş bu bilgisayarda kalır; web, masaüstü ve eşlenen telefon paylaşır.',
|
||||
'settings.integrations.linear.title': 'Linear',
|
||||
|
||||
@@ -150,7 +150,7 @@ export const SETTINGS_PAGE_METADATA: readonly SettingsPageMeta[] = [
|
||||
title: 'Git',
|
||||
group: 'projects',
|
||||
kind: 'single',
|
||||
keywords: ['git', 'github', 'identity', 'identities', 'ssh', 'profiles', 'credentials', 'keys', 'commit', 'gitmoji', 'oauth', 'prs', 'issues'],
|
||||
keywords: ['git', 'identity', 'identities', 'ssh', 'profiles', 'credentials', 'keys', 'commit', 'gitmoji'],
|
||||
isAvailable: (ctx) => !ctx.isVSCode,
|
||||
},
|
||||
{
|
||||
@@ -202,7 +202,7 @@ export const SETTINGS_PAGE_METADATA: readonly SettingsPageMeta[] = [
|
||||
{ slug: 'voice', title: 'Voice', group: 'general', kind: 'single', keywords: ['tts', 'speech', 'voice'], isAvailable: (ctx) => !ctx.isVSCode },
|
||||
{ slug: 'tunnel', title: 'External Tunnel', group: 'projects', kind: 'single', keywords: ['tunnel', 'external', 'cloudflare', 'qr', 'remote', 'mobile', 'share'], isAvailable: (ctx) => !ctx.isVSCode },
|
||||
{ slug: 'about', title: 'About', group: 'general', kind: 'single', keywords: ['about', 'version', 'updates', 'release', 'changelog'], isAvailable: (ctx) => ctx.isMobile && !ctx.isVSCode },
|
||||
{ slug: 'integrations', title: 'Integrations', group: 'general', kind: 'single', keywords: ['integration', 'plugin', 'provider', 'oauth', 'claude', 'cursor', 'command code', 'connect', 'discord', 'telegram', 'messenger', 'linear'] },
|
||||
{ slug: 'integrations', title: 'Integrations', group: 'general', kind: 'single', keywords: ['integration', 'plugin', 'provider', 'oauth', 'claude', 'cursor', 'command code', 'connect', 'discord', 'telegram', 'messenger', 'github', 'linear'] },
|
||||
] as const;
|
||||
|
||||
const LEGACY_SIDEBAR_SECTION_TO_SETTINGS_SLUG: Record<SidebarSection, SettingsPageSlug> = {
|
||||
|
||||
@@ -527,12 +527,6 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
// user to an empty spot on the page.
|
||||
isAvailable: (ctx) => !ctx.isVSCode && useUIStore.getState().agentMemoryFeatureAvailable,
|
||||
},
|
||||
{
|
||||
id: 'git.github-account',
|
||||
page: 'git',
|
||||
titleKey: 'settings.github.page.actions.connect',
|
||||
keywords: ['github', 'account', 'oauth', 'prs', 'issues'],
|
||||
},
|
||||
{
|
||||
id: 'git.identities',
|
||||
page: 'git',
|
||||
@@ -989,7 +983,15 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
page: 'integrations',
|
||||
titleKey: 'settings.integrations.firstParty.title',
|
||||
descriptionKey: 'settings.integrations.firstParty.info',
|
||||
keywords: ['built-in', 'first-party', 'native', 'linear'],
|
||||
keywords: ['built-in', 'first-party', 'native', 'github', 'linear'],
|
||||
isAvailable: (ctx) => !ctx.isVSCode,
|
||||
},
|
||||
{
|
||||
id: 'integrations.github',
|
||||
page: 'integrations',
|
||||
titleKey: 'settings.integrations.github.title',
|
||||
descriptionKey: 'settings.integrations.github.description',
|
||||
keywords: ['github', 'account', 'oauth', 'gh', 'cli', 'prs', 'pull request', 'issues', 'connect'],
|
||||
isAvailable: (ctx) => !ctx.isVSCode,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -27,7 +27,9 @@ edge (`components/layout/ContextPanelRail.tsx`) and rendered by
|
||||
configure button — `ContextRailSurfacesDialog`), drops the plan surface
|
||||
unless plan mode is enabled,
|
||||
drops the walkthrough on VS Code and below `WALKTHROUGH_MIN_WIDTH`, hides
|
||||
Linear unless a workspace is connected, and hides `has-content` surfaces
|
||||
Linear unless a workspace is connected, hides the pull-request surface
|
||||
unless GitHub is connected (OAuth or `gh` CLI — signed in from Settings →
|
||||
Integrations), 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.
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ const baseOptions = {
|
||||
screenWidth: 1200,
|
||||
tabs: [],
|
||||
linearConnected: true,
|
||||
githubConnected: true,
|
||||
} as const;
|
||||
|
||||
describe('getVisibleContextRailSurfaces', () => {
|
||||
@@ -65,12 +66,16 @@ describe('getVisibleContextRailSurfaces', () => {
|
||||
expect(surfaces.slice(0, 2).map((surface) => surface.id)).toEqual(['git', 'context']);
|
||||
});
|
||||
|
||||
test('places Linear after Pull Request in the default order', () => {
|
||||
test('places Linear right after the walkthrough in the default order', () => {
|
||||
const ids = getVisibleContextRailSurfaces(baseOptions).map((surface) => surface.id);
|
||||
const pr = ids.indexOf('pr');
|
||||
const linear = ids.indexOf('linear');
|
||||
expect(pr).toBeGreaterThanOrEqual(0);
|
||||
expect(linear).toBe(pr + 1);
|
||||
const walkthrough = ids.indexOf('walkthrough');
|
||||
expect(walkthrough).toBeGreaterThanOrEqual(0);
|
||||
expect(ids.indexOf('linear')).toBe(walkthrough + 1);
|
||||
});
|
||||
|
||||
test('hides the pull request surface until GitHub is connected', () => {
|
||||
expect(getVisibleContextRailSurfaces({ ...baseOptions, githubConnected: false }).some((s) => s.id === 'pr')).toBe(false);
|
||||
expect(getVisibleContextRailSurfaces({ ...baseOptions, githubConnected: true }).some((s) => s.id === 'pr')).toBe(true);
|
||||
});
|
||||
|
||||
test('hides Linear until a workspace is connected', () => {
|
||||
|
||||
@@ -66,15 +66,6 @@ export const CONTEXT_SURFACES: readonly ContextSurfaceDescriptor[] = [
|
||||
labelKey: 'contextPanel.mode.pr',
|
||||
availability: 'always',
|
||||
},
|
||||
{
|
||||
id: 'linear',
|
||||
descriptionKey: 'contextRail.surface.linear.description',
|
||||
defaultWidthFraction: 0.45,
|
||||
mode: 'linear',
|
||||
icon: 'linear',
|
||||
labelKey: 'contextPanel.mode.linear',
|
||||
availability: 'always',
|
||||
},
|
||||
{
|
||||
id: 'diff',
|
||||
descriptionKey: 'contextRail.surface.diff.description',
|
||||
@@ -93,6 +84,15 @@ export const CONTEXT_SURFACES: readonly ContextSurfaceDescriptor[] = [
|
||||
labelKey: 'contextPanel.mode.walkthrough',
|
||||
availability: 'always',
|
||||
},
|
||||
{
|
||||
id: 'linear',
|
||||
descriptionKey: 'contextRail.surface.linear.description',
|
||||
defaultWidthFraction: 0.45,
|
||||
mode: 'linear',
|
||||
icon: 'linear',
|
||||
labelKey: 'contextPanel.mode.linear',
|
||||
availability: 'always',
|
||||
},
|
||||
{
|
||||
id: 'editor',
|
||||
descriptionKey: 'contextRail.surface.editor.description',
|
||||
@@ -206,6 +206,10 @@ type VisibleRailSurfacesOptions = {
|
||||
tabs: readonly { mode: ContextPanelMode }[];
|
||||
/** Linear's rail icon stays off until a workspace is connected. */
|
||||
linearConnected: boolean;
|
||||
/** The pull-request rail icon stays off until GitHub is connected (OAuth
|
||||
or a detected `gh` CLI login). GitHub is connected from Settings, so
|
||||
hiding the surface removes no entry point. */
|
||||
githubConnected: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -240,6 +244,9 @@ export const getVisibleContextRailSurfaces = (options: VisibleRailSurfacesOption
|
||||
if (surface.id === 'linear' && !options.linearConnected) {
|
||||
return false;
|
||||
}
|
||||
if (surface.id === 'pr' && !options.githubConnected) {
|
||||
return false;
|
||||
}
|
||||
if (surface.availability === 'has-content') {
|
||||
return options.tabs.some((tab) => tab.mode === surface.mode);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user