dsh-ide-git 0.21.2 → 0.23.0

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/src/client.js CHANGED
@@ -315,6 +315,8 @@ window.__ModuleLoader__.load({
315
315
  'settings.placement.tabHint': '关闭后仍可从右侧栏或底部面板打开。',
316
316
  'settings.placement.immersive': '沉浸模式(占满全高,隐藏输入框)',
317
317
  'settings.placement.immersiveHint': '开启后 Git 标签页占满整个会话区高度,输入框自动收起;审批与提问仍会正常显示。',
318
+ 'settings.diff.inline': '在面板内预览差异',
319
+ 'settings.diff.inlineHint': '开启后差异在 Git 面板内打开,可使用左右分栏与自动换行;关闭后交给侧边栏的 diff 预览。',
318
320
  'preview.openFile': '打开文件预览',
319
321
  'lf.added': '+{count}',
320
322
  'lf.deleted': '-{count}',
@@ -399,6 +401,30 @@ window.__ModuleLoader__.load({
399
401
  'operation.revert': '还原进行中',
400
402
  'operation.bisect': '二分定位进行中',
401
403
  'operation.hint': '该仓库还有一个多步操作没有结束,先完成或中止它再执行分支级操作',
404
+ 'worktrees.title': '工作树',
405
+ 'worktrees.new': '新建工作树',
406
+ 'worktrees.prune': '清理失效记录',
407
+ 'worktrees.back': '返回',
408
+ 'worktrees.current': '当前',
409
+ 'worktrees.primary': '主工作树',
410
+ 'worktrees.locked': '已锁定',
411
+ 'worktrees.prunable': '可清理',
412
+ 'worktrees.detached': '游离 HEAD',
413
+ 'worktrees.open': '在面板中打开',
414
+ 'worktrees.remove': '移除工作树',
415
+ 'worktrees.removeConfirm': '移除工作树 {path}?',
416
+ 'worktrees.removeDirty': '工作树 {path} 还有未提交的改动。强制移除会永久丢弃这些改动,确定?',
417
+ 'worktrees.path': '路径',
418
+ 'worktrees.branch': '分支',
419
+ 'worktrees.newBranch': '新分支',
420
+ 'worktrees.existingBranch': '已有分支',
421
+ 'worktrees.base': '起点',
422
+ 'worktrees.create': '创建',
423
+ 'worktrees.empty': '没有工作树',
424
+ 'worktrees.pruneConfirm': '清理已在磁盘上消失的工作树记录?',
425
+ 'toast.worktreeAdded': '已创建工作树 {path}',
426
+ 'toast.worktreeRemoved': '已移除工作树 {path}',
427
+ 'toast.worktreePruned': '已清理失效的工作树记录',
402
428
  'confirm.title': '确认操作',
403
429
  'confirm.cancel': '取消',
404
430
  'confirm.ok': '确定',
@@ -657,6 +683,8 @@ window.__ModuleLoader__.load({
657
683
  'settings.placement.tabHint': 'Off, the panel stays reachable from the right sidebar and the bottom panel.',
658
684
  'settings.placement.immersive': 'Immersive mode (full height, hidden composer)',
659
685
  'settings.placement.immersiveHint': 'On, the Git tab fills the whole conversation area and the composer steps aside; approvals and questions still show.',
686
+ 'settings.diff.inline': 'Preview diffs in the panel',
687
+ 'settings.diff.inlineHint': 'On, diffs open inside the Git panel with split view and line wrap. Off, the sidebar\'s diff preview takes over.',
660
688
  'preview.openFile': 'Open in the file preview',
661
689
  'lf.added': '+{count}',
662
690
  'lf.deleted': '-{count}',
@@ -741,6 +769,30 @@ window.__ModuleLoader__.load({
741
769
  'operation.revert': 'Revert in progress',
742
770
  'operation.bisect': 'Bisect in progress',
743
771
  'operation.hint': 'This repository still has a multi-step operation open — finish or abort it before branch-level actions',
772
+ 'worktrees.title': 'Worktrees',
773
+ 'worktrees.new': 'New worktree',
774
+ 'worktrees.prune': 'Prune stale entries',
775
+ 'worktrees.back': 'Back',
776
+ 'worktrees.current': 'Current',
777
+ 'worktrees.primary': 'Primary',
778
+ 'worktrees.locked': 'Locked',
779
+ 'worktrees.prunable': 'Prunable',
780
+ 'worktrees.detached': 'Detached HEAD',
781
+ 'worktrees.open': 'Open in panel',
782
+ 'worktrees.remove': 'Remove worktree',
783
+ 'worktrees.removeConfirm': 'Remove worktree {path}?',
784
+ 'worktrees.removeDirty': 'Worktree {path} has uncommitted changes. Force removal permanently discards them. Continue?',
785
+ 'worktrees.path': 'Path',
786
+ 'worktrees.branch': 'Branch',
787
+ 'worktrees.newBranch': 'New branch',
788
+ 'worktrees.existingBranch': 'Existing branch',
789
+ 'worktrees.base': 'Start point',
790
+ 'worktrees.create': 'Create',
791
+ 'worktrees.empty': 'No worktrees',
792
+ 'worktrees.pruneConfirm': 'Prune worktree records whose directories are gone?',
793
+ 'toast.worktreeAdded': 'Created worktree {path}',
794
+ 'toast.worktreeRemoved': 'Removed worktree {path}',
795
+ 'toast.worktreePruned': 'Pruned stale worktree records',
744
796
  'confirm.title': 'Confirm',
745
797
  'confirm.cancel': 'Cancel',
746
798
  'confirm.ok': 'OK',
@@ -1007,6 +1059,8 @@ window.__ModuleLoader__.load({
1007
1059
  'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
1008
1060
  'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
1009
1061
  'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
1062
+ 'settings.diff.inline': '喺面板內預覽差異',
1063
+ 'settings.diff.inlineHint': '開啟之後差異喺 Git 面板內開啟,可以用左右分欄同自動換行;關閉之後交畀側邊欄嘅 diff 預覽。',
1010
1064
  'preview.openFile': '開啟檔案預覽',
1011
1065
  'lf.added': '+{count}',
1012
1066
  'lf.deleted': '-{count}',
@@ -1091,6 +1145,30 @@ window.__ModuleLoader__.load({
1091
1145
  'operation.revert': '還原進行中',
1092
1146
  'operation.bisect': 'Bisect 進行中',
1093
1147
  'operation.hint': '呢個存放庫仲有未完成嘅多步操作 —— 請先完成或中止,再進行分支層級嘅操作',
1148
+ 'worktrees.title': '工作樹',
1149
+ 'worktrees.new': '新增工作樹',
1150
+ 'worktrees.prune': '清理失效記錄',
1151
+ 'worktrees.back': '返回',
1152
+ 'worktrees.current': '目前',
1153
+ 'worktrees.primary': '主工作樹',
1154
+ 'worktrees.locked': '已鎖定',
1155
+ 'worktrees.prunable': '可清理',
1156
+ 'worktrees.detached': '分離 HEAD',
1157
+ 'worktrees.open': '喺面板打開',
1158
+ 'worktrees.remove': '移除工作樹',
1159
+ 'worktrees.removeConfirm': '移除工作樹 {path}?',
1160
+ 'worktrees.removeDirty': '工作樹 {path} 仲有未提交嘅改動。強制移除會永久丟棄呢啲改動,確定?',
1161
+ 'worktrees.path': '路徑',
1162
+ 'worktrees.branch': '分支',
1163
+ 'worktrees.newBranch': '新分支',
1164
+ 'worktrees.existingBranch': '現有分支',
1165
+ 'worktrees.base': '起點',
1166
+ 'worktrees.create': '建立',
1167
+ 'worktrees.empty': '冇工作樹',
1168
+ 'worktrees.pruneConfirm': '清理喺磁碟上已經消失嘅工作樹記錄?',
1169
+ 'toast.worktreeAdded': '已建立工作樹 {path}',
1170
+ 'toast.worktreeRemoved': '已移除工作樹 {path}',
1171
+ 'toast.worktreePruned': '已清理失效嘅工作樹記錄',
1094
1172
  'confirm.title': '確認',
1095
1173
  'confirm.cancel': '取消',
1096
1174
  'confirm.ok': '確定',
@@ -1349,6 +1427,8 @@ window.__ModuleLoader__.load({
1349
1427
  'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
1350
1428
  'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
1351
1429
  'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個對話區高度,輸入框自動收起;審核與提問仍會正常顯示。',
1430
+ 'settings.diff.inline': '在面板內預覽差異',
1431
+ 'settings.diff.inlineHint': '開啟後差異在 Git 面板內開啟,可使用左右分欄與自動換行;關閉後交給側邊欄的 diff 預覽。',
1352
1432
  'preview.openFile': '開啟檔案預覽',
1353
1433
  'lf.added': '+{count}',
1354
1434
  'lf.deleted': '-{count}',
@@ -1433,6 +1513,30 @@ window.__ModuleLoader__.load({
1433
1513
  'operation.revert': '還原進行中',
1434
1514
  'operation.bisect': 'Bisect 進行中',
1435
1515
  'operation.hint': '這個版本庫仍有未完成的多步驟操作 —— 請先完成或中止,再進行分支層級的操作',
1516
+ 'worktrees.title': '工作樹',
1517
+ 'worktrees.new': '新增工作樹',
1518
+ 'worktrees.prune': '清理失效記錄',
1519
+ 'worktrees.back': '返回',
1520
+ 'worktrees.current': '目前',
1521
+ 'worktrees.primary': '主工作樹',
1522
+ 'worktrees.locked': '已鎖定',
1523
+ 'worktrees.prunable': '可清理',
1524
+ 'worktrees.detached': '分離 HEAD',
1525
+ 'worktrees.open': '在面板中開啟',
1526
+ 'worktrees.remove': '移除工作樹',
1527
+ 'worktrees.removeConfirm': '移除工作樹 {path}?',
1528
+ 'worktrees.removeDirty': '工作樹 {path} 還有未提交的變更。強制移除會永久捨棄這些變更,確定?',
1529
+ 'worktrees.path': '路徑',
1530
+ 'worktrees.branch': '分支',
1531
+ 'worktrees.newBranch': '新分支',
1532
+ 'worktrees.existingBranch': '現有分支',
1533
+ 'worktrees.base': '起點',
1534
+ 'worktrees.create': '建立',
1535
+ 'worktrees.empty': '沒有工作樹',
1536
+ 'worktrees.pruneConfirm': '清理已在磁碟上消失的工作樹記錄?',
1537
+ 'toast.worktreeAdded': '已建立工作樹 {path}',
1538
+ 'toast.worktreeRemoved': '已移除工作樹 {path}',
1539
+ 'toast.worktreePruned': '已清理失效的工作樹記錄',
1436
1540
  'confirm.title': '確認',
1437
1541
  'confirm.cancel': '取消',
1438
1542
  'confirm.ok': '確定',
@@ -1691,6 +1795,8 @@ window.__ModuleLoader__.load({
1691
1795
  'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
1692
1796
  'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
1693
1797
  'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
1798
+ 'settings.diff.inline': '在面板內預覽差異',
1799
+ 'settings.diff.inlineHint': '開啟後差異在 Git 面板內開啟,可使用左右分欄與自動換行;關閉後交給側邊欄的 diff 預覽。',
1694
1800
  'preview.openFile': '開啟檔案預覽',
1695
1801
  'lf.added': '+{count}',
1696
1802
  'lf.deleted': '-{count}',
@@ -1775,6 +1881,30 @@ window.__ModuleLoader__.load({
1775
1881
  'operation.revert': '還原進行中',
1776
1882
  'operation.bisect': 'Bisect 進行中',
1777
1883
  'operation.hint': '呢個存放庫仲有未完成嘅多步操作 —— 請先完成或中止,再進行分支層級嘅操作',
1884
+ 'worktrees.title': '工作樹',
1885
+ 'worktrees.new': '新增工作樹',
1886
+ 'worktrees.prune': '清理失效記錄',
1887
+ 'worktrees.back': '返回',
1888
+ 'worktrees.current': '目前',
1889
+ 'worktrees.primary': '主工作樹',
1890
+ 'worktrees.locked': '已鎖定',
1891
+ 'worktrees.prunable': '可清理',
1892
+ 'worktrees.detached': '分離 HEAD',
1893
+ 'worktrees.open': '在面板中開啟',
1894
+ 'worktrees.remove': '移除工作樹',
1895
+ 'worktrees.removeConfirm': '移除工作樹 {path}?',
1896
+ 'worktrees.removeDirty': '工作樹 {path} 還有未提交的變更。強制移除會永久捨棄這些變更,確定?',
1897
+ 'worktrees.path': '路徑',
1898
+ 'worktrees.branch': '分支',
1899
+ 'worktrees.newBranch': '新分支',
1900
+ 'worktrees.existingBranch': '現有分支',
1901
+ 'worktrees.base': '起點',
1902
+ 'worktrees.create': '建立',
1903
+ 'worktrees.empty': '沒有工作樹',
1904
+ 'worktrees.pruneConfirm': '清理已在磁碟上消失的工作樹記錄?',
1905
+ 'toast.worktreeAdded': '已建立工作樹 {path}',
1906
+ 'toast.worktreeRemoved': '已移除工作樹 {path}',
1907
+ 'toast.worktreePruned': '已清理失效的工作樹記錄',
1778
1908
  'confirm.title': '確認',
1779
1909
  'confirm.cancel': '取消',
1780
1910
  'confirm.ok': '確定',
@@ -2033,6 +2163,8 @@ window.__ModuleLoader__.load({
2033
2163
  'settings.placement.tabHint': 'オフにしても右サイドバーと下部パネルから開けます。',
2034
2164
  'settings.placement.immersive': '没入モード(全高表示・入力欄を非表示)',
2035
2165
  'settings.placement.immersiveHint': 'オンにすると Git タブが会話エリアの高さいっぱいに広がり、入力欄は引っ込みます。承認や質問は通常どおり表示されます。',
2166
+ 'settings.diff.inline': 'パネル内で差分をプレビュー',
2167
+ 'settings.diff.inlineHint': 'オンの場合、差分は Git パネル内で開き、左右分割と折り返しを使用できます。オフの場合はサイドバーの差分プレビューに任せます。',
2036
2168
  'preview.openFile': 'ファイルプレビューを開く',
2037
2169
  'lf.added': '+{count}',
2038
2170
  'lf.deleted': '-{count}',
@@ -2117,6 +2249,30 @@ window.__ModuleLoader__.load({
2117
2249
  'operation.revert': '打ち消し中',
2118
2250
  'operation.bisect': 'bisect 中',
2119
2251
  'operation.hint': 'このリポジトリには未完了の多段階操作があります —— ブランチ操作の前に完了または中止してください',
2252
+ 'worktrees.title': 'ワークツリー',
2253
+ 'worktrees.new': '新しいワークツリー',
2254
+ 'worktrees.prune': '古い記録を整理',
2255
+ 'worktrees.back': '戻る',
2256
+ 'worktrees.current': '現在',
2257
+ 'worktrees.primary': 'メイン',
2258
+ 'worktrees.locked': 'ロック中',
2259
+ 'worktrees.prunable': '整理可能',
2260
+ 'worktrees.detached': '分離 HEAD',
2261
+ 'worktrees.open': 'パネルで開く',
2262
+ 'worktrees.remove': 'ワークツリーを削除',
2263
+ 'worktrees.removeConfirm': 'ワークツリー {path} を削除しますか?',
2264
+ 'worktrees.removeDirty': 'ワークツリー {path} には未コミットの変更があります。強制削除するとこれらの変更は完全に失われます。続行しますか?',
2265
+ 'worktrees.path': 'パス',
2266
+ 'worktrees.branch': 'ブランチ',
2267
+ 'worktrees.newBranch': '新しいブランチ',
2268
+ 'worktrees.existingBranch': '既存のブランチ',
2269
+ 'worktrees.base': '開始点',
2270
+ 'worktrees.create': '作成',
2271
+ 'worktrees.empty': 'ワークツリーはありません',
2272
+ 'worktrees.pruneConfirm': 'ディスク上に存在しないワークツリーの記録を整理しますか?',
2273
+ 'toast.worktreeAdded': 'ワークツリー {path} を作成しました',
2274
+ 'toast.worktreeRemoved': 'ワークツリー {path} を削除しました',
2275
+ 'toast.worktreePruned': '古いワークツリー記録を整理しました',
2120
2276
  'confirm.title': '確認',
2121
2277
  'confirm.cancel': 'キャンセル',
2122
2278
  'confirm.ok': 'OK',
@@ -2375,6 +2531,8 @@ window.__ModuleLoader__.load({
2375
2531
  'settings.placement.tabHint': '꺼도 오른쪽 사이드바와 하단 패널에서 열 수 있습니다.',
2376
2532
  'settings.placement.immersive': '몰입 모드(전체 높이, 입력창 숨김)',
2377
2533
  'settings.placement.immersiveHint': '켜면 Git 탭이 대화 영역 전체 높이를 차지하고 입력창은 숨겨집니다. 승인과 질문은 정상적으로 표시됩니다.',
2534
+ 'settings.diff.inline': '패널에서 차이 미리보기',
2535
+ 'settings.diff.inlineHint': '켜면 차이가 Git 패널 안에서 열리고 좌우 분할과 자동 줄바꿈을 사용할 수 있습니다. 끄면 사이드바의 차이 미리보기로 넘깁니다.',
2378
2536
  'preview.openFile': '파일 미리보기로 열기',
2379
2537
  'lf.added': '+{count}',
2380
2538
  'lf.deleted': '-{count}',
@@ -2459,6 +2617,30 @@ window.__ModuleLoader__.load({
2459
2617
  'operation.revert': '되돌리기 진행 중',
2460
2618
  'operation.bisect': 'bisect 진행 중',
2461
2619
  'operation.hint': '이 저장소에 아직 끝나지 않은 다단계 작업이 있습니다 —— 브랜치 작업 전에 완료하거나 중단하세요',
2620
+ 'worktrees.title': '워크트리',
2621
+ 'worktrees.new': '새 워크트리',
2622
+ 'worktrees.prune': '오래된 기록 정리',
2623
+ 'worktrees.back': '뒤로',
2624
+ 'worktrees.current': '현재',
2625
+ 'worktrees.primary': '기본',
2626
+ 'worktrees.locked': '잠김',
2627
+ 'worktrees.prunable': '정리 가능',
2628
+ 'worktrees.detached': '분리된 HEAD',
2629
+ 'worktrees.open': '패널에서 열기',
2630
+ 'worktrees.remove': '워크트리 제거',
2631
+ 'worktrees.removeConfirm': '워크트리 {path}을(를) 제거할까요?',
2632
+ 'worktrees.removeDirty': '워크트리 {path}에 커밋되지 않은 변경 사항이 있습니다. 강제 제거하면 이 변경 사항이 영구적으로 사라집니다. 계속할까요?',
2633
+ 'worktrees.path': '경로',
2634
+ 'worktrees.branch': '브랜치',
2635
+ 'worktrees.newBranch': '새 브랜치',
2636
+ 'worktrees.existingBranch': '기존 브랜치',
2637
+ 'worktrees.base': '시작점',
2638
+ 'worktrees.create': '만들기',
2639
+ 'worktrees.empty': '워크트리가 없습니다',
2640
+ 'worktrees.pruneConfirm': '디스크에서 사라진 워크트리 기록을 정리할까요?',
2641
+ 'toast.worktreeAdded': '워크트리 {path}을(를) 만들었습니다',
2642
+ 'toast.worktreeRemoved': '워크트리 {path}을(를) 제거했습니다',
2643
+ 'toast.worktreePruned': '오래된 워크트리 기록을 정리했습니다',
2462
2644
  'confirm.title': '확인',
2463
2645
  'confirm.cancel': '취소',
2464
2646
  'confirm.ok': '확인',
@@ -2717,6 +2899,8 @@ window.__ModuleLoader__.load({
2717
2899
  'settings.placement.tabHint': 'Ausgeschaltet bleibt das Panel über rechte Seitenleiste und unteres Panel erreichbar.',
2718
2900
  'settings.placement.immersive': 'Immersiver Modus (volle Höhe, ausgeblendetes Eingabefeld)',
2719
2901
  '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.',
2902
+ 'settings.diff.inline': 'Diffs im Panel anzeigen',
2903
+ 'settings.diff.inlineHint': 'Ein: Diffs öffnen im Git-Panel, mit geteilter Ansicht und Zeilenumbruch. Aus: Die Diff-Vorschau der Seitenleiste übernimmt.',
2720
2904
  'preview.openFile': 'In der Dateivorschau öffnen',
2721
2905
  'lf.added': '+{count}',
2722
2906
  'lf.deleted': '-{count}',
@@ -2801,6 +2985,30 @@ window.__ModuleLoader__.load({
2801
2985
  'operation.revert': 'Revert läuft',
2802
2986
  'operation.bisect': 'Bisect läuft',
2803
2987
  'operation.hint': 'In diesem Repository läuft noch eine mehrstufige Operation —— bitte zuerst abschließen oder abbrechen',
2988
+ 'worktrees.title': 'Worktrees',
2989
+ 'worktrees.new': 'Neuer Worktree',
2990
+ 'worktrees.prune': 'Verwaiste Einträge bereinigen',
2991
+ 'worktrees.back': 'Zurück',
2992
+ 'worktrees.current': 'Aktuell',
2993
+ 'worktrees.primary': 'Primär',
2994
+ 'worktrees.locked': 'Gesperrt',
2995
+ 'worktrees.prunable': 'Bereinigbar',
2996
+ 'worktrees.detached': 'Losgelöster HEAD',
2997
+ 'worktrees.open': 'Im Panel öffnen',
2998
+ 'worktrees.remove': 'Worktree entfernen',
2999
+ 'worktrees.removeConfirm': 'Worktree {path} entfernen?',
3000
+ 'worktrees.removeDirty': 'Worktree {path} hat nicht committete Änderungen. Erzwungenes Entfernen verwirft sie dauerhaft. Fortfahren?',
3001
+ 'worktrees.path': 'Pfad',
3002
+ 'worktrees.branch': 'Branch',
3003
+ 'worktrees.newBranch': 'Neuer Branch',
3004
+ 'worktrees.existingBranch': 'Vorhandener Branch',
3005
+ 'worktrees.base': 'Startpunkt',
3006
+ 'worktrees.create': 'Erstellen',
3007
+ 'worktrees.empty': 'Keine Worktrees',
3008
+ 'worktrees.pruneConfirm': 'Einträge für nicht mehr vorhandene Worktrees bereinigen?',
3009
+ 'toast.worktreeAdded': 'Worktree {path} erstellt',
3010
+ 'toast.worktreeRemoved': 'Worktree {path} entfernt',
3011
+ 'toast.worktreePruned': 'Verwaiste Worktree-Einträge bereinigt',
2804
3012
  'confirm.title': 'Bestätigen',
2805
3013
  'confirm.cancel': 'Abbrechen',
2806
3014
  'confirm.ok': 'OK',
@@ -3059,6 +3267,8 @@ window.__ModuleLoader__.load({
3059
3267
  'settings.placement.tabHint': 'Désactivé, le panneau reste accessible depuis la barre latérale droite et le panneau inférieur.',
3060
3268
  'settings.placement.immersive': 'Mode immersif (pleine hauteur, zone de saisie masquée)',
3061
3269
  '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.',
3270
+ 'settings.diff.inline': 'Aperçu des diffs dans le panneau',
3271
+ 'settings.diff.inlineHint': 'Activé, les diffs s’ouvrent dans le panneau Git, avec vue scindée et retour à la ligne. Désactivé, l’aperçu diff de la barre latérale prend le relais.',
3062
3272
  'preview.openFile': 'Ouvrir dans l’aperçu du fichier',
3063
3273
  'lf.added': '+{count}',
3064
3274
  'lf.deleted': '-{count}',
@@ -3143,6 +3353,30 @@ window.__ModuleLoader__.load({
3143
3353
  'operation.revert': 'Annulation en cours',
3144
3354
  'operation.bisect': 'Bisect en cours',
3145
3355
  'operation.hint': 'Ce dépôt a encore une opération multi-étapes en cours —— terminez-la ou abandonnez-la avant les actions de branche',
3356
+ 'worktrees.title': 'Arbres de travail',
3357
+ 'worktrees.new': 'Nouvel arbre de travail',
3358
+ 'worktrees.prune': 'Nettoyer les entrées obsolètes',
3359
+ 'worktrees.back': 'Retour',
3360
+ 'worktrees.current': 'Actuel',
3361
+ 'worktrees.primary': 'Principal',
3362
+ 'worktrees.locked': 'Verrouillé',
3363
+ 'worktrees.prunable': 'Nettoyable',
3364
+ 'worktrees.detached': 'HEAD détaché',
3365
+ 'worktrees.open': 'Ouvrir dans le panneau',
3366
+ 'worktrees.remove': 'Supprimer l’arbre de travail',
3367
+ 'worktrees.removeConfirm': 'Supprimer l’arbre de travail {path}?',
3368
+ 'worktrees.removeDirty': 'L’arbre de travail {path} contient des modifications non validées. Une suppression forcée les perdra définitivement. Continuer?',
3369
+ 'worktrees.path': 'Chemin',
3370
+ 'worktrees.branch': 'Branche',
3371
+ 'worktrees.newBranch': 'Nouvelle branche',
3372
+ 'worktrees.existingBranch': 'Branche existante',
3373
+ 'worktrees.base': 'Point de départ',
3374
+ 'worktrees.create': 'Créer',
3375
+ 'worktrees.empty': 'Aucun arbre de travail',
3376
+ 'worktrees.pruneConfirm': 'Nettoyer les enregistrements des arbres de travail disparus du disque?',
3377
+ 'toast.worktreeAdded': 'Arbre de travail {path} créé',
3378
+ 'toast.worktreeRemoved': 'Arbre de travail {path} supprimé',
3379
+ 'toast.worktreePruned': 'Enregistrements obsolètes nettoyés',
3146
3380
  'confirm.title': 'Confirmer',
3147
3381
  'confirm.cancel': 'Annuler',
3148
3382
  'confirm.ok': 'OK',
@@ -3401,6 +3635,8 @@ window.__ModuleLoader__.load({
3401
3635
  'settings.placement.tabHint': 'В выключенном виде панель доступна из правой боковой панели и нижней панели.',
3402
3636
  'settings.placement.immersive': 'Полноэкранный режим (во всю высоту, без поля ввода)',
3403
3637
  'settings.placement.immersiveHint': 'Во включённом виде вкладка Git занимает всю высоту области диалога, поле ввода скрывается; подтверждения и вопросы остаются на виду.',
3638
+ 'settings.diff.inline': 'Просмотр различий в панели',
3639
+ 'settings.diff.inlineHint': 'Включено: различия открываются в панели Git с разделённым видом и переносом строк. Выключено: показ переходит к боковой панели.',
3404
3640
  'preview.openFile': 'Открыть в предпросмотре файла',
3405
3641
  'lf.added': '+{count}',
3406
3642
  'lf.deleted': '-{count}',
@@ -3485,6 +3721,30 @@ window.__ModuleLoader__.load({
3485
3721
  'operation.revert': 'Идёт отмена коммита',
3486
3722
  'operation.bisect': 'Идёт bisect',
3487
3723
  'operation.hint': 'В этом репозитории ещё не завершена многошаговая операция —— завершите или прервите её перед действиями с ветками',
3724
+ 'worktrees.title': 'Рабочие деревья',
3725
+ 'worktrees.new': 'Новое рабочее дерево',
3726
+ 'worktrees.prune': 'Очистить устаревшие записи',
3727
+ 'worktrees.back': 'Назад',
3728
+ 'worktrees.current': 'Текущее',
3729
+ 'worktrees.primary': 'Основное',
3730
+ 'worktrees.locked': 'Заблокировано',
3731
+ 'worktrees.prunable': 'Можно очистить',
3732
+ 'worktrees.detached': 'Отсоединённый HEAD',
3733
+ 'worktrees.open': 'Открыть в панели',
3734
+ 'worktrees.remove': 'Удалить рабочее дерево',
3735
+ 'worktrees.removeConfirm': 'Удалить рабочее дерево {path}?',
3736
+ 'worktrees.removeDirty': 'В рабочем дереве {path} есть незакоммиченные изменения. Принудительное удаление безвозвратно их удалит. Продолжить?',
3737
+ 'worktrees.path': 'Путь',
3738
+ 'worktrees.branch': 'Ветка',
3739
+ 'worktrees.newBranch': 'Новая ветка',
3740
+ 'worktrees.existingBranch': 'Существующая ветка',
3741
+ 'worktrees.base': 'Начальная точка',
3742
+ 'worktrees.create': 'Создать',
3743
+ 'worktrees.empty': 'Нет рабочих деревьев',
3744
+ 'worktrees.pruneConfirm': 'Очистить записи рабочих деревьев, исчезнувших с диска?',
3745
+ 'toast.worktreeAdded': 'Рабочее дерево {path} создано',
3746
+ 'toast.worktreeRemoved': 'Рабочее дерево {path} удалено',
3747
+ 'toast.worktreePruned': 'Устаревшие записи очищены',
3488
3748
  'confirm.title': 'Подтверждение',
3489
3749
  'confirm.cancel': 'Отмена',
3490
3750
  'confirm.ok': 'OK',
@@ -3743,6 +4003,8 @@ window.__ModuleLoader__.load({
3743
4003
  'settings.placement.tabHint': 'Desligado, o painel continua disponível na barra lateral direita e no painel inferior.',
3744
4004
  'settings.placement.immersive': 'Modo imersivo (altura total, campo de entrada oculto)',
3745
4005
  '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.',
4006
+ 'settings.diff.inline': 'Pré-visualizar diferenças no painel',
4007
+ 'settings.diff.inlineHint': 'Ligado, as diferenças abrem no painel Git, com vista dividida e quebra de linha. Desligado, a pré-visualização da barra lateral assume.',
3746
4008
  'preview.openFile': 'Abrir na pré-visualização do arquivo',
3747
4009
  'lf.added': '+{count}',
3748
4010
  'lf.deleted': '-{count}',
@@ -3827,6 +4089,30 @@ window.__ModuleLoader__.load({
3827
4089
  'operation.revert': 'Reversão em andamento',
3828
4090
  'operation.bisect': 'Bisect em andamento',
3829
4091
  'operation.hint': 'Este repositório ainda tem uma operação de várias etapas aberta —— conclua ou aborte antes das ações de branch',
4092
+ 'worktrees.title': 'Árvores de trabalho',
4093
+ 'worktrees.new': 'Nova árvore de trabalho',
4094
+ 'worktrees.prune': 'Limpar registros obsoletos',
4095
+ 'worktrees.back': 'Voltar',
4096
+ 'worktrees.current': 'Atual',
4097
+ 'worktrees.primary': 'Principal',
4098
+ 'worktrees.locked': 'Bloqueada',
4099
+ 'worktrees.prunable': 'Limpável',
4100
+ 'worktrees.detached': 'HEAD destacado',
4101
+ 'worktrees.open': 'Abrir no painel',
4102
+ 'worktrees.remove': 'Remover árvore de trabalho',
4103
+ 'worktrees.removeConfirm': 'Remover a árvore de trabalho {path}?',
4104
+ 'worktrees.removeDirty': 'A árvore de trabalho {path} tem alterações não commitadas. A remoção forçada irá descartá-las permanentemente. Continuar?',
4105
+ 'worktrees.path': 'Caminho',
4106
+ 'worktrees.branch': 'Branch',
4107
+ 'worktrees.newBranch': 'Nova branch',
4108
+ 'worktrees.existingBranch': 'Branch existente',
4109
+ 'worktrees.base': 'Ponto de partida',
4110
+ 'worktrees.create': 'Criar',
4111
+ 'worktrees.empty': 'Nenhuma árvore de trabalho',
4112
+ 'worktrees.pruneConfirm': 'Limpar registros de árvores de trabalho que não existem mais no disco?',
4113
+ 'toast.worktreeAdded': 'Árvore de trabalho {path} criada',
4114
+ 'toast.worktreeRemoved': 'Árvore de trabalho {path} removida',
4115
+ 'toast.worktreePruned': 'Registros obsoletos limpos',
3830
4116
  'confirm.title': 'Confirmar',
3831
4117
  'confirm.cancel': 'Cancelar',
3832
4118
  'confirm.ok': 'OK',
@@ -4085,6 +4371,8 @@ window.__ModuleLoader__.load({
4085
4371
  'settings.placement.tabHint': 'Da spento, il pannello resta raggiungibile dalla barra laterale destra e dal pannello inferiore.',
4086
4372
  'settings.placement.immersive': 'Modalità immersiva (altezza piena, input nascosto)',
4087
4373
  '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.',
4374
+ 'settings.diff.inline': 'Anteprima delle differenze nel pannello',
4375
+ 'settings.diff.inlineHint': 'Attivo: le differenze si aprono nel pannello Git, con vista affiancata e ritorno a capo. Disattivo: subentra l’anteprima della barra laterale.',
4088
4376
  'preview.openFile': 'Apri nell’anteprima del file',
4089
4377
  'lf.added': '+{count}',
4090
4378
  'lf.deleted': '-{count}',
@@ -4169,6 +4457,30 @@ window.__ModuleLoader__.load({
4169
4457
  'operation.revert': 'Annullamento in corso',
4170
4458
  'operation.bisect': 'Bisect in corso',
4171
4459
  'operation.hint': 'Questo repository ha ancora un’operazione a più passaggi aperta —— completala o interrompila prima delle azioni sui branch',
4460
+ 'worktrees.title': 'Alberi di lavoro',
4461
+ 'worktrees.new': 'Nuovo albero di lavoro',
4462
+ 'worktrees.prune': 'Pulisci record obsoleti',
4463
+ 'worktrees.back': 'Indietro',
4464
+ 'worktrees.current': 'Attuale',
4465
+ 'worktrees.primary': 'Principale',
4466
+ 'worktrees.locked': 'Bloccato',
4467
+ 'worktrees.prunable': 'Pulibile',
4468
+ 'worktrees.detached': 'HEAD scollegato',
4469
+ 'worktrees.open': 'Apri nel pannello',
4470
+ 'worktrees.remove': 'Rimuovi albero di lavoro',
4471
+ 'worktrees.removeConfirm': 'Rimuovere l’albero di lavoro {path}?',
4472
+ 'worktrees.removeDirty': 'L’albero di lavoro {path} ha modifiche non committate. La rimozione forzata le eliminerà definitivamente. Continuare?',
4473
+ 'worktrees.path': 'Percorso',
4474
+ 'worktrees.branch': 'Branch',
4475
+ 'worktrees.newBranch': 'Nuovo branch',
4476
+ 'worktrees.existingBranch': 'Branch esistente',
4477
+ 'worktrees.base': 'Punto di partenza',
4478
+ 'worktrees.create': 'Crea',
4479
+ 'worktrees.empty': 'Nessun albero di lavoro',
4480
+ 'worktrees.pruneConfirm': 'Pulisci i record degli alberi di lavoro scomparsi dal disco?',
4481
+ 'toast.worktreeAdded': 'Albero di lavoro {path} creato',
4482
+ 'toast.worktreeRemoved': 'Albero di lavoro {path} rimosso',
4483
+ 'toast.worktreePruned': 'Record obsoleti puliti',
4172
4484
  'confirm.title': 'Conferma',
4173
4485
  'confirm.cancel': 'Annulla',
4174
4486
  'confirm.ok': 'OK',
@@ -4427,6 +4739,8 @@ window.__ModuleLoader__.load({
4427
4739
  'settings.placement.tabHint': 'Uitgeschakeld blijft het paneel bereikbaar via de rechterzijbalk en het onderpaneel.',
4428
4740
  'settings.placement.immersive': 'Meeslepende modus (volledige hoogte, verborgen invoerveld)',
4429
4741
  'settings.placement.immersiveHint': 'Aan: het Git-tabblad vult de hele gespreksruimte en het invoerveld wijkt; goedkeuringen en vragen blijven zichtbaar.',
4742
+ 'settings.diff.inline': 'Diffs in het paneel bekijken',
4743
+ 'settings.diff.inlineHint': 'Aan: diffs openen in het Git-paneel, met gesplitste weergave en regelterugloop. Uit: de diff-voorvertoning in de zijbalk neemt het over.',
4430
4744
  'preview.openFile': 'Openen in de bestandsvoorbeeld',
4431
4745
  'lf.added': '+{count}',
4432
4746
  'lf.deleted': '-{count}',
@@ -4511,6 +4825,30 @@ window.__ModuleLoader__.load({
4511
4825
  'operation.revert': 'Terugdraaien bezig',
4512
4826
  'operation.bisect': 'Bisect bezig',
4513
4827
  'operation.hint': 'Deze repository heeft nog een meerstapsbewerking openstaan —— rond die af of breek die af voordat je branchacties uitvoert',
4828
+ 'worktrees.title': 'Werkbomen',
4829
+ 'worktrees.new': 'Nieuwe werkboom',
4830
+ 'worktrees.prune': 'Verouderde records opschonen',
4831
+ 'worktrees.back': 'Terug',
4832
+ 'worktrees.current': 'Huidige',
4833
+ 'worktrees.primary': 'Primair',
4834
+ 'worktrees.locked': 'Vergrendeld',
4835
+ 'worktrees.prunable': 'Opschoonbaar',
4836
+ 'worktrees.detached': 'Losgekoppelde HEAD',
4837
+ 'worktrees.open': 'In paneel openen',
4838
+ 'worktrees.remove': 'Werkboom verwijderen',
4839
+ 'worktrees.removeConfirm': 'Werkboom {path} verwijderen?',
4840
+ 'worktrees.removeDirty': 'Werkboom {path} heeft niet-gecommitte wijzigingen. Geforceerd verwijderen gooit ze definitief weg. Doorgaan?',
4841
+ 'worktrees.path': 'Pad',
4842
+ 'worktrees.branch': 'Branch',
4843
+ 'worktrees.newBranch': 'Nieuwe branch',
4844
+ 'worktrees.existingBranch': 'Bestaande branch',
4845
+ 'worktrees.base': 'Startpunt',
4846
+ 'worktrees.create': 'Aanmaken',
4847
+ 'worktrees.empty': 'Geen werkbomen',
4848
+ 'worktrees.pruneConfirm': 'Records van werkbomen die niet meer op schijf staan opschonen?',
4849
+ 'toast.worktreeAdded': 'Werkboom {path} aangemaakt',
4850
+ 'toast.worktreeRemoved': 'Werkboom {path} verwijderd',
4851
+ 'toast.worktreePruned': 'Verouderde werkboomrecords opgeschoond',
4514
4852
  'confirm.title': 'Bevestigen',
4515
4853
  'confirm.cancel': 'Annuleren',
4516
4854
  'confirm.ok': 'OK',
@@ -4769,6 +5107,8 @@ window.__ModuleLoader__.load({
4769
5107
  'settings.placement.tabHint': 'Po wyłączeniu panel pozostaje dostępny z prawego paska bocznego i dolnego panelu.',
4770
5108
  'settings.placement.immersive': 'Tryb immersyjny (pełna wysokość, ukryte pole wpisywania)',
4771
5109
  'settings.placement.immersiveHint': 'Po włączeniu karta Git wypełnia całą wysokość rozmowy, a pole wpisywania się chowa; zatwierdzenia i pytania pozostają widoczne.',
5110
+ 'settings.diff.inline': 'Podgląd różnic w panelu',
5111
+ 'settings.diff.inlineHint': 'Włączone: różnice otwierają się w panelu Git, z widokiem dzielonym i zawijaniem wierszy. Wyłączone: podgląd różnic w pasku bocznym przejmuje.',
4772
5112
  'preview.openFile': 'Otwórz w podglądzie pliku',
4773
5113
  'lf.added': '+{count}',
4774
5114
  'lf.deleted': '-{count}',
@@ -4853,6 +5193,30 @@ window.__ModuleLoader__.load({
4853
5193
  'operation.revert': 'Trwa wycofywanie',
4854
5194
  'operation.bisect': 'Trwa bisect',
4855
5195
  'operation.hint': 'W tym repozytorium wciąż trwa wieloetapowa operacja —— zakończ ją lub przerwij przed działaniami na gałęziach',
5196
+ 'worktrees.title': 'Drzewa robocze',
5197
+ 'worktrees.new': 'Nowe drzewo robocze',
5198
+ 'worktrees.prune': 'Wyczyść nieaktualne wpisy',
5199
+ 'worktrees.back': 'Wstecz',
5200
+ 'worktrees.current': 'Bieżące',
5201
+ 'worktrees.primary': 'Główne',
5202
+ 'worktrees.locked': 'Zablokowane',
5203
+ 'worktrees.prunable': 'Do wyczyszczenia',
5204
+ 'worktrees.detached': 'Odłączony HEAD',
5205
+ 'worktrees.open': 'Otwórz w panelu',
5206
+ 'worktrees.remove': 'Usuń drzewo robocze',
5207
+ 'worktrees.removeConfirm': 'Usunąć drzewo robocze {path}?',
5208
+ 'worktrees.removeDirty': 'Drzewo robocze {path} ma niezatwierdzone zmiany. Wymuszone usunięcie bezpowrotnie je utraci. Kontynuować?',
5209
+ 'worktrees.path': 'Ścieżka',
5210
+ 'worktrees.branch': 'Gałąź',
5211
+ 'worktrees.newBranch': 'Nowa gałąź',
5212
+ 'worktrees.existingBranch': 'Istniejąca gałąź',
5213
+ 'worktrees.base': 'Punkt początkowy',
5214
+ 'worktrees.create': 'Utwórz',
5215
+ 'worktrees.empty': 'Brak drzew roboczych',
5216
+ 'worktrees.pruneConfirm': 'Wyczyścić wpisy drzew roboczych, które zniknęły z dysku?',
5217
+ 'toast.worktreeAdded': 'Utworzono drzewo robocze {path}',
5218
+ 'toast.worktreeRemoved': 'Usunięto drzewo robocze {path}',
5219
+ 'toast.worktreePruned': 'Wyczyszczono nieaktualne wpisy',
4856
5220
  'confirm.title': 'Potwierdzenie',
4857
5221
  'confirm.cancel': 'Anuluj',
4858
5222
  'confirm.ok': 'OK',
@@ -5111,6 +5475,8 @@ window.__ModuleLoader__.load({
5111
5475
  'settings.placement.tabHint': 'Avstängd finns panelen kvar via höger sidopanel och nedre panelen.',
5112
5476
  'settings.placement.immersive': 'Uppslukande läge (full höjd, dolt inmatningsfält)',
5113
5477
  'settings.placement.immersiveHint': 'På: Git-fliken fyller hela konversationsytan och inmatningsfältet träder åt sidan; godkännanden och frågor visas fortfarande.',
5478
+ 'settings.diff.inline': 'Förhandsgranska diffar i panelen',
5479
+ 'settings.diff.inlineHint': 'På: diffar öppnas i Git-panelen med delad vy och radbrytning. Av: sidopanelens diff-förhandsgranskning tar över.',
5114
5480
  'preview.openFile': 'Öppna i filförhandsvisningen',
5115
5481
  'lf.added': '+{count}',
5116
5482
  'lf.deleted': '-{count}',
@@ -5195,6 +5561,30 @@ window.__ModuleLoader__.load({
5195
5561
  'operation.revert': 'Återställning pågår',
5196
5562
  'operation.bisect': 'Bisect pågår',
5197
5563
  'operation.hint': 'Det här arkivet har fortfarande en flerstegsoperation öppen —— slutför eller avbryt den före grenåtgärder',
5564
+ 'worktrees.title': 'Arbetsträd',
5565
+ 'worktrees.new': 'Nytt arbetsträd',
5566
+ 'worktrees.prune': 'Rensa inaktuella poster',
5567
+ 'worktrees.back': 'Tillbaka',
5568
+ 'worktrees.current': 'Aktuellt',
5569
+ 'worktrees.primary': 'Primärt',
5570
+ 'worktrees.locked': 'Låst',
5571
+ 'worktrees.prunable': 'Rensningsbart',
5572
+ 'worktrees.detached': 'Frånkopplad HEAD',
5573
+ 'worktrees.open': 'Öppna i panelen',
5574
+ 'worktrees.remove': 'Ta bort arbetsträd',
5575
+ 'worktrees.removeConfirm': 'Ta bort arbetsträdet {path}?',
5576
+ 'worktrees.removeDirty': 'Arbetsträdet {path} har ej committade ändringar. Tvingad borttagning slänger dem permanent. Fortsätt?',
5577
+ 'worktrees.path': 'Sökväg',
5578
+ 'worktrees.branch': 'Gren',
5579
+ 'worktrees.newBranch': 'Ny gren',
5580
+ 'worktrees.existingBranch': 'Befintlig gren',
5581
+ 'worktrees.base': 'Startpunkt',
5582
+ 'worktrees.create': 'Skapa',
5583
+ 'worktrees.empty': 'Inga arbetsträd',
5584
+ 'worktrees.pruneConfirm': 'Rensa poster för arbetsträd som försvunnit från disken?',
5585
+ 'toast.worktreeAdded': 'Arbetsträdet {path} skapat',
5586
+ 'toast.worktreeRemoved': 'Arbetsträdet {path} borttaget',
5587
+ 'toast.worktreePruned': 'Inaktuella arbetsträdsposter rensade',
5198
5588
  'confirm.title': 'Bekräfta',
5199
5589
  'confirm.cancel': 'Avbryt',
5200
5590
  'confirm.ok': 'OK',
@@ -5453,6 +5843,8 @@ window.__ModuleLoader__.load({
5453
5843
  'settings.placement.tabHint': 'Kapatıldığında panel sağ kenar çubuğundan ve alt panelden açılabilir.',
5454
5844
  'settings.placement.immersive': 'Odaklanma modu (tam yükseklik, gizli giriş alanı)',
5455
5845
  '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.',
5846
+ 'settings.diff.inline': 'Farkları panelde önizle',
5847
+ 'settings.diff.inlineHint': 'Açık: farklar Git panelinde açılır; bölünmüş görünüm ve satır kaydırma kullanılabilir. Kapalı: kenar çubuğundaki fark önizlemesi devralır.',
5456
5848
  'preview.openFile': 'Dosya önizlemesinde aç',
5457
5849
  'lf.added': '+{count}',
5458
5850
  'lf.deleted': '-{count}',
@@ -5537,6 +5929,30 @@ window.__ModuleLoader__.load({
5537
5929
  'operation.revert': 'Geri alma sürüyor',
5538
5930
  'operation.bisect': 'Bisect sürüyor',
5539
5931
  'operation.hint': 'Bu depoda hâlâ açık çok adımlı bir işlem var —— dal işlemlerinden önce tamamlayın veya iptal edin',
5932
+ 'worktrees.title': 'Çalışma ağaçları',
5933
+ 'worktrees.new': 'Yeni çalışma ağacı',
5934
+ 'worktrees.prune': 'Eskimiş kayıtları temizle',
5935
+ 'worktrees.back': 'Geri',
5936
+ 'worktrees.current': 'Geçerli',
5937
+ 'worktrees.primary': 'Birincil',
5938
+ 'worktrees.locked': 'Kilitli',
5939
+ 'worktrees.prunable': 'Temizlenebilir',
5940
+ 'worktrees.detached': 'Ayrık HEAD',
5941
+ 'worktrees.open': 'Panelde aç',
5942
+ 'worktrees.remove': 'Çalışma ağacını kaldır',
5943
+ 'worktrees.removeConfirm': '{path} çalışma ağacı kaldırılsın mı?',
5944
+ 'worktrees.removeDirty': '{path} çalışma ağacında işlenmemiş değişiklikler var. Zorla kaldırma bunları kalıcı olarak siler. Devam?',
5945
+ 'worktrees.path': 'Yol',
5946
+ 'worktrees.branch': 'Dal',
5947
+ 'worktrees.newBranch': 'Yeni dal',
5948
+ 'worktrees.existingBranch': 'Mevcut dal',
5949
+ 'worktrees.base': 'Başlangıç noktası',
5950
+ 'worktrees.create': 'Oluştur',
5951
+ 'worktrees.empty': 'Çalışma ağacı yok',
5952
+ 'worktrees.pruneConfirm': 'Diskten kaybolan çalışma ağacı kayıtları temizlensin mi?',
5953
+ 'toast.worktreeAdded': '{path} çalışma ağacı oluşturuldu',
5954
+ 'toast.worktreeRemoved': '{path} çalışma ağacı kaldırıldı',
5955
+ 'toast.worktreePruned': 'Eskimiş çalışma ağacı kayıtları temizlendi',
5540
5956
  'confirm.title': 'Onayla',
5541
5957
  'confirm.cancel': 'İptal',
5542
5958
  'confirm.ok': 'Tamam',
@@ -5795,6 +6211,8 @@ window.__ModuleLoader__.load({
5795
6211
  'settings.placement.tabHint': 'Dimatikan, panel tetap bisa dibuka dari bilah sisi kanan dan panel bawah.',
5796
6212
  'settings.placement.immersive': 'Mode imersif (tinggi penuh, kolom masukan tersembunyi)',
5797
6213
  'settings.placement.immersiveHint': 'Aktif: tab Git memenuhi seluruh tinggi area percakapan dan kolom masukan mengalah; persetujuan dan pertanyaan tetap tampil.',
6214
+ 'settings.diff.inline': 'Pratinjau diff di panel',
6215
+ 'settings.diff.inlineHint': 'Aktif: diff terbuka di panel Git, dengan tampilan terbagi dan pelipatan baris. Nonaktif: pratinjau diff bilah sisi mengambil alih.',
5798
6216
  'preview.openFile': 'Buka di pratinjau berkas',
5799
6217
  'lf.added': '+{count}',
5800
6218
  'lf.deleted': '-{count}',
@@ -5879,6 +6297,30 @@ window.__ModuleLoader__.load({
5879
6297
  'operation.revert': 'Pembatalan berlangsung',
5880
6298
  'operation.bisect': 'Bisect berlangsung',
5881
6299
  'operation.hint': 'Repositori ini masih punya operasi bertahap yang terbuka —— selesaikan atau batalkan sebelum tindakan tingkat branch',
6300
+ 'worktrees.title': 'Worktree',
6301
+ 'worktrees.new': 'Worktree baru',
6302
+ 'worktrees.prune': 'Bersihkan catatan usang',
6303
+ 'worktrees.back': 'Kembali',
6304
+ 'worktrees.current': 'Saat ini',
6305
+ 'worktrees.primary': 'Utama',
6306
+ 'worktrees.locked': 'Terkunci',
6307
+ 'worktrees.prunable': 'Dapat dibersihkan',
6308
+ 'worktrees.detached': 'HEAD terlepas',
6309
+ 'worktrees.open': 'Buka di panel',
6310
+ 'worktrees.remove': 'Hapus worktree',
6311
+ 'worktrees.removeConfirm': 'Hapus worktree {path}?',
6312
+ 'worktrees.removeDirty': 'Worktree {path} memiliki perubahan yang belum di-commit. Penghapusan paksa akan membuangnya secara permanen. Lanjutkan?',
6313
+ 'worktrees.path': 'Jalur',
6314
+ 'worktrees.branch': 'Branch',
6315
+ 'worktrees.newBranch': 'Branch baru',
6316
+ 'worktrees.existingBranch': 'Branch yang ada',
6317
+ 'worktrees.base': 'Titik awal',
6318
+ 'worktrees.create': 'Buat',
6319
+ 'worktrees.empty': 'Tidak ada worktree',
6320
+ 'worktrees.pruneConfirm': 'Bersihkan catatan worktree yang sudah hilang dari disk?',
6321
+ 'toast.worktreeAdded': 'Worktree {path} dibuat',
6322
+ 'toast.worktreeRemoved': 'Worktree {path} dihapus',
6323
+ 'toast.worktreePruned': 'Catatan worktree usang dibersihkan',
5882
6324
  'confirm.title': 'Konfirmasi',
5883
6325
  'confirm.cancel': 'Batal',
5884
6326
  'confirm.ok': 'OK',
@@ -6137,6 +6579,8 @@ window.__ModuleLoader__.load({
6137
6579
  'settings.placement.tabHint': 'Tắt rồi, bảng vẫn mở được từ thanh bên phải và bảng dưới.',
6138
6580
  'settings.placement.immersive': 'Chế độ đắm chìm (chiều cao đầy đủ, ẩn ô nhập)',
6139
6581
  '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.',
6582
+ 'settings.diff.inline': 'Xem trước khác biệt trong bảng',
6583
+ 'settings.diff.inlineHint': 'Bật: khác biệt mở trong bảng Git, có chế độ chia đôi và tự động xuống dòng. Tắt: phần xem trước ở thanh bên đảm nhận.',
6140
6584
  'preview.openFile': 'Mở trong xem trước tệp',
6141
6585
  'lf.added': '+{count}',
6142
6586
  'lf.deleted': '-{count}',
@@ -6221,6 +6665,30 @@ window.__ModuleLoader__.load({
6221
6665
  'operation.revert': 'Đang hoàn tác',
6222
6666
  'operation.bisect': 'Đang bisect',
6223
6667
  'operation.hint': 'Kho này vẫn còn một thao tác nhiều bước chưa xong —— hãy hoàn tất hoặc huỷ trước khi thao tác ở mức nhánh',
6668
+ 'worktrees.title': 'Cây làm việc',
6669
+ 'worktrees.new': 'Cây làm việc mới',
6670
+ 'worktrees.prune': 'Dọn bản ghi cũ',
6671
+ 'worktrees.back': 'Quay lại',
6672
+ 'worktrees.current': 'Hiện tại',
6673
+ 'worktrees.primary': 'Chính',
6674
+ 'worktrees.locked': 'Đã khóa',
6675
+ 'worktrees.prunable': 'Có thể dọn',
6676
+ 'worktrees.detached': 'HEAD tách rời',
6677
+ 'worktrees.open': 'Mở trong bảng',
6678
+ 'worktrees.remove': 'Xóa cây làm việc',
6679
+ 'worktrees.removeConfirm': 'Xóa cây làm việc {path}?',
6680
+ 'worktrees.removeDirty': 'Cây làm việc {path} có thay đổi chưa commit. Xóa cưỡng bức sẽ mất vĩnh viễn. Tiếp tục?',
6681
+ 'worktrees.path': 'Đường dẫn',
6682
+ 'worktrees.branch': 'Nhánh',
6683
+ 'worktrees.newBranch': 'Nhánh mới',
6684
+ 'worktrees.existingBranch': 'Nhánh hiện có',
6685
+ 'worktrees.base': 'Điểm bắt đầu',
6686
+ 'worktrees.create': 'Tạo',
6687
+ 'worktrees.empty': 'Không có cây làm việc',
6688
+ 'worktrees.pruneConfirm': 'Dọn bản ghi cây làm việc đã biến mất khỏi ổ đĩa?',
6689
+ 'toast.worktreeAdded': 'Đã tạo cây làm việc {path}',
6690
+ 'toast.worktreeRemoved': 'Đã xóa cây làm việc {path}',
6691
+ 'toast.worktreePruned': 'Đã dọn bản ghi cũ',
6224
6692
  'confirm.title': 'Xác nhận',
6225
6693
  'confirm.cancel': 'Huỷ',
6226
6694
  'confirm.ok': 'OK',
@@ -6479,6 +6947,8 @@ window.__ModuleLoader__.load({
6479
6947
  'settings.placement.tabHint': 'عند الإيقاف تبقى اللوحة متاحة من الشريط الجانبي الأيمن واللوحة السفلية.',
6480
6948
  'settings.placement.immersive': 'الوضع الغامر (ارتفاع كامل، إخفاء صندوق الكتابة)',
6481
6949
  'settings.placement.immersiveHint': 'عند التشغيل يملأ تبويب Git منطقة المحادثة بكامل ارتفاعها ويتراجع صندوق الكتابة؛ تظل الموافقات والأسئلة ظاهرة كالمعتاد.',
6950
+ 'settings.diff.inline': 'معاينة الفروق داخل اللوحة',
6951
+ 'settings.diff.inlineHint': 'عند التشغيل تفتح الفروق داخل لوحة Git مع العرض المنقسم والتفاف الأسطر. عند الإيقاف تتولاها معاينة الفروق في الشريط الجانبي.',
6482
6952
  'preview.openFile': 'افتح في معاينة الملف',
6483
6953
  'lf.added': '+{count}',
6484
6954
  'lf.deleted': '-{count}',
@@ -6563,6 +7033,30 @@ window.__ModuleLoader__.load({
6563
7033
  'operation.revert': 'التراجع جارٍ',
6564
7034
  'operation.bisect': 'bisect جارٍ',
6565
7035
  'operation.hint': 'لا يزال في هذا المستودع إجراء متعدد الخطوات مفتوح —— أكمله أو ألغِه قبل إجراءات الفروع',
7036
+ 'worktrees.title': 'أشجار العمل',
7037
+ 'worktrees.new': 'شجرة عمل جديدة',
7038
+ 'worktrees.prune': 'تنظيف السجلات القديمة',
7039
+ 'worktrees.back': 'رجوع',
7040
+ 'worktrees.current': 'الحالي',
7041
+ 'worktrees.primary': 'الرئيسي',
7042
+ 'worktrees.locked': 'مقفل',
7043
+ 'worktrees.prunable': 'قابل للتنظيف',
7044
+ 'worktrees.detached': 'HEAD منفصل',
7045
+ 'worktrees.open': 'فتح في اللوحة',
7046
+ 'worktrees.remove': 'إزالة شجرة العمل',
7047
+ 'worktrees.removeConfirm': 'إزالة شجرة العمل {path}؟',
7048
+ 'worktrees.removeDirty': 'شجرة العمل {path} تحتوي على تغييرات غير مُودعة. الإزالة القسرية ستحذفها نهائيًا. هل تريد المتابعة؟',
7049
+ 'worktrees.path': 'المسار',
7050
+ 'worktrees.branch': 'الفرع',
7051
+ 'worktrees.newBranch': 'فرع جديد',
7052
+ 'worktrees.existingBranch': 'فرع موجود',
7053
+ 'worktrees.base': 'نقطة البداية',
7054
+ 'worktrees.create': 'إنشاء',
7055
+ 'worktrees.empty': 'لا توجد أشجار عمل',
7056
+ 'worktrees.pruneConfirm': 'تنظيف سجلات أشجار العمل التي اختفت من القرص؟',
7057
+ 'toast.worktreeAdded': 'تم إنشاء شجرة العمل {path}',
7058
+ 'toast.worktreeRemoved': 'تمت إزالة شجرة العمل {path}',
7059
+ 'toast.worktreePruned': 'تم تنظيف السجلات القديمة',
6566
7060
  'confirm.title': 'تأكيد',
6567
7061
  'confirm.cancel': 'إلغاء',
6568
7062
  'confirm.ok': 'موافق',
@@ -6821,6 +7315,8 @@ window.__ModuleLoader__.load({
6821
7315
  'settings.placement.tabHint': 'बंद करने पर भी पैनल दाएँ साइडबार और निचले पैनल से खुलता है।',
6822
7316
  'settings.placement.immersive': 'इमर्सिव मोड (पूरी ऊँचाई, छिपा इनपुट बॉक्स)',
6823
7317
  'settings.placement.immersiveHint': 'चालू होने पर Git टैब पूरे संवाद क्षेत्र की ऊँचाई घेर लेता है और इनपुट बॉक्स हट जाता है; अनुमोदन और प्रश्न फिर भी दिखते हैं।',
7318
+ 'settings.diff.inline': 'पैनल में अंतर देखें',
7319
+ 'settings.diff.inlineHint': 'चालू: अंतर Git पैनल में खुलते हैं, स्प्लिट व्यू और लाइन रैप के साथ। बंद: साइडबार का अंतर पूर्वावलोकन संभालता है।',
6824
7320
  'preview.openFile': 'फ़ाइल पूर्वावलोकन में खोलें',
6825
7321
  'lf.added': '+{count}',
6826
7322
  'lf.deleted': '-{count}',
@@ -6905,6 +7401,30 @@ window.__ModuleLoader__.load({
6905
7401
  'operation.revert': 'वापसी जारी है',
6906
7402
  'operation.bisect': 'bisect जारी है',
6907
7403
  'operation.hint': 'इस रिपॉज़िटरी में अभी एक बहु-चरणीय कार्य खुला है —— शाखा क्रियाओं से पहले उसे पूरा करें या रद्द करें',
7404
+ 'worktrees.title': 'वर्कट्री',
7405
+ 'worktrees.new': 'नई वर्कट्री',
7406
+ 'worktrees.prune': 'पुराने रिकॉर्ड साफ़ करें',
7407
+ 'worktrees.back': 'वापस',
7408
+ 'worktrees.current': 'मौजूदा',
7409
+ 'worktrees.primary': 'प्राथमिक',
7410
+ 'worktrees.locked': 'लॉक किया गया',
7411
+ 'worktrees.prunable': 'साफ़ करने योग्य',
7412
+ 'worktrees.detached': 'अलग HEAD',
7413
+ 'worktrees.open': 'पैनल में खोलें',
7414
+ 'worktrees.remove': 'वर्कट्री हटाएँ',
7415
+ 'worktrees.removeConfirm': 'वर्कट्री {path} हटाएँ?',
7416
+ 'worktrees.removeDirty': 'वर्कट्री {path} में अन-कमिट बदलाव हैं। ज़बरदस्ती हटाने से ये हमेशा के लिए चले जाएँगे। जारी रखें?',
7417
+ 'worktrees.path': 'पाथ',
7418
+ 'worktrees.branch': 'शाखा',
7419
+ 'worktrees.newBranch': 'नई शाखा',
7420
+ 'worktrees.existingBranch': 'मौजूदा शाखा',
7421
+ 'worktrees.base': 'शुरुआती बिंदु',
7422
+ 'worktrees.create': 'बनाएँ',
7423
+ 'worktrees.empty': 'कोई वर्कट्री नहीं',
7424
+ 'worktrees.pruneConfirm': 'डिस्क से गायब वर्कट्री रिकॉर्ड साफ़ करें?',
7425
+ 'toast.worktreeAdded': 'वर्कट्री {path} बनाई गई',
7426
+ 'toast.worktreeRemoved': 'वर्कट्री {path} हटाई गई',
7427
+ 'toast.worktreePruned': 'पुराने वर्कट्री रिकॉर्ड साफ़ किए गए',
6908
7428
  'confirm.title': 'पुष्टि',
6909
7429
  'confirm.cancel': 'रद्द करें',
6910
7430
  'confirm.ok': 'ठीक',
@@ -7163,6 +7683,8 @@ window.__ModuleLoader__.load({
7163
7683
  'settings.placement.tabHint': 'ปิดแล้วยังเปิดแผงจากแถบข้างขวาหรือแผงล่างได้',
7164
7684
  'settings.placement.immersive': 'โหมดดื่มด่ำ (เต็มความสูง, ซ่อนกล่องพิมพ์)',
7165
7685
  'settings.placement.immersiveHint': 'เมื่อเปิด แท็บ Git จะกินพื้นที่สูงทั้งหมดของบทสนทนาและกล่องพิมพ์จะถอยออก; การอนุมัติและคำถามยังแสดงตามปกติ',
7686
+ 'settings.diff.inline': 'ดูตัวอย่างความแตกต่างในแผง',
7687
+ 'settings.diff.inlineHint': 'เปิด: ความแตกต่างเปิดในแผง Git พร้อมมุมมองแยกและตัดบรรทัดอัตโนมัติ ปิด: ตัวอย่างความแตกต่างในแถบข้างจะรับหน้าที่',
7166
7688
  'preview.openFile': 'เปิดในตัวอย่างไฟล์',
7167
7689
  'lf.added': '+{count}',
7168
7690
  'lf.deleted': '-{count}',
@@ -7247,6 +7769,30 @@ window.__ModuleLoader__.load({
7247
7769
  'operation.revert': 'กำลังย้อน',
7248
7770
  'operation.bisect': 'กำลัง bisect',
7249
7771
  'operation.hint': 'รีโพซิทอรีนี้ยังมีงานหลายขั้นตอนค้างอยู่ —— ทำให้เสร็จหรือยกเลิกก่อนทำการกับแบรนช์',
7772
+ 'worktrees.title': 'เวิร์กทรี',
7773
+ 'worktrees.new': 'เวิร์กทรีใหม่',
7774
+ 'worktrees.prune': 'ล้างบันทึกที่ค้างอยู่',
7775
+ 'worktrees.back': 'กลับ',
7776
+ 'worktrees.current': 'ปัจจุบัน',
7777
+ 'worktrees.primary': 'หลัก',
7778
+ 'worktrees.locked': 'ถูกล็อก',
7779
+ 'worktrees.prunable': 'ล้างได้',
7780
+ 'worktrees.detached': 'HEAD แยก',
7781
+ 'worktrees.open': 'เปิดในแผง',
7782
+ 'worktrees.remove': 'ลบเวิร์กทรี',
7783
+ 'worktrees.removeConfirm': 'ลบเวิร์กทรี {path}?',
7784
+ 'worktrees.removeDirty': 'เวิร์กทรี {path} มีการเปลี่ยนแปลงที่ยังไม่คอมมิต การลบแบบบังคับจะทิ้งถาวร ดำเนินการต่อ?',
7785
+ 'worktrees.path': 'พาธ',
7786
+ 'worktrees.branch': 'แบรนช์',
7787
+ 'worktrees.newBranch': 'แบรนช์ใหม่',
7788
+ 'worktrees.existingBranch': 'แบรนช์ที่มีอยู่',
7789
+ 'worktrees.base': 'จุดเริ่มต้น',
7790
+ 'worktrees.create': 'สร้าง',
7791
+ 'worktrees.empty': 'ไม่มีเวิร์กทรี',
7792
+ 'worktrees.pruneConfirm': 'ล้างบันทึกเวิร์กทรีที่หายไปจากดิสก์?',
7793
+ 'toast.worktreeAdded': 'สร้างเวิร์กทรี {path} แล้ว',
7794
+ 'toast.worktreeRemoved': 'ลบเวิร์กทรี {path} แล้ว',
7795
+ 'toast.worktreePruned': 'ล้างบันทึกเวิร์กทรีที่ค้างแล้ว',
7250
7796
  'confirm.title': 'ยืนยัน',
7251
7797
  'confirm.cancel': 'ยกเลิก',
7252
7798
  'confirm.ok': 'ตกลง',
@@ -7917,6 +8463,7 @@ window.__ModuleLoader__.load({
7917
8463
  compact is the only chrome with a header button, and it used to render
7918
8464
  that button only while the tree was OPEN. */
7919
8465
  { id: 'tree', icon: 'branch', key: 'toolbar.tree', tone: 'secondary' },
8466
+ { id: 'worktrees', icon: 'folder', key: 'worktrees.title', tone: 'secondary' },
7920
8467
  /* The official right-sidebar dock (float / re-dock / split / full screen).
7921
8468
  They only render where that service exists — see dockFaceOf(). */
7922
8469
  { id: 'float', icon: 'float', key: 'action.float', tone: 'accent' },
@@ -7944,7 +8491,7 @@ window.__ModuleLoader__.load({
7944
8491
  const RAIL_IDS = RAIL_SPECS.map((spec) => spec.id)
7945
8492
  /* Actions that arrange the view rather than touch the repository, so a running
7946
8493
  git command never greys them out. */
7947
- const RAIL_VIEW_IDS = ['tree', 'float', 'split', 'fullscreen']
8494
+ const RAIL_VIEW_IDS = ['tree', 'worktrees', 'float', 'split', 'fullscreen']
7948
8495
  const RAIL_DOCK_IDS = ['float', 'split', 'fullscreen']
7949
8496
 
7950
8497
  function normalizeRail(raw) {
@@ -8821,47 +9368,6 @@ window.__ModuleLoader__.load({
8821
9368
  return false
8822
9369
  }
8823
9370
 
8824
- /* One single-file unified diff (what the host `diff` method returns) → the
8825
- one DiffHunk DiffBlock renders, by replaying ` `/`-`/`+` lines back into
8826
- the two sides. No `@@` at all (binary patches, mode-only changes) or a
8827
- pathless header → null, and the caller keeps the hand-drawn rows. */
8828
- function patchToHunk(patch) {
8829
- const text = typeof patch === 'string' ? patch : ''
8830
- if (text === '' || text.indexOf('@@') < 0) return null
8831
- const lines = text.split('\n')
8832
- let path = null
8833
- const oldText = []
8834
- const newText = []
8835
- for (const line of lines) {
8836
- if (line.indexOf('+++ ') === 0) {
8837
- const name = line.slice(4).split('\t')[0]
8838
- if (name !== '/dev/null') path = name.replace(/^b\//, '')
8839
- continue
8840
- }
8841
- if (line.indexOf('--- ') === 0) {
8842
- if (path === null) {
8843
- const name = line.slice(4).split('\t')[0]
8844
- if (name !== '/dev/null') path = name.replace(/^a\//, '')
8845
- }
8846
- continue
8847
- }
8848
- if (line.indexOf('@@') === 0) continue
8849
- if (line.indexOf('+') === 0) { newText.push(line.slice(1)); continue }
8850
- if (line.indexOf('-') === 0) { oldText.push(line.slice(1)); continue }
8851
- if (line.indexOf('\\') === 0) continue
8852
- if (line.indexOf(' ') === 0) { oldText.push(line.slice(1)); newText.push(line.slice(1)); continue }
8853
- // diff --git / index / new file mode / rename from ...: metadata above
8854
- // the first hunk; anything else after it would be malformed.
8855
- continue
8856
- }
8857
- if (path === null || path === '') return null
8858
- // A patch without body lines (mode-only, bare metadata) replays into an
8859
- // empty hunk that DiffBlock draws as nothing at all — keep the hand-drawn
8860
- // rows for those, they at least show what happened.
8861
- if (oldText.length === 0 && newText.length === 0) return null
8862
- return { path: path, oldText: oldText.length === 0 ? null : oldText.join('\n'), newText: newText.join('\n') }
8863
- }
8864
-
8865
9371
  /* ============================== diff ============================== */
8866
9372
 
8867
9373
  function diffLines(patch) {
@@ -8898,13 +9404,13 @@ window.__ModuleLoader__.load({
8898
9404
 
8899
9405
  /* ---- diff selection core
8900
9406
  Pure line-number plumbing for the diff-pane context menu (v0.13.5).
8901
- The host DiffBlock draws rows WITHOUT line-number attributes — a row is
8902
- `<div class={line + kindClass}>{row.text}</div>` (DiffBlock.tsx:204) and
8903
- the `+`/`-`/two-space prefixes live in CSS `::before` content — so the
8904
- menu reconstructs numbers by replaying the ROW KINDS in document order
8905
- from the patch's first hunk header. The hand-drawn rows already carry
8906
- parsed numbers (diffLines above). Kept DOM-free between the markers so
8907
- tests/smoke.mjs can slice the block and drive the real implementation. */
9407
+ For a host DiffBlock body (kept as a forward-compat fallback) rows do
9408
+ not expose numbers, so the menu replays ROW KINDS in document order from
9409
+ the patch's first hunk header; hand-drawn rows carry parsed numbers
9410
+ (diffLines above). The review renderer used today (#22) reads its own
9411
+ printed numbers instead (reviewRowSpanOf, DOM side below). Kept DOM-free
9412
+ between the markers so tests/smoke.mjs can slice the block and drive the
9413
+ real implementation. */
8908
9414
 
8909
9415
  const DIFF_SNIPPET_MAX = 600
8910
9416
 
@@ -9050,13 +9556,13 @@ window.__ModuleLoader__.load({
9050
9556
 
9051
9557
  /* ---------- DOM side of the diff-pane context menu (browser only) ----------
9052
9558
  SSR never calls these: they run inside the pane's onContextMenu, after
9053
- the selection has been captured. The row element under a selection
9054
- endpoint is either a hand-drawn `.dig-diff-line`, or a DiffBlock row —
9055
- a div child of the body whose OWN parent carries the `data-diff`
9056
- marker. `data-diff` is a component attribute (DiffBlock.tsx:199), not a
9057
- hashed CSS-module class, so the lookup does not bet on build-time class
9058
- renaming; the row KIND comes from the `::before` content the kinds are
9059
- painted with, for the same reason. */
9559
+ the selection has been captured. A row under a selection endpoint is
9560
+ either a review row (data-diff-line on .dig-lf-line / .dig-lf-splitLine
9561
+ / .dig-lf-sideLine), a hand-drawn `.dig-diff-line`, or a host DiffBlock
9562
+ row (a div child of the body whose OWN parent carries `data-diff`). The
9563
+ review path reads the printed .dig-lf-number cells; the host path gets
9564
+ its KIND from the `::before` content the rows are painted with. Both
9565
+ rely on component attributes, not hashed CSS-module classes. */
9060
9566
 
9061
9567
  function savedSelectionOf() {
9062
9568
  try {
@@ -9077,6 +9583,15 @@ window.__ModuleLoader__.load({
9077
9583
  if (element.classList !== undefined && element.classList.contains('dig-diff-line') === true) {
9078
9584
  return { kind: 'drawn', row: element, body: element.parentElement }
9079
9585
  }
9586
+ /* The review renderer (LineDiff, used by BOTH the inline pane and the
9587
+ native preview since #22) stamps every row with data-diff-line and
9588
+ carries the real line numbers in .dig-lf-number cells. */
9589
+ if (element.hasAttribute !== undefined && element.hasAttribute('data-diff-line') === true
9590
+ && element.classList !== undefined
9591
+ && (element.classList.contains('dig-lf-line') === true || element.classList.contains('dig-lf-splitLine') === true || element.classList.contains('dig-lf-sideLine') === true)) {
9592
+ const body = element.closest === undefined ? null : element.closest('.dig-lf-body')
9593
+ if (body !== null) return { kind: 'review', row: element, body: body }
9594
+ }
9080
9595
  const parent = element.parentElement
9081
9596
  if (parent !== null && parent.getAttribute !== undefined && parent.hasAttribute('data-diff') === false) {
9082
9597
  const root = parent.parentElement
@@ -9107,6 +9622,49 @@ window.__ModuleLoader__.load({
9107
9622
  return { rows: rows, kinds: kinds }
9108
9623
  }
9109
9624
 
9625
+ /* The review renderer's rows carry their printed numbers already: read
9626
+ them straight off the selected rows (unified: old then new; split with
9627
+ wrap: the two cells; split without wrap: one side per column). This is
9628
+ simpler and more accurate than replaying kinds, and it follows the
9629
+ SPLIT view, where one row is two files' lines. */
9630
+ function reviewRowSpanOf(body, fromRow, toRow) {
9631
+ if (body === null || body === undefined) return null
9632
+ const rows = Array.prototype.slice.call(body.querySelectorAll('[data-diff-line]'))
9633
+ const fromIndex = rows.indexOf(fromRow)
9634
+ const toIndex = rows.indexOf(toRow)
9635
+ if (fromIndex < 0 || toIndex < 0) return null
9636
+ let newFrom = null
9637
+ let newTo = null
9638
+ let oldFrom = null
9639
+ let oldTo = null
9640
+ const read = (node) => {
9641
+ const text = node === undefined || node === null ? '' : String(node.textContent).trim()
9642
+ if (text === '') return null
9643
+ const value = Number.parseInt(text, 10)
9644
+ return Number.isFinite(value) ? value : null
9645
+ }
9646
+ for (let index = Math.min(fromIndex, toIndex); index <= Math.max(fromIndex, toIndex); index += 1) {
9647
+ const row = rows[index]
9648
+ const numbers = row.querySelectorAll('.dig-lf-number')
9649
+ let oldLine = null
9650
+ let newLine = null
9651
+ if (row.classList.contains('dig-lf-sideLine') === true) {
9652
+ const side = row.closest === undefined ? null : row.closest('[data-diff-side]')
9653
+ const value = read(numbers[0])
9654
+ if (side !== null && side.getAttribute('data-diff-side') === 'left') oldLine = value
9655
+ else newLine = value
9656
+ } else {
9657
+ oldLine = read(numbers[0])
9658
+ newLine = read(numbers[1])
9659
+ }
9660
+ if (oldLine !== null) { oldFrom = oldFrom === null ? oldLine : oldFrom; oldTo = oldLine }
9661
+ if (newLine !== null) { newFrom = newFrom === null ? newLine : newFrom; newTo = newLine }
9662
+ }
9663
+ if (newFrom !== null) return { start: newFrom, end: newTo === null ? newFrom : newTo, old: false }
9664
+ if (oldFrom !== null) return { start: oldFrom, end: oldTo === null ? oldFrom : oldTo, old: true }
9665
+ return null
9666
+ }
9667
+
9110
9668
  /* { kind: 'lines', span } for a selection inside ONE diff body, or
9111
9669
  { kind: 'snippet' } when the rows are there but the numbers cannot be
9112
9670
  reconstructed. null (rows missing entirely) means "not a diff
@@ -9118,7 +9676,9 @@ window.__ModuleLoader__.load({
9118
9676
  if (from === null || to === null) return null
9119
9677
  if (from.kind !== to.kind || from.body !== to.body) return { kind: 'snippet', text: saved.text }
9120
9678
  let span = null
9121
- if (from.kind === 'drawn') {
9679
+ if (from.kind === 'review') {
9680
+ span = reviewRowSpanOf(from.body, from.row, to.row)
9681
+ } else if (from.kind === 'drawn') {
9122
9682
  const rows = diffLines(patch)
9123
9683
  const all = from.body === null || from.body === undefined ? [] : Array.prototype.slice.call(from.body.querySelectorAll('.dig-diff-line'))
9124
9684
  const fromIndex = all.indexOf(from.row)
@@ -9140,55 +9700,19 @@ window.__ModuleLoader__.load({
9140
9700
  }
9141
9701
 
9142
9702
 
9143
- /* DiffBlock's own 16-line cap would REPLACE scrolling with a fold button;
9144
- this panel keeps the old contract (render everything, scroll inside the
9145
- .dig-diff-pane max-height), so the cap is pushed out of reach and the
9146
- fold labels below are placeholders that never render at this size. */
9147
- const DIFF_BLOCK_MAX_LINES = 1000000
9148
-
9149
- function diffLabelsOf(t) {
9150
- return {
9151
- codeLabel: t('diff.codeLabel'),
9152
- wrapLabel: t('diff.wrapLabel'),
9153
- unwrapLabel: t('diff.unwrapLabel'),
9154
- copy: t('diff.copy'),
9155
- copied: t('diff.copied'),
9156
- collapseAria: 'Collapse diff',
9157
- expandAria: (hidden) => 'Show ' + String(hidden) + ' more diff lines',
9158
- collapse: 'Show less',
9159
- expand: (hidden) => 'Show ' + String(hidden) + ' more',
9160
- }
9161
- }
9162
-
9703
+ /* The inline pane body and the native preview tab share ONE renderer:
9704
+ LineDiff is the ported official ReviewTab/FileDiff and carries the
9705
+ split / wrap toolbar (issue #22). The former host DiffBlock body only
9706
+ had wrap / copy, so turning the external hand-off off silently dropped
9707
+ the 分栏 switch the user had asked for. One renderer also keeps both
9708
+ diff surfaces visually identical. */
9163
9709
  function DiffBody(props) {
9164
- const rows = useMemo(() => diffLines(props.patch), [props.patch])
9165
- if (props.loading === true) return E('div', { className: 'dig-empty' }, props.t('diff.loading'))
9166
- if (props.binary === true) return E('div', { className: 'dig-empty' }, props.t('diff.binary'))
9167
- if (rows.length === 0) return E('div', { className: 'dig-empty' }, props.t('diff.empty'))
9168
- /* Host-rendered first (v0.14): the ui-primitives diff card brings the
9169
- shared toolbar (copy / wrap / language) and the product's diff colors.
9170
- A patch we cannot replay into a hunk (binary, mode-only) or a host
9171
- without the seed module falls back to the hand-drawn rows below. This
9172
- IS the official diff renderer (the platform seed table ships it, the
9173
- host's own surfaces render diffs through it) — the external preview
9174
- tab uses the same one, so every diff in the product reads alike. */
9175
- if (UIPrimitives !== null && typeof UIPrimitives.DiffBlock === 'function') {
9176
- const hunk = patchToHunk(props.patch)
9177
- if (hunk !== null) {
9178
- return E('div', { className: 'dig-diff-host' },
9179
- E(UIPrimitives.DiffBlock, {
9180
- diffs: [hunk],
9181
- labels: diffLabelsOf(props.t),
9182
- maxLines: DIFF_BLOCK_MAX_LINES,
9183
- className: 'dig-diff-card',
9184
- }))
9185
- }
9186
- }
9187
- return E('div', { className: 'dig-diff' }, rows.map((row, index) => E('div', { key: index, className: 'dig-diff-line dig-diff-' + row.kind },
9188
- E('span', { className: 'dig-diff-gutter' }, row.oldLine === null ? '' : String(row.oldLine)),
9189
- E('span', { className: 'dig-diff-gutter' }, row.newLine === null ? '' : String(row.newLine)),
9190
- E('span', { className: 'dig-diff-text' }, row.text === '' ? ' ' : row.text),
9191
- )))
9710
+ return E(LineDiff, {
9711
+ patch: props.patch,
9712
+ loading: props.loading,
9713
+ t: props.t,
9714
+ onOpenFile: props.onOpenFile,
9715
+ })
9192
9716
  }
9193
9717
 
9194
9718
  /* ---- external preview diff (v0.14.3): a faithful port of the review view ----
@@ -10386,6 +10910,43 @@ window.__ModuleLoader__.load({
10386
10910
  E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: 'branch', size: 13 })),
10387
10911
  E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.composer.title'))),
10388
10912
  E('div', { className: 'dig-settings-subnote' }, t('settings.composer.hint')))
10913
+ })(),
10914
+ (() => {
10915
+ /* Inline diff preview (issue #22): ON keeps the panel's own diff pane
10916
+ reachable, including its split / wrap switches. OFF restores the
10917
+ external hand-off to dsh-better-sidebar or the native preview seat.
10918
+ Same optimistic-write contract as the three rows above. */
10919
+ const field = 'inlineDiff'
10920
+ const inputId = 'dig-placement-' + field
10921
+ const shown = placementShown(field)
10922
+ const toggle = (event) => {
10923
+ if (readOnly === true) { setNote(t('settings.rail.readonly')); return }
10924
+ const fromControl = event !== undefined && event !== null && event.target !== null
10925
+ && event.target !== undefined && typeof event.target.checked === 'boolean'
10926
+ ? event.target.checked
10927
+ : null
10928
+ const wanted = placementTarget(field, fromControl)
10929
+ intent.current[field] = wanted
10930
+ dirty.current.set(field, wanted)
10931
+ setPending((now) => Object.assign({}, now, { [field]: wanted }))
10932
+ setNote('')
10933
+ bump()
10934
+ scheduleFlush()
10935
+ }
10936
+ return E('div', { key: field },
10937
+ E('div', { className: 'dig-settings-row', 'data-placement-row': field },
10938
+ E('input', {
10939
+ id: inputId,
10940
+ type: 'checkbox',
10941
+ className: 'dig-settings-check',
10942
+ 'aria-labelledby': inputId + '-label',
10943
+ checked: shown,
10944
+ disabled: readOnly === true,
10945
+ onChange: toggle,
10946
+ }),
10947
+ E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: 'diff', size: 13 })),
10948
+ E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.diff.inline'))),
10949
+ E('div', { className: 'dig-settings-subnote' }, t('settings.diff.inlineHint')))
10389
10950
  })()),
10390
10951
  /* Section 3 — the AI commit message (issue #6): model, reasoning and the
10391
10952
  prompt template. */
@@ -10562,6 +11123,79 @@ window.__ModuleLoader__.load({
10562
11123
  E('button', { type: 'button', className: 'dig-btn dig-btn-primary', disabled: ready === false, onClick: submit }, props.okLabel))))
10563
11124
  }
10564
11125
 
11126
+ /* A worktree create dialog: unlike CloneDialog it has a mode (a NEW branch
11127
+ versus an existing one), so the branch field means two different things
11128
+ and the base field only exists in the first mode. Validation is local
11129
+ (non-empty fields) plus whatever the host rejects; onSubmit resolves to
11130
+ null on success or the message to show under the fields. */
11131
+ function WorktreeDialog(props) {
11132
+ const t = props.t
11133
+ const [mode, setMode] = useState('new')
11134
+ const [path, setPath] = useState('')
11135
+ const [branch, setBranch] = useState('')
11136
+ const [base, setBase] = useState('')
11137
+ const [error, setError] = useState(null)
11138
+ const [sending, setSending] = useState(false)
11139
+ const pathRef = useRef(null)
11140
+ const alive = useRef(true)
11141
+ useEffect(() => { if (pathRef.current !== null) pathRef.current.focus() }, [])
11142
+ useEffect(() => () => { alive.current = false }, [])
11143
+ const ready = path.trim() !== '' && branch.trim() !== '' && sending === false
11144
+ const submit = async () => {
11145
+ if (ready !== true) return
11146
+ const draft = { path: path.trim(), branch: branch.trim(), createBranch: mode === 'new' }
11147
+ if (mode === 'new' && base.trim() !== '') draft.base = base.trim()
11148
+ setError(null)
11149
+ setSending(true)
11150
+ try {
11151
+ const message = await props.onSubmit(draft)
11152
+ if (alive.current === true && typeof message === 'string' && message !== '') setError(message)
11153
+ } finally {
11154
+ if (alive.current === true) setSending(false)
11155
+ }
11156
+ }
11157
+ const onKey = (event) => {
11158
+ if (event.key === 'Enter') { event.preventDefault(); void submit() }
11159
+ if (event.key === 'Escape') { event.preventDefault(); props.onCancel() }
11160
+ }
11161
+ return E('div', { className: 'dig-overlay' },
11162
+ E('div', { className: 'dig-dialog dig-wt-dialog' },
11163
+ E('div', { className: 'dig-dialog-title' }, props.title),
11164
+ E('label', { className: 'dig-wt-field' },
11165
+ E('span', { className: 'dig-wt-label' }, t('worktrees.path')),
11166
+ E('input', {
11167
+ ref: pathRef, className: 'dig-input', value: path, spellCheck: false,
11168
+ placeholder: '/abs/path/to/worktree',
11169
+ onChange: (event) => setPath(event.target.value), onKeyDown: onKey,
11170
+ })),
11171
+ E('div', { className: 'dig-wt-field' },
11172
+ E('span', { className: 'dig-wt-label' }, t('worktrees.branch')),
11173
+ E(Segmented, {
11174
+ value: mode,
11175
+ onChange: setMode,
11176
+ items: [
11177
+ { id: 'new', label: t('worktrees.newBranch') },
11178
+ { id: 'existing', label: t('worktrees.existingBranch') },
11179
+ ],
11180
+ }),
11181
+ E('input', {
11182
+ className: 'dig-input', value: branch, spellCheck: false,
11183
+ placeholder: 'feature/name',
11184
+ onChange: (event) => setBranch(event.target.value), onKeyDown: onKey,
11185
+ })),
11186
+ mode === 'new' ? E('label', { className: 'dig-wt-field' },
11187
+ E('span', { className: 'dig-wt-label' }, t('worktrees.base')),
11188
+ E('input', {
11189
+ className: 'dig-input', value: base, spellCheck: false,
11190
+ placeholder: 'HEAD',
11191
+ onChange: (event) => setBase(event.target.value), onKeyDown: onKey,
11192
+ })) : null,
11193
+ error === null ? null : E('div', { className: 'dig-wt-error' }, error),
11194
+ E('div', { className: 'dig-dialog-actions' },
11195
+ E('button', { type: 'button', className: 'dig-btn', onClick: props.onCancel }, props.cancelLabel),
11196
+ E('button', { type: 'button', className: 'dig-btn dig-btn-primary', disabled: ready !== true, onClick: () => { void submit() } }, t('worktrees.create')))))
11197
+ }
11198
+
10565
11199
  /* VS Code's "Git output" view (showOutput): the tail of the host's git-run
10566
11200
  ring buffer, fetched through the gitLogs method. A debugging view —
10567
11201
  opened on demand, refreshed by hand; newest land at the bottom and the
@@ -11516,7 +12150,7 @@ window.__ModuleLoader__.load({
11516
12150
  title: t('changes.aiQuota'),
11517
12151
  onClick: () => { if (message.trim() === '') void aiRun(); else setAiAsk(true) },
11518
12152
  }, aiBusy === true ? t('changes.aiBusy') : t('changes.aiWrite')),
11519
- E(ModelRoutePicker, { sessionId: props.sessionId, t: t }),
12153
+ E(ModelRoutePicker, { sessionId: props.sessionId, t: t, ctx: props.ctx }),
11520
12154
  E('button', {
11521
12155
  type: 'button', className: 'dig-btn dig-btn-primary dig-btn-small',
11522
12156
  disabled: message.trim() === '' || props.busy === true,
@@ -11538,7 +12172,7 @@ window.__ModuleLoader__.load({
11538
12172
  disabled: aiBusy === true || props.busy === true,
11539
12173
  onClick: () => { if (message.trim() === '') void aiRun(); else setAiAsk(true) },
11540
12174
  }, aiBusy === true ? t('changes.aiBusy') : t('changes.aiWrite')),
11541
- E(ModelRoutePicker, { sessionId: props.sessionId, t: t }),
12175
+ E(ModelRoutePicker, { sessionId: props.sessionId, t: t, ctx: props.ctx }),
11542
12176
  E('span', { className: 'dig-commit-ai-hint' }, t('changes.aiQuota')),
11543
12177
  aiUndo === null ? null : E('button', {
11544
12178
  type: 'button', className: 'dig-link',
@@ -12323,6 +12957,86 @@ window.__ModuleLoader__.load({
12323
12957
  file.deletions > 0 ? E('span', { className: 'dig-stat-del' }, '-' + file.deletions) : null))))
12324
12958
  }
12325
12959
 
12960
+ /* Worktrees view (v0.22.0): the repository's real working trees, read from
12961
+ summary.worktrees (git worktree list --porcelain -z). It is a full-body
12962
+ view rather than a fourth pane: opening one switches the panel's
12963
+ repository to that path, and every row action stays inside the current
12964
+ panel. Current / primary / locked / prunable trees are protected here for
12965
+ the same reasons the host refuses them; the host is still the authority. */
12966
+ function WorktreesPane(props) {
12967
+ const t = props.t
12968
+ const items = Array.isArray(props.worktrees) ? props.worktrees : []
12969
+ const busy = props.busy === true
12970
+ const hasPrunable = items.some((item) => item.prunable === true)
12971
+ const open = (item) => {
12972
+ if (item.current === true || item.prunable === true) return
12973
+ props.onOpen(item.path)
12974
+ }
12975
+ const badgesOf = (item) => {
12976
+ const badges = []
12977
+ if (item.current === true) badges.push({ key: 'current', className: 'dig-wt-badge-current', label: t('worktrees.current'), title: undefined })
12978
+ if (item.primary === true) badges.push({ key: 'primary', className: 'dig-wt-badge-primary', label: t('worktrees.primary'), title: undefined })
12979
+ if (item.locked === true) badges.push({ key: 'locked', className: 'dig-wt-badge-locked', label: t('worktrees.locked'), title: item.lockReason === '' || item.lockReason === undefined ? t('worktrees.locked') : item.lockReason })
12980
+ if (item.prunable === true) badges.push({ key: 'prunable', className: 'dig-wt-badge-prunable', label: t('worktrees.prunable'), title: item.pruneReason === '' || item.pruneReason === undefined ? t('worktrees.prunable') : item.pruneReason })
12981
+ return badges
12982
+ }
12983
+ const removable = (item) => item.current !== true && item.primary !== true && item.locked !== true && item.prunable !== true
12984
+ return E('div', { className: 'dig-wt' },
12985
+ E('div', { className: 'dig-wt-head' },
12986
+ E('span', { className: 'dig-wt-title' }, t('worktrees.title')),
12987
+ E('span', { className: 'dig-wt-count' }, String(items.length)),
12988
+ E('span', { className: 'dig-topbar-spacer' }),
12989
+ E('button', {
12990
+ type: 'button', className: 'dig-icon-btn', title: t('worktrees.new'), disabled: busy,
12991
+ onClick: props.onCreate,
12992
+ }, E(Icon, { name: 'branchCreate', size: 14 })),
12993
+ E('button', {
12994
+ type: 'button', className: 'dig-icon-btn', title: t('worktrees.prune'), disabled: busy || hasPrunable !== true,
12995
+ onClick: props.onPrune,
12996
+ }, E(Icon, { name: 'trash', size: 14 })),
12997
+ E('button', {
12998
+ type: 'button', className: 'dig-icon-btn', title: t('worktrees.back'),
12999
+ onClick: props.onBack,
13000
+ }, E(Icon, { name: 'chevron', size: 14 }))),
13001
+ items.length === 0
13002
+ ? E('div', { className: 'dig-empty' }, t('worktrees.empty'))
13003
+ : E('div', { className: 'dig-wt-scroll' }, items.map((item) => {
13004
+ const badges = badgesOf(item)
13005
+ const label = item.detached === true ? t('worktrees.detached') : String(item.branch === undefined || item.branch === '' ? t('worktrees.detached') : item.branch)
13006
+ return E('div', {
13007
+ key: item.path,
13008
+ className: 'dig-wt-row' + (item.current === true ? ' dig-wt-row-current' : ''),
13009
+ onDoubleClick: () => open(item),
13010
+ },
13011
+ E('div', { className: 'dig-wt-row-main' },
13012
+ E('div', { className: 'dig-wt-top' },
13013
+ E(Icon, { name: item.detached === true ? 'commit' : 'branch', size: 13 }),
13014
+ E('span', { className: 'dig-wt-branch', title: label }, label),
13015
+ badges.length === 0 ? null : E('span', { className: 'dig-wt-badges' }, badges.map((badge) => E('span', {
13016
+ key: badge.key, className: 'dig-wt-badge ' + badge.className, title: badge.title === undefined ? badge.label : badge.title,
13017
+ }, badge.label)))),
13018
+ E('div', { className: 'dig-wt-path dig-mono', title: item.path }, String(item.path)),
13019
+ E('div', { className: 'dig-wt-meta' },
13020
+ item.head === undefined || item.head === null || item.head === '' ? null : E('span', { className: 'dig-wt-hash dig-mono' }, String(item.head).slice(0, 8)),
13021
+ item.pruneReason === '' || item.pruneReason === undefined ? null : E('span', { className: 'dig-wt-reason', title: item.pruneReason }, item.pruneReason))),
13022
+ E('div', { className: 'dig-wt-row-actions' },
13023
+ E('button', {
13024
+ type: 'button', className: 'dig-icon-btn dig-icon-btn-small', title: t('worktrees.open'),
13025
+ disabled: busy || item.current === true || item.prunable === true,
13026
+ onClick: () => open(item),
13027
+ }, E(Icon, { name: 'checkout', size: 13 })),
13028
+ E('button', {
13029
+ type: 'button', className: 'dig-icon-btn dig-icon-btn-small', title: t('action.copyPath'),
13030
+ onClick: () => props.onCopy(item.path),
13031
+ }, E(Icon, { name: 'copy', size: 13 })),
13032
+ E('button', {
13033
+ type: 'button', className: 'dig-icon-btn dig-icon-btn-small', title: t('worktrees.remove'),
13034
+ disabled: busy || removable(item) !== true,
13035
+ onClick: () => props.onRemove(item),
13036
+ }, E(Icon, { name: 'trash', size: 13 }))))
13037
+ })))
13038
+ }
13039
+
12326
13040
  /* ============================== panel ============================== */
12327
13041
 
12328
13042
  function Panel(props) {
@@ -13064,6 +13778,11 @@ window.__ModuleLoader__.load({
13064
13778
  repoRoot 并进它自己的 git 调用,多仓库工作区不会落错仓库。 */
13065
13779
  const previewDiff = useCallback((seed) => {
13066
13780
  if (repoRoot === null) return false
13781
+ /* Inline-first (issue #22): the switch defaults to ON, so the panel's
13782
+ own diff pane — and its split / wrap controls — is what a click
13783
+ opens. Turning it off hands the preview to the external chain
13784
+ below, which is the only behaviour older releases had. */
13785
+ if (inlineDiffEnabled() === true) return false
13067
13786
  const commitHash = seed.hash !== undefined && seed.hash !== null ? String(seed.hash) : null
13068
13787
  const ref = commitHash !== null
13069
13788
  ? { kind: 'commit', hash: commitHash.slice(0, 7), hashFull: commitHash, subject: seed.subject || '', worktree: repoRoot, repoRoot: repoRoot }
@@ -13306,6 +14025,67 @@ window.__ModuleLoader__.load({
13306
14025
  pushToast({ text: fill(t('selection.copied'), { n: values.length }), icon: 'tag', tone: 'ok', ttl: 2500 })
13307
14026
  }, [pushToast, t])
13308
14027
 
14028
+ /* ---------- worktrees (v0.22.0) ---------- */
14029
+ /* These bypass run(): worktreeAdd/Remove/Prune have their own payload
14030
+ shapes and refresh only the repository's worktree list, and a dirty
14031
+ removal is a two-step confirm (the first attempt returns
14032
+ worktree-dirty, which reopens the confirm with the destructive force
14033
+ wording). Busy is held by hand because refresh() has its own guard. */
14034
+ const createWorktree = async (draft) => {
14035
+ setBusy(true)
14036
+ setError(null)
14037
+ try {
14038
+ await request('worktreeAdd', Object.assign({}, base, draft))
14039
+ setDialog(null)
14040
+ pushToast({ text: fill(t('toast.worktreeAdded'), { path: draft.path }), icon: 'check', tone: 'ok' })
14041
+ await refresh()
14042
+ return null
14043
+ } catch (caught) {
14044
+ return caught instanceof Error ? caught.message : String(caught)
14045
+ } finally {
14046
+ setBusy(false)
14047
+ }
14048
+ }
14049
+ const removeWorktree = async (tree, force) => {
14050
+ setBusy(true)
14051
+ setError(null)
14052
+ try {
14053
+ await request('worktreeRemove', Object.assign({}, base, { path: tree.path, confirm: true, force: force === true }))
14054
+ setDialog(null)
14055
+ pushToast({ text: fill(t('toast.worktreeRemoved'), { path: tree.path }), icon: 'check', tone: 'ok' })
14056
+ await refresh()
14057
+ return null
14058
+ } catch (caught) {
14059
+ const message = caught instanceof Error ? caught.message : String(caught)
14060
+ if (caught instanceof Error && caught.code === 'worktree-dirty' && force !== true) {
14061
+ setDialog({ kind: 'worktreeRemove', tree: tree, force: true })
14062
+ return message
14063
+ }
14064
+ setDialog(null)
14065
+ setError(message)
14066
+ return message
14067
+ } finally {
14068
+ setBusy(false)
14069
+ }
14070
+ }
14071
+ const pruneWorktrees = async () => {
14072
+ setBusy(true)
14073
+ setError(null)
14074
+ try {
14075
+ const data = await request('worktreePrune', Object.assign({}, base, { confirm: true }))
14076
+ setDialog(null)
14077
+ pushToast({ text: t('toast.worktreePruned'), icon: 'check', tone: 'ok' })
14078
+ await refresh()
14079
+ return data
14080
+ } catch (caught) {
14081
+ setDialog(null)
14082
+ setError(caught instanceof Error ? caught.message : String(caught))
14083
+ return null
14084
+ } finally {
14085
+ setBusy(false)
14086
+ }
14087
+ }
14088
+
13309
14089
  /* Resolve the change selection against the CURRENT summary: paths move
13310
14090
  between groups on every stage/unstage, so the key ('group:path') is
13311
14091
  the only stable handle and the entry lookup must be fresh. */
@@ -13812,13 +14592,28 @@ window.__ModuleLoader__.load({
13812
14592
  /* ---------- shared pieces ---------- */
13813
14593
 
13814
14594
  const repoOptions = repoState === null ? [] : repoState.repos
14595
+ /* A linked worktree opened from the worktree view is a repository root
14596
+ the workspace scan never listed. Without a header entry the top bar
14597
+ kept naming the primary checkout while every branch and change below
14598
+ it came from the worktree. Append the current root as a transient
14599
+ option so the header names what the panel is actually showing. */
14600
+ let headerRepos = repoOptions
14601
+ if (repoRoot !== null && repoOptions.every((repo) => repo.path !== repoRoot)) {
14602
+ const origin = summary === null ? '' : summary.origin
14603
+ headerRepos = repoOptions.concat([{
14604
+ path: repoRoot,
14605
+ name: origin !== undefined && origin !== null && origin !== '' ? origin : baseNameOf(repoRoot),
14606
+ branch: branches === null ? null : branches.branch,
14607
+ worktree: true,
14608
+ }])
14609
+ }
13815
14610
  const dirty = summary === null ? 0 : summary.changes.staged.length + summary.changes.unstaged.length + summary.changes.untracked.length + summary.changes.conflicted.length
13816
14611
 
13817
14612
  /* Both header switchers are menu-driven now (see RepoSelect): the
13818
14613
  popovers reuse openMenuAt, so they clamp to the panel box and share
13819
14614
  the opaque, frosted menu surface instead of the OS select popup. */
13820
14615
  const openRepoMenu = useCallback((event) => {
13821
- openMenuAt(event, repoOptions.map((repo) => ({
14616
+ openMenuAt(event, headerRepos.map((repo) => ({
13822
14617
  id: 'repo:' + repo.path,
13823
14618
  icon: 'folder',
13824
14619
  tone: 'accent',
@@ -13826,7 +14621,7 @@ window.__ModuleLoader__.load({
13826
14621
  active: repo.path === repoRoot,
13827
14622
  run: () => { if (repo.path !== repoRoot) pickRepo(repo.path) },
13828
14623
  })))
13829
- }, [openMenuAt, repoOptions, repoRoot, pickRepo])
14624
+ }, [openMenuAt, headerRepos, repoRoot, pickRepo])
13830
14625
 
13831
14626
  const openBranchSwitchMenu = useCallback((event) => {
13832
14627
  const local = branches === null ? [] : branches.local
@@ -13842,7 +14637,7 @@ window.__ModuleLoader__.load({
13842
14637
 
13843
14638
  const topBar = E('div', { className: 'dig-topbar' },
13844
14639
  E(RepoSelect, {
13845
- t: t, repos: repoOptions, value: repoRoot,
14640
+ t: t, repos: headerRepos, value: repoRoot,
13846
14641
  loading: repoState === null,
13847
14642
  onOpenMenu: openRepoMenu,
13848
14643
  }),
@@ -14165,7 +14960,7 @@ window.__ModuleLoader__.load({
14165
14960
  }
14166
14961
  const commitSettings = readCommitSettings()
14167
14962
  const changesPane = E(ChangesPanel, {
14168
- t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
14963
+ t: t, ctx: props.ctx, summary: summary, busy: busy, compact: compact, hideHeader: compact,
14169
14964
  groupBy: groupBy, sortKey: sortKey, onGroupBy: setGroupBy,
14170
14965
  foldedDirs: foldedDirs, onFoldedDirs: setFoldedDirs,
14171
14966
  onMoreMenu: moreMenu,
@@ -14288,6 +15083,10 @@ window.__ModuleLoader__.load({
14288
15083
  else { entry.disabled = remoteReady !== true || blocked; entry.run = () => setDialog({ kind: 'push' }) }
14289
15084
  }
14290
15085
  else if (spec.id === 'tree') { entry.active = treeOpen; entry.run = () => foldTree(!treeOpen) }
15086
+ else if (spec.id === 'worktrees') {
15087
+ entry.active = view === 'worktrees'
15088
+ entry.run = () => { setPatch(''); setDiffContext(null); setView(view === 'worktrees' ? 'history' : 'worktrees') }
15089
+ }
14291
15090
  else if (spec.id === 'float') {
14292
15091
  // Label AND icon follow where the panel actually is: floating panels get
14293
15092
  // the way back, docked ones get the way out.
@@ -14429,7 +15228,7 @@ window.__ModuleLoader__.load({
14429
15228
 
14430
15229
  /* ---------- body per chrome ---------- */
14431
15230
 
14432
- /* ---------- body per chrome ---------- */
15231
+ const worktrees = summary !== null && Array.isArray(summary.worktrees) ? summary.worktrees : []
14433
15232
 
14434
15233
  /* The history↔diff separator only exists while a diff is on screen, and it
14435
15234
  lives inside the middle pane — the diff is the pane below it. */
@@ -14445,6 +15244,18 @@ window.__ModuleLoader__.load({
14445
15244
  body = E('div', { className: 'dig-body', ref: bodyRef }, E(RepoPicker, {
14446
15245
  t: t, cwd: cwd === undefined ? '' : cwd, repos: repoOptions, onPick: pickRepo,
14447
15246
  }))
15247
+ } else if (view === 'worktrees') {
15248
+ body = E('div', { className: 'dig-body dig-body-worktrees', ref: bodyRef }, E(WorktreesPane, {
15249
+ t: t,
15250
+ worktrees: worktrees,
15251
+ busy: busy,
15252
+ onBack: () => setView('history'),
15253
+ onCreate: () => setDialog({ kind: 'worktreeAdd' }),
15254
+ onPrune: () => setDialog({ kind: 'worktreePrune' }),
15255
+ onOpen: pickRepo,
15256
+ onCopy: (path) => copyValues([path]),
15257
+ onRemove: (tree) => setDialog({ kind: 'worktreeRemove', tree: tree, force: false }),
15258
+ }))
14448
15259
  } else if (compact) {
14449
15260
  body = E('div', { className: 'dig-body dig-body-compact', ref: bodyRef },
14450
15261
  E('div', { className: 'dig-compact-bar' },
@@ -14541,6 +15352,35 @@ window.__ModuleLoader__.load({
14541
15352
  onSubmit: (url, dir) => { setDialog(null); void run('clone', { url: url, dir: dir }) },
14542
15353
  }))
14543
15354
  }
15355
+ if (dialog !== null && dialog.kind === 'worktreeAdd') {
15356
+ overlays.push(E(WorktreeDialog, {
15357
+ key: 'worktreeAdd', t: t,
15358
+ title: t('worktrees.new'),
15359
+ cancelLabel: t('confirm.cancel'),
15360
+ onSubmit: (draft) => createWorktree(draft),
15361
+ }))
15362
+ }
15363
+ if (dialog !== null && dialog.kind === 'worktreeRemove') {
15364
+ const force = dialog.force === true
15365
+ overlays.push(E(ConfirmDialog, {
15366
+ key: 'worktreeRemove', t: t,
15367
+ title: t('worktrees.remove'),
15368
+ text: fill(force === true ? t('worktrees.removeDirty') : t('worktrees.removeConfirm'), { path: dialog.tree.path }),
15369
+ okLabel: t('worktrees.remove'), cancelLabel: t('confirm.cancel'),
15370
+ onCancel: () => setDialog(null),
15371
+ onConfirm: () => { const tree = dialog.tree; const forced = force; void removeWorktree(tree, forced) },
15372
+ }))
15373
+ }
15374
+ if (dialog !== null && dialog.kind === 'worktreePrune') {
15375
+ overlays.push(E(ConfirmDialog, {
15376
+ key: 'worktreePrune', t: t,
15377
+ title: t('worktrees.prune'),
15378
+ text: t('worktrees.pruneConfirm'),
15379
+ okLabel: t('worktrees.prune'), cancelLabel: t('confirm.cancel'),
15380
+ onCancel: () => setDialog(null),
15381
+ onConfirm: () => { void pruneWorktrees() },
15382
+ }))
15383
+ }
14544
15384
  /* SCM ▸ additions (v0.15): each destructive one is a ConfirmDialog whose
14545
15385
  text names the exact target; the tag/remote ones carry the picked
14546
15386
  remote through. */
@@ -15392,6 +16232,10 @@ window.__ModuleLoader__.load({
15392
16232
  '.dig-mrp-root{position:relative;display:inline-flex;flex:none;min-width:0}',
15393
16233
  '.dig-mrp-trigger{display:inline-flex;align-items:center;gap:4px;min-width:0;max-width:220px;font-size:11px;line-height:16px}',
15394
16234
  '.dig-mrp-trigger[data-open=true]{color:var(--dsw-alias-brand-primary)}',
16235
+ '.dig-mrp-badge{flex:none;display:inline-block;max-width:84px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;padding:0 6px;border-radius:999px;corner-shape:round;font-size:10px;line-height:15px;font-weight:500;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}',
16236
+ '.dig-mrp-badge[data-tone=info]{background:color-mix(in srgb,var(--dsw-alias-state-business-primary,var(--dsw-alias-brand-primary)) 12%,transparent);color:var(--dsw-alias-state-business-primary,var(--dsw-alias-brand-primary))}',
16237
+ '.dig-mrp-badge[data-tone=success]{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 12%,transparent);color:var(--dsw-alias-state-success-primary)}',
16238
+ '.dig-mrp-badge[data-tone=warning]{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary)) 12%,transparent);color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary))}',
15395
16239
  '.dig-mrp-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
15396
16240
  '.dig-mrp-effort{flex:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:10px}',
15397
16241
  '.dig-mrp-menu{max-width:300px}',
@@ -15404,6 +16248,39 @@ window.__ModuleLoader__.load({
15404
16248
  '.dig-mrp-empty{padding:10px;color:var(--dsw-alias-label-tertiary);font-size:12px}',
15405
16249
  '.dig-mrp-error{padding:6px 10px;margin-bottom:2px;border-radius:var(--dsw-radius-sm,8px);background:rgba(220,38,38,.12);color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px;word-break:break-word}',
15406
16250
  'body[data-ds-dark-theme] .dig-mrp-error{background:rgba(248,113,113,.16)}',
16251
+ /* ---- worktrees view (v0.22.0) ---- */
16252
+ '.dig-body-worktrees{flex-direction:column;min-width:0}',
16253
+ '.dig-wt{display:flex;flex-direction:column;flex:1;min-height:0;min-width:0}',
16254
+ '.dig-wt-head{flex:none;display:flex;align-items:center;gap:5px;padding:4px 8px;min-height:26px;box-sizing:border-box;border-bottom:1px solid var(--dsw-alias-border-l1)}',
16255
+ '.dig-wt-title{font-weight:600;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
16256
+ '.dig-wt-count{flex:none;min-width:16px;padding:0 5px;border-radius:999px;corner-shape:round;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:10px;line-height:16px;text-align:center;font-variant-numeric:tabular-nums}',
16257
+ '.dig-wt-head .dig-icon-btn{flex:none}',
16258
+ '.dig-wt-scroll{flex:1;min-height:0;overflow:auto;padding:6px;display:flex;flex-direction:column;gap:4px}',
16259
+ '.dig-wt-row{display:flex;align-items:flex-start;gap:4px;flex-wrap:wrap;padding:7px 8px;min-width:0;border:1px solid transparent;border-radius:var(--dsw-radius-md,10px);background:var(--dsw-alias-bg-layer-1)}',
16260
+ '.dig-wt-row:hover{background:var(--dsw-alias-interactive-bg-hover)}',
16261
+ '.dig-wt-row-current{border-color:var(--dsw-alias-state-business-primary,var(--dsw-alias-brand-primary));background:var(--dsw-alias-bg-layer-2)}',
16262
+ '.dig-wt-row-current:hover{background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2))}',
16263
+ '.dig-wt-row-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}',
16264
+ '.dig-wt-top{display:flex;align-items:center;gap:5px;min-width:0}',
16265
+ '.dig-wt-top svg{flex:none;color:var(--dsw-alias-label-secondary)}',
16266
+ '.dig-wt-branch{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-weight:600}',
16267
+ '.dig-wt-badges{display:inline-flex;flex-wrap:wrap;gap:3px;flex:none;margin-left:2px}',
16268
+ '.dig-wt-badge{display:inline-flex;align-items:center;padding:0 5px;border-radius:999px;corner-shape:round;font-size:10px;line-height:15px;font-weight:500;white-space:nowrap}',
16269
+ '.dig-wt-badge-current{background:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-label-primary-inverted,#fff)}',
16270
+ '.dig-wt-badge-primary{background:var(--dsw-alias-state-business-primary,var(--dsw-alias-brand-primary));color:var(--dsw-alias-label-primary-inverted,#fff)}',
16271
+ '.dig-wt-badge-locked{background:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-label-primary-inverted,#fff)}',
16272
+ '.dig-wt-badge-prunable{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-primary-inverted,#fff)}',
16273
+ '.dig-wt-path{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:15px;word-break:break-all}',
16274
+ '.dig-wt-meta{display:flex;align-items:center;gap:6px;min-width:0;color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:14px}',
16275
+ '.dig-wt-hash{flex:none;letter-spacing:.02em}',
16276
+ '.dig-wt-reason{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
16277
+ '.dig-wt-row-actions{flex:none;display:flex;align-items:center;gap:1px;margin-left:auto;opacity:.7}',
16278
+ '.dig-wt-row:hover .dig-wt-row-actions,.dig-wt-row-current .dig-wt-row-actions{opacity:1}',
16279
+ '.dig-wt-dialog{width:460px;max-width:calc(100% - 24px)}',
16280
+ '.dig-wt-field{display:flex;flex-direction:column;gap:4px;min-width:0;margin-bottom:10px}',
16281
+ '.dig-wt-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500}',
16282
+ '.dig-wt-error{padding:6px 8px;margin-bottom:8px;border-radius:var(--dsw-radius-sm,8px);background:rgba(220,38,38,.12);color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px;word-break:break-word}',
16283
+ 'body[data-ds-dark-theme] .dig-wt-error{background:rgba(248,113,113,.16)}',
15407
16284
  ].concat(LANE_TRACE_RULES).join('\n')
15408
16285
 
15409
16286
  /* ============================== plugin ============================== */
@@ -15539,6 +16416,18 @@ window.__ModuleLoader__.load({
15539
16416
  return value !== false
15540
16417
  }
15541
16418
 
16419
+ /* Inline-first diff preview (issue #22). Default ON: the panel's own diff
16420
+ pane owns the split / wrap switches, so installing dsh-better-sidebar or
16421
+ opening the native preview seat must not silently take that capability
16422
+ away. The settings card flips this field; false restores the external
16423
+ hand-off chain in previewDiff. */
16424
+ function inlineDiffEnabled() {
16425
+ const ready = railFormReady()
16426
+ if (ready === null) return true
16427
+ const value = ready.value !== null && typeof ready.value === 'object' ? ready.value.inlineDiff : undefined
16428
+ return value !== false
16429
+ }
16430
+
15542
16431
  const COMPOSER_POLL_MS = 30000
15543
16432
  const COMPOSER_ERROR_TTL_MS = 6000
15544
16433
  /* Line counts re-run at most this often without a real change: the two
@@ -15649,6 +16538,30 @@ window.__ModuleLoader__.load({
15649
16538
  inject declaration); until it lands, and on any older host, the picker
15650
16539
  hides itself instead of throwing, and the AI-write button still works
15651
16540
  through the host's served-models fallback. */
16541
+ /* A listing badge's text may be a literal string or a language table
16542
+ (llm-deepseek only accepts lowercase language ids plus "en"). The
16543
+ official ModelSelect resolves this through its slot inject, which a
16544
+ panel-side picker cannot see; read the active tag here the same way the
16545
+ dictionary does, and fall back to English exactly like the host. */
16546
+ function modelBadgeTextOf(ctx, badge) {
16547
+ if (badge === null || typeof badge !== 'object') return ''
16548
+ const text = badge.text
16549
+ if (typeof text === 'string') return text
16550
+ if (text === null || typeof text !== 'object') return ''
16551
+ const tag = activeLocaleOf(ctx).toLowerCase()
16552
+ const keys = []
16553
+ if (tag !== '') {
16554
+ keys.push(tag)
16555
+ const primary = tag.split('-')[0]
16556
+ if (primary !== '' && primary !== tag) keys.push(primary)
16557
+ }
16558
+ keys.push('en')
16559
+ for (const key of keys) {
16560
+ const candidate = text[key]
16561
+ if (typeof candidate === 'string' && candidate !== '') return candidate
16562
+ }
16563
+ return ''
16564
+ }
15652
16565
  let modelDirectoriesRef = null
15653
16566
  const modelDirectorySubs = new Set()
15654
16567
  function ModelRoutePicker(props) {
@@ -15729,6 +16642,21 @@ window.__ModuleLoader__.load({
15729
16642
  ? (current.reasoningEffort === undefined ? '' : current.reasoningEffort)
15730
16643
  : (effectiveEffort === undefined ? t('modelRoute.effortDefault') : effortName(effectiveEffort))
15731
16644
  const modelName = current === null ? '' : routeModel === null ? current.model : routeModel.name
16645
+ /* Listing badges are optional and independently translated; a model
16646
+ without one renders exactly as before. Unknown tones fall to neutral,
16647
+ matching the host's listing decoder. */
16648
+ const badgeOf = (model) => {
16649
+ if (model === null || model === undefined || model.badge === null || typeof model.badge !== 'object') return null
16650
+ const text = modelBadgeTextOf(props.ctx, model.badge)
16651
+ if (text === '') return null
16652
+ const tone = model.badge.tone === 'info' || model.badge.tone === 'success' || model.badge.tone === 'warning' ? model.badge.tone : 'neutral'
16653
+ return { text: text, tone: tone }
16654
+ }
16655
+ const badgeNode = (model) => {
16656
+ const badge = badgeOf(model)
16657
+ return badge === null ? null : E('span', { className: 'dig-mrp-badge', 'data-tone': badge.tone }, badge.text)
16658
+ }
16659
+ const routeBadge = badgeOf(routeModel)
15732
16660
  const effortChoices = reasoning === undefined ? [] : ([
15733
16661
  ...(reasoning.defaultEffort === undefined ? [{ key: 'provider-default', effort: undefined, label: t('modelRoute.effortDefault') }] : []),
15734
16662
  ...reasoning.efforts.map((row) => ({ key: row.id, effort: row.id, label: row.name })),
@@ -15750,6 +16678,7 @@ window.__ModuleLoader__.load({
15750
16678
  onClick: () => { setOpen(!open); setPane('root') },
15751
16679
  },
15752
16680
  E(Icon, { name: 'settings', size: 11 }),
16681
+ routeBadge === null ? null : E('span', { className: 'dig-mrp-badge', 'data-tone': routeBadge.tone }, routeBadge.text),
15753
16682
  E('span', { className: 'dig-mrp-label' }, current === null ? t('modelRoute.pick') : modelName),
15754
16683
  effortLabel === '' ? null : E('span', { className: 'dig-mrp-effort' }, effortLabel),
15755
16684
  E(Icon, { name: 'chevron', size: 9 })),
@@ -15794,6 +16723,7 @@ window.__ModuleLoader__.load({
15794
16723
  onClick: () => choose({ provider: group.id, model: model.id }),
15795
16724
  },
15796
16725
  E('span', { className: 'dig-cs-item-name' }, model.name === undefined || model.name === null ? model.id : model.name),
16726
+ badgeNode(model),
15797
16727
  E('span', { className: 'dig-cs-item-note' }, selected === true ? E(Icon, { name: 'check', size: 11 }) : null))
15798
16728
  }))),
15799
16729
  pane === 'effort' && current !== null && effortChoices.map((level) => {
@@ -16344,9 +17274,9 @@ window.__ModuleLoader__.load({
16344
17274
  diff — whatever the store holds — fetched fresh from the host route on
16345
17275
  every seed. The seed carries cwd + repoRoot so a multi-root workspace
16346
17276
  lands on the right repository (the route requires cwd; repoRoot skips
16347
- the rev-parse). Rendered through the SAME DiffBody the inline pane uses,
16348
- so host DiffBlock reuse, the hand-drawn fallback and the binary/empty
16349
- states are all identical. */
17277
+ the rev-parse). Rendered through the SAME LineDiff the inline pane's
17278
+ DiffBody uses, so the header tool set (split / wrap / open file) and
17279
+ the binary/empty states are identical on both surfaces. */
16350
17280
  function NativePreviewPanel(props) {
16351
17281
  const t = props.t
16352
17282
  const [seed, setSeed] = useState(() => previewStore.seed)