dsh-ide-git 0.19.1 → 0.19.3

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
@@ -101,7 +101,8 @@ window.__ModuleLoader__.load({
101
101
  'branches.head': 'HEAD(当前分支)',
102
102
  'branches.favorites': '收藏',
103
103
  'branches.starHint': "已收藏",
104
- 'branches.pickHint': '单击选中,双击签出',
104
+ 'branches.pickHint': '单击选中,双击查看提交历史',
105
+ 'branches.headHint': '双击查看当前分支的提交历史',
105
106
  'branches.worktree': '已在另一工作区检出',
106
107
  'action.stash': '贮藏',
107
108
  'action.favorite': '收藏/取消收藏当前分支',
@@ -347,6 +348,7 @@ window.__ModuleLoader__.load({
347
348
  'action.unavailable': '当前状态不可用',
348
349
  'filter.branch': '分支',
349
350
  'filter.user': '用户',
351
+ 'history.me': '我',
350
352
  'filter.date': '日期',
351
353
  'filter.path': '路径',
352
354
  'filter.all': '全部',
@@ -426,7 +428,8 @@ window.__ModuleLoader__.load({
426
428
  'branches.head': 'HEAD (current branch)',
427
429
  'branches.favorites': 'Favorites',
428
430
  'branches.starHint': "Favorited",
429
- 'branches.pickHint': 'Click to select, double-click to check out',
431
+ 'branches.pickHint': 'Click to select, double-click to browse its history',
432
+ 'branches.headHint': 'Double-click to browse the current branch\'s history',
430
433
  'branches.worktree': 'Checked out in another working tree',
431
434
  'action.stash': 'Stash',
432
435
  'action.favorite': 'Favorite / unfavorite current branch',
@@ -672,6 +675,7 @@ window.__ModuleLoader__.load({
672
675
  'action.unavailable': 'Not available in the current state',
673
676
  'filter.branch': 'Branch',
674
677
  'filter.user': 'User',
678
+ 'history.me': 'Me',
675
679
  'filter.date': 'Date',
676
680
  'filter.path': 'Path',
677
681
  'filter.all': 'All',
@@ -759,7 +763,8 @@ window.__ModuleLoader__.load({
759
763
  'branches.head': 'HEAD(當前分支)',
760
764
  'branches.favorites': '我的最愛',
761
765
  'branches.starHint': "已收藏",
762
- 'branches.pickHint': '單擊選取,雙擊簽出',
766
+ 'branches.pickHint': '單擊選中,雙擊查看提交歷史',
767
+ 'branches.headHint': '雙擊查看當前分支的提交歷史',
763
768
  'branches.worktree': '已喺另一個工作區簽出',
764
769
  'action.stash': '貯藏',
765
770
  'action.favorite': '加入/取消我的最愛',
@@ -1005,6 +1010,7 @@ window.__ModuleLoader__.load({
1005
1010
  'action.unavailable': '當前狀態下唔可用',
1006
1011
  'filter.branch': '分支',
1007
1012
  'filter.user': '提交者',
1013
+ 'history.me': '我',
1008
1014
  'filter.date': '日期',
1009
1015
  'filter.path': '路徑',
1010
1016
  'filter.all': '全部',
@@ -1084,7 +1090,8 @@ window.__ModuleLoader__.load({
1084
1090
  'branches.head': 'HEAD(目前分支)',
1085
1091
  'branches.favorites': '我的最愛',
1086
1092
  'branches.starHint': "已收藏",
1087
- 'branches.pickHint': '單擊選取,雙擊簽出',
1093
+ 'branches.pickHint': '單擊選取,雙擊檢視提交歷史',
1094
+ 'branches.headHint': '雙擊檢視目前分支的提交歷史',
1088
1095
  'branches.worktree': '已在另一個工作區簽出',
1089
1096
  'action.stash': '暫存擱置',
1090
1097
  'action.favorite': '加入/移除我的最愛',
@@ -1330,6 +1337,7 @@ window.__ModuleLoader__.load({
1330
1337
  'action.unavailable': '目前狀態下無法使用',
1331
1338
  'filter.branch': '分支',
1332
1339
  'filter.user': '提交者',
1340
+ 'history.me': '我',
1333
1341
  'filter.date': '日期',
1334
1342
  'filter.path': '路徑',
1335
1343
  'filter.all': '全部',
@@ -1409,7 +1417,8 @@ window.__ModuleLoader__.load({
1409
1417
  'branches.head': 'HEAD(當前分支)',
1410
1418
  'branches.favorites': '我的最愛',
1411
1419
  'branches.starHint': "已收藏",
1412
- 'branches.pickHint': '單擊選取,雙擊簽出',
1420
+ 'branches.pickHint': '單擊選中,雙擊查看提交歷史',
1421
+ 'branches.headHint': '雙擊查看當前分支的提交歷史',
1413
1422
  'branches.worktree': '已喺另一個工作區簽出',
1414
1423
  'action.stash': '貯藏',
1415
1424
  'action.favorite': '加入/取消我的最愛',
@@ -1655,6 +1664,7 @@ window.__ModuleLoader__.load({
1655
1664
  'action.unavailable': '當前狀態下唔可用',
1656
1665
  'filter.branch': '分支',
1657
1666
  'filter.user': '提交者',
1667
+ 'history.me': '我',
1658
1668
  'filter.date': '日期',
1659
1669
  'filter.path': '路徑',
1660
1670
  'filter.all': '全部',
@@ -1734,7 +1744,8 @@ window.__ModuleLoader__.load({
1734
1744
  'branches.head': 'HEAD(現在のブランチ)',
1735
1745
  'branches.favorites': 'お気に入り',
1736
1746
  'branches.starHint': "お気に入り済み",
1737
- 'branches.pickHint': 'クリックで選択、ダブルクリックでチェックアウト',
1747
+ 'branches.pickHint': 'クリックで選択、ダブルクリックで履歴を表示',
1748
+ 'branches.headHint': 'ダブルクリックで現在のブランチの履歴を表示',
1738
1749
  'branches.worktree': '別のワークツリーでチェックアウト済み',
1739
1750
  'action.stash': 'スタッシュ',
1740
1751
  'action.favorite': '現在のブランチをお気に入りに追加/解除',
@@ -1980,6 +1991,7 @@ window.__ModuleLoader__.load({
1980
1991
  'action.unavailable': '現在の状態では使用できません',
1981
1992
  'filter.branch': 'ブランチ',
1982
1993
  'filter.user': 'コミット者',
1994
+ 'history.me': '自分',
1983
1995
  'filter.date': '日付',
1984
1996
  'filter.path': 'パス',
1985
1997
  'filter.all': 'すべて',
@@ -2059,7 +2071,8 @@ window.__ModuleLoader__.load({
2059
2071
  'branches.head': 'HEAD(현재 브랜치)',
2060
2072
  'branches.favorites': '즐겨찾기',
2061
2073
  'branches.starHint': "즐겨찾기",
2062
- 'branches.pickHint': '클릭하여 선택, 더블클릭하여 체크아웃',
2074
+ 'branches.pickHint': '클릭하여 선택, 더블클릭으로 커밋 히스토리 보기',
2075
+ 'branches.headHint': '더블클릭으로 현재 브랜치의 히스토리 보기',
2063
2076
  'branches.worktree': '다른 워크트리에서 체크아웃됨',
2064
2077
  'action.stash': '스태시',
2065
2078
  'action.favorite': '현재 브랜치 즐겨찾기 추가/해제',
@@ -2305,6 +2318,7 @@ window.__ModuleLoader__.load({
2305
2318
  'action.unavailable': '현재 상태에서는 사용할 수 없습니다',
2306
2319
  'filter.branch': '브랜치',
2307
2320
  'filter.user': '작성자',
2321
+ 'history.me': '나',
2308
2322
  'filter.date': '날짜',
2309
2323
  'filter.path': '경로',
2310
2324
  'filter.all': '전체',
@@ -2384,7 +2398,8 @@ window.__ModuleLoader__.load({
2384
2398
  'branches.head': 'HEAD (aktueller Branch)',
2385
2399
  'branches.favorites': 'Favoriten',
2386
2400
  'branches.starHint': "Favorit",
2387
- 'branches.pickHint': 'Klicken zum Auswählen, doppelklicken zum Auschecken',
2401
+ 'branches.pickHint': 'Klicken zum Auswählen, Doppelklick zeigt den Verlauf',
2402
+ 'branches.headHint': 'Doppelklick zeigt den Verlauf des aktuellen Branches',
2388
2403
  'branches.worktree': 'In einem anderen Arbeitsbaum ausgecheckt',
2389
2404
  'action.stash': 'Stash',
2390
2405
  'action.favorite': 'Aktuellen Branch als Favorit setzen/entfernen',
@@ -2630,6 +2645,7 @@ window.__ModuleLoader__.load({
2630
2645
  'action.unavailable': 'Im aktuellen Zustand nicht verfügbar',
2631
2646
  'filter.branch': 'Branch',
2632
2647
  'filter.user': 'Autor',
2648
+ 'history.me': 'Ich',
2633
2649
  'filter.date': 'Datum',
2634
2650
  'filter.path': 'Pfad',
2635
2651
  'filter.all': 'Alle',
@@ -2709,7 +2725,8 @@ window.__ModuleLoader__.load({
2709
2725
  'branches.head': 'HEAD (branche courante)',
2710
2726
  'branches.favorites': 'Favoris',
2711
2727
  'branches.starHint': "Favori",
2712
- 'branches.pickHint': 'Cliquer pour sélectionner, double-cliquer pour extraire',
2728
+ 'branches.pickHint': 'Clic pour sélectionner, double-clic pour ouvrir l\'historique',
2729
+ 'branches.headHint': 'Double-clic pour l\'historique de la branche courante',
2713
2730
  'branches.worktree': 'Extraite dans un autre arbre de travail',
2714
2731
  'action.stash': 'Remisage',
2715
2732
  'action.favorite': 'Ajouter/retirer la branche courante des favoris',
@@ -2955,6 +2972,7 @@ window.__ModuleLoader__.load({
2955
2972
  'action.unavailable': 'Indisponible dans l\'état actuel',
2956
2973
  'filter.branch': 'Branche',
2957
2974
  'filter.user': 'Auteur',
2975
+ 'history.me': 'Moi',
2958
2976
  'filter.date': 'Date',
2959
2977
  'filter.path': 'Chemin',
2960
2978
  'filter.all': 'Tout',
@@ -3034,7 +3052,8 @@ window.__ModuleLoader__.load({
3034
3052
  'branches.head': 'HEAD (текущая ветка)',
3035
3053
  'branches.favorites': 'Избранное',
3036
3054
  'branches.starHint': "В избранном",
3037
- 'branches.pickHint': 'Щёлкните для выбора, двойной щелчок — переключение',
3055
+ 'branches.pickHint': 'Клик — выбор, двойной клик — история коммитов',
3056
+ 'branches.headHint': 'Двойной клик — история текущей ветки',
3038
3057
  'branches.worktree': 'Уже развёрнута в другом рабочем дереве',
3039
3058
  'action.stash': 'Отложить',
3040
3059
  'action.favorite': 'Добавить/убрать текущую ветку из избранного',
@@ -3280,6 +3299,7 @@ window.__ModuleLoader__.load({
3280
3299
  'action.unavailable': 'Недоступно в текущем состоянии',
3281
3300
  'filter.branch': 'Ветка',
3282
3301
  'filter.user': 'Автор',
3302
+ 'history.me': 'Я',
3283
3303
  'filter.date': 'Дата',
3284
3304
  'filter.path': 'Путь',
3285
3305
  'filter.all': 'Все',
@@ -3359,7 +3379,8 @@ window.__ModuleLoader__.load({
3359
3379
  'branches.head': 'HEAD (branch atual)',
3360
3380
  'branches.favorites': 'Favoritas',
3361
3381
  'branches.starHint': "Favorito",
3362
- 'branches.pickHint': 'Clique para selecionar, clique duplo para fazer checkout',
3382
+ 'branches.pickHint': 'Clique para selecionar, duplo clique abre o histórico',
3383
+ 'branches.headHint': 'Duplo clique abre o histórico do branch atual',
3363
3384
  'branches.worktree': 'Já em checkout em outra árvore de trabalho',
3364
3385
  'action.stash': 'Stash',
3365
3386
  'action.favorite': 'Adicionar/remover a branch atual dos favoritos',
@@ -3605,6 +3626,7 @@ window.__ModuleLoader__.load({
3605
3626
  'action.unavailable': 'Indisponível no estado atual',
3606
3627
  'filter.branch': 'Branch',
3607
3628
  'filter.user': 'Autor',
3629
+ 'history.me': 'Eu',
3608
3630
  'filter.date': 'Data',
3609
3631
  'filter.path': 'Caminho',
3610
3632
  'filter.all': 'Tudo',
@@ -3684,7 +3706,8 @@ window.__ModuleLoader__.load({
3684
3706
  'branches.head': 'HEAD (branch corrente)',
3685
3707
  'branches.favorites': 'Preferiti',
3686
3708
  'branches.starHint': "Preferito",
3687
- 'branches.pickHint': 'Clic per selezionare, doppio clic per fare checkout',
3709
+ 'branches.pickHint': 'Clic per selezionare, doppio clic apre la cronologia',
3710
+ 'branches.headHint': 'Doppio clic apre la cronologia del branch corrente',
3688
3711
  'branches.worktree': 'Già in checkout in un altro albero di lavoro',
3689
3712
  'action.stash': 'Stash',
3690
3713
  'action.favorite': 'Aggiungi/rimuovi il branch corrente dai preferiti',
@@ -3930,6 +3953,7 @@ window.__ModuleLoader__.load({
3930
3953
  'action.unavailable': 'Non disponibile nello stato attuale',
3931
3954
  'filter.branch': 'Branch',
3932
3955
  'filter.user': 'Autore',
3956
+ 'history.me': 'Io',
3933
3957
  'filter.date': 'Data',
3934
3958
  'filter.path': 'Percorso',
3935
3959
  'filter.all': 'Tutti',
@@ -4009,7 +4033,8 @@ window.__ModuleLoader__.load({
4009
4033
  'branches.head': 'HEAD (huidige branch)',
4010
4034
  'branches.favorites': 'Favorieten',
4011
4035
  'branches.starHint': "Favoriet",
4012
- 'branches.pickHint': 'Klik om te selecteren, dubbelklik om uit te checken',
4036
+ 'branches.pickHint': 'Klik om te selecteren, dubbelklik opent de geschiedenis',
4037
+ 'branches.headHint': 'Dubbelklik opent de geschiedenis van de huidige branch',
4013
4038
  'branches.worktree': 'Al uitgecheckt in een andere werkboom',
4014
4039
  'action.stash': 'Stash',
4015
4040
  'action.favorite': 'Huidige branch aan favorieten toevoegen/onttrekken',
@@ -4255,6 +4280,7 @@ window.__ModuleLoader__.load({
4255
4280
  'action.unavailable': 'Niet beschikbaar in de huidige staat',
4256
4281
  'filter.branch': 'Branch',
4257
4282
  'filter.user': 'Auteur',
4283
+ 'history.me': 'Ik',
4258
4284
  'filter.date': 'Datum',
4259
4285
  'filter.path': 'Pad',
4260
4286
  'filter.all': 'Alles',
@@ -4334,7 +4360,8 @@ window.__ModuleLoader__.load({
4334
4360
  'branches.head': 'HEAD (bieżąca gałąź)',
4335
4361
  'branches.favorites': 'Ulubione',
4336
4362
  'branches.starHint': "Ulubione",
4337
- 'branches.pickHint': 'Kliknij, aby wybrać; kliknij dwukrotnie, aby przełączyć',
4363
+ 'branches.pickHint': 'Kliknij, aby zaznaczyć, dwuklik otwiera historię',
4364
+ 'branches.headHint': 'Dwuklik otwiera historię bieżącej gałęzi',
4338
4365
  'branches.worktree': 'Już wybrana w innym drzewie roboczym',
4339
4366
  'action.stash': 'Schowek',
4340
4367
  'action.favorite': 'Dodaj/usuń bieżącą gałąź z ulubionych',
@@ -4580,6 +4607,7 @@ window.__ModuleLoader__.load({
4580
4607
  'action.unavailable': 'Niedostępne w bieżącym stanie',
4581
4608
  'filter.branch': 'Gałąź',
4582
4609
  'filter.user': 'Autor',
4610
+ 'history.me': 'Ja',
4583
4611
  'filter.date': 'Data',
4584
4612
  'filter.path': 'Ścieżka',
4585
4613
  'filter.all': 'Wszystkie',
@@ -4659,7 +4687,8 @@ window.__ModuleLoader__.load({
4659
4687
  'branches.head': 'HEAD (aktuell gren)',
4660
4688
  'branches.favorites': 'Favoriter',
4661
4689
  'branches.starHint': "Favorit",
4662
- 'branches.pickHint': 'Klicka för att välja, dubbelklicka för att checka ut',
4690
+ 'branches.pickHint': 'Klicka för att välja, dubbelklick visar historiken',
4691
+ 'branches.headHint': 'Dubbelklick visar historiken för aktuell gren',
4663
4692
  'branches.worktree': 'Redan utcheckad i ett annat arbetsträd',
4664
4693
  'action.stash': 'Stash',
4665
4694
  'action.favorite': 'Lägg till/ta bort aktuell gren som favorit',
@@ -4905,6 +4934,7 @@ window.__ModuleLoader__.load({
4905
4934
  'action.unavailable': 'Inte tillgängligt i aktuellt läge',
4906
4935
  'filter.branch': 'Gren',
4907
4936
  'filter.user': 'Författare',
4937
+ 'history.me': 'Jag',
4908
4938
  'filter.date': 'Datum',
4909
4939
  'filter.path': 'Sökväg',
4910
4940
  'filter.all': 'Alla',
@@ -4984,7 +5014,8 @@ window.__ModuleLoader__.load({
4984
5014
  'branches.head': 'HEAD (geçerli dal)',
4985
5015
  'branches.favorites': 'Sık kullanılanlar',
4986
5016
  'branches.starHint': "Favori",
4987
- 'branches.pickHint': 'Seçmek için tıklayın, çıkarmak için çift tıklayın',
5017
+ 'branches.pickHint': 'Seçmek için tek tık, geçmişi görmek için çift tık',
5018
+ 'branches.headHint': 'Çift tık, geçerli dalın geçmişini açar',
4988
5019
  'branches.worktree': 'Başka bir çalışma ağacında çıkarılmış',
4989
5020
  'action.stash': 'Saklama',
4990
5021
  'action.favorite': 'Geçerli dalı sık kullanılanlara ekle/çıkar',
@@ -5230,6 +5261,7 @@ window.__ModuleLoader__.load({
5230
5261
  'action.unavailable': 'Geçerli durumda kullanılamaz',
5231
5262
  'filter.branch': 'Dal',
5232
5263
  'filter.user': 'Yazar',
5264
+ 'history.me': 'Ben',
5233
5265
  'filter.date': 'Tarih',
5234
5266
  'filter.path': 'Yol',
5235
5267
  'filter.all': 'Tümü',
@@ -5309,7 +5341,8 @@ window.__ModuleLoader__.load({
5309
5341
  'branches.head': 'HEAD (branch saat ini)',
5310
5342
  'branches.favorites': 'Favorit',
5311
5343
  'branches.starHint': "Favorit",
5312
- 'branches.pickHint': 'Klik untuk memilih, klik ganda untuk checkout',
5344
+ 'branches.pickHint': 'Klik untuk memilih, klik ganda membuka riwayat',
5345
+ 'branches.headHint': 'Klik ganda membuka riwayat branch saat ini',
5313
5346
  'branches.worktree': 'Sudah di-checkout di worktree lain',
5314
5347
  'action.stash': 'Stash',
5315
5348
  'action.favorite': 'Tambah/hapus branch saat ini dari favorit',
@@ -5555,6 +5588,7 @@ window.__ModuleLoader__.load({
5555
5588
  'action.unavailable': 'Tidak tersedia pada keadaan saat ini',
5556
5589
  'filter.branch': 'Branch',
5557
5590
  'filter.user': 'Penulis',
5591
+ 'history.me': 'Saya',
5558
5592
  'filter.date': 'Tanggal',
5559
5593
  'filter.path': 'Jalur',
5560
5594
  'filter.all': 'Semua',
@@ -5634,7 +5668,8 @@ window.__ModuleLoader__.load({
5634
5668
  'branches.head': 'HEAD (nhánh hiện tại)',
5635
5669
  'branches.favorites': 'Yêu thích',
5636
5670
  'branches.starHint': "Đã yêu thích",
5637
- 'branches.pickHint': 'Nhấp để chọn, nhấp đôi để checkout',
5671
+ 'branches.pickHint': 'Bấm để chọn, nhấp đúp mở lịch sử commit',
5672
+ 'branches.headHint': 'Nhấp đúp mở lịch sử của nhánh hiện tại',
5638
5673
  'branches.worktree': 'Đã được checkout ở một worktree khác',
5639
5674
  'action.stash': 'Tạm cất',
5640
5675
  'action.favorite': 'Thêm/bỏ nhánh hiện tại khỏi yêu thích',
@@ -5880,6 +5915,7 @@ window.__ModuleLoader__.load({
5880
5915
  'action.unavailable': 'Không dùng được ở trạng thái hiện tại',
5881
5916
  'filter.branch': 'Nhánh',
5882
5917
  'filter.user': 'Tác giả',
5918
+ 'history.me': 'Tôi',
5883
5919
  'filter.date': 'Ngày',
5884
5920
  'filter.path': 'Đường dẫn',
5885
5921
  'filter.all': 'Tất cả',
@@ -5959,7 +5995,8 @@ window.__ModuleLoader__.load({
5959
5995
  'branches.head': 'HEAD (الفرع الحالي)',
5960
5996
  'branches.favorites': 'المفضلة',
5961
5997
  'branches.starHint': "مفضل",
5962
- 'branches.pickHint': 'انقر للتحديد، وانقر نقرًا مزدوجًا للتبديل',
5998
+ 'branches.pickHint': 'انقر للتحديد، ونقرتان مزدوجتان لعرض السجل',
5999
+ 'branches.headHint': 'نقرتان مزدوجتان لعرض سجل الفرع الحالي',
5963
6000
  'branches.worktree': 'مُستخرَج بالفعل في شجرة عمل أخرى',
5964
6001
  'action.stash': 'التخزين المؤقت',
5965
6002
  'action.favorite': 'إضافة الفرع الحالي إلى المفضلة أو إزالته',
@@ -6205,6 +6242,7 @@ window.__ModuleLoader__.load({
6205
6242
  'action.unavailable': 'غير متاح في الحالة الحالية',
6206
6243
  'filter.branch': 'الفرع',
6207
6244
  'filter.user': 'المؤلف',
6245
+ 'history.me': 'أنا',
6208
6246
  'filter.date': 'التاريخ',
6209
6247
  'filter.path': 'المسار',
6210
6248
  'filter.all': 'الكل',
@@ -6284,7 +6322,8 @@ window.__ModuleLoader__.load({
6284
6322
  'branches.head': 'HEAD (वर्तमान शाखा)',
6285
6323
  'branches.favorites': 'पसंदीदा',
6286
6324
  'branches.starHint': "पसंदीदा",
6287
- 'branches.pickHint': 'चुनने के लिए क्लिक करें, चेकआउट के लिए डबल-क्लिक करें',
6325
+ 'branches.pickHint': 'चुनने के लिए क्लिक करें, इतिहास देखने के लिए डबल-क्लिक',
6326
+ 'branches.headHint': 'डबल-क्लिक से वर्तमान ब्रांच का इतिहास देखें',
6288
6327
  'branches.worktree': 'पहले से किसी अन्य वर्कट्री में चेकआउट है',
6289
6328
  'action.stash': 'स्टैश',
6290
6329
  'action.favorite': 'वर्तमान शाखा को पसंदीदा में जोड़ें/हटाएँ',
@@ -6530,6 +6569,7 @@ window.__ModuleLoader__.load({
6530
6569
  'action.unavailable': 'वर्तमान स्थिति में उपलब्ध नहीं',
6531
6570
  'filter.branch': 'शाखा',
6532
6571
  'filter.user': 'लेखक',
6572
+ 'history.me': 'मैं',
6533
6573
  'filter.date': 'दिनांक',
6534
6574
  'filter.path': 'पथ',
6535
6575
  'filter.all': 'सभी',
@@ -6609,7 +6649,8 @@ window.__ModuleLoader__.load({
6609
6649
  'branches.head': 'HEAD (แบรนช์ปัจจุบัน)',
6610
6650
  'branches.favorites': 'รายการโปรด',
6611
6651
  'branches.starHint': "รายการโปรด",
6612
- 'branches.pickHint': 'คลิกเพื่อเลือก ดับเบิลคลิกเพื่อเช็กเอาต์',
6652
+ 'branches.pickHint': 'คลิกเพื่อเลือก ดับเบิลคลิกเพื่อดูประวัติคอมมิต',
6653
+ 'branches.headHint': 'ดับเบิลคลิกเพื่อดูประวัติของแบรนช์ปัจจุบัน',
6613
6654
  'branches.worktree': 'เช็กเอาต์อยู่ในเวิร์กทรีอื่นแล้ว',
6614
6655
  'action.stash': 'สแตช',
6615
6656
  'action.favorite': 'เพิ่ม/移除แบรนช์ปัจจุบันในรายการโปรด',
@@ -6855,6 +6896,7 @@ window.__ModuleLoader__.load({
6855
6896
  'action.unavailable': 'ใช้ไม่ได้ในสถานะปัจจุบัน',
6856
6897
  'filter.branch': 'แบรนช์',
6857
6898
  'filter.user': 'ผู้เขียน',
6899
+ 'history.me': 'ฉัน',
6858
6900
  'filter.date': 'วันที่',
6859
6901
  'filter.path': 'พาธ',
6860
6902
  'filter.all': 'ทั้งหมด',
@@ -9302,11 +9344,16 @@ window.__ModuleLoader__.load({
9302
9344
  closeRef.current()
9303
9345
  }
9304
9346
  const onKey = (event) => { if (event.key === 'Escape') closeRef.current() }
9305
- const timer = setTimeout(() => document.addEventListener('mousedown', onDown, true), 0)
9347
+ /* pointerdown, not mousedown: the list pointer-down handlers call
9348
+ preventDefault(), which suppresses the COMPATIBILITY mouse events
9349
+ (mousedown included) — a capture-phase mousedown listener then never
9350
+ fires and the menu stayed open when the next press landed on a list
9351
+ blank (user report). pointerdown always dispatches. */
9352
+ const timer = setTimeout(() => document.addEventListener('pointerdown', onDown, true), 0)
9306
9353
  document.addEventListener('keydown', onKey)
9307
9354
  return () => {
9308
9355
  clearTimeout(timer)
9309
- document.removeEventListener('mousedown', onDown, true)
9356
+ document.removeEventListener('pointerdown', onDown, true)
9310
9357
  document.removeEventListener('keydown', onKey)
9311
9358
  }
9312
9359
  }, [])
@@ -10293,14 +10340,18 @@ window.__ModuleLoader__.load({
10293
10340
  // A single click only SELECTS (now with Shift/Ctrl+click multi-select —
10294
10341
  // the modifiers are resolved one level up in BranchTree): checking a
10295
10342
  // branch out is a real change of working tree, and a stray click in a
10296
- // long list must not do that (reported on v0.3.7). Double-click checks
10297
- // out, right-click has the whole menu.
10343
+ // long list must not do that (reported on v0.3.7). Since v0.19.3 a
10344
+ // double-click BROWSES that branch's commit history (checkout moved into
10345
+ // the right-click menu), right-click has the whole menu.
10298
10346
  return E('div', {
10299
10347
  className: 'dig-row dig-row-' + kind + (props.selected === true ? ' dig-row-selected' : ''),
10300
10348
  style: { paddingLeft: (BRANCH_INDENT_BASE + (props.depth === undefined ? 0 : props.depth) * BRANCH_INDENT_STEP) + 'px' },
10349
+ /* Rubber-band anchor: the marquee intersects [data-mkey] rows to
10350
+ rebuild the picked set (selKey = local:/remote:/tag: prefixed). */
10351
+ 'data-mkey': props.selKey,
10301
10352
  title: where + ' · ' + t('branches.pickHint'),
10302
10353
  onClick: (event) => props.onPick(event, entry, props.selKey),
10303
- onDoubleClick: () => props.onCheckout(entry),
10354
+ onDoubleClick: () => { if (props.onBrowse !== undefined) props.onBrowse(entry) },
10304
10355
  onContextMenu: (event) => { event.preventDefault(); props.onMenu(event, entry) },
10305
10356
  },
10306
10357
  E('span', { className: 'dig-row-icon dig-tone-' + tone + (starred === true ? ' dig-star-filled' : '') }, E(Icon, { name: glyph, size: 12 })),
@@ -10352,6 +10403,10 @@ window.__ModuleLoader__.load({
10352
10403
  // favorites section and the local tree show the starred rows first.
10353
10404
  const locals = (branches === null ? [] : branches.local).filter((entry) => match(entry.name))
10354
10405
  .sort((a, b) => (favorites.indexOf(a.name) >= 0 ? 0 : 1) - (favorites.indexOf(b.name) >= 0 ? 0 : 1))
10406
+ /* The HEAD row acts like a branch row since v0.19.3: click selects,
10407
+ double-click browses the current branch's history, right-click opens
10408
+ the same row menu (quick jump back to HEAD lives there). */
10409
+ const headEntry = locals.find((entry) => entry.head === true) || null
10355
10410
  /* A refresh that makes a picked ref disappear must not keep it selected. */
10356
10411
  useEffect(() => {
10357
10412
  setPicks((previous) => {
@@ -10439,7 +10494,7 @@ window.__ModuleLoader__.load({
10439
10494
  selected: picks.has(branchSelKeyOf(entry)) === true,
10440
10495
  starred: entry.tag !== true && entry.remote !== true && favorites.indexOf(entry.name) >= 0,
10441
10496
  onPick: handlePick,
10442
- onCheckout: props.onCheckout, onMenu: onRowMenu,
10497
+ onBrowse: props.onBrowse, onMenu: onRowMenu,
10443
10498
  })
10444
10499
  const rowsOf = (node, prefix, depth) => {
10445
10500
  const out = []
@@ -10504,6 +10559,30 @@ window.__ModuleLoader__.load({
10504
10559
  }
10505
10560
  props.onBranchMenu(event, entry, null)
10506
10561
  }
10562
+ /* v0.19.3: the tree rubber-bands like the other two lists (shared hook).
10563
+ Tree rows have no fixed height and render unfolded-only, so the row
10564
+ rects are a LIVE DOM measure over the [data-mkey] rows BranchRow
10565
+ carries; section/folder headers are buttons and never start a drag.
10566
+ Favorited branches render twice (favorites + local sections) — both
10567
+ map to one selKey, so a Set dedupes them for free. */
10568
+ const treeHostRef = useRef(null)
10569
+ const treeScrollRef = useRef(null)
10570
+ const treeMarquee = useRubberBand({
10571
+ scrollRef: treeScrollRef,
10572
+ drawRef: treeHostRef,
10573
+ rectsOf: () => {
10574
+ const host = treeScrollRef.current
10575
+ if (host === null) return []
10576
+ const hostRect = host.getBoundingClientRect()
10577
+ const rects = []
10578
+ host.querySelectorAll('[data-mkey]').forEach((row) => {
10579
+ const rect = row.getBoundingClientRect()
10580
+ rects.push({ key: row.getAttribute('data-mkey'), top: rect.top - hostRect.top + host.scrollTop, bottom: rect.bottom - hostRect.top + host.scrollTop })
10581
+ })
10582
+ return rects
10583
+ },
10584
+ onPick: (keys) => setPicks(new Set(keys)),
10585
+ })
10507
10586
  /* List-scope keyboard: Ctrl/Cmd+C copies the picked names, Delete asks
10508
10587
  Panel for a grouped delete (head branches are skipped — git refuses
10509
10588
  them anyway), Ctrl+A selects every visible row, Esc clears. */
@@ -10533,6 +10612,7 @@ window.__ModuleLoader__.load({
10533
10612
  }
10534
10613
  return E('div', {
10535
10614
  className: 'dig-tree',
10615
+ ref: treeHostRef,
10536
10616
  tabIndex: 0,
10537
10617
  onKeyDown: onTreeKeyDown,
10538
10618
  onPointerDownCapture: (event) => {
@@ -10545,12 +10625,26 @@ window.__ModuleLoader__.load({
10545
10625
  placeholder: t('branches.filter'),
10546
10626
  onChange: (event) => setFilter(event.target.value),
10547
10627
  })),
10548
- E('div', { className: 'dig-tree-scroll' },
10549
- E('div', { className: 'dig-section-head dig-section-head-static' }, t('branches.head')),
10628
+ E('div', {
10629
+ className: 'dig-tree-scroll',
10630
+ ref: treeScrollRef,
10631
+ onPointerDown: treeMarquee.onPointerDown,
10632
+ onClickCapture: (event) => {
10633
+ if (treeMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation() }
10634
+ },
10635
+ },
10636
+ headEntry === null ? E('div', { className: 'dig-section-head dig-section-head-static' }, t('branches.head')) : E('div', {
10637
+ className: 'dig-section-head dig-section-head-static dig-head-branch',
10638
+ title: headEntry.name + ' · ' + t('branches.headHint'),
10639
+ onClick: (event) => { event.stopPropagation(); handlePick(event, headEntry, branchSelKeyOf(headEntry)) },
10640
+ onDoubleClick: () => { if (props.onBrowse !== undefined) props.onBrowse(headEntry) },
10641
+ onContextMenu: (event) => { event.preventDefault(); onRowMenu(event, headEntry) },
10642
+ }, t('branches.head')),
10550
10643
  favorites.length === 0 ? null : section('favorites', t('branches.favorites'), locals.filter((entry) => favorites.indexOf(entry.name) >= 0), (entry) => entry),
10551
10644
  section('local', t('branches.local'), locals, (entry) => entry, true),
10552
10645
  section('remote', t('branches.remote'), remotes, (entry) => Object.assign({}, entry, { remote: true })),
10553
- section('tags', t('branches.tags'), tags, (entry) => Object.assign({}, entry, { tag: true }))))
10646
+ section('tags', t('branches.tags'), tags, (entry) => Object.assign({}, entry, { tag: true }))),
10647
+ E(MarqueeBox, { box: treeMarquee.box }))
10554
10648
  }
10555
10649
 
10556
10650
  /* ============================== changes ============================== */
@@ -10645,10 +10739,11 @@ window.__ModuleLoader__.load({
10645
10739
  const anchorRef = props.anchorRef
10646
10740
  const changeItemsRef = useRef(null)
10647
10741
  const changeOffsetsRef = useRef(null)
10648
- const [marquee, setMarquee] = useState(null)
10649
- const marqueeFrame = useRef(0)
10650
- const marqueeState = useRef(null)
10651
- const marqueeSwallowClick = useRef(false)
10742
+ /* The marquee lives in the shared useRubberBand hook (v0.19.3: history
10743
+ and the branch tree drag the same way). changesHostRef is the DRAW
10744
+ host — the hook comment explains why the paint rect must be the list
10745
+ wrapper's, not the scroll container's (v0.19.2 offset report). */
10746
+ const changesHostRef = useRef(null)
10652
10747
  /* Rows in their RENDER order (the order windowing flattens them into) —
10653
10748
  Shift-click spans follow what the eye sees, not the git order. */
10654
10749
  const orderedRowKeys = () => {
@@ -10700,96 +10795,10 @@ window.__ModuleLoader__.load({
10700
10795
  not yank the pane away (v0.18.3). */
10701
10796
  setSelection((previous) => previous.size === 1 && previous.has(key) === true ? previous : new Set([key]))
10702
10797
  }, [setSelection, anchorRef, props.onDiff])
10703
- /* ---- rubber-band selection over the windowed list: the fixed row
10704
- heights + the prefix-sum offsets turn a pixel span back into the
10705
- row-index span (the same windowStart binary search the window uses),
10706
- so the drag never needs the DOM rows to exist. The marquee box itself
10707
- is a viewport-anchored absolute div inside .dig-changes (invariant 13:
10708
- never position:fixed inside the workbench's contain:layout). ---- */
10709
- const marqueeKeysAt = (contentTop, contentBottom) => {
10710
- const items = changeItemsRef.current
10711
- const offsets = changeOffsetsRef.current
10712
- if (items === null || offsets === null || items.length === 0) return []
10713
- const first = Math.max(0, windowStart(offsets, contentTop))
10714
- const last = Math.min(items.length - 1, windowStart(offsets, contentBottom))
10715
- const keys = []
10716
- for (let index = first; index <= last; index += 1) {
10717
- const entry = items[index]
10718
- if (entry.kind === 'row' && entry.key.indexOf('ignored:') !== 0) keys.push(entry.key)
10719
- }
10720
- return keys
10721
- }
10722
- const syncMarquee = () => {
10723
- const state = marqueeState.current
10724
- const host = changesScrollRef.current
10725
- if (state === null || host === null) return
10726
- const rect = host.getBoundingClientRect()
10727
- const contentY0 = state.startY - rect.top + state.scrollY0
10728
- const contentY1 = state.clientY - rect.top + host.scrollTop
10729
- setSelection(new Set(marqueeKeysAt(Math.min(contentY0, contentY1), Math.max(contentY0, contentY1))))
10730
- setMarquee({
10731
- x: Math.min(state.startX, state.clientX) - rect.left,
10732
- y: Math.min(state.startY, state.clientY) - rect.top,
10733
- w: Math.abs(state.clientX - state.startX),
10734
- h: Math.abs(state.clientY - state.startY),
10735
- })
10736
- /* Auto-scroll when the pointer rides an edge; keep the loop alive while
10737
- it does so the selection follows the content streaming past. */
10738
- const EDGE = 28
10739
- let speed = 0
10740
- if (state.clientY - rect.top < EDGE) speed = -Math.ceil((EDGE - (state.clientY - rect.top)) / 3)
10741
- else if (rect.bottom - state.clientY < EDGE) speed = Math.ceil((EDGE - (rect.bottom - state.clientY)) / 3)
10742
- if (speed !== 0) {
10743
- host.scrollTop += speed * 2
10744
- cancelAnimationFrame(marqueeFrame.current)
10745
- marqueeFrame.current = requestAnimationFrame(syncMarquee)
10746
- }
10747
- }
10748
- const scheduleMarqueeSync = () => {
10749
- cancelAnimationFrame(marqueeFrame.current)
10750
- marqueeFrame.current = requestAnimationFrame(syncMarquee)
10751
- }
10752
- const onListPointerDown = (event) => {
10753
- if (event.button !== 0) return
10754
- const host = changesScrollRef.current
10755
- if (host === null) return
10756
- if (event.target instanceof Element && event.target.closest('button, input, textarea, select, a') !== null) return
10757
- // Keep the browser from starting a text selection under the drag; the
10758
- // real clicks still fire (only the selection machinery is suppressed).
10759
- event.preventDefault()
10760
- const rect = host.getBoundingClientRect()
10761
- marqueeState.current = {
10762
- startX: event.clientX,
10763
- startY: event.clientY,
10764
- scrollY0: host.scrollTop,
10765
- clientX: event.clientX,
10766
- clientY: event.clientY,
10767
- active: false,
10768
- }
10769
- const onMove = (moveEvent) => {
10770
- const state = marqueeState.current
10771
- if (state === null) return
10772
- state.clientX = moveEvent.clientX
10773
- state.clientY = moveEvent.clientY
10774
- if (state.active === false && Math.abs(moveEvent.clientX - state.startX) < 4 && Math.abs(moveEvent.clientY - state.startY) < 4) return
10775
- state.active = true
10776
- marqueeSwallowClick.current = true
10777
- scheduleMarqueeSync()
10778
- }
10779
- const onUp = () => {
10780
- window.removeEventListener('pointermove', onMove)
10781
- window.removeEventListener('pointerup', onUp)
10782
- cancelAnimationFrame(marqueeFrame.current)
10783
- const wasActive = marqueeState.current !== null && marqueeState.current.active === true
10784
- marqueeState.current = null
10785
- setMarquee(null)
10786
- // The drag's pointerup is followed by a synthetic click on whatever
10787
- // row is under it — swallow exactly that one click.
10788
- if (wasActive === true) setTimeout(() => { marqueeSwallowClick.current = false }, 0)
10789
- }
10790
- window.addEventListener('pointermove', onMove)
10791
- window.addEventListener('pointerup', onUp)
10792
- }
10798
+ /* Rubber-band selection now comes from the shared useRubberBand hook
10799
+ (see its comment): the fixed row heights + the prefix-sum offsets
10800
+ still turn a pixel span back into row keys without any DOM rows —
10801
+ the rectsOf below is the exact former marqueeKeysAt, reshaped. */
10793
10802
  const changes = summary === null ? null : summary.changes
10794
10803
  const conflicted = changes === null ? [] : changes.conflicted
10795
10804
  const staged = changes === null ? [] : changes.staged
@@ -10859,6 +10868,22 @@ window.__ModuleLoader__.load({
10859
10868
  mounted. A tree with thousands of files scrolls at frame rate. */
10860
10869
  const changesScrollRef = useRef(null)
10861
10870
  const changesViewport = useViewportBox(changesScrollRef)
10871
+ const changeMarquee = useRubberBand({
10872
+ scrollRef: changesScrollRef,
10873
+ drawRef: changesHostRef,
10874
+ rectsOf: () => {
10875
+ const items = changeItemsRef.current
10876
+ const offsets = changeOffsetsRef.current
10877
+ if (items === null || offsets === null || items.length === 0) return []
10878
+ const rects = []
10879
+ for (let index = 0; index < items.length; index += 1) {
10880
+ const entry = items[index]
10881
+ if (entry.kind === 'row' && entry.key.indexOf('ignored:') !== 0) rects.push({ key: entry.key, top: offsets[index], bottom: offsets[index + 1] })
10882
+ }
10883
+ return rects
10884
+ },
10885
+ onPick: (keys) => setSelection(new Set(keys)),
10886
+ })
10862
10887
  const changeItems = useMemo(() => {
10863
10888
  const out = []
10864
10889
  const groupHead = (key, title, entries) => E('div', { className: 'dig-group-head' },
@@ -11042,7 +11067,7 @@ window.__ModuleLoader__.load({
11042
11067
  props.onBulkDiscard === undefined ? null : E('button', { type: 'button', className: 'dig-link dig-link-danger', onClick: () => props.onBulkDiscard() }, t('action.discard')),
11043
11068
  props.onCopySelection === undefined ? null : E('button', { type: 'button', className: 'dig-link', onClick: () => props.onCopySelection() }, t('selection.copyPaths')),
11044
11069
  E('button', { type: 'button', className: 'dig-link', onClick: () => setSelection(new Set()) }, t('selection.clear')))
11045
- return E('div', { className: 'dig-changes' },
11070
+ return E('div', { className: 'dig-changes', ref: changesHostRef },
11046
11071
  aiConfirmDialog,
11047
11072
  head,
11048
11073
  E('div', { className: 'dig-changes-body' },
@@ -11055,9 +11080,9 @@ window.__ModuleLoader__.load({
11055
11080
  onPointerDownCapture: (event) => {
11056
11081
  if (isTypingTarget(event.target) === false) event.currentTarget.focus({ preventScroll: true })
11057
11082
  },
11058
- onPointerDown: onListPointerDown,
11083
+ onPointerDown: changeMarquee.onPointerDown,
11059
11084
  onClickCapture: (event) => {
11060
- if (marqueeSwallowClick.current === true) { event.preventDefault(); event.stopPropagation() }
11085
+ if (changeMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation() }
11061
11086
  },
11062
11087
  },
11063
11088
  collapsed ? null : [
@@ -11074,10 +11099,7 @@ window.__ModuleLoader__.load({
11074
11099
  and nothing on the list (group head, rows) is ever covered. */
11075
11100
  selectionBar,
11076
11101
  composer),
11077
- marquee === null ? null : E('div', {
11078
- className: 'dig-marquee',
11079
- style: { left: marquee.x + 'px', top: marquee.y + 'px', width: marquee.w + 'px', height: marquee.h + 'px' },
11080
- }))
11102
+ E(MarqueeBox, { box: changeMarquee.box }))
11081
11103
  }
11082
11104
 
11083
11105
  /* ============================== history ============================== */
@@ -11143,6 +11165,122 @@ window.__ModuleLoader__.load({
11143
11165
  return { scrollTop: scrollTop, viewH: viewH }
11144
11166
  }
11145
11167
 
11168
+ /* ---- rubber-band (marquee) selection, shared by all three lists ----
11169
+ One hook, three wiring sites (changes, history, branch tree). The
11170
+ contract: `scrollRef` is the scrolling container the SELECTION math
11171
+ runs against, `drawRef` is the element the marquee box is PAINTED in
11172
+ (its own rect — v0.18.x painted from the scroll container's rect while
11173
+ the box lived inside the list wrapper, so the toolbar height above the
11174
+ list shifted every box visually upward; reported v0.19.2), `rectsOf`
11175
+ returns {key,top,bottom} rows in scroll-content coordinates (exact
11176
+ prefix-sums for the windowed lists, a live DOM measure for the
11177
+ variable-height tree), and `onPick` receives the intersecting keys.
11178
+ A 4px threshold keeps plain clicks clicky; pointerdown is preventDefault-ed
11179
+ so drags never start a native text selection; edge auto-scroll and the
11180
+ rAF-throttled sync carry over unchanged from v0.18.x. */
11181
+ function useRubberBand(options) {
11182
+ const [box, setBox] = useState(null)
11183
+ const stateRef = useRef(null)
11184
+ const frameRef = useRef(0)
11185
+ const swallowRef = useRef(false)
11186
+ const optionsRef = useRef(options)
11187
+ optionsRef.current = options
11188
+ const sync = () => {
11189
+ const opts = optionsRef.current
11190
+ const state = stateRef.current
11191
+ const host = opts.scrollRef.current
11192
+ const draw = opts.drawRef.current
11193
+ if (state === null || host === null || draw === null) return
11194
+ const hostRect = host.getBoundingClientRect()
11195
+ const drawRect = draw.getBoundingClientRect()
11196
+ const y0 = state.startY - hostRect.top + state.scrollY0
11197
+ const y1 = state.clientY - hostRect.top + host.scrollTop
11198
+ const low = Math.min(y0, y1)
11199
+ const high = Math.max(y0, y1)
11200
+ const keys = []
11201
+ for (const rect of opts.rectsOf()) {
11202
+ if (rect.top < high && rect.bottom > low) keys.push(rect.key)
11203
+ }
11204
+ opts.onPick(keys)
11205
+ setBox({
11206
+ x: Math.min(state.startX, state.clientX) - drawRect.left,
11207
+ y: Math.min(state.startY, state.clientY) - drawRect.top,
11208
+ w: Math.abs(state.clientX - state.startX),
11209
+ h: Math.abs(state.clientY - state.startY),
11210
+ })
11211
+ /* Auto-scroll when the pointer rides an edge; keep the loop alive while
11212
+ it does so the selection follows the content streaming past. */
11213
+ const EDGE = 28
11214
+ let speed = 0
11215
+ if (state.clientY - hostRect.top < EDGE) speed = -Math.ceil((EDGE - (state.clientY - hostRect.top)) / 3)
11216
+ else if (hostRect.bottom - state.clientY < EDGE) speed = Math.ceil((EDGE - (hostRect.bottom - state.clientY)) / 3)
11217
+ if (speed !== 0) {
11218
+ host.scrollTop += speed * 2
11219
+ cancelAnimationFrame(frameRef.current)
11220
+ frameRef.current = requestAnimationFrame(sync)
11221
+ }
11222
+ }
11223
+ const schedule = () => {
11224
+ cancelAnimationFrame(frameRef.current)
11225
+ frameRef.current = requestAnimationFrame(sync)
11226
+ }
11227
+ useEffect(() => () => cancelAnimationFrame(frameRef.current), [])
11228
+ const onPointerDown = (event) => {
11229
+ if (event.button !== 0) return
11230
+ const opts = optionsRef.current
11231
+ const host = opts.scrollRef.current
11232
+ if (host === null) return
11233
+ if (event.target instanceof Element && event.target.closest('button, input, textarea, select, a') !== null) return
11234
+ // Keep the browser from starting a text selection under the drag (the
11235
+ // history/tree lists dragged out blue text before); the real clicks
11236
+ // still fire — only the selection machinery is suppressed.
11237
+ event.preventDefault()
11238
+ stateRef.current = {
11239
+ startX: event.clientX,
11240
+ startY: event.clientY,
11241
+ scrollY0: host.scrollTop,
11242
+ clientX: event.clientX,
11243
+ clientY: event.clientY,
11244
+ active: false,
11245
+ }
11246
+ const onMove = (moveEvent) => {
11247
+ const state = stateRef.current
11248
+ if (state === null) return
11249
+ state.clientX = moveEvent.clientX
11250
+ state.clientY = moveEvent.clientY
11251
+ if (state.active === false && Math.abs(moveEvent.clientX - state.startX) < 4 && Math.abs(moveEvent.clientY - state.startY) < 4) return
11252
+ state.active = true
11253
+ swallowRef.current = true
11254
+ schedule()
11255
+ }
11256
+ const onUp = () => {
11257
+ window.removeEventListener('pointermove', onMove)
11258
+ window.removeEventListener('pointerup', onUp)
11259
+ cancelAnimationFrame(frameRef.current)
11260
+ const wasActive = stateRef.current !== null && stateRef.current.active === true
11261
+ stateRef.current = null
11262
+ setBox(null)
11263
+ // The drag's pointerup is followed by a synthetic click on whatever
11264
+ // row is under it — swallow exactly that one click.
11265
+ if (wasActive === true) setTimeout(() => { swallowRef.current = false }, 0)
11266
+ }
11267
+ window.addEventListener('pointermove', onMove)
11268
+ window.addEventListener('pointerup', onUp)
11269
+ }
11270
+ return { box: box, onPointerDown: onPointerDown, swallowRef: swallowRef }
11271
+ }
11272
+
11273
+ /* The marquee box itself: a viewport-anchored absolute div inside the
11274
+ draw host (invariant 13: never position:fixed inside the workbench's
11275
+ contain:layout). */
11276
+ function MarqueeBox(props) {
11277
+ if (props.box === null) return null
11278
+ return E('div', {
11279
+ className: 'dig-marquee',
11280
+ style: { left: props.box.x + 'px', top: props.box.y + 'px', width: props.box.w + 'px', height: props.box.h + 'px' },
11281
+ })
11282
+ }
11283
+
11146
11284
  /* First index whose cumulative offset exceeds `at` (binary search over the
11147
11285
  prefix-sum array; offsets.length === items.length + 1). */
11148
11286
  function windowStart(offsets, at) {
@@ -11249,11 +11387,44 @@ window.__ModuleLoader__.load({
11249
11387
  return 0
11250
11388
  }
11251
11389
 
11390
+ /* The author filter pins "me (name)" to the top (v0.19.3). The identity
11391
+ is resolved once per repo root and kept for the panel lifetime — git
11392
+ identities don't churn within a session — so re-opening the Git tab or
11393
+ hopping sessions never re-runs `git config`. */
11394
+ const gitMeCache = new Map()
11395
+
11252
11396
  function HistoryList(props) {
11253
11397
  const t = props.t
11254
11398
  const commits = props.commits
11255
11399
  const historyScrollRef = useRef(null)
11400
+ const [me, setMe] = useState(() => (props.base !== undefined && gitMeCache.has(props.base.repoRoot) === true ? gitMeCache.get(props.base.repoRoot) : null))
11401
+ useEffect(() => {
11402
+ const base = props.base
11403
+ if (base === undefined || base.repoRoot === undefined) return
11404
+ if (gitMeCache.has(base.repoRoot) === true) { setMe(gitMeCache.get(base.repoRoot)); return }
11405
+ let alive = true
11406
+ props.request('me', Object.assign({}, base)).then((data) => {
11407
+ const who = data !== null && typeof data === 'object' && typeof data.name === 'string' && data.name !== ''
11408
+ ? { name: data.name, email: typeof data.email === 'string' ? data.email : '' }
11409
+ : null
11410
+ gitMeCache.set(base.repoRoot, who)
11411
+ if (alive === true) setMe(who)
11412
+ }).catch((error) => { void error })
11413
+ return () => { alive = false }
11414
+ }, [props.base])
11256
11415
  const viewport = useViewportBox(historyScrollRef)
11416
+ /* v0.19.3: the commit log rubber-bands exactly like the changes list —
11417
+ same hook, same gestures. Fixed HISTORY_ROW_H rows make the rects a
11418
+ pure index map, so even a windowed (mostly unmounted) list drags
11419
+ perfectly; picks land in the SAME pickedHashes set the Shift/Ctrl
11420
+ clicks and the commit context menu already share. */
11421
+ const historyHostRef = useRef(null)
11422
+ const historyMarquee = useRubberBand({
11423
+ scrollRef: historyScrollRef,
11424
+ drawRef: historyHostRef,
11425
+ rectsOf: () => rows.map((row, index) => ({ key: row.commit.hash, top: index * HISTORY_ROW_H, bottom: (index + 1) * HISTORY_ROW_H })),
11426
+ onPick: (keys) => { if (props.onPicksChange !== undefined) props.onPicksChange(new Set(keys)) },
11427
+ })
11257
11428
  const pickAnchor = useRef(null)
11258
11429
  const [text, setText] = useState('')
11259
11430
  const [author, setAuthor] = useState('')
@@ -11309,6 +11480,92 @@ window.__ModuleLoader__.load({
11309
11480
  }, [commits, text, author, refName, since, order])
11310
11481
 
11311
11482
  const rows = useMemo(() => buildRows(filtered), [filtered])
11483
+
11484
+ /* Weibo-style infinite scroll (v0.19.3): descending lists auto-load when
11485
+ the user scrolls near the bottom edge, ascending ones near the top
11486
+ edge — wheel and scrollbar drags both surface as scroll events, no
11487
+ extra clicks. The button stays as the visible affordance. The guard
11488
+ keeps one request in flight; it resets when busy settles. */
11489
+ const infiniteGuardRef = useRef(false)
11490
+ const growAnchorRef = useRef(null)
11491
+ const mergeLocatedRef = useRef(false)
11492
+ const requestMore = () => {
11493
+ if (props.hasMore !== true || props.busy === true || infiniteGuardRef.current === true) return
11494
+ const host = historyScrollRef.current
11495
+ growAnchorRef.current = { length: rows.length, height: host === null ? 0 : host.scrollHeight }
11496
+ infiniteGuardRef.current = true
11497
+ props.onLoadMore()
11498
+ }
11499
+ /* The button itself is the trigger (weibo-style, v0.19.3): an
11500
+ IntersectionObserver on the load-more button fires wherever it sits —
11501
+ bottom while descending, top while ascending — covering wheel,
11502
+ scrollbar drags, first paint, and short pages alike. Re-created per
11503
+ page so a settled load re-evaluates: if the button is STILL visible
11504
+ (huge viewport / tiny page) the next page follows automatically. */
11505
+ const loadMoreRef = useRef(null)
11506
+ /* A scroll that lands on the button WHILE a guarded load is still in
11507
+ flight loses its one IO callback to the busy bail-out, and the
11508
+ observer is not re-created when that load finishes with the same
11509
+ page size (mount refresh). Re-check the button's geometry when busy
11510
+ settles: still within the rootMargin window → continue loading. */
11511
+ useEffect(() => {
11512
+ if (props.busy !== false) return
11513
+ infiniteGuardRef.current = false
11514
+ const node = loadMoreRef.current
11515
+ const root = historyScrollRef.current
11516
+ if (node === null || root === null || props.hasMore !== true) return
11517
+ const rect = node.getBoundingClientRect()
11518
+ const box = root.getBoundingClientRect()
11519
+ if (rect.bottom >= box.top - 240 && rect.top <= box.bottom + 240) requestMore()
11520
+ }, [props.busy])
11521
+ useEffect(() => {
11522
+ const node = loadMoreRef.current
11523
+ const root = historyScrollRef.current
11524
+ if (node === null || root === null || props.hasMore !== true) return
11525
+ let frame = 0
11526
+ const io = new IntersectionObserver((entries) => {
11527
+ if (entries.some((entry) => entry.isIntersecting === true) === false) return
11528
+ if (frame === 0) {
11529
+ frame = requestAnimationFrame(() => { frame = 0; requestMore() })
11530
+ }
11531
+ }, { root: root, rootMargin: '240px 0px' })
11532
+ io.observe(node)
11533
+ return () => { if (frame !== 0) cancelAnimationFrame(frame); io.disconnect() }
11534
+ }, [props.hasMore, order, rows.length])
11535
+ /* Ascending growth inserts rows above the viewport: re-anchor by the
11536
+ height delta so the rows the user was reading stay put. */
11537
+ useEffect(() => {
11538
+ const anchor = growAnchorRef.current
11539
+ if (anchor === null || rows.length <= anchor.length) return
11540
+ growAnchorRef.current = null
11541
+ const host = historyScrollRef.current
11542
+ if (order !== 'asc' || host === null) return
11543
+ host.scrollTop = host.scrollTop + (host.scrollHeight - anchor.height)
11544
+ }, [rows.length, order])
11545
+ /* v0.19.3 IDEA-style merge memory: when a browsed branch has a merge
11546
+ relationship with the PREVIOUSLY browsed one, its list opens scrolled
11547
+ to that merge commit ('merge-point' resolved it) — the "2 was merged
11548
+ here" moment is on screen immediately, while 1's newer commits stay
11549
+ one wheel-scroll away. Fires only on anchor CHANGE; null = no
11550
+ relationship (or first hop), leave the top alone. */
11551
+ useEffect(() => {
11552
+ const anchor = props.mergeAnchor
11553
+ if (anchor === undefined || anchor === null) { mergeLocatedRef.current = false; return }
11554
+ const index = rows.findIndex((row) => row.commit.hash === anchor)
11555
+ if (index < 0) {
11556
+ /* The merge point can sit below the first page on a long branch
11557
+ (real repos easily outrun LIMIT). Keep auto-loading — the same
11558
+ weibo path as the edge trigger — until it surfaces or the log
11559
+ is exhausted; one location per anchor, so later 'load more'
11560
+ never yanks the view back. */
11561
+ if (mergeLocatedRef.current === false && props.hasMore === true && props.busy !== true) requestMore()
11562
+ return
11563
+ }
11564
+ if (mergeLocatedRef.current === true) return
11565
+ mergeLocatedRef.current = true
11566
+ const host = historyScrollRef.current
11567
+ if (host !== null) host.scrollTop = Math.max(0, index * HISTORY_ROW_H - 8)
11568
+ }, [props.mergeAnchor, rows.length, props.busy])
11312
11569
  /* Multi-select gestures (issue #13): the rendered row order is what the
11313
11570
  Shift-range walks, and the range is computed over the FILTERED rows —
11314
11571
  the same list the eye sees. A plain click only SELECTS (single-replace,
@@ -11410,7 +11667,12 @@ window.__ModuleLoader__.load({
11410
11667
  }),
11411
11668
  E(FilterSelect, {
11412
11669
  label: t('filter.user'), value: author, hint: t('filter.user'),
11413
- options: [allOption].concat(authors.map((name) => ({ value: name, label: name }))),
11670
+ /* "Me" first (v0.19.3): resolved from git config and offered even
11671
+ when this branch has no commits of mine — selecting it over an
11672
+ empty list is the answer "I never committed here". */
11673
+ options: [allOption]
11674
+ .concat(me !== null && me.name !== '' ? [{ value: me.name, label: t('history.me') + ' (' + me.name + ')' }] : [])
11675
+ .concat(authors.map((name) => ({ value: name, label: name }))),
11414
11676
  onOpenMenu: (event, options) => openFilterMenu(event, options, author, setAuthor),
11415
11677
  }),
11416
11678
  E(FilterSelect, {
@@ -11447,12 +11709,22 @@ window.__ModuleLoader__.load({
11447
11709
  const winEnd = Math.min(rows.length, winStart + Math.ceil(viewport.viewH / HISTORY_ROW_H) + WINDOW_OVERSCAN * 2)
11448
11710
  const visibleRows = rows.slice(winStart, winEnd)
11449
11711
 
11450
- return E('div', { className: 'dig-history' }, filterBar,
11712
+ /* Ascending renders oldest-first, so load-more (older commits) lives
11713
+ at the TOP; descending keeps it at the bottom where it always was. */
11714
+ const loadMoreButton = props.hasMore === true
11715
+ ? E('button', { ref: loadMoreRef, type: 'button', className: 'dig-load-more', disabled: props.busy === true, onClick: requestMore }, t('history.loadMore'))
11716
+ : null
11717
+
11718
+ return E('div', { className: 'dig-history', ref: historyHostRef }, filterBar,
11451
11719
  E('div', {
11452
11720
  className: 'dig-history-scroll',
11453
11721
  ref: historyScrollRef,
11454
11722
  tabIndex: 0,
11455
11723
  onKeyDown: onListKeyDown,
11724
+ onPointerDown: historyMarquee.onPointerDown,
11725
+ onClickCapture: (event) => {
11726
+ if (historyMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation() }
11727
+ },
11456
11728
  onPointerDownCapture: (event) => {
11457
11729
  if (isTypingTarget(event.target) === false) event.currentTarget.focus({ preventScroll: true })
11458
11730
  },
@@ -11471,6 +11743,7 @@ window.__ModuleLoader__.load({
11471
11743
  onMouseLeave: (event) => event.currentTarget.removeAttribute('data-hover-lane'),
11472
11744
  },
11473
11745
  rows.length === 0 ? E('div', { className: 'dig-empty' }, dirtyFilter ? t('filter.none') : t('history.empty')) : null,
11746
+ order === 'asc' ? loadMoreButton : null,
11474
11747
  winStart > 0 ? E('div', { key: 'pad-top', style: { height: String(winStart * HISTORY_ROW_H) + 'px' } }) : null,
11475
11748
  visibleRows.map((row) => E(CommitRow, {
11476
11749
  key: row.commit.hash,
@@ -11480,9 +11753,8 @@ window.__ModuleLoader__.load({
11480
11753
  onRowClick: handleRowClick, onOpenDetail: props.onSelect, onMenu: props.onMenu, openMenuAt: props.openMenuAt,
11481
11754
  })),
11482
11755
  winEnd < rows.length ? E('div', { key: 'pad-bottom', style: { height: String((rows.length - winEnd) * HISTORY_ROW_H) + 'px' } }) : null,
11483
- props.hasMore === true
11484
- ? E('button', { type: 'button', className: 'dig-load-more', disabled: props.busy === true, onClick: props.onLoadMore }, t('history.loadMore'))
11485
- : null))
11756
+ order === 'desc' ? loadMoreButton : null),
11757
+ E(MarqueeBox, { box: historyMarquee.box }))
11486
11758
  }
11487
11759
 
11488
11760
  function CommitDetail(props) {
@@ -11956,15 +12228,25 @@ window.__ModuleLoader__.load({
11956
12228
  return data
11957
12229
  }, [base])
11958
12230
 
12231
+ /* ---- branch browsing (v0.19.3, IDEA-style) ----
12232
+ Double-clicking a tree branch no longer checks it out: it walks THAT
12233
+ branch's log (browseRev = '' keeps the HEAD view). The LAST browsed
12234
+ branch is remembered, so 2-then-1 lands 1's list on the merge commit
12235
+ that carried 2 into 1 ('merge-point') — 1's newer commits stay one
12236
+ wheel-scroll away, nothing is truncated. */
12237
+ const [browseRev, setBrowseRev] = useState('')
12238
+ const [mergeAnchor, setMergeAnchor] = useState(null)
12239
+ const lastBrowseRef = useRef(null)
11959
12240
  const loadCommits = useCallback(async (skip, overridePath) => {
11960
12241
  const activePath = overridePath === undefined ? pathFilter : overridePath
11961
12242
  const payload = Object.assign({}, base, { skip: skip === undefined ? 0 : skip, limit: 120 })
12243
+ if (browseRev !== '') payload.rev = browseRev
11962
12244
  if (activePath !== undefined && activePath !== '') payload.path = activePath
11963
12245
  const data = await request('log', payload)
11964
12246
  setCommits((previous) => (skip === undefined || skip === 0 ? data.commits : previous.concat(data.commits)))
11965
12247
  setHasMore(data.hasMore)
11966
12248
  return data
11967
- }, [base, pathFilter])
12249
+ }, [base, pathFilter, browseRev])
11968
12250
 
11969
12251
  /* `fetchRemote` turns the refresh into what the rail button means: "see
11970
12252
  whether the remote has anything new to pull" — a `git fetch --prune`
@@ -12007,13 +12289,40 @@ window.__ModuleLoader__.load({
12007
12289
  (a paged fetch never reaches this path), replaced on every
12008
12290
  refresh — the auto-refresh included — so a session switch through
12009
12291
  the same workspace seeds fresh data, not the mount-time
12010
- snapshot. */
12011
- writePaintPage(repoRoot, { summary: summaryData, branches: branchesData, commits: page.commits, hasMore: page.hasMore })
12292
+ snapshot. A branch-browsing page (browseRev) must not evict the
12293
+ HEAD page: the next mount opens on HEAD and would otherwise be
12294
+ seeded with another branch's log. */
12295
+ if (browseRev === '') writePaintPage(repoRoot, { summary: summaryData, branches: branchesData, commits: page.commits, hasMore: page.hasMore })
12012
12296
  })
12013
12297
  } finally {
12014
12298
  if (withFetch === true) setRefreshSpinning(false)
12015
12299
  }
12016
- }, [guard, loadSummary, loadBranches, loadCommits, repoRoot, remoteReady, base])
12300
+ }, [guard, loadSummary, loadBranches, loadCommits, repoRoot, remoteReady, base, browseRev])
12301
+
12302
+ /* Double-click a branch (or the HEAD row): browse its history. The merge
12303
+ anchor comes from the PREVIOUSLY browsed branch — 'merge-point' finds
12304
+ the newest merge that carried it into the one now opened, and the
12305
+ history list scrolls there once (null = no relationship, top). */
12306
+ const browseBranch = useCallback(async (entry) => {
12307
+ const name = String(entry.name)
12308
+ const previous = lastBrowseRef.current
12309
+ if (previous === name) return
12310
+ lastBrowseRef.current = name
12311
+ setMergeAnchor(null)
12312
+ setBrowseRev(name)
12313
+ if (previous !== null) {
12314
+ try {
12315
+ const data = await request('merge-point', Object.assign({}, base, { from: previous, to: name }))
12316
+ if (data !== null && typeof data === 'object' && typeof data.hash === 'string' && data.hash !== '') setMergeAnchor(data.hash)
12317
+ } catch (error) { void error }
12318
+ }
12319
+ }, [base, request])
12320
+ /* A repository switch forgets the browsing context entirely. */
12321
+ useEffect(() => {
12322
+ lastBrowseRef.current = null
12323
+ setBrowseRev('')
12324
+ setMergeAnchor(null)
12325
+ }, [repoRoot])
12017
12326
 
12018
12327
  useEffect(() => {
12019
12328
  if (repoRoot === null) return undefined
@@ -12963,6 +13272,9 @@ window.__ModuleLoader__.load({
12963
13272
  onPicksChange: setPickedCommits,
12964
13273
  onSelectAll: (hashes) => setPickedCommits(new Set(hashes)),
12965
13274
  onKeyDown: onHistoryKeyDown,
13275
+ mergeAnchor: mergeAnchor,
13276
+ base: base,
13277
+ request: request,
12966
13278
  })
12967
13279
 
12968
13280
  const diffPane = patch === '' && patchLoading === false ? null : E('div', { className: 'dig-diff-pane', 'data-pane': 'diff', style: paneStyle('diff'), onContextMenu: onDiffPaneContextMenu },
@@ -13512,19 +13824,19 @@ window.__ModuleLoader__.load({
13512
13824
  title: t('toolbar.tree'),
13513
13825
  onClick: () => foldTree(!treeOpen),
13514
13826
  }, E(Icon, { name: treeOpen ? 'eye' : 'eyeOff' }))),
13515
- treeOpen ? E('div', { className: 'dig-compact-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13827
+ treeOpen ? E('div', { className: 'dig-compact-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13516
13828
  treeOpen ? gutter('tree', 'prev') : null,
13517
13829
  view === 'changes' ? changesPane : E('div', { className: 'dig-pane dig-pane-main' }, historyPane, diffGutter, diffPane))
13518
13830
  } else if (columns) {
13519
13831
  body = E('div', { className: 'dig-body dig-body-columns', ref: bodyRef },
13520
- treeOpen ? E('div', { className: 'dig-pane dig-pane-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13832
+ treeOpen ? E('div', { className: 'dig-pane dig-pane-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13521
13833
  treeOpen ? gutter('tree', 'prev') : null,
13522
13834
  E('div', { className: 'dig-pane dig-pane-main' }, historyPane, diffGutter, diffPane),
13523
13835
  gutter('changes', 'next'),
13524
13836
  E('div', { className: 'dig-pane dig-pane-changes', 'data-pane': 'changes', style: paneStyle('changes') }, changesPane))
13525
13837
  } else {
13526
13838
  body = E('div', { className: 'dig-body dig-body-stack', ref: bodyRef },
13527
- treeOpen ? E('div', { className: 'dig-pane dig-pane-tree-stack', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13839
+ treeOpen ? E('div', { className: 'dig-pane dig-pane-tree-stack', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13528
13840
  treeOpen ? gutter('tree', 'prev') : null,
13529
13841
  E('div', { className: 'dig-pane dig-pane-changes-stack', 'data-pane': 'changes', style: paneStyle('changes') }, changesPane),
13530
13842
  gutter('changes', 'prev'),
@@ -14049,7 +14361,10 @@ window.__ModuleLoader__.load({
14049
14361
  '.dig-picker-name{flex:none;font-weight:600}',
14050
14362
  '.dig-picker-kind{color:var(--dsw-alias-label-tertiary);flex:none}',
14051
14363
  '.dig-picker-path{color:var(--dsw-alias-label-tertiary);margin-left:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:50%}',
14052
- '.dig-tree{display:flex;flex-direction:column;min-height:0;flex:1}',
14364
+ /* position:relative: the marquee box paints against this wrapper (the
14365
+ scroll container is offset by the search field — same rule as
14366
+ .dig-history above). */
14367
+ '.dig-tree{display:flex;flex-direction:column;min-height:0;flex:1;position:relative}',
14053
14368
  '.dig-tree-scroll{flex:1;overflow:auto;padding-bottom:6px}',
14054
14369
  '.dig-section{display:flex;flex-direction:column}',
14055
14370
  '.dig-section-head{display:flex;align-items:center;gap:4px;width:100%;padding:3px 8px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-weight:600;cursor:pointer;text-align:left}',
@@ -14072,6 +14387,10 @@ window.__ModuleLoader__.load({
14072
14387
  '.dig-chevron-open{transform:rotate(0deg)}',
14073
14388
  '.dig-row{display:flex;align-items:center;gap:5px;padding:2px 8px;cursor:pointer;white-space:nowrap;overflow:hidden}',
14074
14389
  '.dig-row:hover{background:var(--dsw-alias-interactive-bg-hover)}',
14390
+ /* v0.19.3: the HEAD section row behaves like a branch row (click
14391
+ selects, double-click browses, right-click menus) — same hover cue. */
14392
+ '.dig-head-branch{cursor:pointer;transition:background .12s}',
14393
+ '.dig-head-branch:hover{background:var(--dsw-alias-interactive-bg-hover)}',
14075
14394
  '.dig-row-head{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary))}',
14076
14395
  '.dig-row-selected{background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover));color:var(--dsw-alias-label-primary);box-shadow:inset 2px 0 0 var(--dsw-alias-brand-primary)}',
14077
14396
  '.dig-row-remote .dig-row-label{color:var(--dsw-alias-label-secondary);font-weight:500}',
@@ -14090,7 +14409,10 @@ window.__ModuleLoader__.load({
14090
14409
  '.dig-badge.dig-badge-ahead,.dig-badge.dig-badge-behind{padding:0 6px}',
14091
14410
  '.dig-badge-muted{opacity:.8}',
14092
14411
  '.dig-badge-worktree{padding:0 3px;display:inline-flex;align-items:center;height:14px}',
14093
- '.dig-history{display:flex;flex-direction:column;min-height:0;flex:1}',
14412
+ /* position:relative: the marquee box (rubber-band selection) paints
14413
+ against this wrapper — the scroll container below is offset by the
14414
+ filter bar, and painting from the wrong rect was the v0.19.2 drift. */
14415
+ '.dig-history{display:flex;flex-direction:column;min-height:0;flex:1;position:relative}',
14094
14416
  '.dig-history-scroll{flex:1;overflow:auto}',
14095
14417
  '.dig-commit{display:flex;align-items:center;gap:6px;padding:1px 8px 1px 0;cursor:pointer;height:26px;overflow:hidden}',
14096
14418
  '.dig-commit:hover{background:var(--dsw-alias-interactive-bg-hover)}',
@@ -14372,7 +14694,8 @@ window.__ModuleLoader__.load({
14372
14694
  '.dig-cs-item[data-current=true]{color:var(--dsw-alias-brand-primary)}',
14373
14695
  '.dig-cs-item svg{flex:none}',
14374
14696
  '.dig-cs-item-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
14375
- '.dig-cs-item-path{margin-left:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:11px;max-width:140px}',
14697
+ '.dig-cs-item-main{display:flex;flex-direction:column;min-width:0;flex:1}',
14698
+ '.dig-cs-item-sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:12px}',
14376
14699
  '.dig-cs-item-note{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none}',
14377
14700
  '.dig-cs-item-counts{display:inline-flex;gap:5px;margin-left:auto;flex:none;font-variant-numeric:tabular-nums;font-size:11px}',
14378
14701
  '.dig-cs-static{color:var(--dsw-alias-label-tertiary);cursor:default}',
@@ -14793,17 +15116,27 @@ window.__ModuleLoader__.load({
14793
15116
  ahead > 0 ? E('span', { className: 'dig-cs-badge dig-cs-ahead', title: t('composer.aheadHint') }, '↑' + String(ahead)) : null,
14794
15117
  error !== '' ? E('span', { className: 'dig-cs-error', title: error }, '×') : null,
14795
15118
  menu === 'repo' ? E('div', { className: 'dig-cs-menu', 'data-cordis-panel': '' },
14796
- repoPaths.map((path) => E('button', {
14797
- key: path,
14798
- type: 'button',
14799
- className: 'dig-cs-item',
14800
- 'data-current': path === repoRoot ? 'true' : 'false',
14801
- title: path,
14802
- onClick: () => pickRepoRoot(path),
14803
- },
14804
- E(Icon, { name: 'commit', size: 11 }),
14805
- E('span', { className: 'dig-cs-item-name' }, baseNameOf(path)),
14806
- E('span', { className: 'dig-cs-item-path' }, path))))
15119
+ /* v0.19.2: one row per checkout — the repository's own name first
15120
+ (origin URL basename, falling back to the directory name), and the
15121
+ single-level directory name as a small sub line only when the two
15122
+ differ (two clones of one repository stay tellable apart). The
15123
+ full path lives in the tooltip, not in the row. */
15124
+ data.repos.map((row) => {
15125
+ const dirName = baseNameOf(row.path)
15126
+ const displayName = row.origin !== undefined && row.origin !== '' ? row.origin : dirName
15127
+ return E('button', {
15128
+ key: row.path,
15129
+ type: 'button',
15130
+ className: 'dig-cs-item',
15131
+ 'data-current': row.path === repoRoot ? 'true' : 'false',
15132
+ title: row.path,
15133
+ onClick: () => pickRepoRoot(row.path),
15134
+ },
15135
+ E(Icon, { name: 'commit', size: 11 }),
15136
+ E('span', { className: 'dig-cs-item-main' },
15137
+ E('span', { className: 'dig-cs-item-name' }, displayName),
15138
+ dirName !== displayName ? E('span', { className: 'dig-cs-item-sub' }, dirName) : null))
15139
+ }))
14807
15140
  : null,
14808
15141
  menu === 'branch' ? (branchLoading === true || branchList === null
14809
15142
  ? E('div', { className: 'dig-cs-menu', 'data-cordis-panel': '' }, E('div', { className: 'dig-cs-item dig-cs-static' }, '…'))