diff --git a/docs/external-plugin-guide.md b/docs/external-plugin-guide.md
index 562a2ad..8a78d32 100644
--- a/docs/external-plugin-guide.md
+++ b/docs/external-plugin-guide.md
@@ -27,6 +27,7 @@
| 内置类型接管 | 插件的 `editor` 类型以 `extension` 优先级认领 `dsh-resource://file/**`(压过内置 `text` 预览),并接管内置 `files` 页面 kind(`openTab('files')` 打开插件的文件树);插件卸载/禁用时内置实现自动复位 |
| 终端上限 | 终端 tab 的数量上限只统计插件自己底部工作台里的终端;原生栏里的终端不计入 |
| 底部工作台的开合 | 落到底部工作台的打开一律展开它(新建与聚焦都算),因此 `openTab` 的落点永远可见;开合按钮注册在 DSH 会话头的 utilities 槽(`conversation.session.header.utilities`),不在插件自己的宿主里 |
+| 新建标签页列表 | 每个 tab 类型在原生 guide 里占一行:标题取 `title`,说明取 `description`(未声明则回退通用句),图标取 `icon`;`hidden: true` 的类型不占行。插件的 `editor` 类型不再单独占行(它认领的文件资源由 `files` 接管页承载同一视图) |
| 已移除 | 插件自绘右侧面板(含宽度拖拽 / 新会话默认宽度)与**自由窗口**(`features` 里的 `'floatWindows'` 已删除,v0.18.x 及更早版本的消费者请勿再 gate 该能力);`openByDefault` / `defaultWidthPercent` / `changesDiffFloat` 三个设置项同步删除(旧文档里的键会被忽略) |
---
@@ -178,6 +179,12 @@ interface TabDescriptor {
id: string
/** 标题(i18n 友好:传字符串或返回字符串的函数) */
title: string | (() => string)
+ /**
+ * 一行说明,渲染在标题下方(DSH 原生右侧栏的新建标签页 / guide 列表)。
+ * 不声明时回退到通用句「在 DSH 原生右侧栏打开」——所有插件页面就都长一样了,
+ * 所以请写清这一页到底做什么。函数形式在渲染时求值,跟随语言。
+ */
+ description?: string | (() => string)
/** 图标:ReactNode 或 (size: number) => ReactNode */
icon?: ReactNode | ((size: number) => ReactNode)
/** + 菜单排序(升序);默认 100。内置:editor=10, git=20, subagent=30, sidechat=35, terminal=40, browser=50 */
diff --git a/src/client/builtins/tabs.tsx b/src/client/builtins/tabs.tsx
index 2bc5fc5..0982808 100644
--- a/src/client/builtins/tabs.tsx
+++ b/src/client/builtins/tabs.tsx
@@ -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) => ,
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) => ,
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) => ,
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) => ,
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) => ,
order: 50,
// Declarative settings: the sandbox escape hatch, the link-takeover
diff --git a/src/client/locales-ar.ts b/src/client/locales-ar.ts
index e9ae36c..c13168f 100644
--- a/src/client/locales-ar.ts
+++ b/src/client/locales-ar.ts
@@ -19,6 +19,12 @@
export const ar: Record = {
files: 'الملفات',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'استعرض شجرة ملفات مساحة العمل؛ انقر للمعاينة أو التحرير',
+ guideDescGit: 'تغييرات Git وتعديلات ملفات هذه الجلسة في تبويب واحد',
+ guideDescSubagent: 'بنية الوكلاء الفرعيين والمهام الخلفية مباشرةً',
+ guideDescSidechat: 'خيط متابعة مستقل يرث سياق هذه الجلسة',
+ guideDescTerminal: 'صدفة حقيقية (xterm + pty) مع إعادة الاتصال',
+ guideDescBrowser: 'متصفح مدمج يمكنه فتح روابط المحادثة',
changesSessionEmpty: 'لا توجد عمليات ملفات في هذه الجلسة بعد',
changesRead: 'قراءة',
changesMdReading: 'قراءة',
diff --git a/src/client/locales-de.ts b/src/client/locales-de.ts
index d901a5b..5012597 100644
--- a/src/client/locales-de.ts
+++ b/src/client/locales-de.ts
@@ -4,6 +4,12 @@
export const de: Record = {
files: 'Dateien',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Dateibaum des Arbeitsbereichs durchsuchen; Klick öffnet Vorschau oder Editor',
+ guideDescGit: 'Git-Änderungen und Dateiänderungen dieser Sitzung in einem Tab',
+ guideDescSubagent: 'Subagent-Topologie und Hintergrundaufgaben, live',
+ guideDescSidechat: 'Ein unabhängiger Folge-Thread mit dem Kontext dieser Sitzung',
+ guideDescTerminal: 'Eine echte Shell (xterm + pty) mit Wiederverbindung',
+ guideDescBrowser: 'Eingebetteter Browser, der Chat-Links übernehmen kann',
changesSessionEmpty: 'Noch keine Dateioperationen in dieser Sitzung',
changesRead: 'Lesen',
changesMdReading: 'Lesen',
diff --git a/src/client/locales-fr.ts b/src/client/locales-fr.ts
index a7a0a8f..c01741d 100644
--- a/src/client/locales-fr.ts
+++ b/src/client/locales-fr.ts
@@ -11,6 +11,12 @@
export const fr: Record = {
files: 'Fichiers',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Parcourir l\'arborescence du workspace ; un clic ouvre l\'aperçu ou l\'édition',
+ guideDescGit: 'Modifications Git et éditions de cette session dans un seul onglet',
+ guideDescSubagent: 'Topologie des sous-agents et tâches d\'arrière-plan, en direct',
+ guideDescSidechat: 'Un fil de suivi indépendant héritant du contexte de cette session',
+ guideDescTerminal: 'Un vrai shell (xterm + pty) avec reconnexion',
+ guideDescBrowser: 'Navigateur intégré, qui peut reprendre les liens du chat',
changesSessionEmpty: 'Aucune opération de fichier dans cette session',
changesRead: 'Lecture',
changesMdReading: 'Lecture',
diff --git a/src/client/locales-hi.ts b/src/client/locales-hi.ts
index 7703a95..9c55809 100644
--- a/src/client/locales-hi.ts
+++ b/src/client/locales-hi.ts
@@ -18,6 +18,12 @@
export const hi: Record = {
files: 'फ़ाइलें',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'वर्कस्पेस फ़ाइल ट्री देखें; क्लिक करने पर प्रीव्यू या संपादन खुलता है',
+ guideDescGit: 'Git बदलाव और इस सत्र के फ़ाइल बदलाव एक ही टैब में',
+ guideDescSubagent: 'सबएजेंट टोपोलॉजी और बैकग्राउंड कार्य, लाइव',
+ guideDescSidechat: 'इस सत्र का संदर्भ विरासत में लेने वाला स्वतंत्र फ़ॉलो-अप थ्रेड',
+ guideDescTerminal: 'असली शेल (xterm + pty), रीकनेक्ट सहित',
+ guideDescBrowser: 'अंतर्निहित ब्राउज़र, चैट के लिंक भी खोल सकता है',
changesSessionEmpty: 'इस सत्र में अभी कोई फ़ाइल ऑपरेशन नहीं',
changesRead: 'पढ़ना',
changesMdReading: 'पठन',
diff --git a/src/client/locales-id.ts b/src/client/locales-id.ts
index cefa087..88dc751 100644
--- a/src/client/locales-id.ts
+++ b/src/client/locales-id.ts
@@ -16,6 +16,12 @@
export const id: Record = {
files: 'Berkas',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Telusuri pohon berkas workspace; klik untuk pratinjau atau edit',
+ guideDescGit: 'Perubahan Git dan perubahan berkas sesi ini dalam satu tab',
+ guideDescSubagent: 'Topologi subagent dan tugas latar belakang, langsung',
+ guideDescSidechat: 'Utas lanjutan independen yang mewarisi konteks sesi ini',
+ guideDescTerminal: 'Shell sungguhan (xterm + pty), bisa menyambung ulang',
+ guideDescBrowser: 'Peramban tertanam, bisa mengambil alih tautan chat',
changesSessionEmpty: 'Belum ada operasi file di sesi ini',
changesRead: 'Baca',
changesMdReading: 'Baca',
diff --git a/src/client/locales-it.ts b/src/client/locales-it.ts
index ae54f55..e47bdcf 100644
--- a/src/client/locales-it.ts
+++ b/src/client/locales-it.ts
@@ -9,6 +9,12 @@
export const it: Record = {
files: 'File',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Sfoglia l\'albero del workspace; un clic apre anteprima o editor',
+ guideDescGit: 'Modifiche Git e modifiche ai file di questa sessione in un solo tab',
+ guideDescSubagent: 'Topologia dei subagent e attività in background, in tempo reale',
+ guideDescSidechat: 'Un thread di approfondimento indipendente che eredita il contesto della sessione',
+ guideDescTerminal: 'Una vera shell (xterm + pty) con riconnessione',
+ guideDescBrowser: 'Browser integrato, può aprire i link della chat',
changesSessionEmpty: 'Ancora nessuna operazione sui file in questa sessione',
changesRead: 'Lettura',
changesMdReading: 'Lettura',
diff --git a/src/client/locales-ja.ts b/src/client/locales-ja.ts
index b0d412d..6f03fea 100644
--- a/src/client/locales-ja.ts
+++ b/src/client/locales-ja.ts
@@ -18,6 +18,12 @@
export const ja: Record = {
files: 'ファイル',
nativeGuideDesc: 'DSH のサイドバーで開く',
+ guideDescFiles: 'ワークスペースのファイルツリーを閲覧し、クリックでプレビュー/編集',
+ guideDescGit: 'Git の変更と今回のセッションのファイル変更を 1 つのタブに',
+ guideDescSubagent: 'サブエージェントの構成とバックグラウンドタスクをリアルタイム表示',
+ guideDescSidechat: '現在のセッションの文脈を引き継ぐ独立した追加スレッド',
+ guideDescTerminal: '本物のシェル(xterm + pty)、再接続対応',
+ guideDescBrowser: '埋め込みブラウザ。チャット内のリンクも引き受け可能',
changesSessionEmpty: 'このセッションにはファイル操作がまだありません',
changesRead: '読み取り',
changesMdReading: '閲覧',
diff --git a/src/client/locales-ko.ts b/src/client/locales-ko.ts
index 3654dd8..8be6feb 100644
--- a/src/client/locales-ko.ts
+++ b/src/client/locales-ko.ts
@@ -10,6 +10,12 @@
export const ko: Record = {
files: '파일',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: '워크스페이스 파일 트리를 보고 클릭하면 미리보기/편집',
+ guideDescGit: 'Git 변경 사항과 이번 세션의 파일 변경을 한 탭에서',
+ guideDescSubagent: '하위 에이전트 구조와 백그라운드 작업을 실시간으로',
+ guideDescSidechat: '현재 세션의 컨텍스트를 이어받는 독립 후속 스레드',
+ guideDescTerminal: '실제 셸(xterm + pty), 재연결 지원',
+ guideDescBrowser: '내장 브라우저, 채팅 링크도 대신 열 수 있음',
changesSessionEmpty: '이 세션에 아직 파일 작업이 없습니다',
changesRead: '읽기',
changesMdReading: '읽기',
diff --git a/src/client/locales-nl.ts b/src/client/locales-nl.ts
index d9183e5..dad2700 100644
--- a/src/client/locales-nl.ts
+++ b/src/client/locales-nl.ts
@@ -16,6 +16,12 @@
export const nl: Record = {
files: 'Bestanden',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Blader door de bestandsboom van de workspace; klik opent voorbeeld of editor',
+ guideDescGit: 'Git-wijzigingen en bestandswijzigingen van deze sessie in één tabblad',
+ guideDescSubagent: 'Subagent-topologie en achtergrondtaken, live',
+ guideDescSidechat: 'Een onafhankelijke vervolgthread met de context van deze sessie',
+ guideDescTerminal: 'Een echte shell (xterm + pty) met herverbinding',
+ guideDescBrowser: 'Ingebouwde browser die chatlinks kan overnemen',
changesSessionEmpty: 'Nog geen bestandsbewerkingen in deze sessie',
changesRead: 'Lezen',
changesMdReading: 'Lezen',
diff --git a/src/client/locales-pl.ts b/src/client/locales-pl.ts
index b9dbc76..77758c4 100644
--- a/src/client/locales-pl.ts
+++ b/src/client/locales-pl.ts
@@ -20,6 +20,12 @@
export const pl: Record = {
files: 'Pliki',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Przeglądaj drzewo plików obszaru roboczego; klik otwiera podgląd lub edycję',
+ guideDescGit: 'Zmiany Git i zmiany plików z tej sesji w jednej karcie',
+ guideDescSubagent: 'Topologia subagentów i zadania w tle na żywo',
+ guideDescSidechat: 'Niezależny wątek uzupełniający dziedziczący kontekst tej sesji',
+ guideDescTerminal: 'Prawdziwy shell (xterm + pty) z ponownym łączeniem',
+ guideDescBrowser: 'Wbudowana przeglądarka, może przejmować linki z czatu',
changesSessionEmpty: 'Brak operacji na plikach w tej sesji',
changesRead: 'Odczyt',
changesMdReading: 'Czytanie',
diff --git a/src/client/locales-pt.ts b/src/client/locales-pt.ts
index 4b6a71e..9903ecd 100644
--- a/src/client/locales-pt.ts
+++ b/src/client/locales-pt.ts
@@ -1,6 +1,12 @@
export const pt: Record = {
files: 'Arquivos',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Navegue pela árvore do workspace; um clique abre a pré-visualização ou a edição',
+ guideDescGit: 'Alterações do Git e edições desta sessão em um único separador',
+ guideDescSubagent: 'Topologia de subagentes e tarefas em segundo plano, ao vivo',
+ guideDescSidechat: 'Uma conversa paralela independente que herda o contexto desta sessão',
+ guideDescTerminal: 'Um shell real (xterm + pty) com reconexão',
+ guideDescBrowser: 'Navegador incorporado, que pode abrir links do chat',
changesSessionEmpty: 'Ainda sem operações de arquivo nesta sessão',
changesRead: 'Leitura',
changesMdReading: 'Leitura',
diff --git a/src/client/locales-ru.ts b/src/client/locales-ru.ts
index 722dacb..0e47e5d 100644
--- a/src/client/locales-ru.ts
+++ b/src/client/locales-ru.ts
@@ -14,6 +14,12 @@
export const ru: Record = {
files: 'Файлы',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Дерево файлов рабочей области: клик открывает просмотр или редактор',
+ guideDescGit: 'Изменения Git и правки файлов этой сессии в одной вкладке',
+ guideDescSubagent: 'Топология субагентов и фоновые задачи в реальном времени',
+ guideDescSidechat: 'Независимый дополнительный тред с контекстом этой сессии',
+ guideDescTerminal: 'Настоящий shell (xterm + pty) с переподключением',
+ guideDescBrowser: 'Встроенный браузер, перехватывает ссылки из чата',
changesSessionEmpty: 'В этой сессии пока нет файловых операций',
changesRead: 'Чтение',
changesMdReading: 'Чтение',
diff --git a/src/client/locales-sv.ts b/src/client/locales-sv.ts
index f70ab43..7517621 100644
--- a/src/client/locales-sv.ts
+++ b/src/client/locales-sv.ts
@@ -1,6 +1,12 @@
export const sv: Record = {
files: 'Filer',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Bläddra i arbetsytans filträd; klick öppnar förhandsvisning eller redigering',
+ guideDescGit: 'Git-ändringar och sessionens filändringar i en flik',
+ guideDescSubagent: 'Subagenttopologi och bakgrundsjobb, live',
+ guideDescSidechat: 'En oberoende uppföljningstråd som ärver sessionens kontext',
+ guideDescTerminal: 'Ett riktigt skal (xterm + pty) med återanslutning',
+ guideDescBrowser: 'Inbäddad webbläsare som kan ta över chattlänkar',
changesSessionEmpty: 'Inga filoperationer i den här sessionen ännu',
changesRead: 'Läs',
changesMdReading: 'Läsning',
diff --git a/src/client/locales-th.ts b/src/client/locales-th.ts
index d360d16..64c133b 100644
--- a/src/client/locales-th.ts
+++ b/src/client/locales-th.ts
@@ -18,6 +18,12 @@
export const th: Record = {
files: 'ไฟล์',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'เรียกดูแผนผังไฟล์ของเวิร์กสเปซ คลิกเพื่อดูตัวอย่างหรือแก้ไข',
+ guideDescGit: 'การเปลี่ยนแปลง Git และไฟล์ที่แก้ในเซสชันนี้ในแท็บเดียว',
+ guideDescSubagent: 'โครงสร้างซับเอเจนต์และงานเบื้องหลังแบบเรียลไทม์',
+ guideDescSidechat: 'เธรดถามต่ออิสระที่สืบทอดบริบทของเซสชันนี้',
+ guideDescTerminal: 'เชลล์จริง (xterm + pty) เชื่อมต่อใหม่ได้',
+ guideDescBrowser: 'เบราว์เซอร์ในตัว รับช่วงลิงก์จากแชทได้',
changesSessionEmpty: 'ยังไม่มีการดำเนินการไฟล์ในเซสชันนี้',
changesRead: 'อ่าน',
changesMdReading: 'อ่าน',
diff --git a/src/client/locales-tr.ts b/src/client/locales-tr.ts
index 50da4a8..27c9fb9 100644
--- a/src/client/locales-tr.ts
+++ b/src/client/locales-tr.ts
@@ -18,6 +18,12 @@
export const tr: Record = {
files: 'Dosyalar',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Çalışma alanı dosya ağacına göz atın; tıklayınca önizleme veya düzenleme açılır',
+ guideDescGit: 'Git değişiklikleri ve bu oturumun dosya değişiklikleri tek sekmede',
+ guideDescSubagent: 'Alt ajan topolojisi ve arka plan görevleri, canlı',
+ guideDescSidechat: 'Bu oturumun bağlamını devralan bağımsız bir yan konu',
+ guideDescTerminal: 'Gerçek bir shell (xterm + pty), yeniden bağlanma destekli',
+ guideDescBrowser: 'Sohbet bağlantılarını devralabilen gömülü tarayıcı',
changesSessionEmpty: 'Bu oturumda henüz dosya işlemi yok',
changesRead: 'Okuma',
changesMdReading: 'Okuma',
diff --git a/src/client/locales-vi.ts b/src/client/locales-vi.ts
index 3b3fc28..9b020b9 100644
--- a/src/client/locales-vi.ts
+++ b/src/client/locales-vi.ts
@@ -18,6 +18,12 @@
export const vi: Record = {
files: 'Tệp',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Duyệt cây tệp của workspace; bấm để xem trước hoặc chỉnh sửa',
+ guideDescGit: 'Thay đổi Git và thay đổi tệp của phiên này trong một tab',
+ guideDescSubagent: 'Sơ đồ subagent và tác vụ nền, theo thời gian thực',
+ guideDescSidechat: 'Luồng hỏi tiếp độc lập kế thừa ngữ cảnh của phiên này',
+ guideDescTerminal: 'Shell thật (xterm + pty), tự kết nối lại',
+ guideDescBrowser: 'Trình duyệt nhúng, có thể mở link trong chat',
changesSessionEmpty: 'Chưa có thao tác tệp nào trong phiên này',
changesRead: 'Đọc',
changesMdReading: 'Đọc',
diff --git a/src/client/locales-zh-HK.ts b/src/client/locales-zh-HK.ts
index dcd9e19..ca4b64b 100644
--- a/src/client/locales-zh-HK.ts
+++ b/src/client/locales-zh-HK.ts
@@ -33,6 +33,12 @@
export const zhHK: Record = {
files: '檔案',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: '瀏覽工作區檔案樹,點開即可預覽或編輯',
+ guideDescGit: 'Git 變更與本輪檔案改動,雙視角合一',
+ guideDescSubagent: '子代理拓撲與背景任務,即時狀態',
+ guideDescSidechat: '繼承目前工作階段上下文的獨立追問執行緒',
+ guideDescTerminal: '真實 shell 終端(xterm + pty),斷線重連',
+ guideDescBrowser: '內嵌網頁瀏覽器,可接管聊天中的外鏈',
changesSessionEmpty: '本工作階段還沒有檔案操作',
changesRead: '讀取',
changesMdReading: '閱讀',
diff --git a/src/client/locales-zh-MO.ts b/src/client/locales-zh-MO.ts
index 5b60c3f..7252c5f 100644
--- a/src/client/locales-zh-MO.ts
+++ b/src/client/locales-zh-MO.ts
@@ -33,6 +33,12 @@
export const zhMO: Record = {
files: '檔案',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: '瀏覽工作區檔案樹,點開即可預覽或編輯',
+ guideDescGit: 'Git 變更與本輪檔案改動,雙視角合一',
+ guideDescSubagent: '子代理拓撲與背景任務,即時狀態',
+ guideDescSidechat: '繼承目前工作階段上下文的獨立追問執行緒',
+ guideDescTerminal: '真實 shell 終端(xterm + pty),斷線重連',
+ guideDescBrowser: '內嵌網頁瀏覽器,可接管聊天中的外鏈',
changesSessionEmpty: '本工作階段還沒有檔案操作',
changesRead: '讀取',
changesMdReading: '閱讀',
diff --git a/src/client/locales-zh-TW.ts b/src/client/locales-zh-TW.ts
index 4dbf024..7d37179 100644
--- a/src/client/locales-zh-TW.ts
+++ b/src/client/locales-zh-TW.ts
@@ -33,6 +33,12 @@
export const zhTW: Record = {
files: '檔案',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: '瀏覽工作區檔案樹,點開即可預覽或編輯',
+ guideDescGit: 'Git 變更與本輪檔案改動,雙視角合一',
+ guideDescSubagent: '子代理拓撲與背景任務,即時狀態',
+ guideDescSidechat: '繼承目前工作階段上下文的獨立追問執行緒',
+ guideDescTerminal: '真實 shell 終端(xterm + pty),斷線重連',
+ guideDescBrowser: '內嵌網頁瀏覽器,可接管聊天中的外鏈',
changesSessionEmpty: '本作業階段還沒有檔案操作',
changesRead: '讀取',
changesMdReading: '閱讀',
diff --git a/src/client/locales.ts b/src/client/locales.ts
index 7fab631..b3f64ac 100644
--- a/src/client/locales.ts
+++ b/src/client/locales.ts
@@ -25,6 +25,12 @@
export const zh = {
files: '文件',
nativeGuideDesc: '在 DSH 原生右侧栏打开',
+ guideDescFiles: '浏览工作区文件树,点开即预览或编辑',
+ guideDescGit: 'Git 变更与本轮文件改动,双视角合一',
+ guideDescSubagent: '子代理拓扑与后台任务,实时状态',
+ guideDescSidechat: '继承当前会话上下文的独立追问线程',
+ guideDescTerminal: '真实 shell 终端(xterm + pty),断线重连',
+ guideDescBrowser: '内嵌网页浏览器,可接管聊天里的外链',
changesSessionEmpty: '本会话还没有文件操作',
changesRead: '读取',
changesMdReading: '阅读',
@@ -460,6 +466,12 @@ export const zh = {
export const en: Record = {
files: 'Files',
nativeGuideDesc: 'Open in the DSH sidebar',
+ guideDescFiles: 'Browse the workspace tree; click a file to preview or edit',
+ guideDescGit: 'Git changes and this session\'s file edits, in one tab',
+ guideDescSubagent: 'Subagent topology and background jobs, live',
+ guideDescSidechat: 'An independent follow-up thread inheriting this session\'s context',
+ guideDescTerminal: 'A real shell (xterm + pty) with reconnect',
+ guideDescBrowser: 'An embedded browser that can take over chat links',
changesSessionEmpty: 'No file operations in this session yet',
changesRead: 'Read',
changesMdReading: 'Reading',
diff --git a/src/client/native/index.ts b/src/client/native/index.ts
index 8d2e394..52979cf 100644
--- a/src/client/native/index.ts
+++ b/src/client/native/index.ts
@@ -63,6 +63,17 @@ function titleOf(descriptor: TabDescriptor): string {
return typeof descriptor.title === 'function' ? descriptor.title() : descriptor.title
}
+/**
+ * The descriptor's guide line, evaluated fresh for the current locale. The
+ * host's new-tab list renders one line per entry; without a declared
+ * description every plugin page would read the same generic sentence.
+ */
+function descriptionOf(descriptor: TabDescriptor | undefined): string {
+ const description = descriptor?.description
+ if (description === undefined) return t('nativeGuideDesc')
+ return typeof description === 'function' ? description() : description
+}
+
/**
* The guide row's glyph for a descriptor icon (nothing when it has none).
* The native guide renders `entry.icon`, so a takeover registered without one
@@ -181,7 +192,7 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
guide: [{
order: descriptor.order ?? 100,
title: () => titleOf(descriptor),
- description: () => t('nativeGuideDesc'),
+ description: () => descriptionOf(descriptor),
...guideIconOf(icon),
}],
}),
@@ -208,7 +219,7 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
guide: [{
order: 10,
title: () => t('files'),
- description: () => t('nativeGuideDesc'),
+ description: () => descriptionOf(editor),
// The takeover IS the editor descriptor's page, so it carries the
// editor's glyph: without it the "Files" row is the only guide
// entry with an empty icon slot.
diff --git a/src/client/service.ts b/src/client/service.ts
index f948402..ff981d8 100644
--- a/src/client/service.ts
+++ b/src/client/service.ts
@@ -162,6 +162,14 @@ export interface TabDescriptor {
/** Unique id; also the `SidebarTab.type` value (`'explorer'`, `'my-plugin:db'`). */
id: string
title: string | (() => string)
+ /**
+ * One-line description of what this tab shows, rendered under the title in
+ * the host's new-tab list (DSH's native right Sidebar guide page). Without
+ * it the entry falls back to a generic "opens in the DSH sidebar" line, so
+ * every plugin page reads the same — declare the real purpose instead.
+ * Evaluated at render time, so a function follows the active locale.
+ */
+ description?: string | (() => string)
icon?: ReactNode | ((size: number) => ReactNode)
/** + menu sort order (ascending); default 100. */
order?: number
diff --git a/tests/builtins.spec.ts b/tests/builtins.spec.ts
index ab47fa5..36ed48c 100644
--- a/tests/builtins.spec.ts
+++ b/tests/builtins.spec.ts
@@ -44,6 +44,22 @@ describe('built-in tab registrations', () => {
expect(changes?.component).toBeDefined()
})
+ it('every visible tab declares a real guide line (no generic fallback)', () => {
+ // The native new-tab list renders `description()` under the title; a tab
+ // without one reads the same generic sentence as every other plugin page.
+ const { service } = setup()
+ const visible = service.getTabs().filter(descriptor => descriptor.hidden !== true)
+ expect(visible.length).toBeGreaterThan(0)
+ for (const descriptor of visible) {
+ expect(descriptor.description, `${descriptor.id} must declare a description`).toBeDefined()
+ const line = typeof descriptor.description === 'function' ? descriptor.description() : descriptor.description
+ expect(line, `${descriptor.id} description must be non-empty`).toBeTruthy()
+ }
+ const lines = visible.map(descriptor =>
+ typeof descriptor.description === 'function' ? descriptor.description() : descriptor.description)
+ expect(new Set(lines).size, 'descriptions must differ per tab').toBe(visible.length)
+ })
+
it('the changes tab declares no settings of its own (the diff always docks)', () => {
const { service } = setup()
expect(service.getTab('git')?.settings).toBeUndefined()
diff --git a/tests/native-surface.spec.ts b/tests/native-surface.spec.ts
index b045756..e7e87bd 100644
--- a/tests/native-surface.spec.ts
+++ b/tests/native-surface.spec.ts
@@ -162,8 +162,8 @@ describe('registerNativeSurface lifecycle (service-driven registration)', () =>
const store = createSidebarStore()
store.setSession('s1')
const service = createBetterSidebarService(store)
- service.registerTab({ id: 'terminal', title: 'Terminal', component: () => null })
- service.registerTab({ id: 'editor', title: 'Files', component: () => null, icon: () => null })
+ service.registerTab({ id: 'terminal', title: 'Terminal', component: () => null, description: () => 'Runs a shell' })
+ service.registerTab({ id: 'editor', title: 'Files', component: () => null, icon: () => null, description: () => 'Browse the tree' })
const records = createNativeTabRecords()
const registered: Array<{ id: string; kind: string; title: (address: string) => string; guide: unknown }> = []
@@ -225,8 +225,15 @@ describe('registerNativeSurface lifecycle (service-driven registration)', () =>
expect(filesType?.guide).toBeDefined()
// The takeover carries the editor's glyph, so the "Files" guide row is
// not the only one with a blank icon slot.
- const filesGuide = filesType?.guide as Array<{ icon?: unknown }> | undefined
+ const filesGuide = filesType?.guide as Array<{ icon?: unknown; description?: () => string }> | undefined
expect(filesGuide?.[0]?.icon).toBeDefined()
+ // The guide line is the descriptor's OWN description (the takeover reuses
+ // the editor's), and a descriptor that declares none falls back to the
+ // generic line — otherwise every plugin page would read identically.
+ expect(filesGuide?.[0]?.description?.()).toBe('Browse the tree')
+ const terminalGuide = registered.find(entry => entry.kind === 'terminal')?.guide as
+ Array<{ description?: () => string }> | undefined
+ expect(terminalGuide?.[0]?.description?.()).toBe('Runs a shell')
dispose()
})