diff --git a/packages/web-shell/client/components/panels/EnvironmentPanel.test.tsx b/packages/web-shell/client/components/panels/EnvironmentPanel.test.tsx
index 2d74dd95ddd..f4368a75d1c 100644
--- a/packages/web-shell/client/components/panels/EnvironmentPanel.test.tsx
+++ b/packages/web-shell/client/components/panels/EnvironmentPanel.test.tsx
@@ -3,7 +3,7 @@
import type { DaemonSessionTaskStatus } from '@qwen-code/sdk/daemon';
import { act, type ReactNode } from 'react';
import { createRoot, type Root } from 'react-dom/client';
-import { afterEach, describe, expect, it, vi } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { I18nProvider } from '../../i18n';
import { EnvironmentPanel } from './EnvironmentPanel';
import type { SourcesState } from './SourcesSection';
@@ -44,11 +44,17 @@ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
let container: HTMLDivElement | null = null;
let root: Root | null = null;
+beforeEach(() => {
+ // The add-source button is hidden by default; enable it for these tests.
+ window.history.replaceState({}, '', '/?addSource=1');
+});
+
afterEach(() => {
act(() => root?.unmount());
container?.remove();
container = null;
root = null;
+ window.history.replaceState({}, '', '/');
});
function mount(
diff --git a/packages/web-shell/client/components/panels/SourcesSection.test.tsx b/packages/web-shell/client/components/panels/SourcesSection.test.tsx
new file mode 100644
index 00000000000..c7555b47129
--- /dev/null
+++ b/packages/web-shell/client/components/panels/SourcesSection.test.tsx
@@ -0,0 +1,62 @@
+// @vitest-environment jsdom
+
+import { act } from 'react';
+import { createRoot, type Root } from 'react-dom/client';
+import { afterEach, describe, expect, it, vi } from 'vitest';
+import { I18nProvider } from '../../i18n';
+import { SourcesSection, type SourcesState } from './SourcesSection';
+
+Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
+
+let container: HTMLDivElement | null = null;
+let root: Root | null = null;
+
+afterEach(() => {
+ act(() => root?.unmount());
+ container?.remove();
+ container = null;
+ root = null;
+ window.history.replaceState({}, '', '/');
+});
+
+function supportedState(): SourcesState {
+ return {
+ supported: true,
+ sources: [],
+ owner: { isCurrent: () => true },
+ revision: 1,
+ hydrated: true,
+ loading: false,
+ error: null,
+ refresh: vi.fn(),
+ upsert: vi.fn(),
+ remove: vi.fn(),
+ };
+}
+
+function mount(): HTMLDivElement {
+ container = document.createElement('div');
+ document.body.appendChild(container);
+ root = createRoot(container);
+ act(() => {
+ root!.render(
+
+
+ ,
+ );
+ });
+ return container;
+}
+
+describe('SourcesSection add-source button', () => {
+ it('is hidden by default', () => {
+ const el = mount();
+ expect(el.querySelector('button[aria-label="Add source"]')).toBeNull();
+ });
+
+ it('appears when the addSource URL parameter is set', () => {
+ window.history.replaceState({}, '', '/?addSource=1');
+ const el = mount();
+ expect(el.querySelector('button[aria-label="Add source"]')).not.toBeNull();
+ });
+});
diff --git a/packages/web-shell/client/components/panels/SourcesSection.tsx b/packages/web-shell/client/components/panels/SourcesSection.tsx
index 173d25cedea..81393fac7c9 100644
--- a/packages/web-shell/client/components/panels/SourcesSection.tsx
+++ b/packages/web-shell/client/components/panels/SourcesSection.tsx
@@ -18,6 +18,13 @@ import styles from './EnvironmentPanel.module.css';
export type SourcesState = ReturnType;
+// Escape hatch: the add-source affordance is hidden by default; append
+// ?addSource=1 to the URL to bring it back.
+function isAddSourceEnabled(): boolean {
+ if (typeof window === 'undefined') return false;
+ return new URLSearchParams(window.location.search).get('addSource') === '1';
+}
+
interface SourcesSectionProps {
hidden?: boolean;
state?: SourcesState;
@@ -97,7 +104,7 @@ export function SourcesSection({
{t('sources.title')}{' '}
{entries.length}
- {state?.supported && (
+ {state?.supported && isAddSourceEnabled() && (