dsh-ide-git 0.13.5 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-ide-git",
3
- "version": "0.13.5",
3
+ "version": "0.14.1",
4
4
  "main": "./src/index.js",
5
5
  "description": "IDE 级 Git 操作面板:分支树 + 提交图谱 + 变更/暂存/提交 + 提交详情。装了 dsh-better-sidebar 时挂成它的原生 tab(右侧栏 + 底部工作台);没装时直接挂 DSH 原生右侧栏,单独安装即可用。JetBrains 风格 Git 工具窗口 for DeepSeek Harness.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ide-git",
3
- "version": "0.13.5",
3
+ "version": "0.14.1",
4
4
  "description": "DSH plugin: an IDE-grade Git tool window as a native dsh-better-sidebar tab — branch tree, commit graph, changes, commit details, JetBrains-style actions, in the right sidebar and the bottom panel. | DSH 插件:IDE 级 Git 工具窗口,以 dsh-better-sidebar 原生 Tab 挂载——分支树 / 提交图谱 / 变更与提交详情 / JetBrains 风格操作,右侧栏与底部面板双布局自适应。",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -153,7 +153,6 @@ window.__ModuleLoader__.load({
153
153
  'action.copyHash': '复制修订号',
154
154
  'action.details': '提交详情',
155
155
  'menu.sendToChat': '发送到对话',
156
- 'menu.sendFilePathToChat': '发送文件路径到对话',
157
156
  'send.commitText': 'git 提交 {hash}「{subject}」(仓库 {repo})',
158
157
  'send.branchText': 'git 分支 {name}(仓库 {repo})',
159
158
  'menu.sendDiffLinesToChat': '发送选中行到对话',
@@ -195,6 +194,11 @@ window.__ModuleLoader__.load({
195
194
  'settings.placement.title': '面板放置',
196
195
  'settings.placement.tab': '在主对话区显示 Git 标签页',
197
196
  'settings.placement.tabHint': '关闭后仍可从右侧栏或底部面板打开。',
197
+ 'settings.placement.immersive': '沉浸模式(占满全高,隐藏输入框)',
198
+ 'settings.placement.immersiveHint': '开启后 Git 标签页占满整个会话区高度,输入框自动收起;审批与提问仍会正常显示。',
199
+ 'preview.openFile': '打开文件预览',
200
+ 'preview.title': 'Git 预览',
201
+ 'preview.hint': '点击文件或提交的「显示差异」,差异会在侧边栏这里打开。',
198
202
  'settings.rail.title': '动作条按钮',
199
203
  'settings.rail.hint': '勾选要显示在动作条上的动作;顺序固定为默认顺序,取消勾选即隐藏。',
200
204
  'settings.rail.unavailable': '当前宿主没有设置服务,请用面板内的「动作条设置」。',
@@ -364,7 +368,6 @@ window.__ModuleLoader__.load({
364
368
  'action.copyHash': 'Copy revision number',
365
369
  'action.details': 'Commit details',
366
370
  'menu.sendToChat': 'Send to chat',
367
- 'menu.sendFilePathToChat': 'Send file path to chat',
368
371
  'send.commitText': 'git commit {hash} "{subject}" (repo {repo})',
369
372
  'send.branchText': 'git branch {name} (repo {repo})',
370
373
  'menu.sendDiffLinesToChat': 'Send selected lines to chat',
@@ -406,6 +409,11 @@ window.__ModuleLoader__.load({
406
409
  'settings.placement.title': 'Placement',
407
410
  'settings.placement.tab': 'Show the Git tab in the conversation area',
408
411
  'settings.placement.tabHint': 'Off, the panel stays reachable from the right sidebar and the bottom panel.',
412
+ 'settings.placement.immersive': 'Immersive mode (full height, hidden composer)',
413
+ 'settings.placement.immersiveHint': 'On, the Git tab fills the whole conversation area and the composer steps aside; approvals and questions still show.',
414
+ 'preview.openFile': 'Open in the file preview',
415
+ 'preview.title': 'Git Preview',
416
+ 'preview.hint': 'Click “Show diff” on a file or commit — the diff opens here in the sidebar.',
409
417
  'settings.rail.title': 'Action rail buttons',
410
418
  'settings.rail.hint': 'Tick the actions shown on the rail. The order is fixed; unticking one hides it.',
411
419
  'settings.rail.unavailable': 'This host has no settings service; use the in-panel “Action rail settings”.',
@@ -583,7 +591,6 @@ window.__ModuleLoader__.load({
583
591
  'action.copyHash': '複製修訂號',
584
592
  'action.details': '提交詳情',
585
593
  'menu.sendToChat': '傳送到對話',
586
- 'menu.sendFilePathToChat': '傳送檔案路徑至對話',
587
594
  'send.commitText': 'git 提交 {hash}「{subject}」(存放庫 {repo})',
588
595
  'send.branchText': 'git 分支 {name}(存放庫 {repo})',
589
596
  'menu.sendDiffLinesToChat': '傳送選中行到對話',
@@ -625,6 +632,11 @@ window.__ModuleLoader__.load({
625
632
  'settings.placement.title': '面板放置',
626
633
  'settings.placement.tab': '喺主對話區顯示 Git 分頁',
627
634
  'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
635
+ 'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
636
+ 'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
637
+ 'preview.openFile': '開啟檔案預覽',
638
+ 'preview.title': 'Git 預覽',
639
+ 'preview.hint': '喺檔案或提交撳「顯示差異」,差異就會喺側邊欄度開啟。',
628
640
  'settings.rail.title': '動作列按鈕',
629
641
  'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
630
642
  'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
@@ -794,7 +806,6 @@ window.__ModuleLoader__.load({
794
806
  'action.copyHash': '複製修訂編號',
795
807
  'action.details': '提交詳情',
796
808
  'menu.sendToChat': '傳送至對話',
797
- 'menu.sendFilePathToChat': '傳送檔案路徑至對話',
798
809
  'send.commitText': 'git 提交 {hash}「{subject}」(版本庫 {repo})',
799
810
  'send.branchText': 'git 分支 {name}(版本庫 {repo})',
800
811
  'menu.sendDiffLinesToChat': '傳送選取行至對話',
@@ -836,6 +847,11 @@ window.__ModuleLoader__.load({
836
847
  'settings.placement.title': '面板放置',
837
848
  'settings.placement.tab': '在主對話區顯示 Git 分頁',
838
849
  'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
850
+ 'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
851
+ 'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個對話區高度,輸入框自動收起;審核與提問仍會正常顯示。',
852
+ 'preview.openFile': '開啟檔案預覽',
853
+ 'preview.title': 'Git 預覽',
854
+ 'preview.hint': '點選檔案或提交的「顯示差異」,差異就會在側邊欄開啟。',
839
855
  'settings.rail.title': '動作列按鈕',
840
856
  'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
841
857
  'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
@@ -1005,7 +1021,6 @@ window.__ModuleLoader__.load({
1005
1021
  'action.copyHash': '複製修訂號',
1006
1022
  'action.details': '提交詳情',
1007
1023
  'menu.sendToChat': '傳送至對話',
1008
- 'menu.sendFilePathToChat': '傳送檔案路徑至對話',
1009
1024
  'send.commitText': 'git 提交 {hash}「{subject}」(存放庫 {repo})',
1010
1025
  'send.branchText': 'git 分支 {name}(存放庫 {repo})',
1011
1026
  'menu.sendDiffLinesToChat': '傳送選中行至對話',
@@ -1047,6 +1062,11 @@ window.__ModuleLoader__.load({
1047
1062
  'settings.placement.title': '面板放置',
1048
1063
  'settings.placement.tab': '喺主對話區顯示 Git 分頁',
1049
1064
  'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
1065
+ 'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
1066
+ 'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
1067
+ 'preview.openFile': '開啟檔案預覽',
1068
+ 'preview.title': 'Git 預覽',
1069
+ 'preview.hint': '喺檔案或提交撳「顯示差異」,差異就會喺側邊欄度開啟。',
1050
1070
  'settings.rail.title': '動作列按鈕',
1051
1071
  'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
1052
1072
  'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
@@ -1216,7 +1236,6 @@ window.__ModuleLoader__.load({
1216
1236
  'action.copyHash': 'リビジョン番号をコピー',
1217
1237
  'action.details': 'コミットの詳細',
1218
1238
  'menu.sendToChat': 'チャットへ送る',
1219
- 'menu.sendFilePathToChat': 'ファイルパスをチャットへ送る',
1220
1239
  'send.commitText': 'git コミット {hash}「{subject}」(リポジトリ {repo})',
1221
1240
  'send.branchText': 'git ブランチ {name}(リポジトリ {repo})',
1222
1241
  'menu.sendDiffLinesToChat': '選択した行をチャットへ送る',
@@ -1258,6 +1277,11 @@ window.__ModuleLoader__.load({
1258
1277
  'settings.placement.title': 'パネル配置',
1259
1278
  'settings.placement.tab': '会話エリアに Git タブを表示',
1260
1279
  'settings.placement.tabHint': 'オフにしても右サイドバーと下部パネルから開けます。',
1280
+ 'settings.placement.immersive': '没入モード(全高表示・入力欄を非表示)',
1281
+ 'settings.placement.immersiveHint': 'オンにすると Git タブが会話エリアの高さいっぱいに広がり、入力欄は引っ込みます。承認や質問は通常どおり表示されます。',
1282
+ 'preview.openFile': 'ファイルプレビューを開く',
1283
+ 'preview.title': 'Git プレビュー',
1284
+ 'preview.hint': 'ファイルやコミットの「差分を表示」をクリックすると、差分がここに開きます。',
1261
1285
  'settings.rail.title': 'アクションレールのボタン',
1262
1286
  'settings.rail.hint': 'レールに表示する操作にチェックを入れてください。順序は固定で、チェックを外すと非表示になります。',
1263
1287
  'settings.rail.unavailable': 'このホストには設定サービスがないため、パネル内の「アクションレール設定」を使ってください。',
@@ -1427,7 +1451,6 @@ window.__ModuleLoader__.load({
1427
1451
  'action.copyHash': '리비전 번호 복사',
1428
1452
  'action.details': '커밋 상세',
1429
1453
  'menu.sendToChat': '대화로 보내기',
1430
- 'menu.sendFilePathToChat': '파일 경로를 대화로 보내기',
1431
1454
  'send.commitText': 'git 커밋 {hash} "{subject}" (저장소 {repo})',
1432
1455
  'send.branchText': 'git 브랜치 {name} (저장소 {repo})',
1433
1456
  'menu.sendDiffLinesToChat': '선택한 줄 대화로 보내기',
@@ -1469,6 +1492,11 @@ window.__ModuleLoader__.load({
1469
1492
  'settings.placement.title': '패널 배치',
1470
1493
  'settings.placement.tab': '대화 영역에 Git 탭 표시',
1471
1494
  'settings.placement.tabHint': '꺼도 오른쪽 사이드바와 하단 패널에서 열 수 있습니다.',
1495
+ 'settings.placement.immersive': '몰입 모드(전체 높이, 입력창 숨김)',
1496
+ 'settings.placement.immersiveHint': '켜면 Git 탭이 대화 영역 전체 높이를 차지하고 입력창은 숨겨집니다. 승인과 질문은 정상적으로 표시됩니다.',
1497
+ 'preview.openFile': '파일 미리보기로 열기',
1498
+ 'preview.title': 'Git 미리보기',
1499
+ 'preview.hint': '파일이나 커밋의 「차이 보기」를 클릭하면 차이가 사이드바 여기에 열립니다.',
1472
1500
  'settings.rail.title': '액션 레일 버튼',
1473
1501
  'settings.rail.hint': '레일에 표시할 동작을 선택하세요. 순서는 고정이며 선택을 해제하면 숨겨집니다.',
1474
1502
  'settings.rail.unavailable': '이 호스트에는 설정 서비스가 없으므로 패널의 "액션 레일 설정"을 사용하세요.',
@@ -1638,7 +1666,6 @@ window.__ModuleLoader__.load({
1638
1666
  'action.copyHash': 'Revisionsnummer kopieren',
1639
1667
  'action.details': 'Commit-Details',
1640
1668
  'menu.sendToChat': 'Im Chat senden',
1641
- 'menu.sendFilePathToChat': 'Dateipfad im Chat senden',
1642
1669
  'send.commitText': 'git-Commit {hash} "{subject}" (Repository {repo})',
1643
1670
  'send.branchText': 'git-Branch {name} (Repository {repo})',
1644
1671
  'menu.sendDiffLinesToChat': 'Ausgewählte Zeilen in den Chat senden',
@@ -1680,6 +1707,11 @@ window.__ModuleLoader__.load({
1680
1707
  'settings.placement.title': 'Panel-Platzierung',
1681
1708
  'settings.placement.tab': 'Git-Tab im Gesprächsbereich anzeigen',
1682
1709
  'settings.placement.tabHint': 'Ausgeschaltet bleibt das Panel über rechte Seitenleiste und unteres Panel erreichbar.',
1710
+ 'settings.placement.immersive': 'Immersiver Modus (volle Höhe, ausgeblendetes Eingabefeld)',
1711
+ 'settings.placement.immersiveHint': 'Eingeschaltet füllt der Git-Tab die gesamte Gesprächshöhe und das Eingabefeld weicht zurück; Freigaben und Rückfragen bleiben sichtbar.',
1712
+ 'preview.openFile': 'In der Dateivorschau öffnen',
1713
+ 'preview.title': 'Git-Vorschau',
1714
+ 'preview.hint': '„Diff anzeigen“ an einer Datei oder einem Commit öffnet den Diff hier in der Seitenleiste.',
1683
1715
  'settings.rail.title': 'Schaltflächen der Aktionsleiste',
1684
1716
  'settings.rail.hint': 'Die auf der Leiste sichtbaren Aktionen ankreuzen. Die Reihenfolge ist fest; abwählen blendet eine aus.',
1685
1717
  'settings.rail.unavailable': 'Dieser Host hat keinen Einstellungsdienst; nutze die „Aktionsleisten-Einstellungen“ im Panel.',
@@ -1849,7 +1881,6 @@ window.__ModuleLoader__.load({
1849
1881
  'action.copyHash': 'Copier le numéro de révision',
1850
1882
  'action.details': 'Détails du commit',
1851
1883
  'menu.sendToChat': 'Envoyer dans la conversation',
1852
- 'menu.sendFilePathToChat': 'Envoyer le chemin du fichier dans la conversation',
1853
1884
  'send.commitText': 'commit git {hash} « {subject} » (dépôt {repo})',
1854
1885
  'send.branchText': 'branche git {name} (dépôt {repo})',
1855
1886
  'menu.sendDiffLinesToChat': 'Envoyer les lignes sélectionnées dans la conversation',
@@ -1891,6 +1922,11 @@ window.__ModuleLoader__.load({
1891
1922
  'settings.placement.title': 'Placement du panneau',
1892
1923
  'settings.placement.tab': 'Afficher l’onglet Git dans la zone de conversation',
1893
1924
  'settings.placement.tabHint': 'Désactivé, le panneau reste accessible depuis la barre latérale droite et le panneau inférieur.',
1925
+ 'settings.placement.immersive': 'Mode immersif (pleine hauteur, zone de saisie masquée)',
1926
+ 'settings.placement.immersiveHint': 'Activé, l’onglet Git occupe toute la hauteur de la conversation et la zone de saisie s’efface ; approbations et questions restent visibles.',
1927
+ 'preview.openFile': 'Ouvrir dans l’aperçu du fichier',
1928
+ 'preview.title': 'Aperçu Git',
1929
+ 'preview.hint': 'Cliquez sur « Afficher le diff » sur un fichier ou un commit : le diff s’ouvre ici, dans la barre latérale.',
1894
1930
  'settings.rail.title': 'Boutons de la barre d’actions',
1895
1931
  'settings.rail.hint': 'Cochez les actions affichées sur la barre. L’ordre est fixe ; décocher masque une action.',
1896
1932
  'settings.rail.unavailable': 'Cet hôte n’a pas de service de réglages ; utilisez « Réglages de la barre d’actions » dans le panneau.',
@@ -2060,7 +2096,6 @@ window.__ModuleLoader__.load({
2060
2096
  'action.copyHash': 'Скопировать номер ревизии',
2061
2097
  'action.details': 'Сведения о коммите',
2062
2098
  'menu.sendToChat': 'Отправить в чат',
2063
- 'menu.sendFilePathToChat': 'Отправить путь к файлу в чат',
2064
2099
  'send.commitText': 'git-коммит {hash} «{subject}» (репозиторий {repo})',
2065
2100
  'send.branchText': 'git-ветка {name} (репозиторий {repo})',
2066
2101
  'menu.sendDiffLinesToChat': 'Отправить выбранные строки в чат',
@@ -2102,6 +2137,11 @@ window.__ModuleLoader__.load({
2102
2137
  'settings.placement.title': 'Размещение панели',
2103
2138
  'settings.placement.tab': 'Показывать вкладку Git в области диалога',
2104
2139
  'settings.placement.tabHint': 'В выключенном виде панель доступна из правой боковой панели и нижней панели.',
2140
+ 'settings.placement.immersive': 'Полноэкранный режим (во всю высоту, без поля ввода)',
2141
+ 'settings.placement.immersiveHint': 'Во включённом виде вкладка Git занимает всю высоту области диалога, поле ввода скрывается; подтверждения и вопросы остаются на виду.',
2142
+ 'preview.openFile': 'Открыть в предпросмотре файла',
2143
+ 'preview.title': 'Git-предпросмотр',
2144
+ 'preview.hint': 'Нажмите «Показать различия» у файла или коммита — различия откроются здесь, в боковой панели.',
2105
2145
  'settings.rail.title': 'Кнопки панели действий',
2106
2146
  'settings.rail.hint': 'Отметьте действия, показываемые на панели. Порядок фиксирован; снятие флажка скрывает действие.',
2107
2147
  'settings.rail.unavailable': 'На этом хосте нет службы настроек; используйте «Настройки панели действий» в панели.',
@@ -2271,7 +2311,6 @@ window.__ModuleLoader__.load({
2271
2311
  'action.copyHash': 'Copiar número da revisão',
2272
2312
  'action.details': 'Detalhes do commit',
2273
2313
  'menu.sendToChat': 'Enviar para a conversa',
2274
- 'menu.sendFilePathToChat': 'Enviar caminho do arquivo para a conversa',
2275
2314
  'send.commitText': 'commit git {hash} "{subject}" (repositório {repo})',
2276
2315
  'send.branchText': 'branch git {name} (repositório {repo})',
2277
2316
  'menu.sendDiffLinesToChat': 'Enviar linhas selecionadas para a conversa',
@@ -2313,6 +2352,11 @@ window.__ModuleLoader__.load({
2313
2352
  'settings.placement.title': 'Posição do painel',
2314
2353
  'settings.placement.tab': 'Mostrar a aba Git na área de conversa',
2315
2354
  'settings.placement.tabHint': 'Desligado, o painel continua disponível na barra lateral direita e no painel inferior.',
2355
+ 'settings.placement.immersive': 'Modo imersivo (altura total, campo de entrada oculto)',
2356
+ 'settings.placement.immersiveHint': 'Ativado, a aba Git ocupa toda a altura da conversa e o campo de entrada fica de lado; aprovações e perguntas continuam visíveis.',
2357
+ 'preview.openFile': 'Abrir na pré-visualização do arquivo',
2358
+ 'preview.title': 'Pré-visualização do Git',
2359
+ 'preview.hint': 'Clique em “Mostrar diff” num arquivo ou commit — o diff abre aqui, na barra lateral.',
2316
2360
  'settings.rail.title': 'Botões da barra de ações',
2317
2361
  'settings.rail.hint': 'Marque as ações exibidas na barra. A ordem é fixa; desmarcar oculta uma ação.',
2318
2362
  'settings.rail.unavailable': 'Este host não tem serviço de configurações; use «Configurações da barra de ações» no painel.',
@@ -2482,7 +2526,6 @@ window.__ModuleLoader__.load({
2482
2526
  'action.copyHash': 'Copia il numero di revisione',
2483
2527
  'action.details': 'Dettagli del commit',
2484
2528
  'menu.sendToChat': 'Invia alla conversazione',
2485
- 'menu.sendFilePathToChat': 'Invia il percorso del file alla conversazione',
2486
2529
  'send.commitText': 'commit git {hash} "{subject}" (repository {repo})',
2487
2530
  'send.branchText': 'branch git {name} (repository {repo})',
2488
2531
  'menu.sendDiffLinesToChat': 'Invia le righe selezionate alla conversazione',
@@ -2524,6 +2567,11 @@ window.__ModuleLoader__.load({
2524
2567
  'settings.placement.title': 'Posizione del pannello',
2525
2568
  'settings.placement.tab': 'Mostra la scheda Git nell’area conversazione',
2526
2569
  'settings.placement.tabHint': 'Da spento, il pannello resta raggiungibile dalla barra laterale destra e dal pannello inferiore.',
2570
+ 'settings.placement.immersive': 'Modalità immersiva (altezza piena, input nascosto)',
2571
+ 'settings.placement.immersiveHint': 'Attiva, la scheda Git occupa tutta l’altezza della conversazione e la barra di input resta nascosta; approvazioni e domande restano visibili.',
2572
+ 'preview.openFile': 'Apri nell’anteprima del file',
2573
+ 'preview.title': 'Anteprima Git',
2574
+ 'preview.hint': 'Fai clic su «Mostra diff» su un file o un commit: il diff si apre qui, nella barra laterale.',
2527
2575
  'settings.rail.title': 'Pulsanti della barra azioni',
2528
2576
  'settings.rail.hint': 'Seleziona le azioni mostrate sulla barra. L’ordine è fisso; deselezionando se ne nasconde una.',
2529
2577
  'settings.rail.unavailable': 'Questo host non ha un servizio di impostazioni; usa «Impostazioni barra azioni» nel pannello.',
@@ -2693,7 +2741,6 @@ window.__ModuleLoader__.load({
2693
2741
  'action.copyHash': 'Revisienummer kopiëren',
2694
2742
  'action.details': 'Commit-details',
2695
2743
  'menu.sendToChat': 'Naar het gesprek verzenden',
2696
- 'menu.sendFilePathToChat': 'Bestandspad naar het gesprek verzenden',
2697
2744
  'send.commitText': 'git-commit {hash} "{subject}" (repository {repo})',
2698
2745
  'send.branchText': 'git-branch {name} (repository {repo})',
2699
2746
  'menu.sendDiffLinesToChat': 'Geselecteerde regels naar het gesprek verzenden',
@@ -2735,6 +2782,11 @@ window.__ModuleLoader__.load({
2735
2782
  'settings.placement.title': 'Paneelplaatsing',
2736
2783
  'settings.placement.tab': 'Git-tabblad in het gespreksgebied tonen',
2737
2784
  'settings.placement.tabHint': 'Uitgeschakeld blijft het paneel bereikbaar via de rechterzijbalk en het onderpaneel.',
2785
+ 'settings.placement.immersive': 'Meeslepende modus (volledige hoogte, verborgen invoerveld)',
2786
+ 'settings.placement.immersiveHint': 'Aan: het Git-tabblad vult de hele gespreksruimte en het invoerveld wijkt; goedkeuringen en vragen blijven zichtbaar.',
2787
+ 'preview.openFile': 'Openen in de bestandsvoorbeeld',
2788
+ 'preview.title': 'Git-voorbeeld',
2789
+ 'preview.hint': 'Klik op „Diff tonen“ bij een bestand of commit — de diff opent hier in de zijbalk.',
2738
2790
  'settings.rail.title': 'Knoppen van de actiebalk',
2739
2791
  'settings.rail.hint': 'Vink de acties aan die op de balk verschijnen. De volgorde ligt vast; uitvinken verbergt er een.',
2740
2792
  'settings.rail.unavailable': 'Deze host heeft geen instellingenservice; gebruik "Actiebalk-instellingen" in het paneel.',
@@ -2904,7 +2956,6 @@ window.__ModuleLoader__.load({
2904
2956
  'action.copyHash': 'Kopiuj numer wersji',
2905
2957
  'action.details': 'Szczegóły commita',
2906
2958
  'menu.sendToChat': 'Wyślij do rozmowy',
2907
- 'menu.sendFilePathToChat': 'Wyślij ścieżkę pliku do rozmowy',
2908
2959
  'send.commitText': 'commit git {hash} „{subject}" (repozytorium {repo})',
2909
2960
  'send.branchText': 'gałąź git {name} (repozytorium {repo})',
2910
2961
  'menu.sendDiffLinesToChat': 'Wyślij zaznaczone wiersze do rozmowy',
@@ -2946,6 +2997,11 @@ window.__ModuleLoader__.load({
2946
2997
  'settings.placement.title': 'Rozmieszczenie panelu',
2947
2998
  'settings.placement.tab': 'Pokazuj kartę Git w obszarze rozmowy',
2948
2999
  'settings.placement.tabHint': 'Po wyłączeniu panel pozostaje dostępny z prawego paska bocznego i dolnego panelu.',
3000
+ 'settings.placement.immersive': 'Tryb immersyjny (pełna wysokość, ukryte pole wpisywania)',
3001
+ 'settings.placement.immersiveHint': 'Po włączeniu karta Git wypełnia całą wysokość rozmowy, a pole wpisywania się chowa; zatwierdzenia i pytania pozostają widoczne.',
3002
+ 'preview.openFile': 'Otwórz w podglądzie pliku',
3003
+ 'preview.title': 'Podgląd Git',
3004
+ 'preview.hint': 'Kliknij „Pokaż różnice” przy pliku lub commicie — różnice otworzą się tutaj, na pasku bocznym.',
2949
3005
  'settings.rail.title': 'Przyciski paska akcji',
2950
3006
  'settings.rail.hint': 'Zaznacz akcje widoczne na pasku. Kolejność jest stała; odznaczenie ukrywa akcję.',
2951
3007
  'settings.rail.unavailable': 'Ten host nie ma usługi ustawień; użyj „Ustawienia paska akcji” w panelu.',
@@ -3115,7 +3171,6 @@ window.__ModuleLoader__.load({
3115
3171
  'action.copyHash': 'Kopiera revisionsnummer',
3116
3172
  'action.details': 'Commit-detaljer',
3117
3173
  'menu.sendToChat': 'Skicka till samtalet',
3118
- 'menu.sendFilePathToChat': 'Skicka filsökvägen till samtalet',
3119
3174
  'send.commitText': 'git-commit {hash} ”{subject}” (arkiv {repo})',
3120
3175
  'send.branchText': 'git-gren {name} (arkiv {repo})',
3121
3176
  'menu.sendDiffLinesToChat': 'Skicka markerade rader till samtalet',
@@ -3157,6 +3212,11 @@ window.__ModuleLoader__.load({
3157
3212
  'settings.placement.title': 'Panelplacering',
3158
3213
  'settings.placement.tab': 'Visa Git-fliken i konversationsytan',
3159
3214
  'settings.placement.tabHint': 'Avstängd finns panelen kvar via höger sidopanel och nedre panelen.',
3215
+ 'settings.placement.immersive': 'Uppslukande läge (full höjd, dolt inmatningsfält)',
3216
+ 'settings.placement.immersiveHint': 'På: Git-fliken fyller hela konversationsytan och inmatningsfältet träder åt sidan; godkännanden och frågor visas fortfarande.',
3217
+ 'preview.openFile': 'Öppna i filförhandsvisningen',
3218
+ 'preview.title': 'Git-förhandsvisning',
3219
+ 'preview.hint': 'Klicka på ”Visa diff” på en fil eller en commit — diffen öppnas här i sidopanelen.',
3160
3220
  'settings.rail.title': 'Knappar i åtgärdsfältet',
3161
3221
  'settings.rail.hint': 'Markera de åtgärder som visas i fältet. Ordningen är fast; avmarkering döljer en.',
3162
3222
  'settings.rail.unavailable': 'Denna värd saknar inställningstjänst; använd ”Åtgärdsfältets inställningar” i panelen.',
@@ -3326,7 +3386,6 @@ window.__ModuleLoader__.load({
3326
3386
  'action.copyHash': 'Revizyon numarasını kopyala',
3327
3387
  'action.details': 'Commit ayrıntıları',
3328
3388
  'menu.sendToChat': 'Sohbete gönder',
3329
- 'menu.sendFilePathToChat': 'Dosya yolunu sohbete gönder',
3330
3389
  'send.commitText': 'git commit {hash} "{subject}" (depo {repo})',
3331
3390
  'send.branchText': 'git dalı {name} (depo {repo})',
3332
3391
  'menu.sendDiffLinesToChat': 'Seçili satırları sohbete gönder',
@@ -3368,6 +3427,11 @@ window.__ModuleLoader__.load({
3368
3427
  'settings.placement.title': 'Panel yerleşimi',
3369
3428
  'settings.placement.tab': 'Konuşma alanında Git sekmesini göster',
3370
3429
  'settings.placement.tabHint': 'Kapatıldığında panel sağ kenar çubuğundan ve alt panelden açılabilir.',
3430
+ 'settings.placement.immersive': 'Odaklanma modu (tam yükseklik, gizli giriş alanı)',
3431
+ 'settings.placement.immersiveHint': 'Açıkken Git sekmesi tüm konuşma alanını kaplar ve giriş alanı gizlenir; onaylar ve sorular yine de görünür.',
3432
+ 'preview.openFile': 'Dosya önizlemesinde aç',
3433
+ 'preview.title': 'Git Önizlemesi',
3434
+ 'preview.hint': 'Bir dosya veya commit üzerinde „Farkı göster”e tıklayın — fark burada, kenar çubuğunda açılır.',
3371
3435
  'settings.rail.title': 'Eylem çubuğu düğmeleri',
3372
3436
  'settings.rail.hint': 'Çubukta gösterilecek eylemleri işaretleyin. Sıra sabittir; işareti kaldırmak eylemi gizler.',
3373
3437
  'settings.rail.unavailable': 'Bu ana bilgisayarda ayar hizmeti yok; paneldeki "Eylem çubuğu ayarları"nı kullanın.',
@@ -3537,7 +3601,6 @@ window.__ModuleLoader__.load({
3537
3601
  'action.copyHash': 'Salin nomor revisi',
3538
3602
  'action.details': 'Detail commit',
3539
3603
  'menu.sendToChat': 'Kirim ke obrolan',
3540
- 'menu.sendFilePathToChat': 'Kirim jalur berkas ke obrolan',
3541
3604
  'send.commitText': 'commit git {hash} "{subject}" (repositori {repo})',
3542
3605
  'send.branchText': 'cabang git {name} (repositori {repo})',
3543
3606
  'menu.sendDiffLinesToChat': 'Kirim baris terpilih ke obrolan',
@@ -3579,6 +3642,11 @@ window.__ModuleLoader__.load({
3579
3642
  'settings.placement.title': 'Penempatan panel',
3580
3643
  'settings.placement.tab': 'Tampilkan tab Git di area percakapan',
3581
3644
  'settings.placement.tabHint': 'Dimatikan, panel tetap bisa dibuka dari bilah sisi kanan dan panel bawah.',
3645
+ 'settings.placement.immersive': 'Mode imersif (tinggi penuh, kolom masukan tersembunyi)',
3646
+ 'settings.placement.immersiveHint': 'Aktif: tab Git memenuhi seluruh tinggi area percakapan dan kolom masukan mengalah; persetujuan dan pertanyaan tetap tampil.',
3647
+ 'preview.openFile': 'Buka di pratinjau berkas',
3648
+ 'preview.title': 'Pratinjau Git',
3649
+ 'preview.hint': 'Klik “Tampilkan diff” pada file atau commit — diff terbuka di sini, di bilah sisi.',
3582
3650
  'settings.rail.title': 'Tombol bilah aksi',
3583
3651
  'settings.rail.hint': 'Centang aksi yang ditampilkan di bilah. Urutannya tetap; menghapus centang menyembunyikannya.',
3584
3652
  'settings.rail.unavailable': 'Host ini tidak punya layanan pengaturan; gunakan "Pengaturan bilah aksi" di panel.',
@@ -3748,7 +3816,6 @@ window.__ModuleLoader__.load({
3748
3816
  'action.copyHash': 'Sao chép số hiệu bản sửa',
3749
3817
  'action.details': 'Chi tiết commit',
3750
3818
  'menu.sendToChat': 'Gửi vào hội thoại',
3751
- 'menu.sendFilePathToChat': 'Gửi đường dẫn tệp vào hội thoại',
3752
3819
  'send.commitText': 'git commit {hash} "{subject}" (kho {repo})',
3753
3820
  'send.branchText': 'nhánh git {name} (kho {repo})',
3754
3821
  'menu.sendDiffLinesToChat': 'Gửi các dòng đã chọn vào hội thoại',
@@ -3790,6 +3857,11 @@ window.__ModuleLoader__.load({
3790
3857
  'settings.placement.title': 'Vị trí bảng',
3791
3858
  'settings.placement.tab': 'Hiện thẻ Git trong vùng hội thoại',
3792
3859
  'settings.placement.tabHint': 'Tắt rồi, bảng vẫn mở được từ thanh bên phải và bảng dưới.',
3860
+ 'settings.placement.immersive': 'Chế độ đắm chìm (chiều cao đầy đủ, ẩn ô nhập)',
3861
+ 'settings.placement.immersiveHint': 'Bật: thẻ Git chiếm toàn bộ chiều cao vùng hội thoại và ô nhập thu vào; phê duyệt và câu hỏi vẫn hiển thị bình thường.',
3862
+ 'preview.openFile': 'Mở trong xem trước tệp',
3863
+ 'preview.title': 'Xem trước Git',
3864
+ 'preview.hint': 'Bấm “Xem diff” trên tệp hoặc commit — diff sẽ mở ở đây, trên thanh bên.',
3793
3865
  'settings.rail.title': 'Nút thanh tác vụ',
3794
3866
  'settings.rail.hint': 'Chọn các tác vụ hiển thị trên thanh. Thứ tự cố định; bỏ chọn sẽ ẩn tác vụ đó.',
3795
3867
  'settings.rail.unavailable': 'Máy chủ này không có dịch vụ cài đặt; hãy dùng “Cài đặt thanh tác vụ” trong bảng điều khiển.',
@@ -3959,7 +4031,6 @@ window.__ModuleLoader__.load({
3959
4031
  'action.copyHash': 'نسخ رقم المراجعة',
3960
4032
  'action.details': 'تفاصيل الالتزام',
3961
4033
  'menu.sendToChat': 'إرسال إلى المحادثة',
3962
- 'menu.sendFilePathToChat': 'إرسال مسار الملف إلى المحادثة',
3963
4034
  'send.commitText': 'التزام git {hash} "{subject}" (المستودع {repo})',
3964
4035
  'send.branchText': 'فرع git {name} (المستودع {repo})',
3965
4036
  'menu.sendDiffLinesToChat': 'إرسال الأسطر المحددة إلى المحادثة',
@@ -4001,6 +4072,11 @@ window.__ModuleLoader__.load({
4001
4072
  'settings.placement.title': 'موضع اللوحة',
4002
4073
  'settings.placement.tab': 'إظهار تبويب Git في منطقة المحادثة',
4003
4074
  'settings.placement.tabHint': 'عند الإيقاف تبقى اللوحة متاحة من الشريط الجانبي الأيمن واللوحة السفلية.',
4075
+ 'settings.placement.immersive': 'الوضع الغامر (ارتفاع كامل، إخفاء صندوق الكتابة)',
4076
+ 'settings.placement.immersiveHint': 'عند التشغيل يملأ تبويب Git منطقة المحادثة بكامل ارتفاعها ويتراجع صندوق الكتابة؛ تظل الموافقات والأسئلة ظاهرة كالمعتاد.',
4077
+ 'preview.openFile': 'افتح في معاينة الملف',
4078
+ 'preview.title': 'معاينة Git',
4079
+ 'preview.hint': 'انقر «عرض الفروق» على ملف أو commit — تُفتح الفروق هنا في الشريط الجانبي.',
4004
4080
  'settings.rail.title': 'أزرار شريط الإجراءات',
4005
4081
  'settings.rail.hint': 'حدّد الإجراءات المعروضة على الشريط. الترتيب ثابت، وإلغاء التحديد يخفي الإجراء.',
4006
4082
  'settings.rail.unavailable': 'لا تتوفر خدمة إعدادات على هذا المضيف؛ استخدم «إعدادات شريط الإجراءات» في اللوحة.',
@@ -4170,7 +4246,6 @@ window.__ModuleLoader__.load({
4170
4246
  'action.copyHash': 'रिवीज़न संख्या कॉपी करें',
4171
4247
  'action.details': 'कमिट विवरण',
4172
4248
  'menu.sendToChat': 'बातचीत में भेजें',
4173
- 'menu.sendFilePathToChat': 'फ़ाइल पथ बातचीत में भेजें',
4174
4249
  'send.commitText': 'git कमिट {hash} "{subject}" (रिपॉज़िटरी {repo})',
4175
4250
  'send.branchText': 'git ब्रांच {name} (रिपॉज़िटरी {repo})',
4176
4251
  'menu.sendDiffLinesToChat': 'चयनित पंक्तियाँ बातचीत में भेजें',
@@ -4212,6 +4287,11 @@ window.__ModuleLoader__.load({
4212
4287
  'settings.placement.title': 'पैनल प्लेसमेंट',
4213
4288
  'settings.placement.tab': 'संवाद क्षेत्र में Git टैब दिखाएँ',
4214
4289
  'settings.placement.tabHint': 'बंद करने पर भी पैनल दाएँ साइडबार और निचले पैनल से खुलता है।',
4290
+ 'settings.placement.immersive': 'इमर्सिव मोड (पूरी ऊँचाई, छिपा इनपुट बॉक्स)',
4291
+ 'settings.placement.immersiveHint': 'चालू होने पर Git टैब पूरे संवाद क्षेत्र की ऊँचाई घेर लेता है और इनपुट बॉक्स हट जाता है; अनुमोदन और प्रश्न फिर भी दिखते हैं।',
4292
+ 'preview.openFile': 'फ़ाइल पूर्वावलोकन में खोलें',
4293
+ 'preview.title': 'Git पूर्वावलोकन',
4294
+ 'preview.hint': 'फ़ाइल या कमिट पर “अंतर दिखाएँ” पर क्लिक करें — अंतर यहीं साइडबार में खुलेगा।',
4215
4295
  'settings.rail.title': 'एक्शन रेल बटन',
4216
4296
  'settings.rail.hint': 'रेल पर दिखने वाली क्रियाएँ चुनें। क्रम निश्चित है; चुनाव हटाने पर वह छिप जाती है।',
4217
4297
  'settings.rail.unavailable': 'इस होस्ट में सेटिंग सेवा नहीं है; पैनल में «एक्शन रेल सेटिंग» का उपयोग करें।',
@@ -4381,7 +4461,6 @@ window.__ModuleLoader__.load({
4381
4461
  'action.copyHash': 'คัดลอกเลขรีวิชัน',
4382
4462
  'action.details': 'รายละเอียดคอมมิต',
4383
4463
  'menu.sendToChat': 'ส่งไปยังบทสนทนา',
4384
- 'menu.sendFilePathToChat': 'ส่งพาธไฟล์ไปยังบทสนทนา',
4385
4464
  'send.commitText': 'git คอมมิต {hash} "{subject}" (รีโพซิทอรี {repo})',
4386
4465
  'send.branchText': 'git บรานช์ {name} (รีโพซิทอรี {repo})',
4387
4466
  'menu.sendDiffLinesToChat': 'ส่งบรรทัดที่เลือกไปยังบทสนทนา',
@@ -4423,6 +4502,11 @@ window.__ModuleLoader__.load({
4423
4502
  'settings.placement.title': 'ตำแหน่งแผง',
4424
4503
  'settings.placement.tab': 'แสดงแท็บ Git ในพื้นที่บทสนทนา',
4425
4504
  'settings.placement.tabHint': 'ปิดแล้วยังเปิดแผงจากแถบข้างขวาหรือแผงล่างได้',
4505
+ 'settings.placement.immersive': 'โหมดดื่มด่ำ (เต็มความสูง, ซ่อนกล่องพิมพ์)',
4506
+ 'settings.placement.immersiveHint': 'เมื่อเปิด แท็บ Git จะกินพื้นที่สูงทั้งหมดของบทสนทนาและกล่องพิมพ์จะถอยออก; การอนุมัติและคำถามยังแสดงตามปกติ',
4507
+ 'preview.openFile': 'เปิดในตัวอย่างไฟล์',
4508
+ 'preview.title': 'ดูตัวอย่าง Git',
4509
+ 'preview.hint': 'คลิก “แสดงความต่าง” ที่ไฟล์หรือคอมมิต — ความต่างจะเปิดที่นี่ในแถบข้าง',
4426
4510
  'settings.rail.title': 'ปุ่มแถบการทำงาน',
4427
4511
  'settings.rail.hint': 'ทำเครื่องหมายการทำงานที่จะแสดงบนแถบ ลำดับคงที่; ยกเลิกเครื่องหมายเพื่อซ่อน',
4428
4512
  'settings.rail.unavailable': 'โฮสต์นี้ไม่มีบริการตั้งค่า ให้ใช้ «ตั้งค่าแถบการทำงาน» ในแผง',
@@ -4677,16 +4761,19 @@ window.__ModuleLoader__.load({
4677
4761
  is still essentially correct, so it seeds the states and a background
4678
4762
  refresh lands the fresh data a moment later. Pure functions; the panel
4679
4763
  only ever reads them at effect time and writes after a fetch. */
4680
- /* Two TTLs, because the two seeds age very differently. The repos scan is
4681
- the SLOW part (a multi-repo workspace walks directories) and its result
4682
- barely changes, so its seed stays usable for 10 minutes: freshness is
4683
- the background refresh's job — every mount refreshes anyway — the TTL
4684
- only decides how old a seed may be to still paint the first screen
4685
- (user report: coming back to the Git tab after a while re-ran the whole
4686
- discovery). The first page (summary / branches / log) changes with every
4687
- commit, so its seed keeps the short 30s window it always had. */
4764
+ /* Two seeds age very differently. The repos scan is the SLOW part (a
4765
+ multi-repo workspace walks directories) and its result barely changes,
4766
+ so its seed keeps a 10-minute TTL: freshness is the background refresh's
4767
+ job — every mount refreshes anyway — the TTL only decides how old a seed
4768
+ may be to still paint the first screen. The page seed (summary /
4769
+ branches / log) used to expire after 30s, which is exactly the user
4770
+ complaint this queue answers ("come back to the panel after a while and
4771
+ everything re-loads"): it is a KEEP-WARM QUEUE now — no expiry at all,
4772
+ an LRU of three workspaces. Every page in the queue paints instantly no
4773
+ matter how long the panel stayed closed; the mount refresh re-proves
4774
+ every field silently right after, so freshness never regresses. */
4688
4775
  const PAINT_REPOS_TTL_MS = 600_000
4689
- const PAINT_PAGE_TTL_MS = 30_000
4776
+ const PAINT_PAGE_KEEP_WARM = 3
4690
4777
  const paintRepos = new Map() /* cwd -> { at, data } */
4691
4778
  const paintPage = new Map() /* repoRoot -> { at, summary, branches, commits, hasMore } */
4692
4779
  const paintRoot = new Map() /* cwd -> last repoRoot the user was looking at */
@@ -4726,6 +4813,13 @@ window.__ModuleLoader__.load({
4726
4813
  }
4727
4814
  feed(paintRepos, parsed.repos)
4728
4815
  feed(paintPage, parsed.page)
4816
+ /* The queue cap applies to restored pages too: storage may hold more
4817
+ than the live queue does (an older build wrote 16), trim to the same
4818
+ three — restored insertion order mirrors the order that was in
4819
+ memory, so the survivors are the most recent pages. */
4820
+ while (paintPage.size > PAINT_PAGE_KEEP_WARM) {
4821
+ paintPage.delete(paintPage.keys().next().value)
4822
+ }
4729
4823
  }
4730
4824
 
4731
4825
  function persistPaint() {
@@ -4755,13 +4849,24 @@ window.__ModuleLoader__.load({
4755
4849
  function readPaintPage(repoRoot) {
4756
4850
  hydratePaint()
4757
4851
  const entry = paintPage.get(repoRoot)
4758
- if (entry === undefined || Date.now() - entry.at > PAINT_PAGE_TTL_MS) return undefined
4852
+ if (entry === undefined) return undefined
4853
+ /* A read is a USE: re-insert at the tail so eviction tracks what the
4854
+ user actually visits, not which page happened to be written last. */
4855
+ paintPage.delete(repoRoot)
4856
+ paintPage.set(repoRoot, entry)
4759
4857
  return entry
4760
4858
  }
4761
4859
 
4762
4860
  function writePaintPage(repoRoot, page) {
4763
4861
  paintPage.set(repoRoot, Object.assign({ at: Date.now() }, page))
4764
- if (paintPage.size > 16) paintPage.delete(paintPage.keys().next().value)
4862
+ /* The keep-warm queue cap: three workspaces stay painted, the fourth
4863
+ evicts the least-recently-used one. Pure LRU over insertion order —
4864
+ reads re-tail themselves (readPaintPage), so the survivor set is the
4865
+ three repo pages the user actually keeps coming back to. */
4866
+ while (paintPage.size > PAINT_PAGE_KEEP_WARM) {
4867
+ const oldest = paintPage.keys().next().value
4868
+ paintPage.delete(oldest)
4869
+ }
4765
4870
  persistPaint()
4766
4871
  }
4767
4872
 
@@ -5018,6 +5123,19 @@ window.__ModuleLoader__.load({
5018
5123
  return snapshot
5019
5124
  }
5020
5125
 
5126
+ /* The conversation seat's immersive mode (issue #8 round 3, placement
5127
+ group): the view rides the host's composer-overlay contract so it fills
5128
+ the WHOLE conversation height, and the composer seat hides while the Git
5129
+ view owns the ring. Default ON — that full-height space is why the tab
5130
+ placement exists at all; the settings card flips it live. A host without
5131
+ the row Config keeps the immersive seat (same policy as the tab switch). */
5132
+ function conversationImmersiveEnabled() {
5133
+ const ready = railFormReady()
5134
+ if (ready === null) return true
5135
+ const value = ready.value !== null && typeof ready.value === 'object' ? ready.value.immersive : undefined
5136
+ return value !== false
5137
+ }
5138
+
5021
5139
  function readRailConfig() {
5022
5140
  const ready = railFormReady()
5023
5141
  if (ready !== null) return railConfigOfValues(ready.value)
@@ -6082,7 +6200,10 @@ window.__ModuleLoader__.load({
6082
6200
  /* Host-rendered first (v0.14): the ui-primitives diff card brings the
6083
6201
  shared toolbar (copy / wrap / language) and the product's diff colors.
6084
6202
  A patch we cannot replay into a hunk (binary, mode-only) or a host
6085
- without the seed module falls back to the hand-drawn rows below. */
6203
+ without the seed module falls back to the hand-drawn rows below. This
6204
+ IS the official diff renderer (the platform seed table ships it, the
6205
+ host's own surfaces render diffs through it) — the external preview
6206
+ tab uses the same one, so every diff in the product reads alike. */
6086
6207
  if (UIPrimitives !== null && typeof UIPrimitives.DiffBlock === 'function') {
6087
6208
  const hunk = patchToHunk(props.patch)
6088
6209
  if (hunk !== null) {
@@ -6156,6 +6277,7 @@ window.__ModuleLoader__.load({
6156
6277
  grip: ['M5.6 4.6h4.8', 'M5.6 8h4.8', 'M5.6 11.4h4.8'],
6157
6278
  filter: ['M2 3.6h12l-4.6 5.2v4.2l-2.8-1.4V8.8z'],
6158
6279
  check: ['M3.2 8.4 6.4 11.6 12.8 4.6'],
6280
+ copy: ['M5.5 5.5h8v8h-8z', 'M10.5 5.5v-3h-8v8h3'],
6159
6281
  // dock actions (official right sidebar): lift a panel out, drop it back into
6160
6282
  // the dock, split the pane, and the full-screen pair reuses expand/collapse.
6161
6283
  float: ['M2.5 6.5v7h7v-3', 'M6 10 13.2 2.8', 'M9.4 2.8h3.8v3.8'],
@@ -6655,6 +6777,50 @@ window.__ModuleLoader__.load({
6655
6777
  E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.placement.tab'))),
6656
6778
  E('div', { className: 'dig-settings-subnote' }, t('settings.placement.tabHint')))
6657
6779
  })(),
6780
+ (() => {
6781
+ /* Immersive mode (issue #8 round 3): the conversation seat's
6782
+ full-height overlay layout + hidden composer. Default ON — the
6783
+ full-height space is what the tab placement is for; flipping it
6784
+ re-renders the seat live (the ConversationPanel subscribes to the
6785
+ same config). */
6786
+ const field = 'immersive'
6787
+ const inputId = 'dig-placement-' + field
6788
+ const shown = Object.prototype.hasOwnProperty.call(intent.current, field)
6789
+ ? intent.current[field] === true
6790
+ : values[field] !== false
6791
+ const toggle = (event) => {
6792
+ if (readOnly === true) { setNote(t('settings.rail.readonly')); return }
6793
+ const fromControl = event !== undefined && event !== null && event.target !== null
6794
+ && event.target !== undefined && typeof event.target.checked === 'boolean'
6795
+ ? event.target.checked
6796
+ : null
6797
+ const wanted = fromControl !== null
6798
+ ? fromControl
6799
+ : (Object.prototype.hasOwnProperty.call(intent.current, field)
6800
+ ? intent.current[field] !== true
6801
+ : values[field] !== false) !== true
6802
+ intent.current[field] = wanted
6803
+ dirty.current.set(field, wanted)
6804
+ setPending((now) => Object.assign({}, now, { [field]: wanted }))
6805
+ setNote('')
6806
+ bump()
6807
+ scheduleFlush()
6808
+ }
6809
+ return E('div', { key: field },
6810
+ E('div', { className: 'dig-settings-row', 'data-placement-row': field },
6811
+ E('input', {
6812
+ id: inputId,
6813
+ type: 'checkbox',
6814
+ className: 'dig-settings-check',
6815
+ 'aria-labelledby': inputId + '-label',
6816
+ checked: shown,
6817
+ disabled: readOnly === true,
6818
+ onChange: toggle,
6819
+ }),
6820
+ E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: 'commit', size: 13 })),
6821
+ E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.placement.immersive'))),
6822
+ E('div', { className: 'dig-settings-subnote' }, t('settings.placement.immersiveHint')))
6823
+ })(),
6658
6824
  E('div', { className: 'dig-settings-sub' }, t('settings.commit.title')),
6659
6825
  (() => {
6660
6826
  /* The model row is a picker over the host's provider catalog; while
@@ -7682,8 +7848,23 @@ window.__ModuleLoader__.load({
7682
7848
  props.openSettings()
7683
7849
  }
7684
7850
 
7851
+ /* The immersive conversation seat (v0.14) hands the panel a SQUARE-ish
7852
+ box by construction: the conversation column clamps content width
7853
+ (~920px) while the seat fills the view's whole height, so the
7854
+ bottom-workbench-vs-sidebar ratio (`width >= height * 1.15`) can never
7855
+ hold there and the panel would flip to the stacked layout. The seat is
7856
+ a tool window — neither a horizontal dock nor a narrow rail — so it
7857
+ always takes the columns chrome (compact still wins: a truly narrow
7858
+ viewport keeps the single-row layout). */
7859
+ const [immersiveSeat, setImmersiveSeat] = useState(false)
7860
+ useMeasureLayoutEffect(() => {
7861
+ const node = hostRef.current
7862
+ const holder = node === null || typeof node.closest !== 'function' ? null : node.closest('.dig-conversation-seat[data-immersive="1"]')
7863
+ setImmersiveSeat(holder !== null)
7864
+ })
7685
7865
  const compact = size.width > 0 && size.width < COMPACT_MAX_WIDTH
7686
- const columns = !compact && size.width >= 600 && size.width >= size.height * 1.15
7866
+ const columns = (immersiveSeat === true && compact === false)
7867
+ || (!compact && size.width >= 600 && size.width >= size.height * 1.15)
7687
7868
 
7688
7869
  /* ---------- draggable panes (issue #5) ---------- */
7689
7870
 
@@ -8010,6 +8191,87 @@ window.__ModuleLoader__.load({
8010
8191
  }
8011
8192
  }, [base])
8012
8193
 
8194
+ /* ---------- external diff preview (user request, 2026-10-08) ---------- */
8195
+ /* 「点击文件预览」不再内嵌:预览交给 better-sidebar 内置的 diff tab
8196
+ (type 'diff',Hidden 内置页,DiffTab 自己经它自家 git 通道拉 patch,
8197
+ 我们只递引用)。placement 跟随点击发生的容器——底部工作台里点开在
8198
+ 底部、右栏里点开在右栏,走底座的官方 onOpenDiff 交接(props 注入,
8199
+ placement 由底座 store 决定);主对话区座位与原生座位没有该通道,
8200
+ 直接 betterSidebar.openTab 定向右栏(target: 'right')。两通道皆
8201
+ 不可用(无底座 / 老底座)→ 返回 false,调用方落回内嵌 diff 窗。
8202
+ ref 的 worktree + repoRoot 都填本面板的仓库根:DiffTab 的加载器把
8203
+ repoRoot 并进它自己的 git 调用,多仓库工作区不会落错仓库。 */
8204
+ const previewDiff = useCallback((seed) => {
8205
+ if (repoRoot === null) return false
8206
+ const commitHash = seed.hash !== undefined && seed.hash !== null ? String(seed.hash) : null
8207
+ const ref = commitHash !== null
8208
+ ? { kind: 'commit', hash: commitHash.slice(0, 7), hashFull: commitHash, subject: seed.subject || '', worktree: repoRoot, repoRoot: repoRoot }
8209
+ : { kind: 'worktree', path: seed.path, staged: seed.staged === true, untracked: seed.untracked === true ? true : undefined, worktree: repoRoot, repoRoot: repoRoot }
8210
+ const title = commitHash !== null
8211
+ ? commitHash.slice(0, 7) + (seed.subject ? ' ' + seed.subject : '')
8212
+ : baseName(String(seed.path))
8213
+ const id = commitHash !== null
8214
+ ? 'diff:c:' + encodeURIComponent(repoRoot) + ':' + commitHash
8215
+ : 'diff:w:' + encodeURIComponent(repoRoot) + ':' + (seed.staged === true ? 's' : 'u') + ':' + seed.path
8216
+ const tab = { id: id, type: 'diff', title: title, diff: ref }
8217
+ /* Seat channel first — the bottom workbench opens its own diff tab,
8218
+ the right column opens it there; placement is the base's call. */
8219
+ if (typeof props.openDiffExternal === 'function') {
8220
+ try {
8221
+ props.openDiffExternal(tab)
8222
+ return true
8223
+ } catch (error) { void error }
8224
+ }
8225
+ /* Conversation seat / native seat: aim at the right sidebar. The
8226
+ capability gate keeps an older base (pre-targetedOpen) from
8227
+ receiving a field it cannot place; any refusal falls through to
8228
+ the inline pane below. */
8229
+ let betterSidebar = undefined
8230
+ try { betterSidebar = props.ctx === undefined || props.ctx === null ? undefined : props.ctx.get('betterSidebar') } catch (error) { void error }
8231
+ if (betterSidebar !== undefined && betterSidebar !== null && typeof betterSidebar.openTab === 'function') {
8232
+ const capabilities = betterSidebar.capabilities
8233
+ const targeted = capabilities === undefined || capabilities === null || (Array.isArray(capabilities) && capabilities.indexOf('targetedOpen') >= 0)
8234
+ if (targeted === true) {
8235
+ try {
8236
+ const scopeObject = scope || {}
8237
+ betterSidebar.openTab({ type: 'diff', title: title, id: id, diff: ref, target: 'right' }, { sessionId: scopeObject.sessionId, cwd: scopeObject.cwd })
8238
+ return true
8239
+ } catch (error) { void error }
8240
+ }
8241
+ }
8242
+ /* Native preview tab: the base is absent (or too old), so the plugin's
8243
+ OWN second native right-sidebar type takes the preview. The seed
8244
+ carries cwd (the route requires it) + repoRoot (skips the rev-parse);
8245
+ the tab is a singleton per pane — a second click re-seeds it in
8246
+ place instead of stacking tabs. */
8247
+ let sidebarRight = undefined
8248
+ try { sidebarRight = props.ctx === undefined || props.ctx === null ? undefined : props.ctx.get('sidebarRight') } catch (error) { void error }
8249
+ if (sidebarRight !== undefined && sidebarRight !== null && typeof sidebarRight.openTab === 'function') {
8250
+ try {
8251
+ const scopeObject = scope || {}
8252
+ previewStore.set(commitHash !== null
8253
+ ? { hash: commitHash, subject: seed.subject || '', cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId }
8254
+ : { path: seed.path, staged: seed.staged === true, cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId })
8255
+ sidebarRight.openTab(NATIVE_PREVIEW_KIND)
8256
+ return true
8257
+ } catch (error) { void error }
8258
+ }
8259
+ return false
8260
+ }, [repoRoot, scope, props.openDiffExternal, props.ctx])
8261
+
8262
+ /* The one diff verb every surface calls (row click, row menu, detail
8263
+ file menu, working-diff shortcut): external first, inline fallback. */
8264
+ const showDiff = useCallback((seed) => {
8265
+ if (previewDiff(seed) === true) return
8266
+ if (seed.hash !== undefined && seed.hash !== null) {
8267
+ setDiffContext({ kind: 'commit', hash: seed.hash, subject: seed.subject || '' })
8268
+ void openDiff({ hash: seed.hash, path: seed.path })
8269
+ return
8270
+ }
8271
+ setDiffContext({ kind: 'worktree', staged: seed.staged === true })
8272
+ void openDiff({ path: seed.path, staged: seed.staged === true })
8273
+ }, [previewDiff, openDiff])
8274
+
8013
8275
  const selectCommit = useCallback(async (commit) => {
8014
8276
  setSelectedHash(commit.hash)
8015
8277
  setSelectedPath(null)
@@ -8223,9 +8485,8 @@ window.__ModuleLoader__.load({
8223
8485
 
8224
8486
  const changeMenu = useCallback((event, item, group) => {
8225
8487
  const items = [
8226
- { id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => { setDiffContext({ kind: 'worktree', staged: group === 'staged' }); void openDiff({ path: item.path, staged: group === 'staged' }) } },
8488
+ { id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => { showDiff({ path: item.path, staged: group === 'staged', untracked: group === 'untracked' }) } },
8227
8489
  { id: 'sendRef', icon: 'send', tone: 'accent', label: t('menu.sendToChat'), run: () => sendFileReferenceToChat(item.path) },
8228
- { id: 'sendPath', icon: 'file', tone: 'secondary', label: t('menu.sendFilePathToChat'), run: () => sendToComposer({ text: item.path, fallback: item.path }) },
8229
8490
  null,
8230
8491
  group === 'staged'
8231
8492
  ? { id: 'unstage', icon: 'minus', tone: 'warn', label: t('action.unstage'), run: () => { void run('unstage', { paths: [item.path] }) } }
@@ -8235,7 +8496,7 @@ window.__ModuleLoader__.load({
8235
8496
  { id: 'copy', icon: 'tag', tone: 'secondary', label: t('action.copyPath'), run: () => { copyText(item.path) } },
8236
8497
  ]
8237
8498
  openMenuAt(event, items)
8238
- }, [openDiff, run, t, openMenuAt, sendFileReferenceToChat, sendToComposer])
8499
+ }, [showDiff, run, t, openMenuAt, sendFileReferenceToChat, sendToComposer])
8239
8500
 
8240
8501
  /* The commit-detail file list shares the working-tree row menu, minus the
8241
8502
  staging verbs: a committed file's diff replays against its commit. */
@@ -8243,16 +8504,18 @@ window.__ModuleLoader__.load({
8243
8504
  openMenuAt(event, [
8244
8505
  { id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => {
8245
8506
  setSelectedPath(file.path)
8246
- setDiffContext({ kind: 'commit', hash: detail === null || detail === undefined ? undefined : detail.hash, subject: detail === null || detail === undefined ? '' : detail.subject })
8247
- void openDiff({ hash: detail === null ? undefined : detail.hash, path: file.path })
8507
+ showDiff({
8508
+ hash: detail === null || detail === undefined ? undefined : detail.hash,
8509
+ subject: detail === null || detail === undefined ? '' : detail.subject,
8510
+ path: file.path,
8511
+ })
8248
8512
  } },
8249
8513
  null,
8250
8514
  { id: 'sendRef', icon: 'send', tone: 'accent', label: t('menu.sendToChat'), run: () => sendFileReferenceToChat(file.path) },
8251
- { id: 'sendPath', icon: 'file', tone: 'secondary', label: t('menu.sendFilePathToChat'), run: () => sendToComposer({ text: file.path, fallback: file.path }) },
8252
8515
  null,
8253
8516
  { id: 'copy', icon: 'tag', tone: 'secondary', label: t('action.copyPath'), run: () => { copyText(file.path) } },
8254
8517
  ])
8255
- }, [detail, openDiff, openMenuAt, sendFileReferenceToChat, sendToComposer, t])
8518
+ }, [detail, showDiff, openMenuAt, sendFileReferenceToChat, sendToComposer, t])
8256
8519
 
8257
8520
  /* The diff pane's own context menu (v0.13.5): select text in the diff,
8258
8521
  right-click, send a self-describing line reference to the composer.
@@ -8485,7 +8748,7 @@ window.__ModuleLoader__.load({
8485
8748
  onStageAll: (entries) => { void run('stage', { paths: entries.map((entry) => entry.path) }) },
8486
8749
  onUnstageAll: () => { void run('unstage', { paths: (summary === null ? [] : summary.changes.staged).map((entry) => entry.path) }) },
8487
8750
  onDiscard: (item, group) => setDialog({ kind: 'discard', item: item, group: group }),
8488
- onDiff: (item, group) => { setDiffContext({ kind: 'worktree', staged: group === 'staged' }); void openDiff({ path: item.path, staged: group === 'staged' }) },
8751
+ onDiff: (item, group) => { showDiff({ path: item.path, staged: group === 'staged', untracked: group === 'untracked' }) },
8489
8752
  onChangeMenu: changeMenu,
8490
8753
  })
8491
8754
 
@@ -8495,8 +8758,11 @@ window.__ModuleLoader__.load({
8495
8758
  onBack: () => { setView('history'); setSelectedPath(null); setPatch(''); setDiffContext(null) },
8496
8759
  onSelectFile: (file) => {
8497
8760
  setSelectedPath(file.path)
8498
- setDiffContext({ kind: 'commit', hash: detail === null || detail === undefined ? undefined : detail.hash, subject: detail === null || detail === undefined ? '' : detail.subject })
8499
- void openDiff({ hash: detail === null ? undefined : detail.hash, path: file.path })
8761
+ showDiff({
8762
+ hash: detail === null || detail === undefined ? undefined : detail.hash,
8763
+ subject: detail === null || detail === undefined ? '' : detail.subject,
8764
+ path: file.path,
8765
+ })
8500
8766
  },
8501
8767
  onFileMenu: detailFileMenu,
8502
8768
  })
@@ -8547,7 +8813,7 @@ window.__ModuleLoader__.load({
8547
8813
  ['untracked', changes.untracked, false],
8548
8814
  ]
8549
8815
  for (const entry of order) {
8550
- if (entry[1].length > 0) { void openDiff({ path: entry[1][0].path, staged: entry[2] }); return }
8816
+ if (entry[1].length > 0) { showDiff({ path: entry[1][0].path, staged: entry[2] }); return }
8551
8817
  }
8552
8818
  setNote(t('note.noChanges'))
8553
8819
  }
@@ -9228,6 +9494,40 @@ window.__ModuleLoader__.load({
9228
9494
  '.dig-menu-danger,.dig-menu-danger .dig-menu-label{color:var(--dsw-alias-state-error-primary)}',
9229
9495
  '.dig-menu-disabled{opacity:.38;cursor:default}',
9230
9496
  '.dig-menu-sep{height:1px;margin:3px 6px;background:var(--dsw-alias-hairline,var(--dsw-alias-border-l1))}',
9497
+ /* ---- immersive conversation seat (issue #8 round 3) ---- */
9498
+ /* The seat itself carries `data-conversation-composer-overlay` (the
9499
+ host's own opt-in, what TrajectoryView rides): the host CSS flips the
9500
+ view area to `flex: 1 1 0; min-height: 0; overflow: hidden` and the
9501
+ composer seat to an overlay. What the host does NOT do is hide the
9502
+ composer — that is ours, and it is a single structural rule patterned
9503
+ after dsh-context's `.lc-root` rule: while a Git view owns the
9504
+ conversation ring, the composer seat displays none. The :not() chain
9505
+ is the SAFETY VALVE and its attribute names are verified against the
9506
+ host sources (ui-approval ApprovalPanel `data-approval-key`,
9507
+ ui-user-questions QuestionComposer `data-question-key`, PlanReviewPanel
9508
+ `data-plan-review-key`): a blocking interaction riding the seat must
9509
+ stay reachable even though the plain input card is hidden, or a user
9510
+ sitting in the Git view could never approve anything. */
9511
+ '[data-conversation-scroll]:has(.dig-conversation-seat[data-immersive="1"]) > [data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}',
9512
+ /* The transcript-width drag strips are FOLLOWING SIBLINGS of the shared
9513
+ scroller (children of the conversation body), full-height and
9514
+ z-index 8: over a full-bleed Git pane they glow and drag straight
9515
+ through it. Same sibling combinator lesson dsh-context recorded in
9516
+ its own issue #50 — a descendant combinator here matches nothing. */
9517
+ '[data-conversation-scroll]:has(.dig-conversation-seat[data-immersive="1"]) ~ [data-width-handle]{display:none}',
9518
+ /* ---- native preview tab (external diff preview) ---- */
9519
+ /* The body mounts straight into the right-sidebar pane: full height,
9520
+ internal scrolling in the body region, a one-line header for the
9521
+ subject/path. Same tokens as the rest of the panel — no hard-coded
9522
+ surfaces. */
9523
+ '.dig-preview-root{position:relative;height:100%;display:flex;flex-direction:column;min-height:0;background:var(--dsw-alias-bg-base)}',
9524
+ '.dig-preview-head{flex:none;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border-l1));font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
9525
+ '.dig-preview-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
9526
+ '.dig-preview-body{flex:1;min-height:0;overflow:auto;padding:8px 12px;display:flex;flex-direction:column}',
9527
+ '.dig-preview-body .dig-diff-host,.dig-preview-body .dig-diff{flex:1;min-height:0}',
9528
+ '.dig-preview-actions{display:flex;align-items:center;gap:4px;flex:none}',
9529
+ '.dig-preview-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}',
9530
+ '.dig-preview-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
9231
9531
  ].join('\n')
9232
9532
 
9233
9533
  /* ============================== plugin ============================== */
@@ -9243,6 +9543,34 @@ window.__ModuleLoader__.load({
9243
9543
  Chat (0) and Trajectory (10), and after dsh-context's tab (20) when both
9244
9544
  are installed. */
9245
9545
  const CONVERSATION_VIEW_ID = 'ide-git'
9546
+ /* The native preview tab (external diff preview, 2026-10-08): a SECOND
9547
+ native right-sidebar type that exists only to show one diff. It carries
9548
+ no guide entry on purpose — nothing on the start page opens it; it opens
9549
+ itself when a diff verb fires with no better-sidebar to hand off to.
9550
+ Content follows the module-level store below, so every seat (the
9551
+ conversation tab, the native panel) feeds the same single preview. */
9552
+ const NATIVE_PREVIEW_ID = 'dsh-ide-git-preview'
9553
+ const NATIVE_PREVIEW_KIND = 'ide-git-preview'
9554
+
9555
+ /* The one diff seed the native preview tab shows. set() bumps a monotonous
9556
+ counter so a re-click of the SAME file still re-fetches; subscribers get
9557
+ the barest notify and re-read .seed themselves. Memory-only: after a
9558
+ reload the tab (if the layout restored it) shows the empty hint again —
9559
+ the next click re-seeds it. */
9560
+ const previewStore = {
9561
+ seed: null,
9562
+ listeners: new Set(),
9563
+ set(seed) {
9564
+ this.seed = seed
9565
+ for (const listener of [...this.listeners]) {
9566
+ try { listener() } catch (error) { void error }
9567
+ }
9568
+ },
9569
+ subscribe(listener) {
9570
+ this.listeners.add(listener)
9571
+ return () => { this.listeners.delete(listener) }
9572
+ },
9573
+ }
9246
9574
 
9247
9575
  /* The native seat hands a tab the standard props; the panel wants
9248
9576
  `{ scope, t }`. A session's working directory is the path of the
@@ -9272,7 +9600,7 @@ window.__ModuleLoader__.load({
9272
9600
  const openSettings = navigation !== undefined && navigation !== null && typeof navigation.openBundle === 'function'
9273
9601
  ? () => { navigation.openBundle(SETTINGS_BUNDLE) }
9274
9602
  : null
9275
- return E(Panel, { scope: props.scope, t: props.t, visible: props.visible, dock: props.dock, openSettings: openSettings, ctx: props.ctx })
9603
+ return E(Panel, { scope: props.scope, t: props.t, visible: props.visible, dock: props.dock, openSettings: openSettings, ctx: props.ctx, openDiffExternal: props.openDiffExternal })
9276
9604
  }
9277
9605
 
9278
9606
  /* The conversation-area seat (issue #8): the same panel as a view tab
@@ -9310,6 +9638,21 @@ window.__ModuleLoader__.load({
9310
9638
  const ctx = props.ctx
9311
9639
  const sessionId = typeof props.sessionId === 'string' ? props.sessionId : ''
9312
9640
  const [cwd, setCwd] = useState(() => sessionCwdOf(ctx, sessionId))
9641
+ /* Immersive mode (issue #8 round 3): when on, the seat declares the
9642
+ host's composer-overlay contract (`data-conversation-composer-overlay`)
9643
+ and the conversation view area goes `flex: 1 1 0; min-height: 0;
9644
+ overflow: hidden` — the panel fills the WHOLE conversation height and
9645
+ scrolls inside itself, with the composer seat hidden away (CSS keeps
9646
+ the blocking approvals visible). The v0.13.2 pin-to-scrollport
9647
+ machinery below is the FALLBACK for immersive-off: it exists because
9648
+ the active view area is `flex: 1 0 auto` (content height) in the
9649
+ normal layout, which is exactly what the overlay contract replaces. */
9650
+ const [immersive, setImmersive] = useState(() => conversationImmersiveEnabled())
9651
+ useEffect(() => {
9652
+ const sync = () => { setImmersive(conversationImmersiveEnabled()) }
9653
+ sync()
9654
+ return subscribeRailConfig(sync)
9655
+ }, [])
9313
9656
  /* The active conversation's view area is `flex: 1 0 auto; min-height:
9314
9657
  auto` — it GROWS with content and scrolls inside the shared
9315
9658
  conversation scrollport. An auto-height panel there measured
@@ -9324,6 +9667,12 @@ window.__ModuleLoader__.load({
9324
9667
  useEffect(() => {
9325
9668
  const el = seatRef.current
9326
9669
  if (el === null) return undefined
9670
+ /* Immersive ON: the host's overlay layout owns this seat's height
9671
+ (the view area is the flex child now) — no pinning, no convergence,
9672
+ none of the scrollport arithmetic below applies. Toggling the
9673
+ setting re-runs this effect through the dependency below, so the
9674
+ fallback machinery spins back up on the way off. */
9675
+ if (immersive === true) return undefined
9327
9676
  /* Leftover wheel scroll, root cause: the composer seat is sticky, but a
9328
9677
  sticky box still occupies FLOW space inside the scrollport, so even
9329
9678
  with the panel seat pinned to the visible height the scrollHeight
@@ -9408,7 +9757,7 @@ window.__ModuleLoader__.load({
9408
9757
  if (observer !== undefined) observer.disconnect()
9409
9758
  window.removeEventListener('resize', measure)
9410
9759
  }
9411
- }, [])
9760
+ }, [immersive])
9412
9761
  useEffect(() => {
9413
9762
  let alive = true
9414
9763
  const sync = () => { if (alive === true) setCwd(sessionCwdOf(ctx, sessionId)) }
@@ -9432,11 +9781,20 @@ window.__ModuleLoader__.load({
9432
9781
  }
9433
9782
  }, [ctx, sessionId])
9434
9783
  const scope = useMemo(() => ({ cwd: cwd, sessionId: sessionId }), [cwd, sessionId])
9435
- return E('div', {
9784
+ /* The overlay marker rides the SEAT element (a viewArea descendant): the
9785
+ host's `.scrollBody:has([data-conversation-composer-overlay])` rules
9786
+ pick it up from anywhere inside, flipping the view area to
9787
+ `flex: 1 1 0; min-height: 0; overflow: hidden` (full-height, internal
9788
+ scrolling) and the composer seat to an overlay. `data-immersive`
9789
+ drives the plugin's own CSS (composer hide + width-handle hide). */
9790
+ const immersiveAttrs = immersive === true
9791
+ ? { 'data-conversation-composer-overlay': '', 'data-immersive': '1' }
9792
+ : {}
9793
+ return E('div', Object.assign({
9436
9794
  ref: seatRef,
9437
9795
  className: 'dig-conversation-seat',
9438
- style: seatHeight === null ? undefined : { height: seatHeight + 'px' },
9439
- }, E(LocaleLive, { ctx: ctx, scope: scope, t: props.t, visible: true }))
9796
+ style: immersive === true ? { height: '100%' } : (seatHeight === null ? undefined : { height: seatHeight + 'px' }),
9797
+ }, immersiveAttrs), E(LocaleLive, { ctx: ctx, scope: scope, t: props.t, visible: true }))
9440
9798
  }, (prev, next) => prev.sessionId === next.sessionId && prev.t === next.t && prev.ctx === next.ctx)
9441
9799
 
9442
9800
  function NativePanel(props) {
@@ -9461,6 +9819,98 @@ window.__ModuleLoader__.load({
9461
9819
  return E(LocaleLive, { ctx: props.ctx, scope: scope, t: props.t, visible: true, dock: dock })
9462
9820
  }
9463
9821
 
9822
+ /* The native preview tab's body (external diff preview): shows exactly one
9823
+ diff — whatever the store holds — fetched fresh from the host route on
9824
+ every seed. The seed carries cwd + repoRoot so a multi-root workspace
9825
+ lands on the right repository (the route requires cwd; repoRoot skips
9826
+ the rev-parse). Rendered through the SAME DiffBody the inline pane uses,
9827
+ so host DiffBlock reuse, the hand-drawn fallback and the binary/empty
9828
+ states are all identical. */
9829
+ function NativePreviewPanel(props) {
9830
+ const t = props.t
9831
+ const [seed, setSeed] = useState(() => previewStore.seed)
9832
+ useEffect(() => previewStore.subscribe(() => { setSeed(previewStore.seed) }), [])
9833
+ const seedKey = seed === null
9834
+ ? ''
9835
+ : (seed.hash !== undefined && seed.hash !== null ? 'c:' + String(seed.hash) : 'w:' + String(seed.staged === true) + ':' + String(seed.path))
9836
+ const [state, setState] = useState({ loading: false, patch: '', error: null })
9837
+ const [refreshTick, setRefreshTick] = useState(0)
9838
+ const [copied, setCopied] = useState(false)
9839
+ const copyTimer = useRef(null)
9840
+ useEffect(() => () => { if (copyTimer.current !== null) clearTimeout(copyTimer.current) }, [])
9841
+ useEffect(() => {
9842
+ if (seed === null) return undefined
9843
+ let alive = true
9844
+ setState({ loading: true, patch: '', error: null })
9845
+ void (async () => {
9846
+ try {
9847
+ const payload = { cwd: seed.cwd }
9848
+ if (seed.repoRoot !== undefined && seed.repoRoot !== null && seed.repoRoot !== '') payload.repoRoot = seed.repoRoot
9849
+ if (seed.hash !== undefined && seed.hash !== null && seed.hash !== '') payload.hash = seed.hash
9850
+ else { payload.path = seed.path; payload.staged = seed.staged === true }
9851
+ const data = await request('diff', payload)
9852
+ if (alive === true) setState({ loading: false, patch: data === undefined || data === null || typeof data.patch !== 'string' ? '' : data.patch, error: null })
9853
+ } catch (error) {
9854
+ if (alive === true) setState({ loading: false, patch: '', error: error instanceof Error ? error.message : String(error) })
9855
+ }
9856
+ })()
9857
+ return () => { alive = false }
9858
+ // eslint-disable-next-line react-hooks/exhaustive-deps
9859
+ }, [seedKey, refreshTick])
9860
+ const title = seed === null
9861
+ ? ''
9862
+ : (seed.hash !== undefined && seed.hash !== null
9863
+ ? String(seed.hash).slice(0, 7) + (seed.subject ? ' ' + seed.subject : '')
9864
+ : String(seed.path))
9865
+ /* Header controls mirror the base's own diff tab: a refresh button
9866
+ (worktree diffs change under staging/discard), plus a copy button —
9867
+ the official card's toolbar already covers wrap/copy inside the body;
9868
+ these two act on the WHOLE preview. */
9869
+ const copyPatch = () => {
9870
+ try {
9871
+ void navigator.clipboard.writeText(state.patch).then(() => {
9872
+ setCopied(true)
9873
+ if (copyTimer.current !== null) clearTimeout(copyTimer.current)
9874
+ copyTimer.current = setTimeout(() => { setCopied(false); copyTimer.current = null }, 1600)
9875
+ })
9876
+ } catch (error) { void error }
9877
+ }
9878
+ /* Hand the file to the HOST'S own file preview (ReadBlock chrome: path
9879
+ banner, line numbers, syntax colors) — the official openResource
9880
+ channel, the same one every @-mention file click rides. The diff stays
9881
+ the place to read the CHANGE; this jumps to the whole file with the
9882
+ product's line-numbered reader. Address grammar (workspace-path):
9883
+ `dsh-resource://file/session/<sessionId>/<per-segment encoded rel>`. */
9884
+ const openInFilePreview = () => {
9885
+ if (seed === null || seed.path === undefined || seed.path === null || seed.path === '') return
9886
+ let sidebarRight = undefined
9887
+ try { sidebarRight = props.ctx === undefined || props.ctx === null ? undefined : props.ctx.get('sidebarRight') } catch (error) { void error }
9888
+ if (sidebarRight === undefined || sidebarRight === null || typeof sidebarRight.openResource !== 'function') return
9889
+ try {
9890
+ const relative = String(seed.path)
9891
+ const address = 'dsh-resource://file/session/' + encodeURIComponent(String(seed.sessionId || '')) + '/' + relative.split('/').map((segment) => encodeURIComponent(segment)).join('/')
9892
+ sidebarRight.openResource(address)
9893
+ } catch (error) { void error }
9894
+ }
9895
+ return E('div', { className: 'dig-preview-root' },
9896
+ E('div', { className: 'dig-preview-head' },
9897
+ E('span', { className: 'dig-preview-title', title: title }, title === '' ? t('preview.title') : title),
9898
+ seed === null ? null : E('div', { className: 'dig-preview-actions' },
9899
+ seed.path !== undefined && seed.path !== null && seed.path !== ''
9900
+ ? E('button', { type: 'button', className: 'dig-preview-btn', 'aria-label': t('preview.openFile'), title: t('preview.openFile'), onClick: openInFilePreview },
9901
+ E(Icon, { name: 'file', size: 13 }))
9902
+ : null,
9903
+ E('button', { type: 'button', className: 'dig-preview-btn', 'aria-label': t('diff.copy'), title: t('diff.copy'), onClick: copyPatch },
9904
+ E(Icon, { name: copied === true ? 'check' : 'copy', size: 13 })),
9905
+ E('button', { type: 'button', className: 'dig-preview-btn', 'aria-label': t('toolbar.refresh'), title: t('toolbar.refresh'), onClick: () => { setRefreshTick((n) => n + 1) } },
9906
+ E(Icon, { name: 'refresh', size: 13 })))),
9907
+ seed === null
9908
+ ? E('div', { className: 'dig-preview-body' }, E('div', { className: 'dig-empty' }, t('preview.hint')))
9909
+ : state.error !== null
9910
+ ? E('div', { className: 'dig-preview-body' }, E('div', { className: 'dig-empty' }, state.error))
9911
+ : E('div', { className: 'dig-preview-body' }, E(DiffBody, { patch: state.patch, loading: state.loading, binary: false, t: t })))
9912
+ }
9913
+
9464
9914
  function apply(ctx) {
9465
9915
  const style = document.createElement('style')
9466
9916
  style.setAttribute('data-dsh-ide-git', '')
@@ -9524,7 +9974,24 @@ window.__ModuleLoader__.load({
9524
9974
  { name: 'sidebar.right.pane.tab', key: NATIVE_ID },
9525
9975
  (tabProps) => E(NativePanel, Object.assign({}, tabProps, { t: t, ctx: ctx })),
9526
9976
  ))
9977
+ /* The preview type has NO guide entry on purpose (start-page
9978
+ destinations are the main panel's job): it exists to receive the
9979
+ openTab this plugin fires when a diff verb runs without a
9980
+ better-sidebar to hand off to. Omitting `guide` is the registry's
9981
+ documented way to stay off the guide page. */
9982
+ const offPreviewType = tabs.register({
9983
+ id: NATIVE_PREVIEW_ID,
9984
+ kind: NATIVE_PREVIEW_KIND,
9985
+ title: () => t('preview.title'),
9986
+ keepMounted: true,
9987
+ })
9988
+ const offPreviewBody = slots.inject('sidebar.right.pane.tab', () => slots.register(
9989
+ { name: 'sidebar.right.pane.tab', key: NATIVE_PREVIEW_ID },
9990
+ (tabProps) => E(NativePreviewPanel, Object.assign({}, tabProps, { t: t, ctx: ctx })),
9991
+ ))
9527
9992
  return () => {
9993
+ try { offPreviewBody() } catch (error) { void error }
9994
+ try { offPreviewType() } catch (error) { void error }
9528
9995
  try { offBody() } catch (error) { void error }
9529
9996
  try { offType() } catch (error) { void error }
9530
9997
  }
@@ -9557,6 +10024,13 @@ window.__ModuleLoader__.load({
9557
10024
  scope: tabProps.scope,
9558
10025
  t: t,
9559
10026
  visible: tabProps.visible,
10027
+ /* The base's own diff-tab hand-off (OpenTabSeed type 'diff').
10028
+ Where the diff tab lands — bottom workbench vs right column —
10029
+ is the base's store decision, made where the click happened:
10030
+ exactly the "preview beside this panel" behavior the seat
10031
+ should have. Absent on an older base; previewDiff falls back
10032
+ to the service openTab, then to the inline pane. */
10033
+ openDiffExternal: typeof tabProps.onOpenDiff === 'function' ? tabProps.onOpenDiff : undefined,
9560
10034
  }),
9561
10035
  })
9562
10036
  /* The base can go away at RUNTIME (disabled in the plugin panel, or
package/src/index.js CHANGED
@@ -70,6 +70,13 @@ function railConfigSchema(schema) {
70
70
  conversation.view seat at all" — the sidebar and dock seats are not
71
71
  affected; a missing field means the default (shown). */
72
72
  shape.conversationTab = live(schema.boolean().default(true))
73
+ /* Immersive conversation seat (issue #8 round 3): on by default. false means
74
+ the seat falls back to the pin-to-scrollport layout with a visible
75
+ composer; a missing field means the default (on). NOTE: the field MUST be
76
+ declared here — the Host rejects row-Config writes for paths the schema
77
+ does not mark volatile ("Config field … is not volatile"), and the settings
78
+ switch then rolls back silently. */
79
+ shape.immersive = live(schema.boolean().default(true))
73
80
  return schema.object(shape)
74
81
  }
75
82