/** * The 6 built-in tab descriptors: the plugin registers its own pages * (explorer / git / terminal / subagent / editor / diff) through the same * {@link BetterSidebarService} external plugins use — eating its own * dogfood. The terminal descriptor owns its quota (`TERMINAL_LIMIT`) and * mints `terminal:` ids through `createTab`. */ import { IconBranchOutline16, IconCodeOutline16, IconFolderOpen16, IconThinkOutline16 } from '@deepseek-ai/dsh-client-ui-primitives' import type { Context } from '../../context-types.ts' import { allLeaves, isAgentTabId, type SidebarState } from '../state.ts' import { t } from '../locales.ts' import { openSidebarFile } from '../intercept.tsx' import { ExplorerView } from '../ExplorerView.tsx' import { EditorHost } from '../EditorHost.tsx' import { TerminalView } from '../TerminalView.tsx' import { GitView } from '../GitView.tsx' import { DiffTab } from '../DiffTab.tsx' import { SubagentView } from '../SubagentView.tsx' import { IconTerminalOutline16, IconDiffOutline16 } from '../icons.tsx' import type { TabDescriptor } from '../service.ts' /** How many UI-owned terminals may be open at once (agent-owned ones are uncapped). */ export const TERMINAL_LIMIT = 3 /** Count UI-owned terminals (agent:` tabs excluded — they are the model's). */ function uiTerminalCount(state: SidebarState): number { return allLeaves(state.splits) .flatMap(leaf => leaf.tabs) .filter(tab => tab.type === 'terminal' && !isAgentTabId(tab.id)).length } /** The 6 built-in tab descriptors. */ export function builtinTabs(ctx: Context): readonly TabDescriptor[] { return [ { id: 'editor', title: () => t('editor'), icon: (size: number) => , order: -1, hidden: true, dedupeKey: (tab) => tab.path, component: ({ ctx, store, scope, tab }) => ( ), }, { id: 'explorer', title: () => t('explorer'), icon: (size: number) => , order: 10, single: true, component: ({ ctx, store, scope, expanded, onToggleDir, onReferenceFile }) => ( { /* no-op */ })} onOpenFile={(path) => { openSidebarFile(ctx, store, scope.sessionId, path) }} onReferenceFile={onReferenceFile ?? (() => { /* no-op */ })} /> ), }, { id: 'git', title: () => t('git'), icon: (size: number) => , order: 20, single: true, component: ({ ctx, store, scope, onOpenDiff }) => ( { openSidebarFile(ctx, store, scope.sessionId, path) }} onOpenDiff={onOpenDiff ?? (() => { /* no-op */ })} /> ), }, { id: 'subagent', title: () => t('subagent'), icon: (size: number) => , order: 30, single: true, // Declarative settings: the auto-open switch renders under this row in // the Side card settings page (the Subagent page's own related setting). settings: { toggles: [{ key: 'autoOpenSubagent', title: () => t('settingsSubagentTitle'), desc: () => t('settingsSubagentDesc'), }], }, component: ({ ctx, scope, visible, onSubagentJump }) => ( { onSubagentJump?.(address.childSessionId) }} /> ), }, { id: 'terminal', title: () => t('terminal'), icon: (size: number) => , order: 40, available: (_ctx, _scope, state) => uiTerminalCount(state) < TERMINAL_LIMIT, // Declarative settings: the model-facing terminal tools switch renders // under this row in the Side card settings page (the Terminal page's // own related setting; the host gates the toolset on it independently). settings: { toggles: [{ key: 'agentTerminalTools', title: () => t('settingsToolsTitle'), desc: () => t('settingsToolsDesc'), }], }, createTab: (state) => { const count = uiTerminalCount(state) if (count >= TERMINAL_LIMIT) return null return { tab: { id: `terminal:${state.nextTerminal}`, type: 'terminal', title: `${t('terminal')} ${state.nextTerminal}`, }, patch: { nextTerminal: state.nextTerminal + 1 }, } }, component: ({ scope, tab, store }) => ( ), }, { id: 'diff', title: () => t('git'), icon: (size: number) => , order: -1, hidden: true, dedupeKey: (tab) => tab.id, component: ({ scope, tab }) => ( tab.diff === undefined ? null : ), }, ] }