feat(guide): let a tab type declare its own line in the new-tab list

The native new-tab list rendered one generic line ("opens in the DSH
sidebar") under every plugin entry, so six plugin rows read identically.
`TabDescriptor.description` now carries the real purpose (string or
locale-following thunk; absent falls back to the generic line), and the
six visible builtins declare theirs across all 20 dictionaries.

- service.ts: `TabDescriptor.description` + docs (guide §4.1, §0 row).
- native/: the guide entry's description follows the descriptor; the
  `files` takeover reuses the editor's line.
- builtins: files / changes / tasks / side chat / terminal / browser.
- tests: builtins assert every visible type declares a distinct line;
  the lifecycle test asserts descriptor lines (and the takeover reuse).
This commit is contained in:
Menghuan1918
2026-09-09 22:25:59 +08:00
parent 9ebaa09f80
commit d4765819c0
26 changed files with 186 additions and 5 deletions
+6
View File
@@ -82,6 +82,7 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
return [
{
id: 'editor',
description: () => t('guideDescFiles'),
// The single files window: an editor tab with no path IS the file
// explorer (empty hint + docked tree); with a path it previews/edits
// the file. Visible in the + menu in the explorer's old slot.
@@ -148,6 +149,7 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
// log, and the git status needs a fetch — both stay out of the badge).
id: 'git',
title: () => t('changes'),
description: () => t('guideDescGit'),
icon: (size: number) => <IconDiffOutline16 size={size} />,
order: 20,
single: true,
@@ -170,6 +172,7 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
{
id: 'subagent',
title: () => t('subagent'),
description: () => t('guideDescSubagent'),
icon: (size: number) => <IconThinkOutline16 size={size} />,
order: 30,
single: true,
@@ -198,6 +201,7 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
{
id: 'sidechat',
title: () => t('sideChat'),
description: () => t('guideDescSidechat'),
icon: (size: number) => <IconNewChatOutline16 size={size} />,
order: 35,
// Codex-style: EVERY side conversation is its own tab. A plain open
@@ -243,6 +247,7 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
{
id: 'terminal',
title: () => t('terminal'),
description: () => t('guideDescTerminal'),
icon: (size: number) => <IconTerminalOutline16 size={size} />,
order: 40,
available: (_ctx, _scope, state) => uiTerminalCount(state) < TERMINAL_LIMIT,
@@ -307,6 +312,7 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
{
id: 'browser',
title: () => t('browser'),
description: () => t('guideDescBrowser'),
icon: (size: number) => <IconGlobeOutline16 size={size} />,
order: 50,
// Declarative settings: the sandbox escape hatch, the link-takeover