dsh-better-workspace 0.12.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-better-workspace",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "main": "./src/index.js",
5
5
  "description": "侧边栏工作区两层层级树:工作区按磁盘目录嵌套(官方 workspace-tree 语义),名称中的 / 即虚拟分组(web/前端 · web/后端);添加工作区由官方目录流承载。Two-layer hierarchy tree for the DSH sidebar workspace list.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-better-workspace",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "侧边栏工作区两层层级树:磁盘目录嵌套(官方 workspace-tree 语义)+ 名称 / 虚拟分组,添加工作区走官方目录流。Two-layer workspace tree for the DSH sidebar: disk-path nesting plus \"/\"-name groups; add-workspace rides the official directory flow.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -40,6 +40,16 @@ window.__ModuleLoader__.load({
40
40
  'empty': '暂无工作区',
41
41
  'empty.search': '没有匹配的结果',
42
42
  'session.new': '新会话',
43
+ 'viewOptions.label': '视图选项',
44
+ 'viewOptions.groupBy': '分组方式',
45
+ 'viewOptions.byWorkspace': '按工作区',
46
+ 'viewOptions.byWorkspaceTree': '按工作区树',
47
+ 'viewOptions.flat': '单列表',
48
+ 'viewOptions.orderBy': '排序方式',
49
+ 'viewOptions.manualOrder': '手动排序',
50
+ 'viewOptions.updatedOrder': '最近更新',
51
+ 'viewOptions.sessionSlash': '会话按"/"分组',
52
+ 'viewOptions.workspaceSlash': '工作区按"/"分组',
43
53
  'group.ungrouped': '未分组',
44
54
  'sessions.expand': '展开 {n} 个会话',
45
55
  'sessions.collapse': '收起',
@@ -150,6 +160,16 @@ window.__ModuleLoader__.load({
150
160
  'empty': 'No workspaces yet',
151
161
  'empty.search': 'No matches',
152
162
  'session.new': 'New session',
163
+ 'viewOptions.label': 'View options',
164
+ 'viewOptions.groupBy': 'Group by',
165
+ 'viewOptions.byWorkspace': 'Workspaces',
166
+ 'viewOptions.byWorkspaceTree': 'Workspace tree',
167
+ 'viewOptions.flat': 'In one list',
168
+ 'viewOptions.orderBy': 'Order by',
169
+ 'viewOptions.manualOrder': 'Manual',
170
+ 'viewOptions.updatedOrder': 'Last updated',
171
+ 'viewOptions.sessionSlash': 'Group sessions by "/"',
172
+ 'viewOptions.workspaceSlash': 'Group workspaces by "/"',
153
173
  'group.ungrouped': 'Ungrouped',
154
174
  'sessions.expand': 'Show {n} more sessions',
155
175
  'sessions.collapse': 'Collapse',
@@ -268,6 +288,16 @@ window.__ModuleLoader__.load({
268
288
  'empty': 'لا توجد مساحات عمل بعد',
269
289
  'empty.search': 'لا توجد نتائج مطابقة',
270
290
  'session.new': 'جلسة جديدة',
291
+ 'viewOptions.label': 'خيارات العرض',
292
+ 'viewOptions.groupBy': 'التجميع حسب',
293
+ 'viewOptions.byWorkspace': 'مساحات العمل',
294
+ 'viewOptions.byWorkspaceTree': 'شجرة مساحات العمل',
295
+ 'viewOptions.flat': 'قائمة واحدة',
296
+ 'viewOptions.orderBy': 'الترتيب حسب',
297
+ 'viewOptions.manualOrder': 'يدوي',
298
+ 'viewOptions.updatedOrder': 'آخر تحديث',
299
+ 'viewOptions.sessionSlash': 'تجميع الجلسات حسب "/"',
300
+ 'viewOptions.workspaceSlash': 'تجميع مساحات العمل حسب "/"',
271
301
  'group.ungrouped': 'بلا مجموعة',
272
302
  'sessions.expand': 'عرض {n} جلسات إضافية',
273
303
  'sessions.collapse': 'طيّ',
@@ -378,6 +408,16 @@ window.__ModuleLoader__.load({
378
408
  'empty': 'Noch keine Arbeitsbereiche',
379
409
  'empty.search': 'Keine Treffer',
380
410
  'session.new': 'Neue Sitzung',
411
+ 'viewOptions.label': 'Ansichtsoptionen',
412
+ 'viewOptions.groupBy': 'Gruppieren',
413
+ 'viewOptions.byWorkspace': 'Arbeitsbereiche',
414
+ 'viewOptions.byWorkspaceTree': 'Arbeitsbereichsbaum',
415
+ 'viewOptions.flat': 'In einer Liste',
416
+ 'viewOptions.orderBy': 'Sortieren',
417
+ 'viewOptions.manualOrder': 'Manuell',
418
+ 'viewOptions.updatedOrder': 'Zuletzt aktualisiert',
419
+ 'viewOptions.sessionSlash': 'Sitzungen nach "/" gruppieren',
420
+ 'viewOptions.workspaceSlash': 'Arbeitsbereiche nach "/" gruppieren',
381
421
  'group.ungrouped': 'Ohne Gruppe',
382
422
  'sessions.expand': '{n} Sitzungen einblenden',
383
423
  'sessions.collapse': 'Einklappen',
@@ -488,6 +528,16 @@ window.__ModuleLoader__.load({
488
528
  'empty': 'Aucun espace de travail',
489
529
  'empty.search': 'Aucun résultat',
490
530
  'session.new': 'Nouvelle session',
531
+ 'viewOptions.label': 'Options d’affichage',
532
+ 'viewOptions.groupBy': 'Regrouper par',
533
+ 'viewOptions.byWorkspace': 'Espaces de travail',
534
+ 'viewOptions.byWorkspaceTree': 'Arborescence des espaces',
535
+ 'viewOptions.flat': 'En une liste',
536
+ 'viewOptions.orderBy': 'Trier par',
537
+ 'viewOptions.manualOrder': 'Manuel',
538
+ 'viewOptions.updatedOrder': 'Dernière mise à jour',
539
+ 'viewOptions.sessionSlash': 'Grouper les sessions par « / »',
540
+ 'viewOptions.workspaceSlash': 'Grouper les espaces par « / »',
491
541
  'group.ungrouped': 'Sans groupe',
492
542
  'sessions.expand': 'Afficher {n} sessions',
493
543
  'sessions.collapse': 'Réduire',
@@ -598,6 +648,16 @@ window.__ModuleLoader__.load({
598
648
  'empty': 'अभी कोई कार्यस्थान नहीं',
599
649
  'empty.search': 'कोई मेल खाता परिणाम नहीं',
600
650
  'session.new': 'नया सत्र',
651
+ 'viewOptions.label': 'दृश्य विकल्प',
652
+ 'viewOptions.groupBy': 'समूहीकरण',
653
+ 'viewOptions.byWorkspace': 'कार्यक्षेत्र अनुसार',
654
+ 'viewOptions.byWorkspaceTree': 'कार्यक्षेत्र ट्री अनुसार',
655
+ 'viewOptions.flat': 'एक सूची में',
656
+ 'viewOptions.orderBy': 'क्रम',
657
+ 'viewOptions.manualOrder': 'मैन्युअल',
658
+ 'viewOptions.updatedOrder': 'हाल का अपडेट',
659
+ 'viewOptions.sessionSlash': 'सत्रों को "/" से समूहित करें',
660
+ 'viewOptions.workspaceSlash': 'कार्यक्षेत्रों को "/" से समूहित करें',
601
661
  'group.ungrouped': 'बिना समूह',
602
662
  'sessions.expand': '{n} सत्र दिखाएँ',
603
663
  'sessions.collapse': 'समेटें',
@@ -708,6 +768,16 @@ window.__ModuleLoader__.load({
708
768
  'empty': 'Belum ada ruang kerja',
709
769
  'empty.search': 'Tidak ada hasil yang cocok',
710
770
  'session.new': 'Sesi baru',
771
+ 'viewOptions.label': 'Opsi tampilan',
772
+ 'viewOptions.groupBy': 'Kelompokkan berdasarkan',
773
+ 'viewOptions.byWorkspace': 'Ruang kerja',
774
+ 'viewOptions.byWorkspaceTree': 'Pohon ruang kerja',
775
+ 'viewOptions.flat': 'Satu daftar',
776
+ 'viewOptions.orderBy': 'Urutkan',
777
+ 'viewOptions.manualOrder': 'Manual',
778
+ 'viewOptions.updatedOrder': 'Terakhir diperbarui',
779
+ 'viewOptions.sessionSlash': 'Kelompokkan sesi per "/"',
780
+ 'viewOptions.workspaceSlash': 'Kelompokkan ruang kerja per "/"',
711
781
  'group.ungrouped': 'Tanpa grup',
712
782
  'sessions.expand': 'Tampilkan {n} sesi',
713
783
  'sessions.collapse': 'Ciutkan',
@@ -818,6 +888,16 @@ window.__ModuleLoader__.load({
818
888
  'empty': 'Nessuna area di lavoro',
819
889
  'empty.search': 'Nessun risultato',
820
890
  'session.new': 'Nuova sessione',
891
+ 'viewOptions.label': 'Opzioni vista',
892
+ 'viewOptions.groupBy': 'Raggruppa per',
893
+ 'viewOptions.byWorkspace': 'Aree di lavoro',
894
+ 'viewOptions.byWorkspaceTree': 'Albero aree di lavoro',
895
+ 'viewOptions.flat': 'In un elenco',
896
+ 'viewOptions.orderBy': 'Ordina per',
897
+ 'viewOptions.manualOrder': 'Manuale',
898
+ 'viewOptions.updatedOrder': 'Ultimo aggiornamento',
899
+ 'viewOptions.sessionSlash': 'Raggruppa sessioni per "/"',
900
+ 'viewOptions.workspaceSlash': 'Raggruppa aree per "/"',
821
901
  'group.ungrouped': 'Senza gruppo',
822
902
  'sessions.expand': 'Mostra {n} sessioni',
823
903
  'sessions.collapse': 'Comprimi',
@@ -928,6 +1008,16 @@ window.__ModuleLoader__.load({
928
1008
  'empty': 'ワークスペースがありません',
929
1009
  'empty.search': '一致する結果がありません',
930
1010
  'session.new': '新しいセッション',
1011
+ 'viewOptions.label': '表示オプション',
1012
+ 'viewOptions.groupBy': 'グループ化',
1013
+ 'viewOptions.byWorkspace': 'ワークスペースごと',
1014
+ 'viewOptions.byWorkspaceTree': 'ワークスペースツリーごと',
1015
+ 'viewOptions.flat': '1 つのリスト',
1016
+ 'viewOptions.orderBy': '並べ替え',
1017
+ 'viewOptions.manualOrder': '手動',
1018
+ 'viewOptions.updatedOrder': '最近の更新',
1019
+ 'viewOptions.sessionSlash': 'セッションを「/」でグループ化',
1020
+ 'viewOptions.workspaceSlash': 'ワークスペースを「/」でグループ化',
931
1021
  'group.ungrouped': '未分類',
932
1022
  'sessions.expand': '{n} 件のセッションを展開',
933
1023
  'sessions.collapse': '折りたたむ',
@@ -1038,6 +1128,16 @@ window.__ModuleLoader__.load({
1038
1128
  'empty': '워크스페이스가 없습니다',
1039
1129
  'empty.search': '일치하는 결과가 없습니다',
1040
1130
  'session.new': '새 세션',
1131
+ 'viewOptions.label': '보기 옵션',
1132
+ 'viewOptions.groupBy': '그룹화',
1133
+ 'viewOptions.byWorkspace': '작업 공간별',
1134
+ 'viewOptions.byWorkspaceTree': '작업 공간 트리별',
1135
+ 'viewOptions.flat': '하나의 목록',
1136
+ 'viewOptions.orderBy': '정렬',
1137
+ 'viewOptions.manualOrder': '수동',
1138
+ 'viewOptions.updatedOrder': '최근 업데이트',
1139
+ 'viewOptions.sessionSlash': '세션을 "/"로 그룹화',
1140
+ 'viewOptions.workspaceSlash': '작업 공간을 "/"로 그룹화',
1041
1141
  'group.ungrouped': '미분류',
1042
1142
  'sessions.expand': '세션 {n}개 펼치기',
1043
1143
  'sessions.collapse': '접기',
@@ -1148,6 +1248,16 @@ window.__ModuleLoader__.load({
1148
1248
  'empty': 'Nog geen werkruimten',
1149
1249
  'empty.search': 'Geen resultaten',
1150
1250
  'session.new': 'Nieuwe sessie',
1251
+ 'viewOptions.label': 'Weergaveopties',
1252
+ 'viewOptions.groupBy': 'Groeperen op',
1253
+ 'viewOptions.byWorkspace': 'Werkruimtes',
1254
+ 'viewOptions.byWorkspaceTree': 'Werkruimteboom',
1255
+ 'viewOptions.flat': 'In één lijst',
1256
+ 'viewOptions.orderBy': 'Sorteren op',
1257
+ 'viewOptions.manualOrder': 'Handmatig',
1258
+ 'viewOptions.updatedOrder': 'Laatst bijgewerkt',
1259
+ 'viewOptions.sessionSlash': 'Sessies groeperen op "/"',
1260
+ 'viewOptions.workspaceSlash': 'Werkruimtes groeperen op "/"',
1151
1261
  'group.ungrouped': 'Zonder groep',
1152
1262
  'sessions.expand': '{n} sessies uitklappen',
1153
1263
  'sessions.collapse': 'Inklappen',
@@ -1258,6 +1368,16 @@ window.__ModuleLoader__.load({
1258
1368
  'empty': 'Brak obszarów roboczych',
1259
1369
  'empty.search': 'Brak wyników',
1260
1370
  'session.new': 'Nowa sesja',
1371
+ 'viewOptions.label': 'Opcje widoku',
1372
+ 'viewOptions.groupBy': 'Grupuj według',
1373
+ 'viewOptions.byWorkspace': 'Obszary robocze',
1374
+ 'viewOptions.byWorkspaceTree': 'Drzewo obszarów',
1375
+ 'viewOptions.flat': 'Jedna lista',
1376
+ 'viewOptions.orderBy': 'Sortuj według',
1377
+ 'viewOptions.manualOrder': 'Ręcznie',
1378
+ 'viewOptions.updatedOrder': 'Ostatnia aktualizacja',
1379
+ 'viewOptions.sessionSlash': 'Grupuj sesje po "/"',
1380
+ 'viewOptions.workspaceSlash': 'Grupuj obszary po "/"',
1261
1381
  'group.ungrouped': 'Bez grupy',
1262
1382
  'sessions.expand': 'Pokaż {n} sesji',
1263
1383
  'sessions.collapse': 'Zwiń',
@@ -1368,6 +1488,16 @@ window.__ModuleLoader__.load({
1368
1488
  'empty': 'Nenhum espaço de trabalho',
1369
1489
  'empty.search': 'Nenhum resultado',
1370
1490
  'session.new': 'Nova sessão',
1491
+ 'viewOptions.label': 'Opções de vista',
1492
+ 'viewOptions.groupBy': 'Agrupar por',
1493
+ 'viewOptions.byWorkspace': 'Áreas de trabalho',
1494
+ 'viewOptions.byWorkspaceTree': 'Árvore de áreas',
1495
+ 'viewOptions.flat': 'Numa lista',
1496
+ 'viewOptions.orderBy': 'Ordenar por',
1497
+ 'viewOptions.manualOrder': 'Manual',
1498
+ 'viewOptions.updatedOrder': 'Atualização recente',
1499
+ 'viewOptions.sessionSlash': 'Agrupar sessões por "/"',
1500
+ 'viewOptions.workspaceSlash': 'Agrupar áreas por "/"',
1371
1501
  'group.ungrouped': 'Sem grupo',
1372
1502
  'sessions.expand': 'Mostrar {n} sessões',
1373
1503
  'sessions.collapse': 'Recolher',
@@ -1478,6 +1608,16 @@ window.__ModuleLoader__.load({
1478
1608
  'empty': 'Рабочих областей пока нет',
1479
1609
  'empty.search': 'Ничего не найдено',
1480
1610
  'session.new': 'Новая сессия',
1611
+ 'viewOptions.label': 'Параметры вида',
1612
+ 'viewOptions.groupBy': 'Группировка',
1613
+ 'viewOptions.byWorkspace': 'По рабочим областям',
1614
+ 'viewOptions.byWorkspaceTree': 'По дереву областей',
1615
+ 'viewOptions.flat': 'Одним списком',
1616
+ 'viewOptions.orderBy': 'Сортировка',
1617
+ 'viewOptions.manualOrder': 'Вручную',
1618
+ 'viewOptions.updatedOrder': 'По обновлению',
1619
+ 'viewOptions.sessionSlash': 'Группировать сессии по «/»',
1620
+ 'viewOptions.workspaceSlash': 'Группировать области по «/»',
1481
1621
  'group.ungrouped': 'Без группы',
1482
1622
  'sessions.expand': 'Показать ещё {n} сессий',
1483
1623
  'sessions.collapse': 'Свернуть',
@@ -1588,6 +1728,16 @@ window.__ModuleLoader__.load({
1588
1728
  'empty': 'Inga arbetsytor ännu',
1589
1729
  'empty.search': 'Inga träffar',
1590
1730
  'session.new': 'Ny session',
1731
+ 'viewOptions.label': 'Visningsalternativ',
1732
+ 'viewOptions.groupBy': 'Gruppera efter',
1733
+ 'viewOptions.byWorkspace': 'Arbetsytor',
1734
+ 'viewOptions.byWorkspaceTree': 'Arbetsyta-träd',
1735
+ 'viewOptions.flat': 'I en lista',
1736
+ 'viewOptions.orderBy': 'Sortera efter',
1737
+ 'viewOptions.manualOrder': 'Manuellt',
1738
+ 'viewOptions.updatedOrder': 'Senast uppdaterad',
1739
+ 'viewOptions.sessionSlash': 'Gruppera sessioner efter "/"',
1740
+ 'viewOptions.workspaceSlash': 'Gruppera arbetsytor efter "/"',
1591
1741
  'group.ungrouped': 'Utan grupp',
1592
1742
  'sessions.expand': 'Visa {n} sessioner',
1593
1743
  'sessions.collapse': 'Fäll ihop',
@@ -1698,6 +1848,16 @@ window.__ModuleLoader__.load({
1698
1848
  'empty': 'ยังไม่มีพื้นที่ทำงาน',
1699
1849
  'empty.search': 'ไม่พบผลลัพธ์ที่ตรงกัน',
1700
1850
  'session.new': 'เซสชันใหม่',
1851
+ 'viewOptions.label': 'ตัวเลือกมุมมอง',
1852
+ 'viewOptions.groupBy': 'จัดกลุ่มตาม',
1853
+ 'viewOptions.byWorkspace': 'ตามพื้นที่ทำงาน',
1854
+ 'viewOptions.byWorkspaceTree': 'ตามแผนผังพื้นที่ทำงาน',
1855
+ 'viewOptions.flat': 'รายการเดียว',
1856
+ 'viewOptions.orderBy': 'เรียงตาม',
1857
+ 'viewOptions.manualOrder': 'ด้วยตนเอง',
1858
+ 'viewOptions.updatedOrder': 'อัปเดตล่าสุด',
1859
+ 'viewOptions.sessionSlash': 'จัดกลุ่มเซสชันตาม "/"',
1860
+ 'viewOptions.workspaceSlash': 'จัดกลุ่มพื้นที่ทำงานตาม "/"',
1701
1861
  'group.ungrouped': 'ไม่มีกลุ่ม',
1702
1862
  'sessions.expand': 'แสดงอีก {n} เซสชัน',
1703
1863
  'sessions.collapse': 'ย่อ',
@@ -1808,6 +1968,16 @@ window.__ModuleLoader__.load({
1808
1968
  'empty': 'Henüz çalışma alanı yok',
1809
1969
  'empty.search': 'Eşleşen sonuç yok',
1810
1970
  'session.new': 'Yeni oturum',
1971
+ 'viewOptions.label': 'Görünüm seçenekleri',
1972
+ 'viewOptions.groupBy': 'Gruplandır',
1973
+ 'viewOptions.byWorkspace': 'Çalışma alanları',
1974
+ 'viewOptions.byWorkspaceTree': 'Çalışma alanı ağacı',
1975
+ 'viewOptions.flat': 'Tek liste',
1976
+ 'viewOptions.orderBy': 'Sırala',
1977
+ 'viewOptions.manualOrder': 'El ile',
1978
+ 'viewOptions.updatedOrder': 'Son güncelleme',
1979
+ 'viewOptions.sessionSlash': 'Oturumları "/" ile gruplandır',
1980
+ 'viewOptions.workspaceSlash': 'Çalışma alanlarını "/" ile gruplandır',
1811
1981
  'group.ungrouped': 'Grubusuz',
1812
1982
  'sessions.expand': '{n} oturumu göster',
1813
1983
  'sessions.collapse': 'Daralt',
@@ -1918,6 +2088,16 @@ window.__ModuleLoader__.load({
1918
2088
  'empty': 'Chưa có không gian làm việc',
1919
2089
  'empty.search': 'Không có kết quả phù hợp',
1920
2090
  'session.new': 'Phiên mới',
2091
+ 'viewOptions.label': 'Tùy chọn hiển thị',
2092
+ 'viewOptions.groupBy': 'Nhóm theo',
2093
+ 'viewOptions.byWorkspace': 'Theo khu vực làm việc',
2094
+ 'viewOptions.byWorkspaceTree': 'Theo cây khu vực',
2095
+ 'viewOptions.flat': 'Một danh sách',
2096
+ 'viewOptions.orderBy': 'Sắp xếp theo',
2097
+ 'viewOptions.manualOrder': 'Thủ công',
2098
+ 'viewOptions.updatedOrder': 'Cập nhật gần đây',
2099
+ 'viewOptions.sessionSlash': 'Nhóm phiên theo "/"',
2100
+ 'viewOptions.workspaceSlash': 'Nhóm khu vực theo "/"',
1921
2101
  'group.ungrouped': 'Chưa phân nhóm',
1922
2102
  'sessions.expand': 'Hiện thêm {n} phiên',
1923
2103
  'sessions.collapse': 'Thu gọn',
@@ -2028,6 +2208,16 @@ window.__ModuleLoader__.load({
2028
2208
  'empty': '未有工作區',
2029
2209
  'empty.search': '搵唔到符合嘅結果',
2030
2210
  'session.new': '新對話',
2211
+ 'viewOptions.label': '檢視選項',
2212
+ 'viewOptions.groupBy': '分組方式',
2213
+ 'viewOptions.byWorkspace': '按工作區',
2214
+ 'viewOptions.byWorkspaceTree': '按工作區樹',
2215
+ 'viewOptions.flat': '單一列表',
2216
+ 'viewOptions.orderBy': '排序方式',
2217
+ 'viewOptions.manualOrder': '手動排序',
2218
+ 'viewOptions.updatedOrder': '最近更新',
2219
+ 'viewOptions.sessionSlash': '對話按「/」分組',
2220
+ 'viewOptions.workspaceSlash': '工作區按「/」分組',
2031
2221
  'group.ungrouped': '未分組',
2032
2222
  'sessions.expand': '展開 {n} 個對話',
2033
2223
  'sessions.collapse': '收起',
@@ -2138,6 +2328,16 @@ window.__ModuleLoader__.load({
2138
2328
  'empty': '未有工作區',
2139
2329
  'empty.search': '搵唔到符合嘅結果',
2140
2330
  'session.new': '新對話',
2331
+ 'viewOptions.label': '檢視選項',
2332
+ 'viewOptions.groupBy': '分組方式',
2333
+ 'viewOptions.byWorkspace': '按工作區',
2334
+ 'viewOptions.byWorkspaceTree': '按工作區樹',
2335
+ 'viewOptions.flat': '單一列表',
2336
+ 'viewOptions.orderBy': '排序方式',
2337
+ 'viewOptions.manualOrder': '手動排序',
2338
+ 'viewOptions.updatedOrder': '最近更新',
2339
+ 'viewOptions.sessionSlash': '對話按「/」分組',
2340
+ 'viewOptions.workspaceSlash': '工作區按「/」分組',
2141
2341
  'group.ungrouped': '未分組',
2142
2342
  'sessions.expand': '展開 {n} 個對話',
2143
2343
  'sessions.collapse': '收起',
@@ -2248,6 +2448,16 @@ window.__ModuleLoader__.load({
2248
2448
  'empty': '尚無工作區',
2249
2449
  'empty.search': '沒有相符的結果',
2250
2450
  'session.new': '新工作階段',
2451
+ 'viewOptions.label': '檢視選項',
2452
+ 'viewOptions.groupBy': '分組方式',
2453
+ 'viewOptions.byWorkspace': '按工作區',
2454
+ 'viewOptions.byWorkspaceTree': '按工作區樹',
2455
+ 'viewOptions.flat': '單一列表',
2456
+ 'viewOptions.orderBy': '排序方式',
2457
+ 'viewOptions.manualOrder': '手動排序',
2458
+ 'viewOptions.updatedOrder': '最近更新',
2459
+ 'viewOptions.sessionSlash': '工作階段按「/」分組',
2460
+ 'viewOptions.workspaceSlash': '工作區按「/」分組',
2251
2461
  'group.ungrouped': '未分組',
2252
2462
  'sessions.expand': '展開 {n} 個工作階段',
2253
2463
  'sessions.collapse': '收合',
@@ -2495,6 +2705,21 @@ window.__ModuleLoader__.load({
2495
2705
  && !(archivedSet && archivedSet.has(summary.id))
2496
2706
  && (!summary.blank || summary.id === current)
2497
2707
 
2708
+ /**
2709
+ * dsh 0.1.6-alpha.2 dropped SessionListState.current: the "open" session
2710
+ * is now the one retained under the mainView source — navigation belongs
2711
+ * to the view owner (official ui-workspace tree.ts mainSessionId). The
2712
+ * old wire field is gone, so reading it yields a permanent undefined
2713
+ * (no current highlight, and blank rows would filter out everywhere).
2714
+ */
2715
+ const mainSessionIdOf = (list) => {
2716
+ if (!list || !list.byId) return undefined
2717
+ for (const session of Object.values(list.byId)) {
2718
+ if (session && session.retainedBy && (session.retainedBy.mainView || 0) > 0) return session.id
2719
+ }
2720
+ return undefined
2721
+ }
2722
+
2498
2723
  /**
2499
2724
  * Active-schedule marker, mirroring the official tree's
2500
2725
  * hasActiveSchedule(): the list projection carries one entry per active
@@ -2548,9 +2773,15 @@ window.__ModuleLoader__.load({
2548
2773
  * titles. Groups are virtual (projection of names). Rows keep the Host
2549
2774
  * workspace.sessionIds (manual) order — drag-to-reorder must be visible.
2550
2775
  */
2551
- function buildSessionTree(rows) {
2776
+ function buildSessionTree(rows, groupBySlash) {
2552
2777
  const root = { path: '', name: '', groups: [], sessions: [] }
2553
2778
  const byPath = new Map([['', root]])
2779
+ // Slash grouping OFF (v0.13 view option): every row lands at the
2780
+ // root level and keeps its FULL title as the leaf — no levels.
2781
+ if (groupBySlash === false) {
2782
+ for (const row of rows || []) root.sessions.push({ ...row, leaf: row.title })
2783
+ return root
2784
+ }
2554
2785
  // SEGMENT-driven: paths arrive as already-split segment arrays (URL-aware
2555
2786
  // splitTitleSegs can yield segments containing "//", e.g. the
2556
2787
  // "scheme://host" authority segment) and are joined into the path KEY
@@ -2664,7 +2895,7 @@ window.__ModuleLoader__.load({
2664
2895
  }
2665
2896
  return map
2666
2897
  }
2667
- function buildTree(items) {
2898
+ function buildTree(items, groupBySlash) {
2668
2899
  const list = items || []
2669
2900
  const diskParentOf = diskParentMapOf(list)
2670
2901
  const childrenOf = new Map()
@@ -2705,7 +2936,7 @@ window.__ModuleLoader__.load({
2705
2936
  const folderPath = segs.slice(0, -1).join('/')
2706
2937
  const leaf = segs.length > 0 ? segs[segs.length - 1] : (basename(workspace.path) || String(workspace.title || '') || String(workspace.workspaceId || ''))
2707
2938
  const subList = childrenOf.get(workspace.workspaceId)
2708
- ensure(segs.slice(0, -1)).workspaces.push({
2939
+ const entry = {
2709
2940
  workspaceId: workspace.workspaceId,
2710
2941
  title: String(workspace.title || ''),
2711
2942
  path: String(workspace.path || ''),
@@ -2713,7 +2944,17 @@ window.__ModuleLoader__.load({
2713
2944
  leaf,
2714
2945
  folderPath,
2715
2946
  sub: subList ? buildLevel(subList, (idPrefix !== '' ? idPrefix + '//' : '') + workspace.workspaceId) : null,
2716
- })
2947
+ }
2948
+ // Slash grouping OFF (v0.13 view option): skip the name layer —
2949
+ // workspaces mount straight at their disk level; the DISK layer
2950
+ // is a filesystem fact and stays.
2951
+ if (groupBySlash === false) {
2952
+ entry.leaf = String(workspace.title || '') || leaf
2953
+ entry.folderPath = ''
2954
+ root.workspaces.push(entry)
2955
+ continue
2956
+ }
2957
+ ensure(segs.slice(0, -1)).workspaces.push(entry)
2717
2958
  }
2718
2959
  const sortRec = (node) => {
2719
2960
  node.folders.sort((a, b) => a.name.localeCompare(b.name, 'zh'))
@@ -2951,7 +3192,7 @@ window.__ModuleLoader__.load({
2951
3192
  /* ========================== view store =========================== */
2952
3193
 
2953
3194
  const createViewStore = () => storeKit.defineStore({
2954
- init: () => ({ expanded: {}, sessionsExpanded: {}, sessionGroups: {}, sessionOrder: {}, prefs: { compactChains: true }, styling: {} }),
3195
+ init: () => ({ expanded: {}, sessionsExpanded: {}, sessionGroups: {}, sessionOrder: {}, prefs: { compactChains: true }, styling: {}, groupBy: 'workspace-tree', orderBy: 'manual', sessionTitleSlash: true, workspaceTitleSlash: true }),
2955
3196
  // NOTE: hydration REPLACES the state with the persisted whole value —
2956
3197
  // init defaults never merge. Every action must tolerate a missing key
2957
3198
  // (states persisted by older plugin versions lack sessionGroups), and
@@ -2994,6 +3235,14 @@ window.__ModuleLoader__.load({
2994
3235
  if (!Array.isArray(order) || order.length === 0) delete d.sessionOrder[workspaceId]
2995
3236
  else d.sessionOrder[workspaceId] = order.slice()
2996
3237
  },
3238
+ // View options (v0.13): mirror the official browser's group/order
3239
+ // menu plus the two slash-grouping toggles. groupBy mirrors the
3240
+ // official literal set; the bw defaults keep the founding behaviour
3241
+ // (tree + both slash layers on + manual host order).
3242
+ setGroupBy: (d, value) => { d.groupBy = value },
3243
+ setOrderBy: (d, value) => { d.orderBy = value },
3244
+ setSessionTitleSlash: (d, value) => { d.sessionTitleSlash = value !== false },
3245
+ setWorkspaceTitleSlash: (d, value) => { d.workspaceTitleSlash = value !== false },
2997
3246
  },
2998
3247
  })
2999
3248
 
@@ -3951,6 +4200,60 @@ window.__ModuleLoader__.load({
3951
4200
  )
3952
4201
  }
3953
4202
 
4203
+ /* ------------------------- view options menu ---------------------- */
4204
+
4205
+ /**
4206
+ * View options (v0.13): mirrors the official browser's ViewOptionsMenu —
4207
+ * primitives Menu, label/separator/selectedIds structure, portal + dense
4208
+ * + end alignment. Group/order picks close the menu; the two slash
4209
+ * toggles flip in place (they ride selectedIds: present = on) and stay
4210
+ * open. Options that cannot affect the current mode render disabled:
4211
+ * slash toggles mean nothing in the flat list, and the WORKSPACE-name
4212
+ * toggle only shapes the tree mode (the flat workspace level has no
4213
+ * name layer). The primitives Menu is feature-probed like every
4214
+ * component — without it the button simply never renders.
4215
+ */
4216
+ function ViewOptionsMenu({ groupBy, orderBy, sessionSlash, workspaceSlash, onPick, onToggle, t }) {
4217
+ const [open, setOpen] = React.useState(false)
4218
+ if (typeof ui.Menu !== 'function') return null
4219
+ return E(ui.Menu, {
4220
+ open,
4221
+ onClose: () => { setOpen(false) },
4222
+ items: [
4223
+ { type: 'label', id: 'bw-group-by', text: t('viewOptions.groupBy') },
4224
+ { id: 'workspace', label: t('viewOptions.byWorkspace') },
4225
+ { id: 'workspace-tree', label: t('viewOptions.byWorkspaceTree') },
4226
+ { id: 'flat', label: t('viewOptions.flat') },
4227
+ { type: 'separator', id: 'bw-order-sep' },
4228
+ { type: 'label', id: 'bw-order-by', text: t('viewOptions.orderBy') },
4229
+ { id: 'manual', label: t('viewOptions.manualOrder') },
4230
+ { id: 'updated', label: t('viewOptions.updatedOrder') },
4231
+ { type: 'separator', id: 'bw-slash-sep' },
4232
+ { id: 'session-slash', label: t('viewOptions.sessionSlash'), disabled: groupBy === 'flat' },
4233
+ { id: 'workspace-slash', label: t('viewOptions.workspaceSlash'), disabled: groupBy !== 'workspace-tree' },
4234
+ ],
4235
+ selectedIds: [groupBy, orderBy]
4236
+ .concat(sessionSlash ? ['session-slash'] : [])
4237
+ .concat(workspaceSlash ? ['workspace-slash'] : []),
4238
+ onSelect: (id) => {
4239
+ if (id === 'workspace' || id === 'workspace-tree' || id === 'flat') { onPick('groupBy', id); setOpen(false); return }
4240
+ if (id === 'manual' || id === 'updated') { onPick('orderBy', id); setOpen(false); return }
4241
+ if (id === 'session-slash') { onToggle('sessionTitleSlash', !sessionSlash); return }
4242
+ if (id === 'workspace-slash') { onToggle('workspaceTitleSlash', !workspaceSlash); return }
4243
+ },
4244
+ align: 'end',
4245
+ dense: true,
4246
+ portal: true,
4247
+ anchor: E('button', {
4248
+ type: 'button',
4249
+ className: 'bw-icon-btn',
4250
+ 'aria-label': t('viewOptions.label'),
4251
+ title: t('viewOptions.label'),
4252
+ onClick: () => { setOpen(v => !v) },
4253
+ }, icon('IconPersonalizationOutline16')),
4254
+ })
4255
+ }
4256
+
3954
4257
  /* --------------------- customization dialog ----------------------- */
3955
4258
 
3956
4259
  const SWATCHES = ['', '#5b8def', '#3fb950', '#d29922', '#f85149', '#a371f7', '#39c5cf', '#ec6cb9', '#ff9f45', '#6e7681']
@@ -4423,6 +4726,7 @@ window.__ModuleLoader__.load({
4423
4726
  insertWorkspaceBefore, insertSessionBefore,
4424
4727
  useDirectoryFlow,
4425
4728
  pickDirectory, listDirectory, createDirectory,
4729
+ officialT,
4426
4730
  t,
4427
4731
  } = props
4428
4732
 
@@ -4442,6 +4746,13 @@ window.__ModuleLoader__.load({
4442
4746
  const prefsMap = useStore ? (useStore(s => s.prefs) || {}) : {}
4443
4747
  const stylingMap = useStore ? (useStore(s => s.styling) || {}) : {}
4444
4748
  const sessionOrderMap = useStore ? (useStore(s => s.sessionOrder) || {}) : {}
4749
+ // View options (v0.13). Selector reads take fallbacks: hydration
4750
+ // replaces the whole persisted value, so states written by older
4751
+ // plugin versions lack these keys (hard store discipline).
4752
+ const groupBy = useStore ? (useStore(s => s.groupBy) || 'workspace-tree') : 'workspace-tree'
4753
+ const orderBy = useStore ? (useStore(s => s.orderBy) || 'manual') : 'manual'
4754
+ const sessionSlash = useStore ? (useStore(s => s.sessionTitleSlash) !== false) : true
4755
+ const workspaceSlash = useStore ? (useStore(s => s.workspaceTitleSlash) !== false) : true
4445
4756
  // Dual-write wrappers: every preference mutation lands in the local
4446
4757
  // store (immediate echo + scope-less fallback) AND the host settings
4447
4758
  // store (durable cross-device copy for the manual pull on the other
@@ -4451,6 +4762,8 @@ window.__ModuleLoader__.load({
4451
4762
  const statusPulse = prefsMap.statusPulse !== false
4452
4763
  const archivedSet = React.useMemo(() => new Set(archivedSessionIds), [archivedSessionIds])
4453
4764
  const subCounts = React.useMemo(() => subagentRunningCounts(list ? list.byId : {}), [list ? list.byId : null])
4765
+ // Current session per the alpha.2 retention contract (see mainSessionIdOf).
4766
+ const currentId = React.useMemo(() => mainSessionIdOf(list), [list ? list.byId : null])
4454
4767
  // Last-known real titles for the cold-restart fallback window (see
4455
4768
  // the title-cache block). Read per render via getSnapshot: no
4456
4769
  // subscription, because a cache write only happens when summary.title
@@ -4670,7 +4983,7 @@ window.__ModuleLoader__.load({
4670
4983
  if (list && Array.isArray(list.ids)) {
4671
4984
  for (const id of list.ids) {
4672
4985
  const summary = list.byId[id]
4673
- if (accounted.has(id) || !sessionVisible(summary, list.current, archivedSet)) continue
4986
+ if (accounted.has(id) || !sessionVisible(summary, currentId, archivedSet)) continue
4674
4987
  ungrouped.push({
4675
4988
  id,
4676
4989
  title: sessionTitleOf(summary, t, rememberedTitleOf(id)),
@@ -4696,10 +5009,10 @@ window.__ModuleLoader__.load({
4696
5009
  // their drop targets live inside workspace rows, which compression merges.
4697
5010
  const draggingWorkspace = drag !== null && drag.kind === 'workspace'
4698
5011
  const tree = React.useMemo(() => {
4699
- const built = buildTree(items)
5012
+ const built = buildTree(items, workspaceSlash)
4700
5013
  if (!compactChains || draggingWorkspace) return built
4701
5014
  return { ...built, folders: built.folders.map((f) => materializeChain(compressTree(f))), workspaces: built.workspaces }
4702
- }, [items, compactChains, draggingWorkspace])
5015
+ }, [items, compactChains, draggingWorkspace, workspaceSlash])
4703
5016
  // Disk-layer ancestry for drag semantics (same derivation as buildTree).
4704
5017
  const diskParentMap = React.useMemo(() => diskParentMapOf(items), [items])
4705
5018
 
@@ -4714,7 +5027,7 @@ window.__ModuleLoader__.load({
4714
5027
  const rows = []
4715
5028
  for (const id of workspace.sessionIds || []) {
4716
5029
  const summary = list && list.byId ? list.byId[id] : undefined
4717
- if (!sessionVisible(summary, list ? list.current : undefined, archivedSet)) continue
5030
+ if (!sessionVisible(summary, currentId, archivedSet)) continue
4718
5031
  rows.push({
4719
5032
  id,
4720
5033
  title: sessionTitleOf(summary, t, rememberedTitleOf(id)),
@@ -4733,6 +5046,21 @@ window.__ModuleLoader__.load({
4733
5046
  // user dragged in, and is empty everywhere else — so a host that still
4734
5047
  // exposes the action keeps its authoritative order untouched.
4735
5048
  // Host order stays authoritative wherever the action exists.
5049
+ // Recency ordering (v0.13 view option, official orderBy semantics):
5050
+ // updatedAt descending with an id tie-break, and the CURRENT blank
5051
+ // session — the provisional New Session row — pinned on top. Purely
5052
+ // presentational: nothing is persisted, the host order is untouched,
5053
+ // and drag-to-reorder anchors are suppressed while active (an order
5054
+ // the very next render would re-sort is a lie).
5055
+ if (orderBy === 'updated') {
5056
+ const sorted = rows.slice().sort((a, b) => (b.updatedAt - a.updatedAt) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
5057
+ const pin = currentId !== undefined && sorted.find((row) => row.id === currentId && row.blank)
5058
+ if (pin) {
5059
+ const at = sorted.indexOf(pin)
5060
+ if (at > 0) { sorted.splice(at, 1); sorted.unshift(pin) }
5061
+ }
5062
+ return sorted
5063
+ }
4736
5064
  const local = typeof insertSessionBefore === 'function' ? [] : sessionOrderOf(workspace.workspaceId)
4737
5065
  if (local.length === 0) return rows
4738
5066
  const remaining = new Map(rows.map((row) => [row.id, row]))
@@ -4791,7 +5119,7 @@ window.__ModuleLoader__.load({
4791
5119
  // tree AND its disk-nested child levels.
4792
5120
  const wsAllPulseOf = (workspace) => {
4793
5121
  if (!statusPulse || searching) return null
4794
- let best = nodePulseOf(buildSessionTree(sessionsOf(workspace)))
5122
+ let best = nodePulseOf(buildSessionTree(sessionsOf(workspace), sessionSlash))
4795
5123
  if (workspace.sub) {
4796
5124
  for (const folder of workspace.sub.folders) {
4797
5125
  const s = folderPulseOf(folder)
@@ -4985,6 +5313,9 @@ window.__ModuleLoader__.load({
4985
5313
  /* ---------------------- session drag & drop --------------------- */
4986
5314
 
4987
5315
  const sessDropHalf = (sessionId) => {
5316
+ // Recency ordering is purely presentational: an anchor for a move
5317
+ // the very next render would re-sort is a lie — suppress it.
5318
+ if (orderBy === 'updated') return null
4988
5319
  if (!dragMatches('session')) return null
4989
5320
  const over = drag.over
4990
5321
  return over && over.kind === 'session' && over.target === sessionId ? over.half : null
@@ -5104,7 +5435,7 @@ window.__ModuleLoader__.load({
5104
5435
  if (name === target.name) { setDialog(null); return }
5105
5436
  const workspace = (items || []).find(w => w.workspaceId === target.workspaceId)
5106
5437
  if (!workspace) { setDialog(null); return }
5107
- const node = findSessionGroup(buildSessionTree(sessionsOf(workspace)), target.path)
5438
+ const node = findSessionGroup(buildSessionTree(sessionsOf(workspace), sessionSlash), target.path)
5108
5439
  if (!node) { setDialog(null); return }
5109
5440
  const parentPath = target.path.includes('/') ? target.path.slice(0, target.path.lastIndexOf('/')) : ''
5110
5441
  const nextPath = parentPath !== '' ? parentPath + '/' + name : name
@@ -5165,7 +5496,7 @@ window.__ModuleLoader__.load({
5165
5496
  if (!searching && !sessionsOpenOf(workspace.workspaceId)) return []
5166
5497
  const rows = sessionsOf(workspace)
5167
5498
  if (rows.length === 0) return []
5168
- return renderSessionNode(buildSessionTree(rows), workspace.workspaceId, depth)
5499
+ return renderSessionNode(buildSessionTree(rows, sessionSlash), workspace.workspaceId, depth)
5169
5500
  }
5170
5501
  const searchSessionNode = (node) => {
5171
5502
  const groups = []
@@ -5208,7 +5539,7 @@ window.__ModuleLoader__.load({
5208
5539
  key: session.id,
5209
5540
  node: session,
5210
5541
  depth,
5211
- current: list && list.current === session.id,
5542
+ current: currentId === session.id,
5212
5543
  now,
5213
5544
  onOpen: (id) => open(id),
5214
5545
  onContextMenu: (e) => openCtx('session', session, e),
@@ -5221,14 +5552,14 @@ window.__ModuleLoader__.load({
5221
5552
 
5222
5553
  const renderWorkspaceEntry = (entry, depth, pulse) => {
5223
5554
  const { workspace } = entry
5224
- const count = countSessionTree(buildSessionTree(sessionsOf(workspace)))
5555
+ const count = countSessionTree(buildSessionTree(sessionsOf(workspace), sessionSlash))
5225
5556
  const rows = [E(WorkspaceRow, {
5226
5557
  key: 'ws-' + workspace.workspaceId,
5227
5558
  workspace,
5228
5559
  depth,
5229
5560
  count,
5230
5561
  sessionsOpen: searching ? true : sessionsOpenOf(workspace.workspaceId),
5231
- currentInside: !!(list && list.current && (workspace.sessionIds || []).includes(list.current)),
5562
+ currentInside: !!(currentId && (workspace.sessionIds || []).includes(currentId)),
5232
5563
  onToggle: () => { if (!searching) actions.setSessionsExpanded(workspace.workspaceId, !sessionsOpenOf(workspace.workspaceId)) },
5233
5564
  // Starting a session force-expands the workspace row: the user must
5234
5565
  // SEE the new session appear, even if the row was collapsed.
@@ -5286,16 +5617,41 @@ window.__ModuleLoader__.load({
5286
5617
  return rows
5287
5618
  }
5288
5619
 
5620
+ // Single flat list (v0.13 groupBy:'flat', official FlatList semantics):
5621
+ // every visible session — each workspace's rows plus the ungrouped
5622
+ // account — as one depth-0 list. Rows keep their FULL title (no leaf
5623
+ // split, no levels), and render through the workspaceId-less path,
5624
+ // which carries no drag anchors by construction.
5625
+ const renderFlatRows = () => {
5626
+ const rows = []
5627
+ for (const workspace of items || []) rows.push(...sessionsOf(workspace))
5628
+ rows.push(...ungrouped)
5629
+ return rows.map((s) => renderSessionRow({ ...s, leaf: s.title }, 0, undefined))
5630
+ }
5631
+ const renderUngroupedBlock = () => {
5632
+ const rows = []
5633
+ if (ungrouped.length > 0) {
5634
+ rows.push(E('div', { key: 'ungrouped-label', className: 'bw-header-title', style: { padding: '10px 6px 2px' } }, t('group.ungrouped')))
5635
+ for (const s of ungrouped) rows.push(renderSessionRow(s, 0))
5636
+ }
5637
+ return rows
5638
+ }
5289
5639
  let bodyRows = []
5290
5640
  if (searching) {
5291
5641
  for (const entry of searched) bodyRows.push(...renderWorkspaceEntry(entry, 0))
5642
+ } else if (groupBy === 'flat') {
5643
+ bodyRows.push(...renderFlatRows())
5644
+ } else if (groupBy === 'workspace') {
5645
+ // Official 'workspace' mode (v0.13): one level — every workspace at
5646
+ // the root, no name groups, no disk nesting, no chain compression.
5647
+ // Session rows keep the slash toggle; disk-nested children do not
5648
+ // recurse here because raw items carry no .sub level.
5649
+ for (const workspace of items || []) bodyRows.push(...renderWorkspaceEntry({ workspace }, 0, wsPulseOf(workspace)))
5650
+ bodyRows.push(...renderUngroupedBlock())
5292
5651
  } else {
5293
5652
  for (const folder of tree.folders) bodyRows.push(...renderPlainFolder(folder, 0))
5294
5653
  for (const workspace of tree.workspaces) bodyRows.push(...renderWorkspaceEntry({ workspace }, 0, wsPulseOf(workspace)))
5295
- if (ungrouped.length > 0) {
5296
- bodyRows.push(E('div', { key: 'ungrouped-label', className: 'bw-header-title', style: { padding: '10px 6px 2px' } }, t('group.ungrouped')))
5297
- for (const s of ungrouped) bodyRows.push(renderSessionRow(s, 0))
5298
- }
5654
+ bodyRows.push(...renderUngroupedBlock())
5299
5655
  }
5300
5656
  const isEmpty = bodyRows.length === 0
5301
5657
  if (isEmpty) {
@@ -5304,30 +5660,42 @@ window.__ModuleLoader__.load({
5304
5660
 
5305
5661
  /* ------------------------- context menu ------------------------ */
5306
5662
 
5307
- const ctxItems = () => {
5663
+ // Context-menu entries mirror the OFFICIAL row menus item-for-item
5664
+ // (ui-workspace Rows.tsx workspaceMenuItems / sessionMenuItems): same
5665
+ // ids, same icons, same danger semantics, and — through the official
5666
+ // 'workspace' dictionary — the same copy, so upstream rewording lands
5667
+ // here without a plugin release. What the official menus cannot
5668
+ // provide is the bw-only surface: name-group / session-group rename
5669
+ // and Customize appearance, appended after a separator.
5670
+ const ot = typeof officialT === 'function' ? officialT : null
5671
+ const menuEntries = () => {
5308
5672
  if (ctx === null) return []
5673
+ const customizeEntry = { id: 'customize', label: t('custom.title'), icon: icon('IconPersonalizationOutline16', 16) }
5309
5674
  if (ctx.kind === 'folder') return [
5310
- { id: 'rename-folder', label: t('menu.renameFolder') },
5311
- { sep: true },
5312
- { id: 'customize', label: t('custom.title') },
5675
+ { id: 'rename-folder', label: t('menu.renameFolder'), icon: icon('IconEditOutline16', 16) },
5676
+ { type: 'separator', id: 'bw-sep' },
5677
+ customizeEntry,
5313
5678
  ]
5314
5679
  if (ctx.kind === 'workspace') return [
5315
- { id: 'rename', label: t('menu.rename') },
5316
- { id: 'delete', label: t('menu.delete'), danger: true },
5317
- { sep: true },
5318
- { id: 'customize', label: t('custom.title') },
5680
+ { id: 'rename', label: ot ? ot('rename') : t('menu.rename'), icon: icon('IconEditOutline16', 16) },
5681
+ { id: 'delete', label: ot ? ot('delete.workspace') : t('menu.delete'), icon: icon('IconTrashOutline16', 16), danger: true },
5682
+ { type: 'separator', id: 'bw-sep' },
5683
+ customizeEntry,
5319
5684
  ]
5320
5685
  if (ctx.kind === 'session') return [
5321
- { id: 'rename', label: t('menu.rename') },
5322
- { id: 'fork', label: t('menu.fork') },
5323
- { id: 'archive', label: t('menu.archive'), danger: true },
5324
- { sep: true },
5325
- { id: 'customize', label: t('custom.title') },
5686
+ { id: 'rename', label: ot ? ot('rename') : t('menu.rename'), icon: icon('IconEditOutline16', 16) },
5687
+ { id: 'fork', label: ot ? ot('menu.fork') : t('menu.fork'), icon: icon('IconBranchOutline16', 16) },
5688
+ // Official semantics (Rows.tsx): archive hides the row through the
5689
+ // registry-global archive set and never touches the session log —
5690
+ // deliberately NOT styled destructive.
5691
+ { id: 'archive', label: ot ? ot('menu.archiveSession') : t('menu.archive'), icon: icon('IconArchiveOutline20', 16) },
5692
+ { type: 'separator', id: 'bw-sep' },
5693
+ customizeEntry,
5326
5694
  ]
5327
5695
  return [
5328
- { id: 'rename-sgroup', label: t('menu.renameSgroup') },
5329
- { sep: true },
5330
- { id: 'customize', label: t('custom.title') },
5696
+ { id: 'rename-sgroup', label: t('menu.renameSgroup'), icon: icon('IconEditOutline16', 16) },
5697
+ { type: 'separator', id: 'bw-sep' },
5698
+ customizeEntry,
5331
5699
  ]
5332
5700
  }
5333
5701
  const handleCtxPick = (id) => {
@@ -5453,6 +5821,15 @@ window.__ModuleLoader__.load({
5453
5821
  onKeyDown: (e) => { if (e.key === 'Escape') { setQuery(''); setSearchOpen(false) } },
5454
5822
  onBlur: () => { if (query === '') setSearchOpen(false) },
5455
5823
  }) : null,
5824
+ E(ViewOptionsMenu, {
5825
+ groupBy,
5826
+ orderBy,
5827
+ sessionSlash,
5828
+ workspaceSlash,
5829
+ onPick: (kind, value) => { if (kind === 'groupBy') actions.setGroupBy(value); else actions.setOrderBy(value) },
5830
+ onToggle: (key, value) => { if (key === 'sessionTitleSlash') actions.setSessionTitleSlash(value); else actions.setWorkspaceTitleSlash(value) },
5831
+ t,
5832
+ }),
5456
5833
  E('button', { type: 'button', className: 'bw-icon-btn', 'aria-label': t('search.placeholder'), onClick: () => setSearchOpen(v => !v) }, icon('IconSearchOutline16')),
5457
5834
  flowAvailable ? E('button', { type: 'button', className: 'bw-icon-btn', 'aria-label': t('add'), onClick: () => setFlowOpen(true) }, icon('IconProjectAddOutline16')) : null,
5458
5835
  ),
@@ -5471,7 +5848,20 @@ window.__ModuleLoader__.load({
5471
5848
  t,
5472
5849
  }),
5473
5850
  dialogElement,
5474
- ctx !== null ? E('div', {
5851
+ (ctx !== null && typeof ui.Menu === 'function') ? E(ui.Menu, {
5852
+ // Right-click opens the SAME menu surface the official ⋯ button
5853
+ // renders (primitives Menu, portal, dense): getAnchorRect feeds
5854
+ // the event coordinates as the anchor rect — portal mode positions
5855
+ // from it directly, no in-place wrapper needed.
5856
+ open: true,
5857
+ onClose: () => setCtx(null),
5858
+ items: menuEntries(),
5859
+ onSelect: handleCtxPick,
5860
+ align: 'start',
5861
+ dense: true,
5862
+ portal: true,
5863
+ getAnchorRect: () => new DOMRect(ctx.x, ctx.y, 0, 0),
5864
+ }) : (ctx !== null ? E('div', {
5475
5865
  className: 'bw-ctx-overlay',
5476
5866
  onMouseDown: () => setCtx(null),
5477
5867
  onContextMenu: (e) => e.preventDefault(),
@@ -5482,7 +5872,7 @@ window.__ModuleLoader__.load({
5482
5872
  onMouseDown: (e) => e.stopPropagation(),
5483
5873
  onContextMenu: (e) => e.preventDefault(),
5484
5874
  },
5485
- ctxItems().map((item, index) => item.sep
5875
+ menuEntries().map((item, index) => item.type === 'separator'
5486
5876
  ? E('div', { key: 'sep-' + index, className: 'bw-ctx-sep' })
5487
5877
  : E('button', {
5488
5878
  key: item.id,
@@ -5492,7 +5882,7 @@ window.__ModuleLoader__.load({
5492
5882
  }, item.label),
5493
5883
  ),
5494
5884
  ),
5495
- ) : null,
5885
+ ) : null),
5496
5886
  E(CustomizeDialog, {
5497
5887
  open: customize !== null,
5498
5888
  kind: customize ? customize.kind : undefined,
@@ -5555,6 +5945,14 @@ window.__ModuleLoader__.load({
5555
5945
  // the handle before any flow can open.
5556
5946
  pickerState.api = uiWorkspace
5557
5947
 
5948
+ // The OFFICIAL browser's own dictionary (NS 'workspace'), bound so the
5949
+ // context menu COPY follows upstream wording automatically — the menu
5950
+ // mirrors the official row menus item-for-item (Rows.tsx), and this
5951
+ // way upstream rewording lands here without a plugin release. Falls
5952
+ // back to this plugin's aligned dictionary when bind fails.
5953
+ let officialT = null
5954
+ try { officialT = ctx.locale.bind('workspace') } catch { officialT = null }
5955
+
5558
5956
  if (ctx.locale && typeof ctx.locale.register === 'function') {
5559
5957
  ctx.effect(() => {
5560
5958
  try {
@@ -5571,22 +5969,31 @@ window.__ModuleLoader__.load({
5571
5969
  if (!result || !result.ok) throw new Error(result && result.error ? result.error.message : 'session search failed')
5572
5970
  return result.value
5573
5971
  }
5972
+ // dsh 0.1.6-alpha.2 retention contract: rename borrows through
5973
+ // sessions.using with an explicit source — the old plain binding(id)
5974
+ // only ever saw already-retained sessions, so renaming a closed row
5975
+ // reported "unknown session". Fork and open go through the uiWorkspace
5976
+ // navigation service: ISessions no longer carries open() at all
5977
+ // (navigation belongs to the view owner, replaceMain).
5574
5978
  const renameSession = async (sessionId, title) => {
5575
- const binding = sessions.binding(sessionId)
5576
- const session = binding && binding.session
5577
- if (!session) throw new Error('unknown session "' + sessionId + '"')
5578
- const result = await session.rename(title)
5979
+ const result = await sessions.using(
5980
+ sessionId,
5981
+ { source: 'workspaceOperation' },
5982
+ (reference) => reference.binding.session.rename(title),
5983
+ )
5579
5984
  if (!result || !result.ok) throw new Error(result && result.error ? result.error.message : 'session rename failed')
5580
5985
  }
5581
5986
  const forkSession = (sessionId) => {
5582
- sessions.fork({ sessionId, increaseTitle: true })
5583
- .then((childId) => sessions.open(childId))
5987
+ uiWorkspace.forkSession(sessionId)
5584
5988
  .catch(() => { /* keep current selection */ })
5585
5989
  }
5586
5990
 
5587
5991
  const browserInjected = () => ({
5992
+ // Official 'workspace' dictionary translate (menu copy follows upstream);
5993
+ // null when the bind failed — the menu then uses the aligned bw copy.
5994
+ officialT,
5588
5995
  startSession: (workspaceId) => { uiWorkspace.startSession(workspaceId) },
5589
- open: (sessionId) => { sessions.open(sessionId) },
5996
+ open: (sessionId) => { uiWorkspace.openSession(sessionId) },
5590
5997
  searchSessions,
5591
5998
  searchResultLimit: sessions.searchResultLimit !== undefined ? sessions.searchResultLimit : 20,
5592
5999
  renameSession,