mirror of
https://github.com/stablyai/orca.git
synced 2026-10-05 16:02:22 +00:00
- Remove `experimentalUnifiedNewTabLauncher` flag and gate; the unified launcher is now always enabled - Drop the settings toggle from ExperimentalPane and search index - Update placeholder/aria text to "Open any file, URL, agent, ..." - Restyle the input: borderless, flush with dropdown edge, no search icon - Update test fixtures to include agent-detection state fields now unconditionally read by TabBarCreateEntry
This commit is contained in:
@@ -130,7 +130,6 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
|
||||
experimentalTerminalAttention: false,
|
||||
experimentalCompactWorktreeCards: false,
|
||||
experimentalWorktreeSymlinks: false,
|
||||
experimentalUnifiedNewTabLauncher: false,
|
||||
terminalWindowsShell: 'powershell.exe',
|
||||
terminalWindowsPowerShellImplementation: 'powershell.exe',
|
||||
enableGitHubAttribution: true,
|
||||
|
||||
@@ -134,7 +134,6 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
|
||||
experimentalTerminalAttention: false,
|
||||
experimentalCompactWorktreeCards: false,
|
||||
experimentalWorktreeSymlinks: false,
|
||||
experimentalUnifiedNewTabLauncher: false,
|
||||
terminalWindowsShell: 'powershell.exe',
|
||||
terminalWindowsPowerShellImplementation: 'powershell.exe',
|
||||
enableGitHubAttribution: true,
|
||||
|
||||
@@ -33,9 +33,6 @@ export function ExperimentalPane({
|
||||
const showWorktreeSymlinks = matchesSettingsSearch(searchQuery, [
|
||||
EXPERIMENTAL_SEARCH_ENTRY.symlinks
|
||||
])
|
||||
const showUnifiedNewTabLauncher = matchesSettingsSearch(searchQuery, [
|
||||
EXPERIMENTAL_SEARCH_ENTRY.unifiedNewTabLauncher
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -233,46 +230,6 @@ export function ExperimentalPane({
|
||||
</SearchableSetting>
|
||||
) : null}
|
||||
|
||||
{showUnifiedNewTabLauncher ? (
|
||||
<SearchableSetting
|
||||
title="Smart New Tab menu"
|
||||
description="Type in the New Tab menu to open a terminal, launch an agent, visit a URL, or open/create a file."
|
||||
keywords={EXPERIMENTAL_SEARCH_ENTRY.unifiedNewTabLauncher.keywords}
|
||||
className="space-y-3 py-2"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0 shrink space-y-0.5">
|
||||
<Label>Smart New Tab menu</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Type in the New Tab menu to open a terminal, launch an agent, visit a URL, or
|
||||
open/create a file.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={settings.experimentalUnifiedNewTabLauncher}
|
||||
onClick={() =>
|
||||
updateSettings({
|
||||
experimentalUnifiedNewTabLauncher: !settings.experimentalUnifiedNewTabLauncher
|
||||
})
|
||||
}
|
||||
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors ${
|
||||
settings.experimentalUnifiedNewTabLauncher
|
||||
? 'bg-foreground'
|
||||
: 'bg-muted-foreground/30'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-3.5 w-3.5 transform rounded-full bg-background shadow-sm transition-transform ${
|
||||
settings.experimentalUnifiedNewTabLauncher ? 'translate-x-4' : 'translate-x-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</SearchableSetting>
|
||||
) : null}
|
||||
|
||||
{hiddenExperimentalUnlocked ? <HiddenExperimentalGroup /> : null}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -79,26 +79,6 @@ export const EXPERIMENTAL_PANE_SEARCH_ENTRIES: SettingsSearchEntry[] = [
|
||||
'env',
|
||||
'node_modules'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Smart New Tab menu',
|
||||
description:
|
||||
'Type in the New Tab menu to open a terminal, launch an agent, visit a URL, or open/create a file.',
|
||||
keywords: [
|
||||
'experimental',
|
||||
'smart',
|
||||
'new tab',
|
||||
'new tab menu',
|
||||
'launcher',
|
||||
'unified',
|
||||
'plus',
|
||||
'terminal',
|
||||
'agents',
|
||||
'claude',
|
||||
'codex',
|
||||
'url',
|
||||
'file'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -118,6 +98,5 @@ export const EXPERIMENTAL_SEARCH_ENTRY = {
|
||||
activity: findEntry('Agents View'),
|
||||
terminalAttention: findEntry('Terminal attention'),
|
||||
compactWorktreeCards: findEntry('Compact worktree cards'),
|
||||
symlinks: findEntry('Symlinks on worktrees'),
|
||||
unifiedNewTabLauncher: findEntry('Smart New Tab menu')
|
||||
symlinks: findEntry('Symlinks on worktrees')
|
||||
} as const
|
||||
|
||||
@@ -237,17 +237,11 @@ function TabBarInner({
|
||||
const repo = worktree ? s.repos?.find((entry) => entry.id === worktree.repoId) : null
|
||||
return Boolean(repo?.connectionId)
|
||||
})
|
||||
const unifiedNewTabLauncherEnabled = useAppStore(
|
||||
(s) => s.settings?.experimentalUnifiedNewTabLauncher === true
|
||||
)
|
||||
const defaultAgent = useAppStore((s) => s.settings?.defaultTuiAgent)
|
||||
const agentCmdOverrides = useAppStore(
|
||||
(s) => s.settings?.agentCmdOverrides ?? EMPTY_AGENT_CMD_OVERRIDES
|
||||
)
|
||||
const connectionId = useAppStore((s) => {
|
||||
if (!unifiedNewTabLauncherEnabled) {
|
||||
return undefined
|
||||
}
|
||||
const allWorktrees = Object.values(s.worktreesByRepo ?? {}).flat()
|
||||
const worktree = allWorktrees.find((w) => w.id === worktreeId)
|
||||
if (!worktree) {
|
||||
@@ -969,7 +963,7 @@ function TabBarInner({
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
sideOffset={6}
|
||||
className={`${unifiedNewTabLauncherEnabled ? 'w-72 max-w-[calc(100vw-1rem)]' : 'min-w-[11rem]'} rounded-[11px] border-border/80 p-1 shadow-[0_16px_36px_rgba(0,0,0,0.24)]`}
|
||||
className="w-72 max-w-[calc(100vw-1rem)] rounded-[11px] border-border/80 p-1 shadow-[0_16px_36px_rgba(0,0,0,0.24)]"
|
||||
onCloseAutoFocus={(e) => {
|
||||
// Why: terminal-producing menu actions activate a freshly-mounted
|
||||
// xterm. Radix's default focus restore sends focus back to the "+"
|
||||
@@ -978,7 +972,7 @@ function TabBarInner({
|
||||
runPendingNewTabMenuFocusAfterClose()
|
||||
}}
|
||||
>
|
||||
{!terminalOnly && onOpenEntry && unifiedNewTabLauncherEnabled ? (
|
||||
{!terminalOnly && onOpenEntry ? (
|
||||
<>
|
||||
<TabBarCreateEntry
|
||||
worktreeId={worktreeId}
|
||||
|
||||
@@ -11,6 +11,10 @@ const appStoreSnapshot: {
|
||||
worktreesByRepo: Record<string, { id: string; repoId: string }[]>
|
||||
unifiedTabsByWorktree: Record<string, unknown[]>
|
||||
activeGroupIdByWorktree: Record<string, string>
|
||||
detectedAgentIds: string[] | null
|
||||
remoteDetectedAgentIds: Record<string, string[]>
|
||||
isDetectingAgents: boolean
|
||||
isDetectingRemoteAgents: Record<string, boolean>
|
||||
} = {
|
||||
activeTabId: null,
|
||||
activeTabType: null,
|
||||
@@ -18,7 +22,11 @@ const appStoreSnapshot: {
|
||||
repos: [],
|
||||
worktreesByRepo: {},
|
||||
unifiedTabsByWorktree: {},
|
||||
activeGroupIdByWorktree: {}
|
||||
activeGroupIdByWorktree: {},
|
||||
detectedAgentIds: null,
|
||||
remoteDetectedAgentIds: {},
|
||||
isDetectingAgents: false,
|
||||
isDetectingRemoteAgents: {}
|
||||
}
|
||||
const pinTabMock: (tabId: string) => void = vi.fn()
|
||||
const unpinTabMock: (tabId: string) => void = vi.fn()
|
||||
@@ -33,6 +41,10 @@ const useAppStoreMock = vi.fn(
|
||||
worktreesByRepo: Record<string, { id: string; repoId: string }[]>
|
||||
unifiedTabsByWorktree: Record<string, unknown[]>
|
||||
activeGroupIdByWorktree: Record<string, string>
|
||||
detectedAgentIds: string[] | null
|
||||
remoteDetectedAgentIds: Record<string, string[]>
|
||||
isDetectingAgents: boolean
|
||||
isDetectingRemoteAgents: Record<string, boolean>
|
||||
pinTab: typeof pinTabMock
|
||||
unpinTab: typeof unpinTabMock
|
||||
settings: {
|
||||
@@ -50,6 +62,10 @@ const useAppStoreMock = vi.fn(
|
||||
worktreesByRepo: appStoreSnapshot.worktreesByRepo,
|
||||
unifiedTabsByWorktree: appStoreSnapshot.unifiedTabsByWorktree,
|
||||
activeGroupIdByWorktree: appStoreSnapshot.activeGroupIdByWorktree,
|
||||
detectedAgentIds: appStoreSnapshot.detectedAgentIds,
|
||||
remoteDetectedAgentIds: appStoreSnapshot.remoteDetectedAgentIds,
|
||||
isDetectingAgents: appStoreSnapshot.isDetectingAgents,
|
||||
isDetectingRemoteAgents: appStoreSnapshot.isDetectingRemoteAgents,
|
||||
pinTab: pinTabMock,
|
||||
unpinTab: unpinTabMock,
|
||||
settings: {
|
||||
@@ -107,6 +123,10 @@ useAppStoreExport.getState = vi.fn(() => ({
|
||||
worktreesByRepo: appStoreSnapshot.worktreesByRepo,
|
||||
unifiedTabsByWorktree: appStoreSnapshot.unifiedTabsByWorktree,
|
||||
activeGroupIdByWorktree: appStoreSnapshot.activeGroupIdByWorktree,
|
||||
detectedAgentIds: appStoreSnapshot.detectedAgentIds,
|
||||
remoteDetectedAgentIds: appStoreSnapshot.remoteDetectedAgentIds,
|
||||
isDetectingAgents: appStoreSnapshot.isDetectingAgents,
|
||||
isDetectingRemoteAgents: appStoreSnapshot.isDetectingRemoteAgents,
|
||||
pinTab: pinTabMock,
|
||||
unpinTab: unpinTabMock,
|
||||
settings: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { FilePlus, FileText, Globe, Loader2, Search } from 'lucide-react'
|
||||
import { FilePlus, FileText, Globe, Loader2 } from 'lucide-react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { AgentIcon } from '@/lib/agent-catalog'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -100,7 +100,7 @@ export default function TabBarCreateEntry({
|
||||
const statusMessage =
|
||||
statusOption?.classification.kind === 'empty' || statusOption?.classification.kind === 'blocked'
|
||||
? statusOption.classification.message
|
||||
: 'URL, file, or new file'
|
||||
: 'Open any file, URL, agent, ...'
|
||||
|
||||
const submitOption = (option?: ActiveOption) => {
|
||||
if (disabled || pending) {
|
||||
@@ -143,7 +143,7 @@ export default function TabBarCreateEntry({
|
||||
|
||||
return (
|
||||
<form
|
||||
className="px-1 pb-1"
|
||||
className="pb-1"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
submitOption()
|
||||
@@ -164,11 +164,7 @@ export default function TabBarCreateEntry({
|
||||
}}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="relative">
|
||||
<Search
|
||||
className="pointer-events-none absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="-mx-1 flex items-center border-b border-black/14 px-3 dark:border-white/14">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={query}
|
||||
@@ -177,14 +173,14 @@ export default function TabBarCreateEntry({
|
||||
setError(null)
|
||||
}}
|
||||
disabled={disabled}
|
||||
aria-label="Open URL, file, or new file"
|
||||
aria-label="Open any file, URL, agent, ..."
|
||||
aria-invalid={error ? true : undefined}
|
||||
placeholder="URL, file, or new file"
|
||||
className="h-8 rounded-[7px] pl-7 pr-2 text-[12px]"
|
||||
placeholder="Open any file, URL, agent, ..."
|
||||
className="h-9 rounded-none border-0 bg-transparent px-0 text-[12px] shadow-none focus-visible:border-0 focus-visible:ring-0 aria-invalid:border-0 aria-invalid:ring-0 dark:bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
{error || activeOptions.length > 0 || hasQuery ? (
|
||||
<div className="mt-1 space-y-0.5">
|
||||
<div className="mt-1 space-y-0.5 px-1">
|
||||
{error ? (
|
||||
<EntryStatusRow message={error} />
|
||||
) : activeOptions.length > 0 ? (
|
||||
|
||||
@@ -226,7 +226,9 @@ export function getTabEntryOptions(
|
||||
): TabEntryOption[] {
|
||||
const trimmed = query.trim()
|
||||
if (!trimmed) {
|
||||
return [{ id: 'empty', classification: { kind: 'empty', message: 'URL, file, or new file' } }]
|
||||
return [
|
||||
{ id: 'empty', classification: { kind: 'empty', message: 'Open any file, URL, agent, ...' } }
|
||||
]
|
||||
}
|
||||
|
||||
const explicitUrl = classifyExplicitUrl(trimmed)
|
||||
|
||||
@@ -309,7 +309,6 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
|
||||
experimentalTerminalAttention: false,
|
||||
experimentalCompactWorktreeCards: false,
|
||||
experimentalWorktreeSymlinks: false,
|
||||
experimentalUnifiedNewTabLauncher: false,
|
||||
// Why: local desktop remains the default server until the user explicitly
|
||||
// selects a saved runtime environment.
|
||||
activeRuntimeEnvironmentId: null,
|
||||
|
||||
@@ -278,7 +278,6 @@ export const SETTINGS_CHANGED_WHITELIST = [
|
||||
'experimentalActivity',
|
||||
'experimentalTerminalAttention',
|
||||
'experimentalWorktreeSymlinks',
|
||||
'experimentalUnifiedNewTabLauncher',
|
||||
'geminiCliOAuthEnabled'
|
||||
] as const satisfies readonly BooleanGlobalSettingsKey[]
|
||||
export const settingsChangedKeySchema = z.enum(SETTINGS_CHANGED_WHITELIST)
|
||||
|
||||
+1
-3
@@ -2284,9 +2284,7 @@ export type GlobalSettings = {
|
||||
* configuration surface and edge cases (conflicts with existing paths,
|
||||
* cleanup on worktree delete) are still being worked out. */
|
||||
experimentalWorktreeSymlinks: boolean
|
||||
/** Experimental: replaces the New Tab menu's static preview row with a
|
||||
* command-style launcher for terminals, detected agents, URLs, and files. */
|
||||
experimentalUnifiedNewTabLauncher: boolean
|
||||
|
||||
/** Active non-local runtime environment for client-routed RPC. `null`
|
||||
* preserves the current local desktop behavior. */
|
||||
activeRuntimeEnvironmentId?: string | null
|
||||
|
||||
Reference in New Issue
Block a user