From 20389bf02d4c6bc46ca47caccc2ff87063280aad Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 17 May 2026 23:26:09 -0700 Subject: [PATCH] Gate Agents view behind Experimental (#2182) --- src/main/persistence.test.ts | 30 ++++++++-- src/main/persistence.ts | 13 +++-- .../components/settings/ExperimentalPane.tsx | 12 ++-- .../settings/experimental-search.ts | 12 ++-- .../components/sidebar/SidebarNav.test.tsx | 27 +++++++++ .../src/components/sidebar/SidebarNav.tsx | 56 +++++++++++-------- src/renderer/src/store/slices/ui.ts | 19 +++++-- src/shared/constants.ts | 3 +- src/shared/telemetry-events.ts | 1 + src/shared/types.ts | 7 ++- 10 files changed, 130 insertions(+), 50 deletions(-) create mode 100644 src/renderer/src/components/sidebar/SidebarNav.test.tsx diff --git a/src/main/persistence.test.ts b/src/main/persistence.test.ts index 7b9cc79e0..75a2807e7 100644 --- a/src/main/persistence.test.ts +++ b/src/main/persistence.test.ts @@ -222,7 +222,8 @@ describe('Store', () => { expect(settings.showTasksButton).toBe(true) expect(settings.visibleTaskProviders).toEqual(['github', 'gitlab', 'linear']) expect(settings.openInApplications).toEqual([]) - expect(settings.experimentalActivity).toBe(true) + expect(settings.experimentalActivity).toBe(false) + expect(settings.experimentalActivityDefaultedOffForAllUsers).toBe(true) expect(settings.floatingTerminalEnabled).toBe(true) expect(settings.floatingTerminalDefaultedForAllUsers).toBe(true) expect(settings.notifications.customSoundPath).toBeNull() @@ -615,7 +616,8 @@ describe('Store', () => { expect(store.getSettings().showTasksButton).toBe(true) expect(store.getSettings().combinedDiffFileTreeVisibleByDefault).toBe(false) expect(store.getSettings().visibleTaskProviders).toEqual(['github', 'gitlab', 'linear']) - expect(store.getSettings().experimentalActivity).toBe(true) + expect(store.getSettings().experimentalActivity).toBe(false) + expect(store.getSettings().experimentalActivityDefaultedOffForAllUsers).toBe(true) expect(store.getSettings().notifications.customSoundPath).toBeNull() // repos should be loaded expect(store.getRepos()).toHaveLength(1) @@ -1457,12 +1459,32 @@ describe('Store', () => { expect(store.getSettings().experimentalPet).toBe(true) }) - it('promotes legacy experimentalActivity profiles to default-on', async () => { + it('defaults legacy experimentalActivity profiles off once', async () => { writeDataFile({ schemaVersion: 1, repos: [], worktreeMeta: {}, - settings: { experimentalActivity: false }, + settings: { experimentalActivity: true }, + ui: {}, + githubCache: { pr: {}, issue: {} }, + workspaceSession: {} + }) + + const store = await createStore() + + expect(store.getSettings().experimentalActivity).toBe(false) + expect(store.getSettings().experimentalActivityDefaultedOffForAllUsers).toBe(true) + }) + + it('preserves experimentalActivity after the default-off migration has run', async () => { + writeDataFile({ + schemaVersion: 1, + repos: [], + worktreeMeta: {}, + settings: { + experimentalActivity: true, + experimentalActivityDefaultedOffForAllUsers: true + }, ui: {}, githubCache: { pr: {}, issue: {} }, workspaceSession: {} diff --git a/src/main/persistence.ts b/src/main/persistence.ts index 9ba04be50..f5d548ae2 100644 --- a/src/main/persistence.ts +++ b/src/main/persistence.ts @@ -1210,6 +1210,13 @@ export class Store { const migratedFloatingTerminalEnabled = floatingTerminalDefaultedForAllUsers ? (parsed.settings?.floatingTerminalEnabled ?? true) : true + const experimentalActivityDefaultedOffForAllUsers = + parsed.settings?.experimentalActivityDefaultedOffForAllUsers === true + // Why: the Agents view moved back behind Experimental. Flip every + // pre-migration profile off once, then preserve future user opt-ins. + const migratedExperimentalActivity = experimentalActivityDefaultedOffForAllUsers + ? (parsed.settings?.experimentalActivity ?? false) + : false result = { ...defaults, ...parsed, @@ -1221,10 +1228,8 @@ export class Store { // the old persisted flag forward once so enabled users don't lose it. experimentalPet: parsed.settings?.experimentalPet ?? readLegacySidekickFlag(parsed) ?? false, - // Why: Activity graduated from its experimental gate. Force the - // legacy flag on so existing profiles and rollback builds see the - // same default-on behavior as fresh installs. - experimentalActivity: true, + experimentalActivity: migratedExperimentalActivity, + experimentalActivityDefaultedOffForAllUsers: true, terminalMacOptionAsAlt: migratedOptionAsAlt, terminalMacOptionAsAltMigrated: true, floatingTerminalEnabled: migratedFloatingTerminalEnabled, diff --git a/src/renderer/src/components/settings/ExperimentalPane.tsx b/src/renderer/src/components/settings/ExperimentalPane.tsx index a8cb0a578..9422ca389 100644 --- a/src/renderer/src/components/settings/ExperimentalPane.tsx +++ b/src/renderer/src/components/settings/ExperimentalPane.tsx @@ -23,7 +23,7 @@ export function ExperimentalPane({ }: ExperimentalPaneProps): React.JSX.Element { const searchQuery = useAppStore((s) => s.settingsSearchQuery) const showPet = matchesSettingsSearch(searchQuery, [EXPERIMENTAL_SEARCH_ENTRY.pet]) - const showActivity = matchesSettingsSearch(searchQuery, [EXPERIMENTAL_SEARCH_ENTRY.activity]) + const showAgentsView = matchesSettingsSearch(searchQuery, [EXPERIMENTAL_SEARCH_ENTRY.activity]) const showWorktreeSymlinks = matchesSettingsSearch(searchQuery, [ EXPERIMENTAL_SEARCH_ENTRY.symlinks ]) @@ -69,18 +69,18 @@ export function ExperimentalPane({ ) : null} - {showActivity ? ( + {showAgentsView ? (
- +

- Adds an Activity entry under Tasks with a threaded worktree feed for completed + Adds an Agents entry to the left sidebar with a threaded worktree feed for completed agents, blocking questions, unread state, and worktree creation events. Experimental — the event model and UI may change.

diff --git a/src/renderer/src/components/settings/experimental-search.ts b/src/renderer/src/components/settings/experimental-search.ts index 0fab37be7..102c44c55 100644 --- a/src/renderer/src/components/settings/experimental-search.ts +++ b/src/renderer/src/components/settings/experimental-search.ts @@ -16,17 +16,19 @@ export const EXPERIMENTAL_PANE_SEARCH_ENTRIES: SettingsSearchEntry[] = [ ] }, { - title: 'Activity Page', - description: 'Slack-style worktree activity feed for agent completions and blocking states.', + title: 'Agents View', + description: 'Threaded left-sidebar feed for agent completions and blocking states.', keywords: [ 'experimental', + 'agents', + 'agents view', 'activity', 'notifications', - 'agents', 'worktrees', 'timeline', 'unread', - 'bell' + 'bell', + 'sidebar' ] }, { @@ -61,6 +63,6 @@ function findEntry(title: string): SettingsSearchEntry { export const EXPERIMENTAL_SEARCH_ENTRY = { pet: findEntry('Pet'), - activity: findEntry('Activity Page'), + activity: findEntry('Agents View'), symlinks: findEntry('Symlinks on worktrees') } as const diff --git a/src/renderer/src/components/sidebar/SidebarNav.test.tsx b/src/renderer/src/components/sidebar/SidebarNav.test.tsx new file mode 100644 index 000000000..56fc3363b --- /dev/null +++ b/src/renderer/src/components/sidebar/SidebarNav.test.tsx @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest' +import { getDefaultSettings } from '../../../../shared/constants' +import { shouldShowAgentsButton } from './SidebarNav' + +describe('SidebarNav', () => { + it('hides the Agents entry while settings are loading', () => { + expect(shouldShowAgentsButton(null)).toBe(false) + }) + + it('hides the Agents entry while the experimental Agents view is off', () => { + expect( + shouldShowAgentsButton({ + ...getDefaultSettings('/tmp'), + experimentalActivity: false + }) + ).toBe(false) + }) + + it('shows the Agents entry when the experimental Agents view is on', () => { + expect( + shouldShowAgentsButton({ + ...getDefaultSettings('/tmp'), + experimentalActivity: true + }) + ).toBe(true) + }) +}) diff --git a/src/renderer/src/components/sidebar/SidebarNav.tsx b/src/renderer/src/components/sidebar/SidebarNav.tsx index 9d9cf3744..ee9bb9c9c 100644 --- a/src/renderer/src/components/sidebar/SidebarNav.tsx +++ b/src/renderer/src/components/sidebar/SidebarNav.tsx @@ -4,6 +4,7 @@ import { useAppStore } from '@/store' import { useRepoMap } from '@/store/selectors' import { cn } from '@/lib/utils' import { isGitRepoKind } from '../../../../shared/repo-kind' +import type { GlobalSettings } from '../../../../shared/types' import { getTaskPresetQuery, PER_REPO_FETCH_LIMIT } from '@/lib/new-workspace' import { LinearIcon } from '@/components/icons/LinearIcon' import { migrationUnsupportedToAgentStatusEntry } from '@/lib/migration-unsupported-agent-entry' @@ -18,6 +19,12 @@ const isMac = typeof navigator !== 'undefined' && navigator.userAgent.includes(' // rows under that strip should remain clickable when their bounds overlap. const SIDEBAR_NAV_HIT_TARGET_CLASS = 'relative z-20' +export function shouldShowAgentsButton( + settings: Pick | null | undefined +): boolean { + return settings?.experimentalActivity === true +} + const SidebarNav = React.memo(function SidebarNav() { const openTaskPage = useAppStore((s) => s.openTaskPage) const openAutomationsPage = useAppStore((s) => s.openAutomationsPage) @@ -38,6 +45,7 @@ const SidebarNav = React.memo(function SidebarNav() { const linearStatus = useAppStore((s) => s.linearStatus) const linearStatusChecked = useAppStore((s) => s.linearStatusChecked) const checkLinearConnection = useAppStore((s) => s.checkLinearConnection) + const showAgentsButton = useAppStore((s) => shouldShowAgentsButton(s.settings)) const preferredVisibleTaskProviders = React.useMemo( () => normalizeVisibleTaskProviders(rawVisibleTaskProviders), [rawVisibleTaskProviders] @@ -241,29 +249,31 @@ const SidebarNav = React.memo(function SidebarNav() { /> Automations - + {showAgentsButton ? ( + + ) : null}