dsh-ide-git 0.13.2 → 0.13.4

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
@@ -34,6 +34,12 @@ window.__ModuleLoader__.load({
34
34
  without a frame loop (the SSR self-check's shims) get the plain effect —
35
35
  there is no paint to sync with there, and no server warning either. */
36
36
  const useMeasureLayoutEffect = typeof requestAnimationFrame === 'function' ? useLayoutEffect : useEffect
37
+ /* Host-rendering reuse (v0.14): the file-type glyphs and the diff block come
38
+ from the platform seed table, so a plain require always hits — but a host
39
+ that predates the seed entry must degrade to the hand-drawn rows instead
40
+ of throwing at load time. Every consumer checks for null first. */
41
+ let UIPrimitives = null
42
+ try { UIPrimitives = require('@deepseek-ai/dsh-client-ui-primitives') } catch (error) { void error }
37
43
 
38
44
  /* API base is MOUNT-RELATIVE (no leading slash). dsh 0.1.7 serves the
39
45
  shell with <base href="./">, so document.baseURI is the mount the page
@@ -146,6 +152,10 @@ window.__ModuleLoader__.load({
146
152
  'action.resetHard': '重置到此(丢弃更改)',
147
153
  'action.copyHash': '复制修订号',
148
154
  'action.details': '提交详情',
155
+ 'menu.sendToChat': '发送到对话',
156
+ 'menu.sendFilePathToChat': '发送文件路径到对话',
157
+ 'send.commitText': 'git 提交 {hash}「{subject}」(仓库 {repo})',
158
+ 'send.branchText': 'git 分支 {name}(仓库 {repo})',
149
159
  'history.empty': '这个仓库还没有提交',
150
160
  'history.loadMore': '加载更多',
151
161
  'history.filter': '文本或哈希',
@@ -157,6 +167,11 @@ window.__ModuleLoader__.load({
157
167
  'pane.resize': '拖动调整{name}尺寸,双击复位,方向键微调',
158
168
  'diff.binary': '二进制文件',
159
169
  'diff.loading': '正在加载差异...',
170
+ 'diff.codeLabel': '差异',
171
+ 'diff.wrapLabel': '自动换行',
172
+ 'diff.unwrapLabel': '按源列宽显示',
173
+ 'diff.copy': '复制',
174
+ 'diff.copied': '已复制',
160
175
  'toolbar.refresh': '刷新',
161
176
  'toolbar.newBranch': '新建分支',
162
177
  'toolbar.fetch': '抓取(Fetch)',
@@ -223,6 +238,8 @@ window.__ModuleLoader__.load({
223
238
  'toast.discarded': '已丢弃 {path} 的更改',
224
239
  'toast.discardNoUndo': '已丢弃 {path} 的更改(文件过大或过多,未保留撤回)',
225
240
  'toast.restored': '已恢复',
241
+ 'toast.sentToChat': '已发送到对话框',
242
+ 'toast.sendCopied': '无法发送,已复制到剪贴板',
226
243
  'undo.menu': '最近可撤回的操作',
227
244
  'undo.empty': '现在没有可撤回的操作',
228
245
  'undo.branch': '分支',
@@ -337,6 +354,10 @@ window.__ModuleLoader__.load({
337
354
  'action.resetHard': 'Reset here (discard changes)',
338
355
  'action.copyHash': 'Copy revision number',
339
356
  'action.details': 'Commit details',
357
+ 'menu.sendToChat': 'Send to chat',
358
+ 'menu.sendFilePathToChat': 'Send file path to chat',
359
+ 'send.commitText': 'git commit {hash} "{subject}" (repo {repo})',
360
+ 'send.branchText': 'git branch {name} (repo {repo})',
340
361
  'history.empty': 'This repository has no commits yet',
341
362
  'history.loadMore': 'Load more',
342
363
  'history.filter': 'Text or hash',
@@ -348,6 +369,11 @@ window.__ModuleLoader__.load({
348
369
  'pane.resize': 'Drag to resize {name}: double-click resets, arrow keys nudge',
349
370
  'diff.binary': 'Binary file',
350
371
  'diff.loading': 'Loading diff...',
372
+ 'diff.codeLabel': 'Diff',
373
+ 'diff.wrapLabel': 'Wrap lines',
374
+ 'diff.unwrapLabel': 'Keep source columns',
375
+ 'diff.copy': 'Copy',
376
+ 'diff.copied': 'Copied',
351
377
  'toolbar.refresh': 'Refresh',
352
378
  'toolbar.newBranch': 'New branch',
353
379
  'toolbar.fetch': 'Fetch',
@@ -414,6 +440,8 @@ window.__ModuleLoader__.load({
414
440
  'toast.discarded': 'Discarded changes in {path}',
415
441
  'toast.discardNoUndo': 'Discarded changes in {path} (too large to keep an undo)',
416
442
  'toast.restored': 'Restored',
443
+ 'toast.sentToChat': 'Sent to the chat box',
444
+ 'toast.sendCopied': 'Could not send — copied to the clipboard instead',
417
445
  'undo.menu': 'Recently deleted (undo)',
418
446
  'undo.empty': 'Nothing to undo right now',
419
447
  'undo.branch': 'Branch',
@@ -536,6 +564,10 @@ window.__ModuleLoader__.load({
536
564
  'action.resetHard': '重置到此(丟棄變更)',
537
565
  'action.copyHash': '複製修訂號',
538
566
  'action.details': '提交詳情',
567
+ 'menu.sendToChat': '傳送到對話',
568
+ 'menu.sendFilePathToChat': '傳送檔案路徑至對話',
569
+ 'send.commitText': 'git 提交 {hash}「{subject}」(存放庫 {repo})',
570
+ 'send.branchText': 'git 分支 {name}(存放庫 {repo})',
539
571
  'history.empty': '呢個存放庫仲未有提交',
540
572
  'history.loadMore': '載入更多',
541
573
  'history.filter': '文字或雜湊',
@@ -547,6 +579,11 @@ window.__ModuleLoader__.load({
547
579
  'pane.resize': '拖動調整{name}尺寸,雙擊復位,方向鍵微調',
548
580
  'diff.binary': '二進位檔案',
549
581
  'diff.loading': '正在載入差異...',
582
+ 'diff.codeLabel': '差異',
583
+ 'diff.wrapLabel': '自動換行',
584
+ 'diff.unwrapLabel': '按原始欄寬顯示',
585
+ 'diff.copy': '複製',
586
+ 'diff.copied': '已複製',
550
587
  'toolbar.refresh': '重新整理',
551
588
  'toolbar.newBranch': '新建分支',
552
589
  'toolbar.fetch': '抓取',
@@ -613,6 +650,8 @@ window.__ModuleLoader__.load({
613
650
  'toast.discarded': '已丟棄 {path} 嘅變更',
614
651
  'toast.discardNoUndo': '已丟棄 {path} 嘅變更(太大,冇保留撤回)',
615
652
  'toast.restored': '已還原',
653
+ 'toast.sentToChat': '已傳送到對話框',
654
+ 'toast.sendCopied': '無法傳送,已複製到剪貼板',
616
655
  'undo.menu': '最近刪除(撤回)',
617
656
  'undo.empty': '暫時冇嘢可以撤回',
618
657
  'undo.branch': '分支',
@@ -727,6 +766,10 @@ window.__ModuleLoader__.load({
727
766
  'action.resetHard': '重置到此(丟棄變更)',
728
767
  'action.copyHash': '複製修訂編號',
729
768
  'action.details': '提交詳情',
769
+ 'menu.sendToChat': '傳送至對話',
770
+ 'menu.sendFilePathToChat': '傳送檔案路徑至對話',
771
+ 'send.commitText': 'git 提交 {hash}「{subject}」(版本庫 {repo})',
772
+ 'send.branchText': 'git 分支 {name}(版本庫 {repo})',
730
773
  'history.empty': '這個版本庫還沒有提交',
731
774
  'history.loadMore': '載入更多',
732
775
  'history.filter': '文字或雜湊',
@@ -738,6 +781,11 @@ window.__ModuleLoader__.load({
738
781
  'pane.resize': '拖曳調整{name}尺寸,雙擊重設,方向鍵微調',
739
782
  'diff.binary': '二進位檔案',
740
783
  'diff.loading': '正在載入差異...',
784
+ 'diff.codeLabel': '差異',
785
+ 'diff.wrapLabel': '自動換行',
786
+ 'diff.unwrapLabel': '依原始欄寬顯示',
787
+ 'diff.copy': '複製',
788
+ 'diff.copied': '已複製',
741
789
  'toolbar.refresh': '重新整理',
742
790
  'toolbar.newBranch': '新建分支',
743
791
  'toolbar.fetch': '抓取',
@@ -804,6 +852,8 @@ window.__ModuleLoader__.load({
804
852
  'toast.discarded': '已丟棄 {path} 的變更',
805
853
  'toast.discardNoUndo': '已丟棄 {path} 的變更(太大,未保留撤回)',
806
854
  'toast.restored': '已還原',
855
+ 'toast.sentToChat': '已傳送至對話框',
856
+ 'toast.sendCopied': '無法傳送,已複製到剪貼簿',
807
857
  'undo.menu': '最近刪除(撤回)',
808
858
  'undo.empty': '目前沒有可撤回的項目',
809
859
  'undo.branch': '分支',
@@ -918,6 +968,10 @@ window.__ModuleLoader__.load({
918
968
  'action.resetHard': '重置到此(丟棄變更)',
919
969
  'action.copyHash': '複製修訂號',
920
970
  'action.details': '提交詳情',
971
+ 'menu.sendToChat': '傳送至對話',
972
+ 'menu.sendFilePathToChat': '傳送檔案路徑至對話',
973
+ 'send.commitText': 'git 提交 {hash}「{subject}」(存放庫 {repo})',
974
+ 'send.branchText': 'git 分支 {name}(存放庫 {repo})',
921
975
  'history.empty': '呢個存放庫仲未有提交',
922
976
  'history.loadMore': '載入更多',
923
977
  'history.filter': '文字或雜湊',
@@ -929,6 +983,11 @@ window.__ModuleLoader__.load({
929
983
  'pane.resize': '拖動調整{name}尺寸,雙擊復位,方向鍵微調',
930
984
  'diff.binary': '二進位檔案',
931
985
  'diff.loading': '正在載入差異...',
986
+ 'diff.codeLabel': '差異',
987
+ 'diff.wrapLabel': '自動換行',
988
+ 'diff.unwrapLabel': '依原始欄寬顯示',
989
+ 'diff.copy': '複製',
990
+ 'diff.copied': '已複製',
932
991
  'toolbar.refresh': '重新整理',
933
992
  'toolbar.newBranch': '新建分支',
934
993
  'toolbar.fetch': '抓取',
@@ -995,6 +1054,8 @@ window.__ModuleLoader__.load({
995
1054
  'toast.discarded': '已丟棄 {path} 嘅變更',
996
1055
  'toast.discardNoUndo': '已丟棄 {path} 嘅變更(太大,冇保留撤回)',
997
1056
  'toast.restored': '已還原',
1057
+ 'toast.sentToChat': '已傳送至對話框',
1058
+ 'toast.sendCopied': '無法傳送,已複製到剪貼簿',
998
1059
  'undo.menu': '最近刪除(撤回)',
999
1060
  'undo.empty': '暫時冇嘢可以撤回',
1000
1061
  'undo.branch': '分支',
@@ -1109,6 +1170,10 @@ window.__ModuleLoader__.load({
1109
1170
  'action.resetHard': 'ここにリセット(変更を破棄)',
1110
1171
  'action.copyHash': 'リビジョン番号をコピー',
1111
1172
  'action.details': 'コミットの詳細',
1173
+ 'menu.sendToChat': 'チャットへ送る',
1174
+ 'menu.sendFilePathToChat': 'ファイルパスをチャットへ送る',
1175
+ 'send.commitText': 'git コミット {hash}「{subject}」(リポジトリ {repo})',
1176
+ 'send.branchText': 'git ブランチ {name}(リポジトリ {repo})',
1112
1177
  'history.empty': 'このリポジトリにはまだコミットがありません',
1113
1178
  'history.loadMore': 'さらに読み込む',
1114
1179
  'history.filter': 'テキストまたはハッシュ',
@@ -1120,6 +1185,11 @@ window.__ModuleLoader__.load({
1120
1185
  'pane.resize': '{name}のサイズをドラッグで調整(ダブルクリックでリセット、矢印キーで微調整)',
1121
1186
  'diff.binary': 'バイナリファイル',
1122
1187
  'diff.loading': '差分を読み込み中...',
1188
+ 'diff.codeLabel': '差分',
1189
+ 'diff.wrapLabel': '折り返し',
1190
+ 'diff.unwrapLabel': 'ソースの列幅を維持',
1191
+ 'diff.copy': 'コピー',
1192
+ 'diff.copied': 'コピーしました',
1123
1193
  'toolbar.refresh': '更新',
1124
1194
  'toolbar.newBranch': '新しいブランチ',
1125
1195
  'toolbar.fetch': 'フェッチ',
@@ -1186,6 +1256,8 @@ window.__ModuleLoader__.load({
1186
1256
  'toast.discarded': '{path} の変更を破棄しました',
1187
1257
  'toast.discardNoUndo': '{path} の変更を破棄しました(大きすぎるため取り消しは保持していません)',
1188
1258
  'toast.restored': '復元しました',
1259
+ 'toast.sentToChat': '入力欄に送りました',
1260
+ 'toast.sendCopied': '送信できなかったためクリップボードにコピーしました',
1189
1261
  'undo.menu': '最近の削除(元に戻す)',
1190
1262
  'undo.empty': '今すぐ元に戻せるものはありません',
1191
1263
  'undo.branch': 'ブランチ',
@@ -1300,6 +1372,10 @@ window.__ModuleLoader__.load({
1300
1372
  'action.resetHard': '여기로 리셋(변경 버림)',
1301
1373
  'action.copyHash': '리비전 번호 복사',
1302
1374
  'action.details': '커밋 상세',
1375
+ 'menu.sendToChat': '대화로 보내기',
1376
+ 'menu.sendFilePathToChat': '파일 경로를 대화로 보내기',
1377
+ 'send.commitText': 'git 커밋 {hash} "{subject}" (저장소 {repo})',
1378
+ 'send.branchText': 'git 브랜치 {name} (저장소 {repo})',
1303
1379
  'history.empty': '이 저장소에는 아직 커밋이 없습니다',
1304
1380
  'history.loadMore': '더 불러오기',
1305
1381
  'history.filter': '텍스트 또는 해시',
@@ -1311,6 +1387,11 @@ window.__ModuleLoader__.load({
1311
1387
  'pane.resize': '{name} 크기를 드래그해 조절(더블클릭 초기화, 화살표 키 미세 조정)',
1312
1388
  'diff.binary': '바이너리 파일',
1313
1389
  'diff.loading': '차이 불러오는 중...',
1390
+ 'diff.codeLabel': '차이',
1391
+ 'diff.wrapLabel': '줄 바꿈',
1392
+ 'diff.unwrapLabel': '원본 열 너비 유지',
1393
+ 'diff.copy': '복사',
1394
+ 'diff.copied': '복사됨',
1314
1395
  'toolbar.refresh': '새로 고침',
1315
1396
  'toolbar.newBranch': '새 브랜치',
1316
1397
  'toolbar.fetch': '페치',
@@ -1377,6 +1458,8 @@ window.__ModuleLoader__.load({
1377
1458
  'toast.discarded': '{path}의 변경 사항을 버렸습니다',
1378
1459
  'toast.discardNoUndo': '{path}의 변경 사항을 버렸습니다(너무 커서 되돌리기를 보관하지 않음)',
1379
1460
  'toast.restored': '복원했습니다',
1461
+ 'toast.sentToChat': '입력창에 보냈습니다',
1462
+ 'toast.sendCopied': '보낼 수 없어 클립보드에 복사했습니다',
1380
1463
  'undo.menu': '최근 삭제(되돌리기)',
1381
1464
  'undo.empty': '지금 되돌릴 항목이 없습니다',
1382
1465
  'undo.branch': '브랜치',
@@ -1491,6 +1574,10 @@ window.__ModuleLoader__.load({
1491
1574
  'action.resetHard': 'Hierher zurücksetzen (Änderungen verwerfen)',
1492
1575
  'action.copyHash': 'Revisionsnummer kopieren',
1493
1576
  'action.details': 'Commit-Details',
1577
+ 'menu.sendToChat': 'Im Chat senden',
1578
+ 'menu.sendFilePathToChat': 'Dateipfad im Chat senden',
1579
+ 'send.commitText': 'git-Commit {hash} "{subject}" (Repository {repo})',
1580
+ 'send.branchText': 'git-Branch {name} (Repository {repo})',
1494
1581
  'history.empty': 'Dieses Repository hat noch keine Commits',
1495
1582
  'history.loadMore': 'Mehr laden',
1496
1583
  'history.filter': 'Text oder Hash',
@@ -1502,6 +1589,11 @@ window.__ModuleLoader__.load({
1502
1589
  'pane.resize': 'Größe von {name} per Ziehen ändern (Doppelklick setzt zurück, Pfeiltasten justieren)',
1503
1590
  'diff.binary': 'Binärdatei',
1504
1591
  'diff.loading': 'Diff wird geladen...',
1592
+ 'diff.codeLabel': 'Diff',
1593
+ 'diff.wrapLabel': 'Zeilen umbrechen',
1594
+ 'diff.unwrapLabel': 'Quellspalten beibehalten',
1595
+ 'diff.copy': 'Kopieren',
1596
+ 'diff.copied': 'Kopiert',
1505
1597
  'toolbar.refresh': 'Aktualisieren',
1506
1598
  'toolbar.newBranch': 'Neuer Branch',
1507
1599
  'toolbar.fetch': 'Fetch',
@@ -1568,6 +1660,8 @@ window.__ModuleLoader__.load({
1568
1660
  'toast.discarded': 'Änderungen in {path} verworfen',
1569
1661
  'toast.discardNoUndo': 'Änderungen in {path} verworfen (zu groß für ein Rückgängig)',
1570
1662
  'toast.restored': 'Wiederhergestellt',
1663
+ 'toast.sentToChat': 'In das Eingabefeld eingefügt',
1664
+ 'toast.sendCopied': 'Senden nicht möglich – stattdessen in die Zwischenablage kopiert',
1571
1665
  'undo.menu': 'Zuletzt gelöscht (rückgängig)',
1572
1666
  'undo.empty': 'Derzeit nichts rückgängig zu machen',
1573
1667
  'undo.branch': 'Branch',
@@ -1682,6 +1776,10 @@ window.__ModuleLoader__.load({
1682
1776
  'action.resetHard': 'Réinitialiser ici (abandonner les modifications)',
1683
1777
  'action.copyHash': 'Copier le numéro de révision',
1684
1778
  'action.details': 'Détails du commit',
1779
+ 'menu.sendToChat': 'Envoyer dans la conversation',
1780
+ 'menu.sendFilePathToChat': 'Envoyer le chemin du fichier dans la conversation',
1781
+ 'send.commitText': 'commit git {hash} « {subject} » (dépôt {repo})',
1782
+ 'send.branchText': 'branche git {name} (dépôt {repo})',
1685
1783
  'history.empty': 'Ce dépôt n\'a pas encore de commit',
1686
1784
  'history.loadMore': 'Charger plus',
1687
1785
  'history.filter': 'Texte ou empreinte',
@@ -1693,6 +1791,11 @@ window.__ModuleLoader__.load({
1693
1791
  'pane.resize': 'Glisser pour redimensionner {name} (double-clic pour réinitialiser, flèches pour ajuster)',
1694
1792
  'diff.binary': 'Fichier binaire',
1695
1793
  'diff.loading': 'Chargement du diff...',
1794
+ 'diff.codeLabel': 'Diff',
1795
+ 'diff.wrapLabel': 'Renvoyer à la ligne',
1796
+ 'diff.unwrapLabel': 'Conserver les colonnes de la source',
1797
+ 'diff.copy': 'Copier',
1798
+ 'diff.copied': 'Copié',
1696
1799
  'toolbar.refresh': 'Rafraîchir',
1697
1800
  'toolbar.newBranch': 'Nouvelle branche',
1698
1801
  'toolbar.fetch': 'Récupérer',
@@ -1759,6 +1862,8 @@ window.__ModuleLoader__.load({
1759
1862
  'toast.discarded': 'Modifications de {path} abandonnées',
1760
1863
  'toast.discardNoUndo': 'Modifications de {path} abandonnées (trop volumineuses pour être annulées)',
1761
1864
  'toast.restored': 'Restauré',
1865
+ 'toast.sentToChat': 'Envoyé dans la zone de saisie',
1866
+ 'toast.sendCopied': 'Envoi impossible — copié dans le presse-papiers à la place',
1762
1867
  'undo.menu': 'Supprimés récemment (annuler)',
1763
1868
  'undo.empty': 'Rien à annuler pour le moment',
1764
1869
  'undo.branch': 'Branche',
@@ -1873,6 +1978,10 @@ window.__ModuleLoader__.load({
1873
1978
  'action.resetHard': 'Сбросить сюда (отбросить изменения)',
1874
1979
  'action.copyHash': 'Скопировать номер ревизии',
1875
1980
  'action.details': 'Сведения о коммите',
1981
+ 'menu.sendToChat': 'Отправить в чат',
1982
+ 'menu.sendFilePathToChat': 'Отправить путь к файлу в чат',
1983
+ 'send.commitText': 'git-коммит {hash} «{subject}» (репозиторий {repo})',
1984
+ 'send.branchText': 'git-ветка {name} (репозиторий {repo})',
1876
1985
  'history.empty': 'В этом репозитории ещё нет коммитов',
1877
1986
  'history.loadMore': 'Загрузить ещё',
1878
1987
  'history.filter': 'Текст или хеш',
@@ -1884,6 +1993,11 @@ window.__ModuleLoader__.load({
1884
1993
  'pane.resize': 'Перетащите, чтобы изменить размер {name} (двойной клик — сброс, стрелки — точная настройка)',
1885
1994
  'diff.binary': 'Двоичный файл',
1886
1995
  'diff.loading': 'Загрузка различий...',
1996
+ 'diff.codeLabel': 'Различия',
1997
+ 'diff.wrapLabel': 'Переносить строки',
1998
+ 'diff.unwrapLabel': 'Сохранять исходные колонки',
1999
+ 'diff.copy': 'Копировать',
2000
+ 'diff.copied': 'Скопировано',
1887
2001
  'toolbar.refresh': 'Обновить',
1888
2002
  'toolbar.newBranch': 'Новая ветка',
1889
2003
  'toolbar.fetch': 'Получить',
@@ -1950,6 +2064,8 @@ window.__ModuleLoader__.load({
1950
2064
  'toast.discarded': 'Изменения в {path} отменены',
1951
2065
  'toast.discardNoUndo': 'Изменения в {path} отменены (слишком велики, отмена не сохранена)',
1952
2066
  'toast.restored': 'Восстановлено',
2067
+ 'toast.sentToChat': 'Отправлено в поле ввода',
2068
+ 'toast.sendCopied': 'Не удалось отправить — скопировано в буфер обмена',
1953
2069
  'undo.menu': 'Недавно удалённое (отменить)',
1954
2070
  'undo.empty': 'Сейчас нечего отменять',
1955
2071
  'undo.branch': 'Ветка',
@@ -2064,6 +2180,10 @@ window.__ModuleLoader__.load({
2064
2180
  'action.resetHard': 'Redefinir aqui (descartar alterações)',
2065
2181
  'action.copyHash': 'Copiar número da revisão',
2066
2182
  'action.details': 'Detalhes do commit',
2183
+ 'menu.sendToChat': 'Enviar para a conversa',
2184
+ 'menu.sendFilePathToChat': 'Enviar caminho do arquivo para a conversa',
2185
+ 'send.commitText': 'commit git {hash} "{subject}" (repositório {repo})',
2186
+ 'send.branchText': 'branch git {name} (repositório {repo})',
2067
2187
  'history.empty': 'Este repositório ainda não tem commits',
2068
2188
  'history.loadMore': 'Carregar mais',
2069
2189
  'history.filter': 'Texto ou hash',
@@ -2075,6 +2195,11 @@ window.__ModuleLoader__.load({
2075
2195
  'pane.resize': 'Arraste para redimensionar {name} (duplo clique restaura, setas ajustam)',
2076
2196
  'diff.binary': 'Arquivo binário',
2077
2197
  'diff.loading': 'Carregando diff...',
2198
+ 'diff.codeLabel': 'Diff',
2199
+ 'diff.wrapLabel': 'Quebrar linhas',
2200
+ 'diff.unwrapLabel': 'Manter colunas do código',
2201
+ 'diff.copy': 'Copiar',
2202
+ 'diff.copied': 'Copiado',
2078
2203
  'toolbar.refresh': 'Atualizar',
2079
2204
  'toolbar.newBranch': 'Nova branch',
2080
2205
  'toolbar.fetch': 'Buscar',
@@ -2141,6 +2266,8 @@ window.__ModuleLoader__.load({
2141
2266
  'toast.discarded': 'Alterações em {path} descartadas',
2142
2267
  'toast.discardNoUndo': 'Alterações em {path} descartadas (grandes demais para desfazer)',
2143
2268
  'toast.restored': 'Restaurado',
2269
+ 'toast.sentToChat': 'Enviado para a caixa de entrada',
2270
+ 'toast.sendCopied': 'Não foi possível enviar — copiado para a área de transferência',
2144
2271
  'undo.menu': 'Excluídos recentemente (desfazer)',
2145
2272
  'undo.empty': 'Nada para desfazer agora',
2146
2273
  'undo.branch': 'Branch',
@@ -2255,6 +2382,10 @@ window.__ModuleLoader__.load({
2255
2382
  'action.resetHard': 'Ripristina qui (scarta le modifiche)',
2256
2383
  'action.copyHash': 'Copia il numero di revisione',
2257
2384
  'action.details': 'Dettagli del commit',
2385
+ 'menu.sendToChat': 'Invia alla conversazione',
2386
+ 'menu.sendFilePathToChat': 'Invia il percorso del file alla conversazione',
2387
+ 'send.commitText': 'commit git {hash} "{subject}" (repository {repo})',
2388
+ 'send.branchText': 'branch git {name} (repository {repo})',
2258
2389
  'history.empty': 'Questo repository non ha ancora commit',
2259
2390
  'history.loadMore': 'Carica altro',
2260
2391
  'history.filter': 'Testo o hash',
@@ -2266,6 +2397,11 @@ window.__ModuleLoader__.load({
2266
2397
  'pane.resize': 'Trascina per ridimensionare {name} (doppio clic ripristina, frecce per regolare)',
2267
2398
  'diff.binary': 'File binario',
2268
2399
  'diff.loading': 'Caricamento del diff...',
2400
+ 'diff.codeLabel': 'Diff',
2401
+ 'diff.wrapLabel': 'A capo automatico',
2402
+ 'diff.unwrapLabel': 'Mantieni le colonne originali',
2403
+ 'diff.copy': 'Copia',
2404
+ 'diff.copied': 'Copiato',
2269
2405
  'toolbar.refresh': 'Aggiorna',
2270
2406
  'toolbar.newBranch': 'Nuovo branch',
2271
2407
  'toolbar.fetch': 'Fetch',
@@ -2332,6 +2468,8 @@ window.__ModuleLoader__.load({
2332
2468
  'toast.discarded': 'Modifiche in {path} scartate',
2333
2469
  'toast.discardNoUndo': 'Modifiche in {path} scartate (troppo grandi per essere annullate)',
2334
2470
  'toast.restored': 'Ripristinato',
2471
+ 'toast.sentToChat': 'Inviato nella casella di input',
2472
+ 'toast.sendCopied': 'Impossibile inviare — copiato negli appunti',
2335
2473
  'undo.menu': 'Eliminati di recente (annulla)',
2336
2474
  'undo.empty': 'Niente da annullare al momento',
2337
2475
  'undo.branch': 'Branch',
@@ -2446,6 +2584,10 @@ window.__ModuleLoader__.load({
2446
2584
  'action.resetHard': 'Hierheen resetten (wijzigingen weggooien)',
2447
2585
  'action.copyHash': 'Revisienummer kopiëren',
2448
2586
  'action.details': 'Commit-details',
2587
+ 'menu.sendToChat': 'Naar het gesprek verzenden',
2588
+ 'menu.sendFilePathToChat': 'Bestandspad naar het gesprek verzenden',
2589
+ 'send.commitText': 'git-commit {hash} "{subject}" (repository {repo})',
2590
+ 'send.branchText': 'git-branch {name} (repository {repo})',
2449
2591
  'history.empty': 'Deze repository heeft nog geen commits',
2450
2592
  'history.loadMore': 'Meer laden',
2451
2593
  'history.filter': 'Tekst of hash',
@@ -2457,6 +2599,11 @@ window.__ModuleLoader__.load({
2457
2599
  'pane.resize': 'Sleep om {name} te vergroten (dubbelklik herstelt, pijltjes stellen fijn af)',
2458
2600
  'diff.binary': 'Binair bestand',
2459
2601
  'diff.loading': 'Diff laden...',
2602
+ 'diff.codeLabel': 'Diff',
2603
+ 'diff.wrapLabel': 'Regels afbreken',
2604
+ 'diff.unwrapLabel': 'Bronkolommen behouden',
2605
+ 'diff.copy': 'Kopiëren',
2606
+ 'diff.copied': 'Gekopieerd',
2460
2607
  'toolbar.refresh': 'Vernieuwen',
2461
2608
  'toolbar.newBranch': 'Nieuwe branch',
2462
2609
  'toolbar.fetch': 'Fetch',
@@ -2523,6 +2670,8 @@ window.__ModuleLoader__.load({
2523
2670
  'toast.discarded': 'Wijzigingen in {path} weggegooid',
2524
2671
  'toast.discardNoUndo': 'Wijzigingen in {path} weggegooid (te groot om ongedaan te maken)',
2525
2672
  'toast.restored': 'Hersteld',
2673
+ 'toast.sentToChat': 'In het invoerveld geplaatst',
2674
+ 'toast.sendCopied': 'Kon niet worden verzonden — in plaats daarvan naar het klembord gekopieerd',
2526
2675
  'undo.menu': 'Onlangs verwijderd (ongedaan maken)',
2527
2676
  'undo.empty': 'Nu niets om ongedaan te maken',
2528
2677
  'undo.branch': 'Branch',
@@ -2637,6 +2786,10 @@ window.__ModuleLoader__.load({
2637
2786
  'action.resetHard': 'Przywróć tutaj (odrzuć zmiany)',
2638
2787
  'action.copyHash': 'Kopiuj numer wersji',
2639
2788
  'action.details': 'Szczegóły commita',
2789
+ 'menu.sendToChat': 'Wyślij do rozmowy',
2790
+ 'menu.sendFilePathToChat': 'Wyślij ścieżkę pliku do rozmowy',
2791
+ 'send.commitText': 'commit git {hash} „{subject}" (repozytorium {repo})',
2792
+ 'send.branchText': 'gałąź git {name} (repozytorium {repo})',
2640
2793
  'history.empty': 'To repozytorium nie ma jeszcze commitów',
2641
2794
  'history.loadMore': 'Wczytaj więcej',
2642
2795
  'history.filter': 'Tekst lub hash',
@@ -2648,6 +2801,11 @@ window.__ModuleLoader__.load({
2648
2801
  'pane.resize': 'Przeciągnij, aby zmienić rozmiar {name} (podwójne kliknięcie przywraca, strzałki korygują)',
2649
2802
  'diff.binary': 'Plik binarny',
2650
2803
  'diff.loading': 'Wczytywanie różnic...',
2804
+ 'diff.codeLabel': 'Różnice',
2805
+ 'diff.wrapLabel': 'Zawijaj wiersze',
2806
+ 'diff.unwrapLabel': 'Zachowaj kolumny źródła',
2807
+ 'diff.copy': 'Kopiuj',
2808
+ 'diff.copied': 'Skopiowano',
2651
2809
  'toolbar.refresh': 'Odśwież',
2652
2810
  'toolbar.newBranch': 'Nowa gałąź',
2653
2811
  'toolbar.fetch': 'Pobierz',
@@ -2714,6 +2872,8 @@ window.__ModuleLoader__.load({
2714
2872
  'toast.discarded': 'Odrzucono zmiany w {path}',
2715
2873
  'toast.discardNoUndo': 'Odrzucono zmiany w {path} (zbyt duże, aby zachować cofnięcie)',
2716
2874
  'toast.restored': 'Przywrócono',
2875
+ 'toast.sentToChat': 'Wysłano do pola wprowadzania',
2876
+ 'toast.sendCopied': 'Nie udało się wysłać — skopiowano do schowka',
2717
2877
  'undo.menu': 'Ostatnio usunięte (cofnij)',
2718
2878
  'undo.empty': 'Teraz nie ma czego cofać',
2719
2879
  'undo.branch': 'Gałąź',
@@ -2828,6 +2988,10 @@ window.__ModuleLoader__.load({
2828
2988
  'action.resetHard': 'Återställ hit (förkasta ändringar)',
2829
2989
  'action.copyHash': 'Kopiera revisionsnummer',
2830
2990
  'action.details': 'Commit-detaljer',
2991
+ 'menu.sendToChat': 'Skicka till samtalet',
2992
+ 'menu.sendFilePathToChat': 'Skicka filsökvägen till samtalet',
2993
+ 'send.commitText': 'git-commit {hash} ”{subject}” (arkiv {repo})',
2994
+ 'send.branchText': 'git-gren {name} (arkiv {repo})',
2831
2995
  'history.empty': 'Det här arkivet har inga commits ännu',
2832
2996
  'history.loadMore': 'Ladda mer',
2833
2997
  'history.filter': 'Text eller hash',
@@ -2839,6 +3003,11 @@ window.__ModuleLoader__.load({
2839
3003
  'pane.resize': 'Dra för att ändra storleken på {name} (dubbelklick återställer, piltangenter finjusterar)',
2840
3004
  'diff.binary': 'Binär fil',
2841
3005
  'diff.loading': 'Laddar diff...',
3006
+ 'diff.codeLabel': 'Skillnad',
3007
+ 'diff.wrapLabel': 'Radbryt rader',
3008
+ 'diff.unwrapLabel': 'Behåll kolumner från källan',
3009
+ 'diff.copy': 'Kopiera',
3010
+ 'diff.copied': 'Kopierat',
2842
3011
  'toolbar.refresh': 'Uppdatera',
2843
3012
  'toolbar.newBranch': 'Ny gren',
2844
3013
  'toolbar.fetch': 'Fetch',
@@ -2905,6 +3074,8 @@ window.__ModuleLoader__.load({
2905
3074
  'toast.discarded': 'Ändringar i {path} förkastade',
2906
3075
  'toast.discardNoUndo': 'Ändringar i {path} förkastade (för stora för att ångra)',
2907
3076
  'toast.restored': 'Återställd',
3077
+ 'toast.sentToChat': 'Skickat till inmatningsrutan',
3078
+ 'toast.sendCopied': 'Det gick inte att skicka – kopierade till urklipp istället',
2908
3079
  'undo.menu': 'Nyligen borttaget (ångra)',
2909
3080
  'undo.empty': 'Inget att ångra just nu',
2910
3081
  'undo.branch': 'Gren',
@@ -3019,6 +3190,10 @@ window.__ModuleLoader__.load({
3019
3190
  'action.resetHard': 'Buraya sıfırla (değişiklikleri at)',
3020
3191
  'action.copyHash': 'Revizyon numarasını kopyala',
3021
3192
  'action.details': 'Commit ayrıntıları',
3193
+ 'menu.sendToChat': 'Sohbete gönder',
3194
+ 'menu.sendFilePathToChat': 'Dosya yolunu sohbete gönder',
3195
+ 'send.commitText': 'git commit {hash} "{subject}" (depo {repo})',
3196
+ 'send.branchText': 'git dalı {name} (depo {repo})',
3022
3197
  'history.empty': 'Bu depoda henüz commit yok',
3023
3198
  'history.loadMore': 'Daha fazla yükle',
3024
3199
  'history.filter': 'Metin veya hash',
@@ -3030,6 +3205,11 @@ window.__ModuleLoader__.load({
3030
3205
  'pane.resize': '{name} boyutunu sürükleyerek ayarlayın (çift tıklama sıfırlar, ok tuşları ince ayar yapar)',
3031
3206
  'diff.binary': 'İkili dosya',
3032
3207
  'diff.loading': 'Fark yükleniyor...',
3208
+ 'diff.codeLabel': 'Fark',
3209
+ 'diff.wrapLabel': 'Satırları kaydır',
3210
+ 'diff.unwrapLabel': 'Kaynak sütunlarını koru',
3211
+ 'diff.copy': 'Kopyala',
3212
+ 'diff.copied': 'Kopyalandı',
3033
3213
  'toolbar.refresh': 'Yenile',
3034
3214
  'toolbar.newBranch': 'Yeni dal',
3035
3215
  'toolbar.fetch': 'Fetch',
@@ -3096,6 +3276,8 @@ window.__ModuleLoader__.load({
3096
3276
  'toast.discarded': '{path} içindeki değişiklikler atıldı',
3097
3277
  'toast.discardNoUndo': '{path} içindeki değişiklikler atıldı (geri alma için fazla büyük)',
3098
3278
  'toast.restored': 'Geri yüklendi',
3279
+ 'toast.sentToChat': 'Giriş kutusuna gönderildi',
3280
+ 'toast.sendCopied': 'Gönderilemedi – bunun yerine panoya kopyalandı',
3099
3281
  'undo.menu': 'Son silinenler (geri al)',
3100
3282
  'undo.empty': 'Şu anda geri alınacak bir şey yok',
3101
3283
  'undo.branch': 'Dal',
@@ -3210,6 +3392,10 @@ window.__ModuleLoader__.load({
3210
3392
  'action.resetHard': 'Reset ke sini (buang perubahan)',
3211
3393
  'action.copyHash': 'Salin nomor revisi',
3212
3394
  'action.details': 'Detail commit',
3395
+ 'menu.sendToChat': 'Kirim ke obrolan',
3396
+ 'menu.sendFilePathToChat': 'Kirim jalur berkas ke obrolan',
3397
+ 'send.commitText': 'commit git {hash} "{subject}" (repositori {repo})',
3398
+ 'send.branchText': 'cabang git {name} (repositori {repo})',
3213
3399
  'history.empty': 'Repositori ini belum punya commit',
3214
3400
  'history.loadMore': 'Muat lebih banyak',
3215
3401
  'history.filter': 'Teks atau hash',
@@ -3221,6 +3407,11 @@ window.__ModuleLoader__.load({
3221
3407
  'pane.resize': 'Seret untuk mengubah ukuran {name} (klik ganda mengatur ulang, tombol panah menyesuaikan)',
3222
3408
  'diff.binary': 'Berkas biner',
3223
3409
  'diff.loading': 'Memuat diff...',
3410
+ 'diff.codeLabel': 'Perbedaan',
3411
+ 'diff.wrapLabel': 'Lipat baris',
3412
+ 'diff.unwrapLabel': 'Pertahankan kolom sumber',
3413
+ 'diff.copy': 'Salin',
3414
+ 'diff.copied': 'Tersalin',
3224
3415
  'toolbar.refresh': 'Segarkan',
3225
3416
  'toolbar.newBranch': 'Branch baru',
3226
3417
  'toolbar.fetch': 'Fetch',
@@ -3287,6 +3478,8 @@ window.__ModuleLoader__.load({
3287
3478
  'toast.discarded': 'Perubahan di {path} dibuang',
3288
3479
  'toast.discardNoUndo': 'Perubahan di {path} dibuang (terlalu besar untuk diurungkan)',
3289
3480
  'toast.restored': 'Dipulihkan',
3481
+ 'toast.sentToChat': 'Terkirim ke kotak masukan',
3482
+ 'toast.sendCopied': 'Tidak dapat mengirim — disalin ke papan klip sebagai gantinya',
3290
3483
  'undo.menu': 'Baru dihapus (urungkan)',
3291
3484
  'undo.empty': 'Tidak ada yang bisa diurungkan sekarang',
3292
3485
  'undo.branch': 'Branch',
@@ -3401,6 +3594,10 @@ window.__ModuleLoader__.load({
3401
3594
  'action.resetHard': 'Đặt lại về đây (bỏ thay đổi)',
3402
3595
  'action.copyHash': 'Sao chép số hiệu bản sửa',
3403
3596
  'action.details': 'Chi tiết commit',
3597
+ 'menu.sendToChat': 'Gửi vào hội thoại',
3598
+ 'menu.sendFilePathToChat': 'Gửi đường dẫn tệp vào hội thoại',
3599
+ 'send.commitText': 'git commit {hash} "{subject}" (kho {repo})',
3600
+ 'send.branchText': 'nhánh git {name} (kho {repo})',
3404
3601
  'history.empty': 'Kho này chưa có commit nào',
3405
3602
  'history.loadMore': 'Tải thêm',
3406
3603
  'history.filter': 'Văn bản hoặc hash',
@@ -3412,6 +3609,11 @@ window.__ModuleLoader__.load({
3412
3609
  'pane.resize': 'Kéo để đổi kích thước {name} (nhấp đúp để đặt lại, phím mũi tên để tinh chỉnh)',
3413
3610
  'diff.binary': 'Tệp nhị phân',
3414
3611
  'diff.loading': 'Đang tải diff...',
3612
+ 'diff.codeLabel': 'Khác biệt',
3613
+ 'diff.wrapLabel': 'Ngắt dòng',
3614
+ 'diff.unwrapLabel': 'Giữ độ rộng cột gốc',
3615
+ 'diff.copy': 'Sao chép',
3616
+ 'diff.copied': 'Đã sao chép',
3415
3617
  'toolbar.refresh': 'Làm mới',
3416
3618
  'toolbar.newBranch': 'Nhánh mới',
3417
3619
  'toolbar.fetch': 'Fetch',
@@ -3478,6 +3680,8 @@ window.__ModuleLoader__.load({
3478
3680
  'toast.discarded': 'Đã bỏ thay đổi trong {path}',
3479
3681
  'toast.discardNoUndo': 'Đã bỏ thay đổi trong {path} (quá lớn để giữ hoàn tác)',
3480
3682
  'toast.restored': 'Đã khôi phục',
3683
+ 'toast.sentToChat': 'Đã gửi vào ô nhập',
3684
+ 'toast.sendCopied': 'Không thể gửi — đã sao chép vào clipboard để thay thế',
3481
3685
  'undo.menu': 'Vừa xoá (hoàn tác)',
3482
3686
  'undo.empty': 'Hiện không có gì để hoàn tác',
3483
3687
  'undo.branch': 'Nhánh',
@@ -3592,6 +3796,10 @@ window.__ModuleLoader__.load({
3592
3796
  'action.resetHard': 'إعادة التعيين هنا (مع تجاهل التغييرات)',
3593
3797
  'action.copyHash': 'نسخ رقم المراجعة',
3594
3798
  'action.details': 'تفاصيل الالتزام',
3799
+ 'menu.sendToChat': 'إرسال إلى المحادثة',
3800
+ 'menu.sendFilePathToChat': 'إرسال مسار الملف إلى المحادثة',
3801
+ 'send.commitText': 'التزام git {hash} "{subject}" (المستودع {repo})',
3802
+ 'send.branchText': 'فرع git {name} (المستودع {repo})',
3595
3803
  'history.empty': 'لا توجد التزامات في هذا المستودع بعد',
3596
3804
  'history.loadMore': 'تحميل المزيد',
3597
3805
  'history.filter': 'نص أو تجزئة',
@@ -3603,6 +3811,11 @@ window.__ModuleLoader__.load({
3603
3811
  'pane.resize': 'اسحب لتغيير حجم {name} (النقر المزدوج يعيد الضبط، ومفاتيح الأسهم للضبط الدقيق)',
3604
3812
  'diff.binary': 'ملف ثنائي',
3605
3813
  'diff.loading': 'جارٍ تحميل الفروق...',
3814
+ 'diff.codeLabel': 'الفروق',
3815
+ 'diff.wrapLabel': 'التفاف الأسطر',
3816
+ 'diff.unwrapLabel': 'الحفاظ على أعمدة المصدر',
3817
+ 'diff.copy': 'نسخ',
3818
+ 'diff.copied': 'تم النسخ',
3606
3819
  'toolbar.refresh': 'تحديث',
3607
3820
  'toolbar.newBranch': 'فرع جديد',
3608
3821
  'toolbar.fetch': 'جلب',
@@ -3669,6 +3882,8 @@ window.__ModuleLoader__.load({
3669
3882
  'toast.discarded': 'تم تجاهل التغييرات في {path}',
3670
3883
  'toast.discardNoUndo': 'تم تجاهل التغييرات في {path} (أكبر من أن يُحتفظ بالتراجع)',
3671
3884
  'toast.restored': 'تمت الاستعادة',
3885
+ 'toast.sentToChat': 'تم الإرسال إلى مربع الكتابة',
3886
+ 'toast.sendCopied': 'تعذّر الإرسال — تم النسخ إلى الحافظة بدلًا من ذلك',
3672
3887
  'undo.menu': 'المحذوف حديثًا (تراجع)',
3673
3888
  'undo.empty': 'لا شيء لتراجعه الآن',
3674
3889
  'undo.branch': 'فرع',
@@ -3783,6 +3998,10 @@ window.__ModuleLoader__.load({
3783
3998
  'action.resetHard': 'यहाँ रीसेट करें (परिवर्तन छोड़ें)',
3784
3999
  'action.copyHash': 'रिवीज़न संख्या कॉपी करें',
3785
4000
  'action.details': 'कमिट विवरण',
4001
+ 'menu.sendToChat': 'बातचीत में भेजें',
4002
+ 'menu.sendFilePathToChat': 'फ़ाइल पथ बातचीत में भेजें',
4003
+ 'send.commitText': 'git कमिट {hash} "{subject}" (रिपॉज़िटरी {repo})',
4004
+ 'send.branchText': 'git ब्रांच {name} (रिपॉज़िटरी {repo})',
3786
4005
  'history.empty': 'इस रिपॉज़िटरी में अभी कोई कमिट नहीं है',
3787
4006
  'history.loadMore': 'और लोड करें',
3788
4007
  'history.filter': 'टेक्स्ट या हैश',
@@ -3794,6 +4013,11 @@ window.__ModuleLoader__.load({
3794
4013
  'pane.resize': '{name} का आकार बदलने के लिए खींचें (डबल-क्लिक रीसेट, तीर कुंजियाँ सूक्ष्म समायोजन)',
3795
4014
  'diff.binary': 'बाइनरी फ़ाइल',
3796
4015
  'diff.loading': 'अंतर लोड हो रहा है...',
4016
+ 'diff.codeLabel': 'अंतर',
4017
+ 'diff.wrapLabel': 'पंक्तियाँ रैप करें',
4018
+ 'diff.unwrapLabel': 'स्रोत कॉलम बनाए रखें',
4019
+ 'diff.copy': 'कॉपी करें',
4020
+ 'diff.copied': 'कॉपी हो गया',
3797
4021
  'toolbar.refresh': 'ताज़ा करें',
3798
4022
  'toolbar.newBranch': 'नई शाखा',
3799
4023
  'toolbar.fetch': 'फ़ेच',
@@ -3860,6 +4084,8 @@ window.__ModuleLoader__.load({
3860
4084
  'toast.discarded': '{path} में परिवर्तन छोड़े गए',
3861
4085
  'toast.discardNoUndo': '{path} में परिवर्तन छोड़े गए (पूर्ववत रखने के लिए बहुत बड़ा)',
3862
4086
  'toast.restored': 'बहाल किया गया',
4087
+ 'toast.sentToChat': 'इनपुट बॉक्स में भेज दिया गया',
4088
+ 'toast.sendCopied': 'भेजा नहीं जा सका — इसके बजाय क्लिपबोर्ड पर कॉपी कर दिया गया',
3863
4089
  'undo.menu': 'हाल में हटाए गए (पूर्ववत करें)',
3864
4090
  'undo.empty': 'अभी पूर्ववत करने के लिए कुछ नहीं',
3865
4091
  'undo.branch': 'शाखा',
@@ -3974,6 +4200,10 @@ window.__ModuleLoader__.load({
3974
4200
  'action.resetHard': 'รีเซ็ตมาที่นี่ (ทิ้งการเปลี่ยนแปลง)',
3975
4201
  'action.copyHash': 'คัดลอกเลขรีวิชัน',
3976
4202
  'action.details': 'รายละเอียดคอมมิต',
4203
+ 'menu.sendToChat': 'ส่งไปยังบทสนทนา',
4204
+ 'menu.sendFilePathToChat': 'ส่งพาธไฟล์ไปยังบทสนทนา',
4205
+ 'send.commitText': 'git คอมมิต {hash} "{subject}" (รีโพซิทอรี {repo})',
4206
+ 'send.branchText': 'git บรานช์ {name} (รีโพซิทอรี {repo})',
3977
4207
  'history.empty': 'รีโพซิทอรีนี้ยังไม่มีคอมมิต',
3978
4208
  'history.loadMore': 'โหลดเพิ่ม',
3979
4209
  'history.filter': 'ข้อความหรือแฮช',
@@ -3985,6 +4215,11 @@ window.__ModuleLoader__.load({
3985
4215
  'pane.resize': 'ลากเพื่อปรับขนาด {name} (ดับเบิลคลิกเพื่อรีเซ็ต ปุ่มลูกศรปรับละเอียด)',
3986
4216
  'diff.binary': 'ไฟล์ไบนารี',
3987
4217
  'diff.loading': 'กำลังโหลดความต่าง...',
4218
+ 'diff.codeLabel': 'ความต่าง',
4219
+ 'diff.wrapLabel': 'ตัดบรรทัด',
4220
+ 'diff.unwrapLabel': 'คงความกว้างคอลัมน์เดิม',
4221
+ 'diff.copy': 'คัดลอก',
4222
+ 'diff.copied': 'คัดลอกแล้ว',
3988
4223
  'toolbar.refresh': 'รีเฟรช',
3989
4224
  'toolbar.newBranch': 'แบรนช์ใหม่',
3990
4225
  'toolbar.fetch': 'Fetch',
@@ -4051,6 +4286,8 @@ window.__ModuleLoader__.load({
4051
4286
  'toast.discarded': 'ทิ้งการเปลี่ยนแปลงใน {path} แล้ว',
4052
4287
  'toast.discardNoUndo': 'ทิ้งการเปลี่ยนแปลงใน {path} แล้ว (ใหญ่เกินกว่าจะเก็บไว้เลิกทำ)',
4053
4288
  'toast.restored': 'กู้คืนแล้ว',
4289
+ 'toast.sentToChat': 'ส่งไปที่กล่องป้อนข้อความแล้ว',
4290
+ 'toast.sendCopied': 'ส่งไม่สำเร็จ – คัดลอกไปยังคลิปบอร์ดแทนแล้ว',
4054
4291
  'undo.menu': 'ลบล่าสุด (เลิกทำ)',
4055
4292
  'undo.empty': 'ตอนนี้ไม่มีอะไรให้เลิกทำ',
4056
4293
  'undo.branch': 'แบรนช์',
@@ -4251,31 +4488,92 @@ window.__ModuleLoader__.load({
4251
4488
  is still essentially correct, so it seeds the states and a background
4252
4489
  refresh lands the fresh data a moment later. Pure functions; the panel
4253
4490
  only ever reads them at effect time and writes after a fetch. */
4254
- const PAINT_TTL_MS = 30_000
4491
+ /* Two TTLs, because the two seeds age very differently. The repos scan is
4492
+ the SLOW part (a multi-repo workspace walks directories) and its result
4493
+ barely changes, so its seed stays usable for 10 minutes: freshness is
4494
+ the background refresh's job — every mount refreshes anyway — the TTL
4495
+ only decides how old a seed may be to still paint the first screen
4496
+ (user report: coming back to the Git tab after a while re-ran the whole
4497
+ discovery). The first page (summary / branches / log) changes with every
4498
+ commit, so its seed keeps the short 30s window it always had. */
4499
+ const PAINT_REPOS_TTL_MS = 600_000
4500
+ const PAINT_PAGE_TTL_MS = 30_000
4255
4501
  const paintRepos = new Map() /* cwd -> { at, data } */
4256
4502
  const paintPage = new Map() /* repoRoot -> { at, summary, branches, commits, hasMore } */
4257
4503
  const paintRoot = new Map() /* cwd -> last repoRoot the user was looking at */
4258
4504
 
4505
+ /* The seed also survives a FULL PAGE RELOAD: sessionStorage holds the same
4506
+ maps for the lifetime of the tab, so reloading the page (the other way
4507
+ users "come back") paints from the seed instead of re-running the slow
4508
+ discovery. Hydrate runs once, lazily before the first read; every write
4509
+ mirrors the in-memory state back. All of it is best-effort: private
4510
+ mode, quota errors and corrupted payloads degrade to "no seed", never to
4511
+ an error — and never to a rollback of live state (seeding still only
4512
+ fills cold state, see the panel side below). */
4513
+ const PAINT_KEY = 'dsh-ide-git.paint.v1'
4514
+ let paintHydrated = false
4515
+
4516
+ function hydratePaint() {
4517
+ if (paintHydrated === true) return
4518
+ paintHydrated = true
4519
+ let parsed = null
4520
+ try {
4521
+ const raw = window.sessionStorage.getItem(PAINT_KEY)
4522
+ if (typeof raw === 'string' && raw !== '') parsed = JSON.parse(raw)
4523
+ } catch (error) { void error; return }
4524
+ if (parsed === null || typeof parsed !== 'object') return
4525
+ /* Entries already in memory stay untouched: a write that happened before
4526
+ the first read is always fresher than what the storage holds. */
4527
+ const feed = (map, stored) => {
4528
+ if (stored === null || typeof stored !== 'object') return
4529
+ let count = 0
4530
+ for (const key of Object.keys(stored)) {
4531
+ const entry = stored[key]
4532
+ if (map.has(key) || entry === null || typeof entry !== 'object' || typeof entry.at !== 'number') continue
4533
+ if (count >= 16) break
4534
+ map.set(key, entry)
4535
+ count += 1
4536
+ }
4537
+ }
4538
+ feed(paintRepos, parsed.repos)
4539
+ feed(paintPage, parsed.page)
4540
+ }
4541
+
4542
+ function persistPaint() {
4543
+ try {
4544
+ const pack = (map) => {
4545
+ const out = {}
4546
+ for (const [key, entry] of map) out[key] = entry
4547
+ return out
4548
+ }
4549
+ window.sessionStorage.setItem(PAINT_KEY, JSON.stringify({ repos: pack(paintRepos), page: pack(paintPage) }))
4550
+ } catch (error) { void error }
4551
+ }
4552
+
4259
4553
  function readPaintRepos(cwd) {
4554
+ hydratePaint()
4260
4555
  const entry = paintRepos.get(cwd)
4261
- if (entry === undefined || Date.now() - entry.at > PAINT_TTL_MS) return undefined
4556
+ if (entry === undefined || Date.now() - entry.at > PAINT_REPOS_TTL_MS) return undefined
4262
4557
  return entry.data
4263
4558
  }
4264
4559
 
4265
4560
  function writePaintRepos(cwd, data) {
4266
4561
  paintRepos.set(cwd, { at: Date.now(), data: data })
4267
4562
  if (paintRepos.size > 16) paintRepos.delete(paintRepos.keys().next().value)
4563
+ persistPaint()
4268
4564
  }
4269
4565
 
4270
4566
  function readPaintPage(repoRoot) {
4567
+ hydratePaint()
4271
4568
  const entry = paintPage.get(repoRoot)
4272
- if (entry === undefined || Date.now() - entry.at > PAINT_TTL_MS) return undefined
4569
+ if (entry === undefined || Date.now() - entry.at > PAINT_PAGE_TTL_MS) return undefined
4273
4570
  return entry
4274
4571
  }
4275
4572
 
4276
4573
  function writePaintPage(repoRoot, page) {
4277
4574
  paintPage.set(repoRoot, Object.assign({ at: Date.now() }, page))
4278
4575
  if (paintPage.size > 16) paintPage.delete(paintPage.keys().next().value)
4576
+ persistPaint()
4279
4577
  }
4280
4578
 
4281
4579
  function rememberPaintRoot(cwd, repoRoot) {
@@ -4701,12 +4999,19 @@ window.__ModuleLoader__.load({
4701
4999
  is stored is a RATIO of the measured container: resizing the dock or the
4702
5000
  window rescales the panes instead of breaking the layout. */
4703
5001
  const PANES_KEY = 'dsh-ide-git.panes.v1'
5002
+ /* Every chrome can now move all three panes (the detail diff window used to
5003
+ be a fixed max-height:55% in the columns chrome — user report: it could
5004
+ not be resized). The DIFF pane is special: it is carved out of the middle
5005
+ pane, so it is sized by HEIGHT in every chrome, even where the other
5006
+ panes measure width (columns). */
4704
5007
  const PANE_CHROME_KEYS = {
4705
- columns: ['tree', 'changes'],
5008
+ columns: ['tree', 'changes', 'diff'],
4706
5009
  stack: ['tree', 'changes', 'diff'],
4707
5010
  compact: ['tree', 'diff'],
4708
5011
  }
4709
- /* Whether a key measures a height (stacked chromes) or a width (columns). */
5012
+ /* Whether a key measures a height (stacked chromes) or a width (columns).
5013
+ This is the chrome's DEFAULT for the tree/changes panes; the diff pane
5014
+ overrides it to height unconditionally (see paneGeometry). */
4710
5015
  const PANE_HEIGHT_CHROME = { columns: false, stack: true, compact: true }
4711
5016
  /* The pane a separator resizes, and the key its label is translated from. */
4712
5017
  const PANE_NAME_KEYS = { tree: 'toolbar.tree', changes: 'changes.title', diff: 'pane.diff' }
@@ -4715,6 +5020,7 @@ window.__ModuleLoader__.load({
4715
5020
  const PANE_LIMITS = {
4716
5021
  'columns:tree': { min: 140, max: 560 },
4717
5022
  'columns:changes': { min: 200, max: 640 },
5023
+ 'columns:diff': { min: 120, max: 900 },
4718
5024
  'stack:tree': { min: 100, max: 420 },
4719
5025
  'stack:changes': { min: 120, max: 460 },
4720
5026
  'stack:diff': { min: 120, max: 900 },
@@ -4723,10 +5029,12 @@ window.__ModuleLoader__.load({
4723
5029
  }
4724
5030
  /* Today's fixed sizes stay the default — and stay in the stylesheet: a pane
4725
5031
  the user never dragged keeps exactly the look it had before, while a
4726
- dragged one is remembered as a ratio. */
5032
+ dragged one is remembered as a ratio. The diff's 0.55 IS the stylesheet's
5033
+ max-height:55%, so an undragged window renders identically to before. */
4727
5034
  const PANE_DEFAULTS = {
4728
5035
  'columns:tree': { px: 200 },
4729
5036
  'columns:changes': { px: 290 },
5037
+ 'columns:diff': { ratio: 0.55 },
4730
5038
  'stack:tree': { ratio: 0.36 },
4731
5039
  'stack:changes': { ratio: 0.46 },
4732
5040
  'compact:tree': { ratio: 0.42 },
@@ -4829,9 +5137,14 @@ window.__ModuleLoader__.load({
4829
5137
  const effective = {}
4830
5138
  const limits = {}
4831
5139
  const sizeOf = (key) => (effective[key] === undefined ? 0 : effective[key])
5140
+ /* The diff is carved out of the middle pane, so it measures HEIGHT in
5141
+ every chrome — even in columns, where tree/changes run sideways and
5142
+ the chrome's own dimension is width. Its ratios therefore divide the
5143
+ body HEIGHT, whatever `reference` says. */
5144
+ const referenceOf = (key) => (key === 'diff' ? box.height : reference)
4832
5145
  for (const key of keys) {
4833
5146
  const spec = PANE_DEFAULTS[chrome + ':' + key]
4834
- const fallback = spec === undefined ? 0 : (spec.px === undefined ? spec.ratio * reference : spec.px)
5147
+ const fallback = spec === undefined ? 0 : (spec.px === undefined ? spec.ratio * referenceOf(key) : spec.px)
4835
5148
  const live = measured[key]
4836
5149
  effective[key] = typeof live === 'number' && Number.isFinite(live) && live > 0 ? live : fallback
4837
5150
  }
@@ -4846,7 +5159,14 @@ window.__ModuleLoader__.load({
4846
5159
  const current = sizeOf(key)
4847
5160
  let ceiling = spec.max
4848
5161
  if (key === 'diff') {
4849
- const main = space - sizeOf('changes') - sizeOf('tree') - PANE_GUTTER_PX
5162
+ /* The diff lives INSIDE the main pane, so its height budget is the
5163
+ main pane's height: the full body height in the columns chrome
5164
+ (the other panes run sideways there and share none of it), the
5165
+ body height minus the stacked siblings otherwise. Either way the
5166
+ history list keeps its own minimum. */
5167
+ const main = height
5168
+ ? space - sizeOf('changes') - sizeOf('tree') - PANE_GUTTER_PX
5169
+ : box.height
4850
5170
  ceiling = Math.min(ceiling, main - PANE_HISTORY_MIN_H)
4851
5171
  } else {
4852
5172
  let taken = 0
@@ -4868,7 +5188,7 @@ window.__ModuleLoader__.load({
4868
5188
  limits[key] = { min: spec.min, max: high }
4869
5189
  const ratio = ratios[key]
4870
5190
  if (typeof ratio === 'number' && Number.isFinite(ratio)) {
4871
- const px = Math.round(Math.min(Math.max(ratio * reference, spec.min), high))
5191
+ const px = Math.round(Math.min(Math.max(ratio * referenceOf(key), spec.min), high))
4872
5192
  overrides[key] = px
4873
5193
  effective[key] = px
4874
5194
  }
@@ -5126,6 +5446,120 @@ window.__ModuleLoader__.load({
5126
5446
  return E('svg', { className: 'dig-graph', width: width, height: height, viewBox: '0 0 ' + width + ' ' + height }, children)
5127
5447
  }
5128
5448
 
5449
+ /* ============================== composer send ============================== */
5450
+ /* "Send to chat": drop a reference chip / short text into the CURRENT
5451
+ session's composer, the way typing `@file` there would. The host channel
5452
+ is ui-conversation's SessionInput facade (research brief 2026-10-07):
5453
+ `ctx.get('conversation').input.for(ctx.get('sessions').scope(id))`. Every
5454
+ hop is probed — the services may be absent (a trimmed host) and `for`
5455
+ THROWS on a scope without a retained session generation — so a miss
5456
+ degrades to a clipboard copy with a toast, never a dead click.
5457
+
5458
+ Two spellings:
5459
+ - file/folder → insertReference(): a structured Lexical chip whose model
5460
+ serialization is the plain `@path` text (the same thing the @ menu
5461
+ inserts; source 'reference', appearance 'file'/'folder').
5462
+ - text (short hash, branch name, raw path) → the scoped
5463
+ `slash/input-insert-text` bail event, the machine entrance of the same
5464
+ pipeline. NOT `inputTriggers...openReference` — that one opens a
5465
+ preview and never touches the draft. */
5466
+
5467
+ /* `formatFileMention` (@deepseek-ai/dsh-file-reference/grammar) without the
5468
+ require: that package is not on the seed table, so the plugin inlines the
5469
+ grammar — trailing `/` for a directory, `@"path"` when whitespace is
5470
+ present (a quoted directory keeps its quote open), and `null` for paths
5471
+ the editor grammar cannot represent (control characters, quotes). */
5472
+ function fileMentionOf(relPath) {
5473
+ const isDir = relPath.slice(-1) === '/'
5474
+ if (/[\u0000-\u001f\u007f-\u009f"]/u.test(relPath) === true) return null
5475
+ if (/\s/u.test(relPath) === false) return '@' + relPath
5476
+ return isDir === true ? '@"' + relPath : '@"' + relPath + '"'
5477
+ }
5478
+
5479
+ /* Resolve { actx, input } for the current session, or null. `input.for`
5480
+ requires a RETAINED Session scope and throws otherwise — that is part of
5481
+ its contract (contract/input.ts), so the whole probe rides a try/catch. */
5482
+ function composerTargetOf(ctx, sessionId) {
5483
+ try {
5484
+ if (ctx === undefined || ctx === null || ctx.get === undefined) return null
5485
+ if (typeof sessionId !== 'string' || sessionId === '' || sessionId === 'default') return null
5486
+ const sessions = ctx.get('sessions')
5487
+ const conversation = ctx.get('conversation')
5488
+ if (sessions === undefined || sessions === null || conversation === undefined || conversation === null) return null
5489
+ if (typeof sessions.scope !== 'function' || conversation.input === undefined || conversation.input === null) return null
5490
+ const actx = sessions.scope(sessionId)
5491
+ if (actx === undefined || actx === null) return null
5492
+ const input = conversation.input.for(actx)
5493
+ if (input === undefined || input === null) return null
5494
+ return { actx: actx, input: input }
5495
+ } catch (error) { void error; return null }
5496
+ }
5497
+
5498
+ /* apply(actx, input, span) → boolean. The span is an EMPTY span at the draft
5499
+ tail in detect coordinates (state.draft is exactly the projection the CAS
5500
+ compares). A false answer is the user typing between our read and write —
5501
+ re-read once and retry (the brief's advice: one retry, no spinning). The
5502
+ busy phases (adjudicating/submitting) also answer false; that lands on the
5503
+ same clipboard fallback, which is the honest outcome. */
5504
+ function insertIntoComposer(ctx, sessionId, apply) {
5505
+ const target = composerTargetOf(ctx, sessionId)
5506
+ if (target === null) return false
5507
+ for (let attempt = 0; attempt < 2; attempt += 1) {
5508
+ let state
5509
+ try { state = target.input.state.getSnapshot() } catch (error) { void error; return false }
5510
+ if (state === undefined || state === null || typeof state.draft !== 'string' || typeof state.draftRev !== 'number') return false
5511
+ const span = { start: state.draft.length, end: state.draft.length, draftRev: state.draftRev }
5512
+ let applied = false
5513
+ try { applied = apply(target.actx, target.input, span) === true } catch (error) { void error; applied = false }
5514
+ if (applied === true) {
5515
+ try { target.input.focus() } catch (error) { void error }
5516
+ return true
5517
+ }
5518
+ }
5519
+ return false
5520
+ }
5521
+
5522
+ /* One single-file unified diff (what the host `diff` method returns) → the
5523
+ one DiffHunk DiffBlock renders, by replaying ` `/`-`/`+` lines back into
5524
+ the two sides. No `@@` at all (binary patches, mode-only changes) or a
5525
+ pathless header → null, and the caller keeps the hand-drawn rows. */
5526
+ function patchToHunk(patch) {
5527
+ const text = typeof patch === 'string' ? patch : ''
5528
+ if (text === '' || text.indexOf('@@') < 0) return null
5529
+ const lines = text.split('\n')
5530
+ let path = null
5531
+ const oldText = []
5532
+ const newText = []
5533
+ for (const line of lines) {
5534
+ if (line.indexOf('+++ ') === 0) {
5535
+ const name = line.slice(4).split('\t')[0]
5536
+ if (name !== '/dev/null') path = name.replace(/^b\//, '')
5537
+ continue
5538
+ }
5539
+ if (line.indexOf('--- ') === 0) {
5540
+ if (path === null) {
5541
+ const name = line.slice(4).split('\t')[0]
5542
+ if (name !== '/dev/null') path = name.replace(/^a\//, '')
5543
+ }
5544
+ continue
5545
+ }
5546
+ if (line.indexOf('@@') === 0) continue
5547
+ if (line.indexOf('+') === 0) { newText.push(line.slice(1)); continue }
5548
+ if (line.indexOf('-') === 0) { oldText.push(line.slice(1)); continue }
5549
+ if (line.indexOf('\\') === 0) continue
5550
+ if (line.indexOf(' ') === 0) { oldText.push(line.slice(1)); newText.push(line.slice(1)); continue }
5551
+ // diff --git / index / new file mode / rename from ...: metadata above
5552
+ // the first hunk; anything else after it would be malformed.
5553
+ continue
5554
+ }
5555
+ if (path === null || path === '') return null
5556
+ // A patch without body lines (mode-only, bare metadata) replays into an
5557
+ // empty hunk that DiffBlock draws as nothing at all — keep the hand-drawn
5558
+ // rows for those, they at least show what happened.
5559
+ if (oldText.length === 0 && newText.length === 0) return null
5560
+ return { path: path, oldText: oldText.length === 0 ? null : oldText.join('\n'), newText: newText.join('\n') }
5561
+ }
5562
+
5129
5563
  /* ============================== diff ============================== */
5130
5564
 
5131
5565
  function diffLines(patch) {
@@ -5156,11 +5590,47 @@ window.__ModuleLoader__.load({
5156
5590
  return out
5157
5591
  }
5158
5592
 
5593
+ /* DiffBlock's own 16-line cap would REPLACE scrolling with a fold button;
5594
+ this panel keeps the old contract (render everything, scroll inside the
5595
+ .dig-diff-pane max-height), so the cap is pushed out of reach and the
5596
+ fold labels below are placeholders that never render at this size. */
5597
+ const DIFF_BLOCK_MAX_LINES = 1000000
5598
+
5599
+ function diffLabelsOf(t) {
5600
+ return {
5601
+ codeLabel: t('diff.codeLabel'),
5602
+ wrapLabel: t('diff.wrapLabel'),
5603
+ unwrapLabel: t('diff.unwrapLabel'),
5604
+ copy: t('diff.copy'),
5605
+ copied: t('diff.copied'),
5606
+ collapseAria: 'Collapse diff',
5607
+ expandAria: (hidden) => 'Show ' + String(hidden) + ' more diff lines',
5608
+ collapse: 'Show less',
5609
+ expand: (hidden) => 'Show ' + String(hidden) + ' more',
5610
+ }
5611
+ }
5612
+
5159
5613
  function DiffBody(props) {
5160
5614
  const rows = useMemo(() => diffLines(props.patch), [props.patch])
5161
5615
  if (props.loading === true) return E('div', { className: 'dig-empty' }, props.t('diff.loading'))
5162
5616
  if (props.binary === true) return E('div', { className: 'dig-empty' }, props.t('diff.binary'))
5163
5617
  if (rows.length === 0) return E('div', { className: 'dig-empty' }, props.t('diff.empty'))
5618
+ /* Host-rendered first (v0.14): the ui-primitives diff card brings the
5619
+ shared toolbar (copy / wrap / language) and the product's diff colors.
5620
+ A patch we cannot replay into a hunk (binary, mode-only) or a host
5621
+ without the seed module falls back to the hand-drawn rows below. */
5622
+ if (UIPrimitives !== null && typeof UIPrimitives.DiffBlock === 'function') {
5623
+ const hunk = patchToHunk(props.patch)
5624
+ if (hunk !== null) {
5625
+ return E('div', { className: 'dig-diff-host' },
5626
+ E(UIPrimitives.DiffBlock, {
5627
+ diffs: [hunk],
5628
+ labels: diffLabelsOf(props.t),
5629
+ maxLines: DIFF_BLOCK_MAX_LINES,
5630
+ className: 'dig-diff-card',
5631
+ }))
5632
+ }
5633
+ }
5164
5634
  return E('div', { className: 'dig-diff' }, rows.map((row, index) => E('div', { key: index, className: 'dig-diff-line dig-diff-' + row.kind },
5165
5635
  E('span', { className: 'dig-diff-gutter' }, row.oldLine === null ? '' : String(row.oldLine)),
5166
5636
  E('span', { className: 'dig-diff-gutter' }, row.newLine === null ? '' : String(row.newLine)),
@@ -5168,6 +5638,22 @@ window.__ModuleLoader__.load({
5168
5638
  )))
5169
5639
  }
5170
5640
 
5641
+ /* The leading glyph of a change row (v0.14): the host's FileTypeIcon —
5642
+ classifyFileType runs inside when given a path. Directories (the
5643
+ trailing slash an `ls-files --directory` row carries) ask for the
5644
+ folder kind explicitly, since their basename is empty. A host without
5645
+ the seed module returns null and the row keeps its status letter as the
5646
+ only leading mark. Sizing follows the status letter's box (14px, the
5647
+ .dig-row line height). */
5648
+ function FileTypeGlyph(props) {
5649
+ if (UIPrimitives === null || typeof UIPrimitives.FileTypeIcon !== 'function') return null
5650
+ const path = typeof props.path === 'string' ? props.path : ''
5651
+ if (path === '') return null
5652
+ return E(UIPrimitives.FileTypeIcon, path.slice(-1) === '/'
5653
+ ? { kind: 'folder', size: 14, className: 'dig-file-icon' }
5654
+ : { path: path, size: 14, className: 'dig-file-icon' })
5655
+ }
5656
+
5171
5657
  /* ============================== ui kit ============================== */
5172
5658
 
5173
5659
  const ICONS = {
@@ -5213,6 +5699,9 @@ window.__ModuleLoader__.load({
5213
5699
  split: ['M2.5 3h11v10h-11z', 'M8 3v10'],
5214
5700
  expand: ['M4.5 6.2 8 9.7l3.5-3.5', 'M3 12.4h10'],
5215
5701
  collapse: ['M4.5 9.8 8 6.3l3.5 3.5', 'M3 3.6h10'],
5702
+ // "Send to chat": a paper plane — the shared glyph of the three context
5703
+ // menus that drop a reference / hash / branch name into the composer.
5704
+ send: ['M14.3 1.7 1.7 7.4l4.9 1.9 1.9 4.9L14.3 1.7Z', 'M6.6 9.3 14.3 1.7'],
5216
5705
  }
5217
5706
 
5218
5707
  function Icon(props) {
@@ -6174,6 +6663,7 @@ window.__ModuleLoader__.load({
6174
6663
  onClick: () => props.onDiff(item, props.group),
6175
6664
  onContextMenu: (event) => { event.preventDefault(); props.onMenu(event, item, props.group) },
6176
6665
  },
6666
+ FileTypeGlyph({ path: item.path }),
6177
6667
  E('span', { className: 'dig-file-status dig-file-status-' + (status === '?' ? 'U' : status) }, status),
6178
6668
  // An ignored DIRECTORY comes back as "dist/" (ls-files --directory), whose
6179
6669
  // basename is empty — show the whole path instead of a blank label.
@@ -6617,7 +7107,9 @@ window.__ModuleLoader__.load({
6617
7107
  className: 'dig-row dig-row-file' + (props.selectedPath === file.path ? ' dig-row-selected' : ''),
6618
7108
  title: file.path,
6619
7109
  onClick: () => props.onSelectFile(file),
7110
+ onContextMenu: props.onFileMenu === undefined ? undefined : (event) => { event.preventDefault(); props.onFileMenu(event, file) },
6620
7111
  },
7112
+ FileTypeGlyph({ path: file.path }),
6621
7113
  E('span', { className: 'dig-row-label' }, baseName(file.path)),
6622
7114
  E('span', { className: 'dig-row-sub dig-row-dir' }, dirName(file.path)),
6623
7115
  file.additions > 0 ? E('span', { className: 'dig-stat-add' }, '+' + file.additions) : null,
@@ -6767,7 +7259,10 @@ window.__ModuleLoader__.load({
6767
7259
  if (element === null) return found
6768
7260
  for (const node of element.querySelectorAll('[data-pane]')) {
6769
7261
  const rect = node.getBoundingClientRect()
6770
- found[node.getAttribute('data-pane')] = Math.round(paneByWidth ? rect.width : rect.height)
7262
+ // The diff pane is measured in HEIGHT under every chrome — it is
7263
+ // carved out of the middle pane, whose siblings may run sideways.
7264
+ const key = node.getAttribute('data-pane')
7265
+ found[key] = Math.round(key === 'diff' || paneByWidth === false ? rect.height : rect.width)
6771
7266
  }
6772
7267
  return found
6773
7268
  }
@@ -6804,7 +7299,8 @@ window.__ModuleLoader__.load({
6804
7299
  setPaneSizes((current) => (samePaneSizes(current, live) ? current : live))
6805
7300
  const layout = paneGeometry(chrome, paneBox, panesRef.current, live)
6806
7301
  const limit = layout.limits[key]
6807
- const reference = paneByWidth ? paneBox.width : paneBox.height
7302
+ // The diff divides the body HEIGHT in every chrome (see paneGeometry).
7303
+ const reference = key === 'diff' ? paneBox.height : (paneByWidth ? paneBox.width : paneBox.height)
6808
7304
  if (limit === undefined || !(reference > 0)) return
6809
7305
  const px = Math.round(Math.min(Math.max(rawPx, limit.min), limit.max))
6810
7306
  commitPanes(withPaneRatio(panesRef.current, layout.bucket, key, px / reference))
@@ -6834,7 +7330,9 @@ window.__ModuleLoader__.load({
6834
7330
  const paneStyle = (key) => {
6835
7331
  const px = paneLayout.overrides[key]
6836
7332
  if (px === undefined) return null
6837
- return paneByWidth ? { width: px + 'px', maxWidth: px + 'px' } : { height: px + 'px', maxHeight: px + 'px' }
7333
+ // The diff always takes its size as a height, in every chrome.
7334
+ const vertical = key === 'diff' ? true : paneByWidth === false
7335
+ return vertical ? { height: px + 'px', maxHeight: px + 'px' } : { width: px + 'px', maxWidth: px + 'px' }
6838
7336
  }
6839
7337
 
6840
7338
  /* A divider is a control, so it only exists when it can actually move
@@ -6848,7 +7346,10 @@ window.__ModuleLoader__.load({
6848
7346
  key: 'gutter-' + key,
6849
7347
  t: t,
6850
7348
  nameKey: PANE_NAME_KEYS[key],
6851
- vertical: paneByWidth,
7349
+ // The diff divider is a horizontal bar in every chrome: it moves a
7350
+ // window that lives BELOW it, even where tree/changes are dragged
7351
+ // sideways (columns).
7352
+ vertical: key === 'diff' ? false : paneByWidth,
6852
7353
  side: side,
6853
7354
  active: paneDragging === key,
6854
7355
  // Now is clamped into [min, max]: an aria window the panel contradicts is
@@ -7086,6 +7587,86 @@ window.__ModuleLoader__.load({
7086
7587
  })
7087
7588
  }, [])
7088
7589
 
7590
+ /* ---------- toast plumbing ---------- */
7591
+ /* Declared BEFORE the context menus on purpose: the send-to-chat bindings
7592
+ and every menu builder close over pushToast, and a hooks dependency
7593
+ array is evaluated during render (invariant 12) — the plumbing has to
7594
+ exist by the time those arrays are evaluated. */
7595
+ const toastTimers = useRef([])
7596
+ const toastSeq = useRef(0)
7597
+
7598
+ useEffect(() => () => {
7599
+ for (const timer of toastTimers.current) clearTimeout(timer)
7600
+ toastTimers.current = []
7601
+ }, [])
7602
+
7603
+ const dropToast = useCallback((id) => {
7604
+ setToasts((list) => list.filter((item) => item.id !== id))
7605
+ }, [])
7606
+
7607
+ const pushToast = useCallback((toast) => {
7608
+ toastSeq.current += 1
7609
+ const id = 'toast-' + String(toastSeq.current)
7610
+ setToasts((list) => list.concat([Object.assign({ id: id, onClose: () => dropToast(id) }, toast)]))
7611
+ const ttl = toast.ttl === undefined ? (toast.actionLabel === undefined ? 9000 : 30000) : toast.ttl
7612
+ if (ttl > 0) toastTimers.current.push(setTimeout(() => dropToast(id), ttl))
7613
+ return id
7614
+ }, [dropToast])
7615
+
7616
+ /* Send-to-chat (v0.14): one funnel for the three context menus. The
7617
+ insertion target is the CURRENT session's composer (scope.sessionId);
7618
+ a miss — service absent, no retained session, phase busy, CAS raced
7619
+ twice — still gives feedback: the payload is copied to the clipboard
7620
+ and a toast says so. */
7621
+ const sendToComposer = useCallback((payload) => {
7622
+ const applied = insertIntoComposer(props.ctx, sessionId, (actx, input, span) => {
7623
+ if (payload.appearance !== undefined) {
7624
+ return input.insertReference({
7625
+ source: 'reference',
7626
+ ref: payload.mention,
7627
+ label: payload.label,
7628
+ appearance: payload.appearance,
7629
+ clipboardText: payload.mention,
7630
+ }, span)
7631
+ }
7632
+ // Plain text rides the scoped insert-text event (bail value IS the
7633
+ // answer); a trailing space keeps the next keystroke off the token.
7634
+ return actx.bail(actx, 'slash/input-insert-text', { text: payload.text + ' ', span: span }) === true
7635
+ })
7636
+ if (applied === true) {
7637
+ pushToast({ text: t('toast.sentToChat'), icon: 'send', tone: 'ok', ttl: 6000 })
7638
+ return
7639
+ }
7640
+ copyText(payload.fallback)
7641
+ pushToast({ text: t('toast.sendCopied'), icon: 'tag', tone: 'warn', ttl: 6000 })
7642
+ }, [props.ctx, sessionId, pushToast, t])
7643
+
7644
+ const sendFileReferenceToChat = useCallback((path) => {
7645
+ const mention = fileMentionOf(path)
7646
+ if (mention === null) { sendToComposer({ text: path, fallback: path }); return }
7647
+ sendToComposer({
7648
+ mention: mention,
7649
+ label: path.slice(-1) === '/' ? path.slice(0, -1) : baseName(path),
7650
+ appearance: path.slice(-1) === '/' ? 'folder' : 'file',
7651
+ fallback: path,
7652
+ })
7653
+ }, [sendToComposer])
7654
+
7655
+ /* Self-describing text payloads for the commit/branch sends (v0.13.3 user
7656
+ feedback): a bare short hash or branch name is noise out of context —
7657
+ the text must say WHAT it is and WHERE from. The wording rides the
7658
+ fill() templates send.commitText / send.branchText; `repo` is the repo
7659
+ root's basename. An unknown root degrades to a neutral dash — fill()
7660
+ leaves unknown keys verbatim, so the callers must never let an
7661
+ undefined repo reach the template. The file @-chip is already
7662
+ self-describing and stays untouched. */
7663
+ const repoLabel = repoRoot === null ? '' : baseName(repoRoot)
7664
+ const sendGitSummaryToChat = useCallback((key, values) => {
7665
+ const repo = values.repo === undefined || values.repo === '' ? '—' : values.repo
7666
+ const text = fill(t(key), Object.assign({}, values, { repo: repo }))
7667
+ sendToComposer({ text: text, fallback: text })
7668
+ }, [t, sendToComposer])
7669
+
7089
7670
  const branchMenu = useCallback((event, entry) => {
7090
7671
  const current = branches === null ? '' : branches.branch
7091
7672
  const isLocal = entry.remote !== true && entry.tag !== true
@@ -7095,6 +7676,7 @@ window.__ModuleLoader__.load({
7095
7676
  isLocal ? { id: 'rebase', icon: 'compare', tone: 'violet', label: t('action.rebaseCurrentOnto'), disabled: entry.head === true || blocked, reason: blocked ? t('operation.hint') : reason, run: () => { void run('rebase', { onto: entry.name }) } } : null,
7096
7677
  isLocal ? { id: 'merge', icon: 'compare', tone: 'accent', label: t('action.mergeIntoCurrent'), disabled: entry.head === true || blocked, reason: blocked ? t('operation.hint') : reason, run: () => { void run('merge', { branch: entry.name }) } } : null,
7097
7678
  { id: 'compare', icon: 'filter', tone: 'violet', label: t('action.compare'), disabled: entry.head === true || current === '', reason: reason, run: () => { void compareWith(current, entry.name) } },
7679
+ { id: 'send', icon: 'send', tone: 'accent', label: t('menu.sendToChat'), run: () => sendGitSummaryToChat('send.branchText', { name: entry.name, repo: repoLabel }) },
7098
7680
  null,
7099
7681
  { id: 'favorite', icon: 'star', tone: 'warn', label: t('action.favorite'), active: favorites.indexOf(entry.name) >= 0, run: () => toggleFavoriteBranch(entry.name) },
7100
7682
  { id: 'newBranch', icon: 'plus', tone: 'success', label: t('action.newBranchFrom'), run: () => setDialog({ kind: 'newBranch', from: entry.name }) },
@@ -7105,12 +7687,20 @@ window.__ModuleLoader__.load({
7105
7687
  { id: 'push', icon: 'push', tone: 'success', label: t('action.push'), disabled: blocked, reason: t('operation.hint'), run: () => setDialog({ kind: 'push' }) },
7106
7688
  ].filter((item) => item !== null)
7107
7689
  openMenuAt(event, items)
7108
- }, [branches, checkout, run, t, compareWith, toggleFavoriteBranch, openMenuAt, favorites, blocked])
7690
+ }, [branches, checkout, run, t, compareWith, toggleFavoriteBranch, openMenuAt, favorites, blocked, sendGitSummaryToChat, repoLabel])
7109
7691
 
7110
7692
  const commitMenu = useCallback((event, commit) => {
7693
+ const shortHash = commit.hash.slice(0, 7)
7111
7694
  const items = [
7112
7695
  { id: 'details', icon: 'file', tone: 'primary', label: t('action.details'), run: () => { void selectCommit(commit) } },
7113
7696
  { id: 'copy', icon: 'tag', tone: 'secondary', label: t('action.copyHash'), run: () => { copyText(commit.hash) } },
7697
+ { id: 'send', icon: 'send', tone: 'accent', label: t('menu.sendToChat'), run: () => {
7698
+ // A commit whose subject is empty (git tolerates empty messages)
7699
+ // degrades to the old bare-hash payload instead of faking one.
7700
+ const subject = typeof commit.subject === 'string' ? commit.subject.trim() : ''
7701
+ if (subject === '') { sendToComposer({ text: shortHash, fallback: shortHash }); return }
7702
+ sendGitSummaryToChat('send.commitText', { hash: shortHash, subject: subject, repo: repoLabel })
7703
+ } },
7114
7704
  null,
7115
7705
  { id: 'checkout', icon: 'checkout', tone: 'accent', label: t('action.checkout'), run: () => setDialog({ kind: 'checkoutCommit', hash: commit.hash }) },
7116
7706
  { id: 'branch', icon: 'plus', tone: 'success', label: t('action.newBranchHere'), run: () => setDialog({ kind: 'newBranch', from: commit.hash }) },
@@ -7123,11 +7713,13 @@ window.__ModuleLoader__.load({
7123
7713
  { id: 'resetHard', icon: 'trash', tone: 'danger', label: t('action.resetHard'), danger: true, disabled: blocked, reason: t('operation.hint'), run: () => setDialog({ kind: 'reset', hash: commit.hash, mode: 'hard' }) },
7124
7714
  ]
7125
7715
  openMenuAt(event, items)
7126
- }, [run, selectCommit, t, openMenuAt, blocked])
7716
+ }, [run, selectCommit, t, openMenuAt, blocked, sendToComposer, sendGitSummaryToChat, repoLabel])
7127
7717
 
7128
7718
  const changeMenu = useCallback((event, item, group) => {
7129
7719
  const items = [
7130
7720
  { id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => { void openDiff({ path: item.path, staged: group === 'staged' }) } },
7721
+ { id: 'sendRef', icon: 'send', tone: 'accent', label: t('menu.sendToChat'), run: () => sendFileReferenceToChat(item.path) },
7722
+ { id: 'sendPath', icon: 'file', tone: 'secondary', label: t('menu.sendFilePathToChat'), run: () => sendToComposer({ text: item.path, fallback: item.path }) },
7131
7723
  null,
7132
7724
  group === 'staged'
7133
7725
  ? { id: 'unstage', icon: 'minus', tone: 'warn', label: t('action.unstage'), run: () => { void run('unstage', { paths: [item.path] }) } }
@@ -7137,7 +7729,20 @@ window.__ModuleLoader__.load({
7137
7729
  { id: 'copy', icon: 'tag', tone: 'secondary', label: t('action.copyPath'), run: () => { copyText(item.path) } },
7138
7730
  ]
7139
7731
  openMenuAt(event, items)
7140
- }, [openDiff, run, t, openMenuAt])
7732
+ }, [openDiff, run, t, openMenuAt, sendFileReferenceToChat, sendToComposer])
7733
+
7734
+ /* The commit-detail file list shares the working-tree row menu, minus the
7735
+ staging verbs: a committed file's diff replays against its commit. */
7736
+ const detailFileMenu = useCallback((event, file) => {
7737
+ openMenuAt(event, [
7738
+ { id: 'diff', icon: 'file', tone: 'primary', label: t('action.showDiff'), run: () => { setSelectedPath(file.path); void openDiff({ hash: detail === null ? undefined : detail.hash, path: file.path }) } },
7739
+ null,
7740
+ { id: 'sendRef', icon: 'send', tone: 'accent', label: t('menu.sendToChat'), run: () => sendFileReferenceToChat(file.path) },
7741
+ { id: 'sendPath', icon: 'file', tone: 'secondary', label: t('menu.sendFilePathToChat'), run: () => sendToComposer({ text: file.path, fallback: file.path }) },
7742
+ null,
7743
+ { id: 'copy', icon: 'tag', tone: 'secondary', label: t('action.copyPath'), run: () => { copyText(file.path) } },
7744
+ ])
7745
+ }, [detail, openDiff, openMenuAt, sendFileReferenceToChat, sendToComposer, t])
7141
7746
 
7142
7747
  const submitDialog = useCallback(async (state, value) => {
7143
7748
  setDialog(null)
@@ -7148,27 +7753,6 @@ window.__ModuleLoader__.load({
7148
7753
 
7149
7754
  /* ---------- toasts + undo ---------- */
7150
7755
 
7151
- const toastTimers = useRef([])
7152
- const toastSeq = useRef(0)
7153
-
7154
- useEffect(() => () => {
7155
- for (const timer of toastTimers.current) clearTimeout(timer)
7156
- toastTimers.current = []
7157
- }, [])
7158
-
7159
- const dropToast = useCallback((id) => {
7160
- setToasts((list) => list.filter((item) => item.id !== id))
7161
- }, [])
7162
-
7163
- const pushToast = useCallback((toast) => {
7164
- toastSeq.current += 1
7165
- const id = 'toast-' + String(toastSeq.current)
7166
- setToasts((list) => list.concat([Object.assign({ id: id, onClose: () => dropToast(id) }, toast)]))
7167
- const ttl = toast.ttl === undefined ? (toast.actionLabel === undefined ? 9000 : 30000) : toast.ttl
7168
- if (ttl > 0) toastTimers.current.push(setTimeout(() => dropToast(id), ttl))
7169
- return id
7170
- }, [dropToast])
7171
-
7172
7756
  // The delete really happened; the host kept an object id for a while, and this
7173
7757
  // asks it to recreate the branch / stash entry from that id.
7174
7758
  const undoAction = useCallback(async (id) => {
@@ -7343,6 +7927,7 @@ window.__ModuleLoader__.load({
7343
7927
  setSelectedPath(file.path)
7344
7928
  void openDiff({ hash: detail === null ? undefined : detail.hash, path: file.path })
7345
7929
  },
7930
+ onFileMenu: detailFileMenu,
7346
7931
  })
7347
7932
  : E(HistoryList, {
7348
7933
  t: t, commits: commits, hasMore: hasMore, busy: busy, selectedHash: selectedHash,
@@ -8046,6 +8631,14 @@ window.__ModuleLoader__.load({
8046
8631
  '.dig-diff-del{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 16%, transparent)}',
8047
8632
  '.dig-diff-hunk{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)}',
8048
8633
  '.dig-diff-meta{color:var(--dsw-alias-label-tertiary)}',
8634
+ /* Host-rendered diff (ui-primitives DiffBlock): the host box keeps the
8635
+ pane's scroll contract (everything renders, the pane scrolls), and the
8636
+ card's own 16px vertical margin is tightened to the panel rhythm. */
8637
+ '.dig-diff-host{flex:1;overflow:auto;min-height:0}',
8638
+ '.dig-diff-card{margin:4px 6px 8px;font-size:12px}',
8639
+ /* File-type glyphs lead every change row (v0.14); a transparent-theme
8640
+ host still tints them through the component's own category colors. */
8641
+ '.dig-file-icon{flex:none;opacity:.95}',
8049
8642
  '.dig-overlay{position:absolute;inset:0;padding:8px;box-sizing:border-box;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.42));-webkit-backdrop-filter:var(--dsw-mask-blur,none);backdrop-filter:var(--dsw-mask-blur,none);display:flex;align-items:center;justify-content:center;z-index:60}',
8050
8643
  '.dig-dialog{box-sizing:border-box;min-width:min(240px,100%);max-width:min(420px,94%);max-height:100%;overflow:auto;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));border:0;border-radius:var(--dsw-radius-panel,28px);padding:14px;display:flex;flex-direction:column;gap:10px;box-shadow:var(--dsw-elevation-prominent,0 12px 32px rgba(0,0,0,.35))}',
8051
8644
  '.dig-dialog-wide{min-width:min(360px,92%)}',
@@ -8108,7 +8701,7 @@ window.__ModuleLoader__.load({
8108
8701
  const openSettings = navigation !== undefined && navigation !== null && typeof navigation.openBundle === 'function'
8109
8702
  ? () => { navigation.openBundle(SETTINGS_BUNDLE) }
8110
8703
  : null
8111
- return E(Panel, { scope: props.scope, t: props.t, visible: props.visible, dock: props.dock, openSettings: openSettings })
8704
+ return E(Panel, { scope: props.scope, t: props.t, visible: props.visible, dock: props.dock, openSettings: openSettings, ctx: props.ctx })
8112
8705
  }
8113
8706
 
8114
8707
  /* The conversation-area seat (issue #8): the same panel as a view tab
@@ -8160,13 +8753,53 @@ window.__ModuleLoader__.load({
8160
8753
  useEffect(() => {
8161
8754
  const el = seatRef.current
8162
8755
  if (el === null) return undefined
8756
+ /* Leftover wheel scroll, root cause: the composer seat is sticky, but a
8757
+ sticky box still occupies FLOW space inside the scrollport, so even
8758
+ with the panel seat pinned to the visible height the scrollHeight
8759
+ stays clientHeight + that flow space — the wheel could still scroll
8760
+ the whole window by exactly that leftover (user report: the layout
8761
+ fits, yet the page keeps scrolling). v0.13.3 report: content mounts
8762
+ or changes AFTER the first pass (the composer, the hero, a diff), and
8763
+ a single feedback convergence is not enough. measure() therefore
8764
+ recomputes the UNCORRECTED base (base.current) and hands off to the
8765
+ CONTROLLED CONVERGER below, which is re-run by every trigger: the
8766
+ initial mount, rAF/timeout follow-ups, the scrollport's own resize
8767
+ (ResizeObserver) and window resizes, and — new — the scrollport's
8768
+ content mutations (MutationObserver, debounced), because content
8769
+ growing later re-opens the overflow without resizing the scrollport
8770
+ itself. */
8771
+ const leftover = { current: 0 }
8772
+ const base = { current: 0 }
8773
+ /* The converger: up to FOUR rAF-spaced rounds. Each round reads
8774
+ scroll.scrollHeight - scroll.clientHeight; anything over 1px shrinks
8775
+ the seat by exactly that amount and OVERWRITES the leftover record
8776
+ (never adds — every corrected height derives from the fresh base, so
8777
+ repeated rounds cannot compound), clamped at the same 240 floor.
8778
+ Shrink-only: nothing here grows the seat back. A host without the
8779
+ conversation scrollport never reaches this code — measure() skips it
8780
+ all, silently, and never throws. */
8781
+ const converge = (scroll) => {
8782
+ let rounds = 0
8783
+ const step = () => {
8784
+ rounds += 1
8785
+ if (el.isConnected !== true) return
8786
+ const overflow = scroll.scrollHeight - scroll.clientHeight
8787
+ if (overflow <= 1) return
8788
+ leftover.current = overflow
8789
+ setSeatHeight(Math.max(240, base.current - overflow))
8790
+ if (rounds < 4) requestAnimationFrame(step)
8791
+ }
8792
+ requestAnimationFrame(step)
8793
+ }
8163
8794
  const measure = () => {
8164
8795
  let scroll = null
8165
8796
  try { scroll = el.closest('[data-conversation-scroll]') } catch (error) { void error }
8166
8797
  if (scroll === null || scroll === undefined) return
8167
8798
  const offset = Math.max(0, el.getBoundingClientRect().top - scroll.getBoundingClientRect().top)
8168
- const available = Math.round(scroll.clientHeight - offset - 8)
8169
- if (available >= 240) setSeatHeight(available)
8799
+ base.current = Math.round(scroll.clientHeight - offset - 8)
8800
+ const corrected = base.current - leftover.current
8801
+ if (corrected >= 240) setSeatHeight(corrected)
8802
+ converge(scroll)
8170
8803
  }
8171
8804
  measure()
8172
8805
  const raf = requestAnimationFrame(() => { measure() })
@@ -8179,10 +8812,28 @@ window.__ModuleLoader__.load({
8179
8812
  observer.observe(scroll)
8180
8813
  }
8181
8814
  } catch (error) { void error }
8815
+ /* Content that mounts or changes later does not resize the scrollport,
8816
+ so the ResizeObserver stays silent while the overflow re-opens; the
8817
+ mutation watcher re-measures for it. Debounced ~120ms so a mount
8818
+ burst converges once instead of once per node. */
8819
+ let mutationObserver
8820
+ let mutationTimer = null
8821
+ try {
8822
+ const scroll = el.closest('[data-conversation-scroll]')
8823
+ if (scroll !== null && scroll !== undefined && typeof MutationObserver === 'function') {
8824
+ mutationObserver = new MutationObserver(() => {
8825
+ if (mutationTimer !== null) clearTimeout(mutationTimer)
8826
+ mutationTimer = setTimeout(() => { mutationTimer = null; measure() }, 120)
8827
+ })
8828
+ mutationObserver.observe(scroll, { childList: true, subtree: true })
8829
+ }
8830
+ } catch (error) { void error }
8182
8831
  window.addEventListener('resize', measure)
8183
8832
  return () => {
8184
8833
  cancelAnimationFrame(raf)
8185
8834
  clearTimeout(late)
8835
+ if (mutationTimer !== null) clearTimeout(mutationTimer)
8836
+ if (mutationObserver !== undefined) mutationObserver.disconnect()
8186
8837
  if (observer !== undefined) observer.disconnect()
8187
8838
  window.removeEventListener('resize', measure)
8188
8839
  }