dsh-better-workspace 0.32.2 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.js CHANGED
@@ -90,7 +90,11 @@ window.__ModuleLoader__.load({
90
90
  'wsPreset.title': '修改工作区默认预设',
91
91
  'wsPreset.current': '当前默认预设',
92
92
  'wsPreset.currentUnknown': '当前预设未知',
93
- 'wsPreset.noBlank': '该工作区暂无待命的新会话,当前跟随全局默认预设',
93
+ 'presetStandardName': '标准模式',
94
+ 'presetPtcName': 'PTC 模式',
95
+ 'presetMinimalName': '极简模式',
96
+ 'presetCordisName': '创造模式',
97
+ 'wsPreset.createHint': '该工作区暂无待命的新会话,确认后将预备一个使用所选预设的新会话作为工作区默认',
94
98
  'wsPreset.empty': '没有可用的预设',
95
99
  'wsPreset.loadFailed': '预设列表读取失败',
96
100
  'wsPreset.done': '已更新工作区默认预设',
@@ -109,6 +113,13 @@ window.__ModuleLoader__.load({
109
113
  'settings.compactChains.hint': '单层链合并为一行,出现多个子级时自动展开为树状;拖拽工作区期间单链临时展开回文件夹树,可放入任意一级;展开状态与自定义外观保存在当前浏览器。',
110
114
  'settings.statusPulse': '状态呼吸灯',
111
115
  'settings.statusPulse.hint': '被折叠藏起的状态灯(完成绿 / 运行蓝 / 待交互琥珀)沿层级向外冒泡:工作区与分组行以图标呼吸发光(颜色随状态,自定义过发光的标题一起呼吸),会话分组行显示呼吸状态灯;默认开启,可在此关闭。',
116
+ 'settings.density': '行密度',
117
+ 'settings.density.compact': '紧凑',
118
+ 'settings.density.comfortable': '舒适(默认)',
119
+ 'settings.density.roomy': '宽松',
120
+ 'settings.density.hint': '行高跟随官方侧栏:舒适 = 官方默认行高;紧凑更省纵向空间,宽松有更大的点击范围。改动立即生效,保存在当前浏览器。',
121
+ 'settings.showCount': '会话数角标',
122
+ 'settings.showCount.hint': '官方侧栏已移除工作区行尾的会话计数;开启后在本插件行尾恢复显示(悬停时让位给操作按钮)。默认关闭。',
112
123
  'settings.workspaceGroup': "工作区分组",
113
124
  'settings.workspaceGroup.hint': "磁盘目录 = 只按官方文件夹(磁盘目录)嵌套;磁盘 + 名称 = 在磁盘层之上再按标题里的 / 分组;仅名称 = 只按标题里的 / 分组,完全不理会磁盘目录的嵌套关系。旧版本的开关状态会自动迁移。",
114
125
  'settings.sessionLimit': "展开时显示会话数",
@@ -257,7 +268,11 @@ window.__ModuleLoader__.load({
257
268
  'wsPreset.title': 'Change workspace default preset',
258
269
  'wsPreset.current': 'Current default preset',
259
270
  'wsPreset.currentUnknown': 'Current preset unknown',
260
- 'wsPreset.noBlank': 'No standing draft in this workspace; it follows the global default preset',
271
+ 'presetStandardName': 'Standard mode',
272
+ 'presetPtcName': 'PTC mode',
273
+ 'presetMinimalName': 'Minimal mode',
274
+ 'presetCordisName': 'Creator mode',
275
+ 'wsPreset.createHint': 'No standing draft in this workspace; confirming prepares a new draft with the picked preset as the workspace default',
261
276
  'wsPreset.empty': 'No presets available',
262
277
  'wsPreset.loadFailed': 'Failed to load the preset list',
263
278
  'wsPreset.done': 'Workspace default preset updated',
@@ -276,6 +291,13 @@ window.__ModuleLoader__.load({
276
291
  'settings.compactChains.hint': 'Single-child chains merge into one row; levels with multiple children expand as a tree. Chains re-expand into folder rows while you drag a workspace, so it can drop into any level. State and custom styling persist in this browser.',
277
292
  'settings.statusPulse': 'Status breathing light',
278
293
  'settings.statusPulse.hint': 'Status dots hidden by collapse (done green / running blue / pending amber) bubble outward: workspace and folder rows breathe on their icon in the status color (custom-glow labels breathe along), session-group rows show a breathing dot; on by default, turn it off here.',
294
+ 'settings.density': 'Row density',
295
+ 'settings.density.compact': 'Compact',
296
+ 'settings.density.comfortable': 'Comfortable (default)',
297
+ 'settings.density.roomy': 'Roomy',
298
+ 'settings.density.hint': 'Row heights follow the shipped sidebar: Comfortable is the official default; Compact saves vertical space, Roomy adds larger hit targets. Applies immediately, stored in this browser.',
299
+ 'settings.showCount': 'Session count badge',
300
+ 'settings.showCount.hint': 'The shipped sidebar retired the per-workspace session count; turn this on to restore it at the row tail (it yields to the row actions on hover). Off by default.',
279
301
  'settings.workspaceGroup': "Workspace grouping",
280
302
  'settings.workspaceGroup.hint': "Disk folders: nest only by the official folders (disk directories). Disk + names: layer slash-name groups on top of the disk tree. Names only: group purely by slashes in titles and ignore disk nesting entirely. A previous version's switch state migrates automatically.",
281
303
  'settings.sessionLimit': "Sessions shown when expanded",
@@ -432,7 +454,11 @@ window.__ModuleLoader__.load({
432
454
  'wsPreset.title': 'تغيير الإعداد المسبق الافتراضي لمساحة العمل',
433
455
  'wsPreset.current': 'الإعداد المسبق الافتراضي الحالي',
434
456
  'wsPreset.currentUnknown': 'الإعداد المسبق الحالي غير معروف',
435
- 'wsPreset.noBlank': 'لا توجد جلسة جديدة بانتظار في مساحة العمل هذه؛ تتبع الإعداد الافتراضي العام',
457
+ 'presetStandardName': 'الوضع القياسي',
458
+ 'presetPtcName': 'وضع PTC',
459
+ 'presetMinimalName': 'الوضع البسيط',
460
+ 'presetCordisName': 'وضع المبدع',
461
+ 'wsPreset.createHint': 'لا توجد جلسة جديدة بانتظار في مساحة العمل هذه؛ عند التأكيد سيُجهَّز جلسة جديدة بالإعداد المسبق المختار كافتراضي لمساحة العمل',
436
462
  'wsPreset.empty': 'لا توجد إعدادات مسبقة متاحة',
437
463
  'wsPreset.loadFailed': 'فشل تحميل قائمة الإعدادات المسبقة',
438
464
  'wsPreset.done': 'تم تحديث الإعداد المسبق الافتراضي لمساحة العمل',
@@ -451,6 +477,13 @@ window.__ModuleLoader__.load({
451
477
  'settings.compactChains.hint': 'تُدمج المستويات ذات الفرع الواحد في سطر واحد، وتتوسع الشجرة تلقائياً عند وجود أكثر من فرع؛ أثناء سحب مساحة عمل تُفكّ السلاسل مؤقتاً إلى مجلدات ليصبح الإفلات ممكناً في أي مستوى؛ تُحفظ حالة التوسيع والمظهر المخصص في هذا المتصفح.',
452
478
  'settings.statusPulse': 'مؤشر حالة نابض',
453
479
  'settings.statusPulse.hint': 'مؤشرات الحالة التي يخفيها الطيّ (مكتمل أخضر / قيد التشغيل أزرق / بانتظار تفاعل كهرماني) تتصاعد عبر المستويات: تصدر أسطر مساحات العمل والمجلدات وميضاً نابضاً للأيقونة بلون الحالة (وتنبض معها العناوين ذات التوهج المخصص)، وتعرض أسطر مجموعات الجلسات نقطة حالة نابضة؛ مفعّل افتراضياً ويمكن إيقافه هنا.',
480
+ 'settings.density': 'كثافة الصفوف',
481
+ 'settings.density.compact': 'مضغوط',
482
+ 'settings.density.comfortable': 'مريح (افتراضي)',
483
+ 'settings.density.roomy': 'واسع',
484
+ 'settings.density.hint': 'ارتفاعات الصفوف تتبع الشريط الجانبي الرسمي: «مريح» هو الافتراضي الرسمي؛ «مضغوط» يوفر المساحة العمودية، «واسع» يمنح أهداف لمس أكبر. يسري فوراً ويُحفظ في هذا المتصفح.',
485
+ 'settings.showCount': 'شارة عدد الجلسات',
486
+ 'settings.showCount.hint': 'أزال الشريط الجانبي الرسمي عدّاد الجلسات لكل مساحة عمل؛ فعّله لإعادته في نهاية الصف (يتنازل لأزرار الصف عند التحويم). معطّل افتراضياً.',
454
487
  'settings.workspaceGroup': "تجميع مساحات العمل",
455
488
  'settings.workspaceGroup.hint': "مجلدات القرص: التداخل حسب المجلدات الرسمية (أدلة القرص) فقط. القرص + الأسماء: إضافة مجموعات الأسماء بشرطة مائلة فوق شجرة القرص. الأسماء فقط: التجميع بحسب الشرطة المائلة في العناوين وتجاهل تداخل القرص تماماً. تُنقل حالة مفتاح الإصدار السابق تلقائياً.",
456
489
  'settings.sessionLimit': "عدد الجلسات عند التوسيع",
@@ -599,7 +632,11 @@ window.__ModuleLoader__.load({
599
632
  'wsPreset.title': 'Workspace-Standardpreset ändern',
600
633
  'wsPreset.current': 'Aktueller Standardpreset',
601
634
  'wsPreset.currentUnknown': 'Aktueller Preset unbekannt',
602
- 'wsPreset.noBlank': 'Kein wartender Sitzungsentwurf in diesem Workspace; es gilt der globale Standard',
635
+ 'presetStandardName': 'Standardmodus',
636
+ 'presetPtcName': 'PTC-Modus',
637
+ 'presetMinimalName': 'Minimalmodus',
638
+ 'presetCordisName': 'Kreativmodus',
639
+ 'wsPreset.createHint': 'Kein wartender Sitzungsentwurf in diesem Workspace; nach dem Bestätigen wird ein neuer Entwurf mit dem gewählten Preset als Workspace-Standard vorbereitet',
603
640
  'wsPreset.empty': 'Keine Presets verfügbar',
604
641
  'wsPreset.loadFailed': 'Preset-Liste konnte nicht geladen werden',
605
642
  'wsPreset.done': 'Workspace-Standardpreset aktualisiert',
@@ -618,6 +655,13 @@ window.__ModuleLoader__.load({
618
655
  'settings.compactChains.hint': 'Ebenen mit genau einem Kind werden zu einer Zeile zusammengefasst; bei mehreren Kindern klappt der Baum auf; beim Ziehen eines Arbeitsbereichs klappen die Ketten vorübergehend zum Ordnerbaum auf, sodass jede Ebene als Ziel dient; Klappzustand und eigenes Aussehen bleiben in diesem Browser gespeichert.',
619
656
  'settings.statusPulse': 'Status-Pulslicht',
620
657
  'settings.statusPulse.hint': 'Vom Einklappen verdeckte Statuspunkte (fertig grün / läuft blau / wartet amber) steigen die Hierarchie hinauf: Arbeitsbereich- und Ordnerzeilen lassen ihr Symbol in der Statusfarbe pulsieren (eigens gefärbte Titel pulsieren mit), Sitzungsgruppen zeigen einen pulsierenden Statuspunkt; standardmäßig an, hier abschaltbar.',
658
+ 'settings.density': 'Zeilendichte',
659
+ 'settings.density.compact': 'Kompakt',
660
+ 'settings.density.comfortable': 'Komfortabel (Standard)',
661
+ 'settings.density.roomy': 'Großzügig',
662
+ 'settings.density.hint': 'Die Zeilenhöhen folgen der offiziellen Seitenleiste: Komfortabel ist der offizielle Standard; Kompakt spart vertikalen Platz, Großzügig bietet größere Trefferflächen. Wirkt sofort, wird in diesem Browser gespeichert.',
663
+ 'settings.showCount': 'Sitzungszähler',
664
+ 'settings.showCount.hint': 'Die offizielle Seitenleiste hat den Sitzungszähler entfernt; schalte ihn hier wieder ein (er weicht beim Hover den Zeilenaktionen). Standardmäßig aus.',
621
665
  'settings.workspaceGroup': "Arbeitsbereich-Gruppierung",
622
666
  'settings.workspaceGroup.hint': "Ordner: verschachtelt nur nach den offiziellen Ordnern (Festplattenpfade). Ordner + Namen: schichtet Namensgruppen über den Ordnerbaum. Nur Namen: gruppiert rein nach Schrägstrichen im Titel und ignoriert die Festplatten-Verschachtelung. Ein früherer Schalterzustand wandert automatisch mit.",
623
667
  'settings.sessionLimit': "Sitzungen beim Aufklappen",
@@ -766,7 +810,11 @@ window.__ModuleLoader__.load({
766
810
  'wsPreset.title': 'Modifier le préréglage par défaut du workspace',
767
811
  'wsPreset.current': 'Préréglage par défaut actuel',
768
812
  'wsPreset.currentUnknown': 'Préréglage actuel inconnu',
769
- 'wsPreset.noBlank': 'Aucune nouvelle session en attente : ce workspace suit le préréglage global par défaut',
813
+ 'presetStandardName': 'Mode standard',
814
+ 'presetPtcName': 'Mode PTC',
815
+ 'presetMinimalName': 'Mode minimal',
816
+ 'presetCordisName': 'Mode créateur',
817
+ 'wsPreset.createHint': 'Aucune nouvelle session en attente dans ce workspace ; la confirmation prépare une nouvelle session avec le préréglage choisi comme valeur par défaut',
770
818
  'wsPreset.empty': 'Aucun préréglage disponible',
771
819
  'wsPreset.loadFailed': 'Échec du chargement de la liste des préréglages',
772
820
  'wsPreset.done': 'Préréglage par défaut du workspace mis à jour',
@@ -785,6 +833,13 @@ window.__ModuleLoader__.load({
785
833
  'settings.compactChains.hint': 'Les niveaux à un seul enfant se fusionnent en une ligne; dès qu\'il y a plusieurs enfants, l\'arborescence se déploie; pendant le glisser-déposer d\'un espace de travail, les chaînes se déploient temporairement en dossiers pour permettre le dépôt à n\'importe quel niveau; l\'état déplié et l\'apparence personnalisée sont conservés dans ce navigateur.',
786
834
  'settings.statusPulse': 'Voyant d\'état pulsant',
787
835
  'settings.statusPulse.hint': 'Les voyants masqués par le repli (terminé vert / en cours bleu / en attente ambre) remontent la hiérarchie: les lignes d\'espace de travail et de dossier font pulser leur icône dans la couleur d\'état (les titres à lueur personnalisée pulsent aussi), les lignes de groupe de sessions affichent un voyant pulsant; activé par défaut, désactivable ici.',
836
+ 'settings.density': 'Densité des lignes',
837
+ 'settings.density.compact': 'Compact',
838
+ 'settings.density.comfortable': 'Confortable (par défaut)',
839
+ 'settings.density.roomy': 'Spacieux',
840
+ 'settings.density.hint': 'Les hauteurs de lignes suivent la barre latérale officielle : Confortable est la valeur officielle ; Compact gagne de la place verticale, Spacieux offre de plus grandes cibles. Effectif immédiatement, enregistré dans ce navigateur.',
841
+ 'settings.showCount': 'Compteur de sessions',
842
+ 'settings.showCount.hint': 'La barre officielle a retiré le compteur de sessions ; réactivez-le ici (il s\'efface devant les actions au survol). Désactivé par défaut.',
788
843
  'settings.workspaceGroup': "Regroupement des espaces",
789
844
  'settings.workspaceGroup.hint': "Dossiers disque : imbrique uniquement selon les dossiers officiels (répertoires disque). Disque + noms : ajoute les groupes de noms par barre oblique au-dessus de l’arborescence disque. Noms seuls : regroupe uniquement par barre oblique dans les titres et ignore l’imbrication disque. L’état de l’ancien interrupteur migre automatiquement.",
790
845
  'settings.sessionLimit': "Sessions affichées à l’ouverture",
@@ -933,7 +988,11 @@ window.__ModuleLoader__.load({
933
988
  'wsPreset.title': 'वर्कस्पेस डिफ़ॉल्ट प्रीसेट बदलें',
934
989
  'wsPreset.current': 'वर्तमान डिफ़ॉल्ट प्रीसेट',
935
990
  'wsPreset.currentUnknown': 'वर्तमान प्रीसेट अज्ञात',
936
- 'wsPreset.noBlank': 'इस वर्कस्पेस में कोई प्रतीक्षारत नया सेशन नहीं है; यह वैश्विक डिफ़ॉल्ट का पालन करता है',
991
+ 'presetStandardName': 'मानक मोड',
992
+ 'presetPtcName': 'PTC मोड',
993
+ 'presetMinimalName': 'मिनिमल मोड',
994
+ 'presetCordisName': 'क्रिएटर मोड',
995
+ 'wsPreset.createHint': 'इस वर्कस्पेस में कोई प्रतीक्षारत नया सेशन नहीं है; पुष्टि करने पर चयनित प्रीसेट के साथ एक नया सेशन वर्कस्पेस डिफ़ॉल्ट के रूप में तैयार किया जाएगा',
937
996
  'wsPreset.empty': 'कोई प्रीसेट उपलब्ध नहीं',
938
997
  'wsPreset.loadFailed': 'प्रीसेट सूची लोड करने में विफल',
939
998
  'wsPreset.done': 'वर्कस्पेस डिफ़ॉल्ट प्रीसेट अपडेट हुआ',
@@ -952,6 +1011,13 @@ window.__ModuleLoader__.load({
952
1011
  'settings.compactChains.hint': 'जिन स्तरों पर केवल एक संतान होती है वे एक पंक्ति में मिल जाते हैं; कई संतानें होने पर वृक्ष खुल जाता है; कार्यस्थान खींचते समय श्रृंखलाएँ अस्थायी रूप से फ़ोल्डरों में खुल जाती हैं ताकि किसी भी स्तर पर छोड़ा जा सके; खुलने की स्थिति और अनुकूलित रूप इसी ब्राउज़र में सहेजे जाते हैं।',
953
1012
  'settings.statusPulse': 'स्थिति की साँस लेती बत्ती',
954
1013
  'settings.statusPulse.hint': 'समेटने से छिपी स्थिति बत्तियाँ (पूर्ण हरी / चल रहा नीली / प्रतीक्षारत अंबर) पदानुक्रम में ऊपर उठती हैं: कार्यस्थान और फ़ोल्डर पंक्तियों का चिह्न स्थिति के रंग में साँस लेता है (अनुकूलित चमक वाले शीर्षक भी साथ साँस लेते हैं), सत्र समूह पंक्तियों पर साँस लेता स्थिति बिंदु दिखता है; डिफ़ॉल्ट रूप से चालू, यहाँ बंद किया जा सकता है।',
1014
+ 'settings.density': 'पंक्ति घनत्व',
1015
+ 'settings.density.compact': 'संकुचित',
1016
+ 'settings.density.comfortable': 'आरामदायक (डिफ़ॉल्ट)',
1017
+ 'settings.density.roomy': 'विस्तृत',
1018
+ 'settings.density.hint': 'पंक्ति ऊँचाइयाँ आधिकारिक साइडबार के अनुसार: आरामदायक आधिकारिक डिफ़ॉल्ट है; संकुचित ऊर्ध्वाधर जगह बचाता है, विस्तृत बड़े क्लिक क्षेत्र देता है। तुरंत लागू, इस ब्राउज़र में सहेजा जाता है।',
1019
+ 'settings.showCount': 'सेशन गिनती बैज',
1020
+ 'settings.showCount.hint': 'आधिकारिक साइडबार ने सेशन गिनती हटा दी; इसे पंक्ति के अंत में वापस लाने के लिए चालू करें (होवर पर पंक्ति क्रियाओं को जगह देता है)। डिफ़ॉल्ट रूप से बंद।',
955
1021
  'settings.workspaceGroup': "वर्कस्पेस समूहीकरण",
956
1022
  'settings.workspaceGroup.hint': "डिस्क फ़ोल्डर: केवल आधिकारिक फ़ोल्डर (डिस्क निर्देशिका) से नेस्ट। डिस्क + नाम: डिस्क ट्री के ऊपर स्लैश नाम समूह जोड़ता है। केवल नाम: केवल शीर्षक के स्लैश से समूहित करता है, डिस्क नेस्टिंग की अनदेखी करता है। पुराने स्विच की स्थिति स्वतः माइग्रेट होती है।",
957
1023
  'settings.sessionLimit': "विस्तार पर दिखने वाले सेशन",
@@ -1100,7 +1166,11 @@ window.__ModuleLoader__.load({
1100
1166
  'wsPreset.title': 'Ubah preset bawaan ruang kerja',
1101
1167
  'wsPreset.current': 'Preset bawaan saat ini',
1102
1168
  'wsPreset.currentUnknown': 'Preset saat ini tidak diketahui',
1103
- 'wsPreset.noBlank': 'Tidak ada sesi baru yang menunggu di ruang kerja ini; mengikuti bawaan global',
1169
+ 'presetStandardName': 'Mode standar',
1170
+ 'presetPtcName': 'Mode PTC',
1171
+ 'presetMinimalName': 'Mode minimal',
1172
+ 'presetCordisName': 'Mode kreator',
1173
+ 'wsPreset.createHint': 'Tidak ada sesi baru yang menunggu di ruang kerja ini; setelah dikonfirmasi, sesi baru dengan prasetel pilihan akan disiapkan sebagai bawaan ruang kerja',
1104
1174
  'wsPreset.empty': 'Tidak ada preset yang tersedia',
1105
1175
  'wsPreset.loadFailed': 'Gagal memuat daftar preset',
1106
1176
  'wsPreset.done': 'Preset bawaan ruang kerja diperbarui',
@@ -1119,6 +1189,13 @@ window.__ModuleLoader__.load({
1119
1189
  'settings.compactChains.hint': 'Tingkat yang hanya punya satu anak digabung jadi satu baris; bila anaknya banyak, pohon terbentang; saat menyeret ruang kerja, rantai sementara terbentang kembali jadi folder sehingga bisa dijatuhkan di tingkat mana pun; status terbentang dan tampilan khusus disimpan di browser ini.',
1120
1190
  'settings.statusPulse': 'Lampu status bernapas',
1121
1191
  'settings.statusPulse.hint': 'Lampu status yang tersembunyi karena pelipatan (selesai hijau / berjalan biru / menunggu kuning) menggelembung ke tingkat atas: baris ruang kerja dan folder membuat ikonnya bernapas dalam warna status (judul dengan pendar khusus ikut bernapas), baris grup sesi menampilkan titik status yang bernapas; aktif secara bawaan, bisa dimatikan di sini.',
1192
+ 'settings.density': 'Kepadatan baris',
1193
+ 'settings.density.compact': 'Ringkas',
1194
+ 'settings.density.comfortable': 'Nyaman (bawaan)',
1195
+ 'settings.density.roomy': 'Lega',
1196
+ 'settings.density.hint': 'Tinggi baris mengikuti bilah samping resmi: Nyaman adalah bawaan resmi; Ringkas menghemat ruang vertikal, Lega memberi area klik lebih besar. Berlaku langsung, disimpan di peramban ini.',
1197
+ 'settings.showCount': 'Lencana jumlah sesi',
1198
+ 'settings.showCount.hint': 'Bilah samping resmi menghapus hitungan sesi; aktifkan untuk menampilkannya lagi di ujung baris (memberi tempat untuk tombol baris saat hover). Mati secara bawaan.',
1122
1199
  'settings.workspaceGroup': "Pengelompokan ruang kerja",
1123
1200
  'settings.workspaceGroup.hint': "Folder disk: bersarang hanya mengikuti folder resmi (direktori disk). Disk + nama: menambahkan grup nama garis miring di atas pohon disk. Hanya nama: mengelompokkan murni dari garis miring pada judul dan mengabaikan struktur disk. Status sakelar versi lama bermigrasi otomatis.",
1124
1201
  'settings.sessionLimit': "Sesi saat dibuka",
@@ -1267,7 +1344,11 @@ window.__ModuleLoader__.load({
1267
1344
  'wsPreset.title': 'Modifica il preset predefinito dello workspace',
1268
1345
  'wsPreset.current': 'Preset predefinito attuale',
1269
1346
  'wsPreset.currentUnknown': 'Preset attuale sconosciuto',
1270
- 'wsPreset.noBlank': 'Nessuna nuova sessione in attesa in questo workspace; vale il predefinito globale',
1347
+ 'presetStandardName': 'Modalità standard',
1348
+ 'presetPtcName': 'Modalità PTC',
1349
+ 'presetMinimalName': 'Modalità minimale',
1350
+ 'presetCordisName': 'Modalità creatore',
1351
+ 'wsPreset.createHint': 'Nessuna nuova sessione in attesa in questo workspace; confermando verrà preparata una nuova sessione con il preset scelto come predefinito del workspace',
1271
1352
  'wsPreset.empty': 'Nessun preset disponibile',
1272
1353
  'wsPreset.loadFailed': 'Impossibile caricare la lista dei preset',
1273
1354
  'wsPreset.done': 'Preset predefinito dello workspace aggiornato',
@@ -1286,6 +1367,13 @@ window.__ModuleLoader__.load({
1286
1367
  'settings.compactChains.hint': 'I livelli con un solo figlio si fondono in una riga; con più figli l\'albero si espande; durante il trascinamento di un\'area di lavoro le catene si riespandono temporaneamente in cartelle, così puoi rilasciare su qualsiasi livello; stato di espansione e aspetto personalizzato restano in questo browser.',
1287
1368
  'settings.statusPulse': 'Spia di stato pulsante',
1288
1369
  'settings.statusPulse.hint': 'Le spie nascoste dalla compressione (completato verde / in esecuzione blu / in attesa ambra) risalgono la gerarchia: le righe di aree di lavoro e cartelle fanno pulsare l\'icona nel colore di stato (anche i titoli con bagliore personalizzato pulsano), le righe dei gruppi di sessioni mostrano una spia pulsante; attiva per impostazione predefinita, disattivabile qui.',
1370
+ 'settings.density': 'Densità delle righe',
1371
+ 'settings.density.compact': 'Compatto',
1372
+ 'settings.density.comfortable': 'Comodo (predefinito)',
1373
+ 'settings.density.roomy': 'Ampio',
1374
+ 'settings.density.hint': 'Le altezze delle righe seguono la barra laterale ufficiale: Comodo è il predefinito ufficiale; Compatto risparmia spazio verticale, Ampio offre aree di clic maggiori. Immediato, salvato in questo browser.',
1375
+ 'settings.showCount': 'Conteggio sessioni',
1376
+ 'settings.showCount.hint': 'La barra ufficiale ha rimosso il conteggio sessioni; attivalo per ripristinarlo in coda alla riga (cede ai pulsanti della riga al passaggio del mouse). Disattivato per impostazione predefinita.',
1289
1377
  'settings.workspaceGroup': "Raggruppamento aree di lavoro",
1290
1378
  'settings.workspaceGroup.hint': "Cartelle disco: annida solo secondo le cartelle ufficiali (directory disco). Disco + nomi: aggiunge gruppi di nomi per barra obliqua sopra l’albero disco. Solo nomi: raggruppa solo per barra obliqua nei titoli ignorando l’annidamento disco. Lo stato del vecchio interruttore migra automaticamente.",
1291
1379
  'settings.sessionLimit': "Sessioni mostrate all’apertura",
@@ -1434,7 +1522,11 @@ window.__ModuleLoader__.load({
1434
1522
  'wsPreset.title': 'ワークスペース既定プリセットを変更',
1435
1523
  'wsPreset.current': '現在の既定プリセット',
1436
1524
  'wsPreset.currentUnknown': '現在のプリセットは不明',
1437
- 'wsPreset.noBlank': 'このワークスペースには待機中の新規セッションがなく、グローバル既定に従います',
1525
+ 'presetStandardName': '標準モード',
1526
+ 'presetPtcName': 'PTC モード',
1527
+ 'presetMinimalName': 'ミニマルモード',
1528
+ 'presetCordisName': 'クリエイターモード',
1529
+ 'wsPreset.createHint': 'このワークスペースには待機中の新規セッションがありません。確定すると、選択したプリセットで新しいセッションをワークスペースの既定として用意します',
1438
1530
  'wsPreset.empty': '利用可能なプリセットがありません',
1439
1531
  'wsPreset.loadFailed': 'プリセット一覧の読み込みに失敗しました',
1440
1532
  'wsPreset.done': 'ワークスペース既定プリセットを更新しました',
@@ -1453,6 +1545,13 @@ window.__ModuleLoader__.load({
1453
1545
  'settings.compactChains.hint': '子が 1 つだけの階層は 1 行にまとまり,複数の子がある階層はツリーとして展開されます;ワークスペースのドラッグ中は一時的にフォルダーツリーへ戻り,どの階層にもドロップできます;展開状態とカスタム外観はこのブラウザーに保存されます。',
1454
1546
  'settings.statusPulse': 'ステータス表示灯',
1455
1547
  'settings.statusPulse.hint': '折りたたみで隠れたステータス表示(完了は緑 / 実行中は青 / 操作待ちは琥珀)が上位の階層へ伝わります:ワークスペースとフォルダーの行はアイコンがステータス色で明滅し(グローを設定したタイトルも連動),セッショングループの行には明滅するステータスドットが出ます;既定はオンで,ここでオフにできます。',
1548
+ 'settings.density': '行の密度',
1549
+ 'settings.density.compact': 'コンパクト',
1550
+ 'settings.density.comfortable': '標準(既定)',
1551
+ 'settings.density.roomy': 'ゆったり',
1552
+ 'settings.density.hint': '行の高さは公式サイドバーに従います:標準(既定)が公式の既定値。コンパクトは縦幅を節約、ゆったりはクリック領域を広く。変更は即時反映され、このブラウザーに保存されます。',
1553
+ 'settings.showCount': 'セッション数バッジ',
1554
+ 'settings.showCount.hint': '公式サイドバーはワークスペース行のセッション数を廃止しました。オンにすると行末に再表示します(ホバー時は操作ボタンに譲ります)。既定はオフ。',
1456
1555
  'settings.workspaceGroup': "ワークスペースのグループ化",
1457
1556
  'settings.workspaceGroup.hint': "フォルダー:公式フォルダー(ディスク ディレクトリ)のみで階層化。フォルダー + 名前:ディスク ツリーに加えてタイトルのスラッシュでもグループ化。名前のみ:タイトルのスラッシュだけでグループ化し、ディスクの階層は無視。旧バージョンのスイッチ状態は自動的に引き継がれます。",
1458
1557
  'settings.sessionLimit': "展開時に表示するセッション数",
@@ -1601,7 +1700,11 @@ window.__ModuleLoader__.load({
1601
1700
  'wsPreset.title': '작업 공간 기본 프리셋 변경',
1602
1701
  'wsPreset.current': '현재 기본 프리셋',
1603
1702
  'wsPreset.currentUnknown': '현재 프리셋을 알 수 없음',
1604
- 'wsPreset.noBlank': '이 작업 공간에는 대기 중인 새 세션이 없으며 전역 기본값을 따릅니다',
1703
+ 'presetStandardName': '표준 모드',
1704
+ 'presetPtcName': 'PTC 모드',
1705
+ 'presetMinimalName': '미니멀 모드',
1706
+ 'presetCordisName': '크리에이터 모드',
1707
+ 'wsPreset.createHint': '이 작업 공간에는 대기 중인 새 세션이 없습니다. 확인하면 선택한 프리셋으로 새 세션을 준비해 작업 공간 기본값으로 사용합니다',
1605
1708
  'wsPreset.empty': '사용 가능한 프리셋이 없습니다',
1606
1709
  'wsPreset.loadFailed': '프리셋 목록을 불러오지 못했습니다',
1607
1710
  'wsPreset.done': '작업 공간 기본 프리셋이 업데이트되었습니다',
@@ -1620,6 +1723,13 @@ window.__ModuleLoader__.load({
1620
1723
  'settings.compactChains.hint': '자식이 하나뿐인 단계는 한 줄로 합쳐지고, 자식이 여러 개면 트리로 펼쳐집니다; 워크스페이스를 끌어 놓는 동안에는 체인이 잠시 폴더 트리로 펼쳐져 어느 단계에나 놓을 수 있습니다; 펼침 상태와 사용자 지정 모양은 이 브라우저에 저장됩니다.',
1621
1724
  'settings.statusPulse': '상태 표시등',
1622
1725
  'settings.statusPulse.hint': '접혀서 가려진 상태 표시(완료는 초록 / 실행은 파랑 / 대기는 호박색)가 상위 단계로 번집니다: 워크스페이스와 폴더 행은 아이콘이 상태 색으로 깜빡이고(글로우를 지정한 제목도 함께 깜빡임), 세션 그룹 행에는 깜빡이는 상태 점이 표시됩니다; 기본값은 켜짐이며 여기서 끌 수 있습니다.',
1726
+ 'settings.density': '행 밀도',
1727
+ 'settings.density.compact': '컴팩트',
1728
+ 'settings.density.comfortable': '편안함(기본값)',
1729
+ 'settings.density.roomy': '여유',
1730
+ 'settings.density.hint': '행 높이는 공식 사이드바를 따릅니다: 편안함이 공식 기본값이며, 컴팩트는 세로 공간을 아끼고 여유는 더 큰 클릭 영역을 줍니다. 즉시 적용되며 이 브라우저에 저장됩니다.',
1731
+ 'settings.showCount': '세션 수 배지',
1732
+ 'settings.showCount.hint': '공식 사이드바는 워크스페이스 행의 세션 수를 제거했습니다. 켜면 행 끝에 다시 표시합니다(호버 시 작업 버튼에 양보). 기본값은 꺼짐입니다.',
1623
1733
  'settings.workspaceGroup': "작업 공간 그룹화",
1624
1734
  'settings.workspaceGroup.hint': "디스크 폴더: 공식 폴더(디스크 디렉터리)로만 중첩합니다. 디스크 + 이름: 디스크 트리 위에 슬래시 이름 그룹을 얹습니다. 이름만: 제목의 슬래시로만 그룹화하고 디스크 중첩은 무시합니다. 이전 버전 스위치 상태는 자동으로 이전됩니다.",
1625
1735
  'settings.sessionLimit': "펼칠 때 표시할 세션",
@@ -1768,7 +1878,11 @@ window.__ModuleLoader__.load({
1768
1878
  'wsPreset.title': 'Standaardpreset van workspace wijzigen',
1769
1879
  'wsPreset.current': 'Huidige standaardpreset',
1770
1880
  'wsPreset.currentUnknown': 'Huidige preset onbekend',
1771
- 'wsPreset.noBlank': 'Geen wachtende nieuwe sessie in deze workspace; de globale standaard geldt',
1881
+ 'presetStandardName': 'Standaardmodus',
1882
+ 'presetPtcName': 'PTC-modus',
1883
+ 'presetMinimalName': 'Minimale modus',
1884
+ 'presetCordisName': 'Creatormodus',
1885
+ 'wsPreset.createHint': 'Geen wachtende nieuwe sessie in deze workspace; bij bevestigen wordt een nieuwe sessie met de gekozen preset als workspace-standaard voorbereid',
1772
1886
  'wsPreset.empty': 'Geen presets beschikbaar',
1773
1887
  'wsPreset.loadFailed': 'Laden van de presetlijst is mislukt',
1774
1888
  'wsPreset.done': 'Standaardpreset van workspace bijgewerkt',
@@ -1787,6 +1901,13 @@ window.__ModuleLoader__.load({
1787
1901
  'settings.compactChains.hint': 'Niveaus met één kind worden tot één regel samengevoegd; bij meerdere kinderen klapt de boom open; tijdens het slepen van een werkruimte klappen de ketens tijdelijk terug naar mappen, zodat je op elk niveau kunt neerzetten; de uitgeklapte staat en het eigen uiterlijk blijven in deze browser bewaard.',
1788
1902
  'settings.statusPulse': 'Pulserend statuslampje',
1789
1903
  'settings.statusPulse.hint': 'Statuslampjes die door het inklappen verborgen zijn (voltooid groen / actief blauw / wachtend amber) borrelen door de hiërarchie omhoog: werkruimte- en mapregels laten hun pictogram in de statuskleur pulseren (titels met eigen gloed pulseren mee), sessiegroepregels tonen een pulserend statusstipje; standaard aan, hier uit te zetten.',
1904
+ 'settings.density': 'Rijdichtheid',
1905
+ 'settings.density.compact': 'Compact',
1906
+ 'settings.density.comfortable': 'Comfortabel (standaard)',
1907
+ 'settings.density.roomy': 'Ruim',
1908
+ 'settings.density.hint': 'Rijhoogtes volgen de officiële zijbalk: Comfortabel is de officiële standaard; Compact bespaart verticale ruimte, Ruim geeft grotere klikgebieden. Direct actief, opgeslagen in deze browser.',
1909
+ 'settings.showCount': 'Sessieteller',
1910
+ 'settings.showCount.hint': 'De officiële zijbalk heeft de sessieteller verwijderd; zet hem hier weer aan (hij wijkt bij hover voor de rijacties). Standaard uit.',
1790
1911
  'settings.workspaceGroup': "Werkruimtegroepering",
1791
1912
  'settings.workspaceGroup.hint': "Schijfmappen: nest alleen volgens de officiële mappen (schijfdirectories). Schijf + namen: stapelt naamgroepen bovenop de schijfboom. Alleen namen: groept puur op schuine strepen in titels en negeert schijfnesting. De oude schakelaarstand migreert automatisch.",
1792
1913
  'settings.sessionLimit': "Sessies bij uitklappen",
@@ -1935,7 +2056,11 @@ window.__ModuleLoader__.load({
1935
2056
  'wsPreset.title': 'Zmień domyślny preset obszaru roboczego',
1936
2057
  'wsPreset.current': 'Aktualny domyślny preset',
1937
2058
  'wsPreset.currentUnknown': 'Aktualny preset nieznany',
1938
- 'wsPreset.noBlank': 'Brak oczekującej nowej sesji w tym obszarze; obowiązuje globalne ustawienie domyślne',
2059
+ 'presetStandardName': 'Tryb standardowy',
2060
+ 'presetPtcName': 'Tryb PTC',
2061
+ 'presetMinimalName': 'Tryb minimalny',
2062
+ 'presetCordisName': 'Tryb twórcy',
2063
+ 'wsPreset.createHint': 'Brak oczekującej nowej sesji w tym obszarze roboczym; po potwierdzeniu zostanie przygotowana nowa sesja z wybranym ustawieniem jako domyślnym obszaru roboczego',
1939
2064
  'wsPreset.empty': 'Brak dostępnych presetów',
1940
2065
  'wsPreset.loadFailed': 'Nie udało się wczytać listy presetów',
1941
2066
  'wsPreset.done': 'Zaktualizowano domyślny preset obszaru roboczego',
@@ -1954,6 +2079,13 @@ window.__ModuleLoader__.load({
1954
2079
  'settings.compactChains.hint': 'Poziomy z jednym dzieckiem scalają się w jeden wiersz; przy większej liczbie dzieci drzewo się rozwija; podczas przeciągania obszaru roboczego łańcuchy tymczasowo rozwijają się do folderów, więc upuszczenie działa na każdym poziomie; stan rozwinięcia i własny wygląd są zapisywane w tej przeglądarce.',
1955
2080
  'settings.statusPulse': 'Pulsująca lampka stanu',
1956
2081
  'settings.statusPulse.hint': 'Lampki ukryte przez zwinięcie (ukończono zielona / trwa niebieska / czeka bursztynowa) bąbelkują w górę hierarchii: wiersze obszarów roboczych i folderów pulsują ikoną w kolorze stanu (tytuły z własną poświatą pulsują razem), wiersze grup sesji pokazują pulsującą kropkę stanu; domyślnie włączone, tutaj można wyłączyć.',
2082
+ 'settings.density': 'Gęstość wierszy',
2083
+ 'settings.density.compact': 'Zwięzły',
2084
+ 'settings.density.comfortable': 'Wygodny (domyślny)',
2085
+ 'settings.density.roomy': 'Przestronny',
2086
+ 'settings.density.hint': 'Wysokości wierszy są zgodne z oficjalnym panelem: Wygodny to oficjalny domyślny; Zwięzły oszczędza miejsce w pionie, Przestronny daje większe pola klikania. Działa natychmiast, zapis w tej przeglądarce.',
2087
+ 'settings.showCount': 'Licznik sesji',
2088
+ 'settings.showCount.hint': 'Oficjalny panel usunął licznik sesji; włącz, aby przywrócić go na końcu wiersza (ustępuje przyciskom wiersza po najechaniu). Domyślnie wyłączone.',
1957
2089
  'settings.workspaceGroup': "Grupowanie obszarów roboczych",
1958
2090
  'settings.workspaceGroup.hint': "Foldery dysku: zagnieżdżanie wyłącznie według oficjalnych folderów (katalogów dysku). Dysk + nazwy: dokłada grupy nazw nad drzewem dysku. Tylko nazwy: grupuje wyłącznie po ukośnikach w tytułach, ignorując zagnieżdżanie dyskowe. Stan starego przełącznika migruje automatycznie.",
1959
2091
  'settings.sessionLimit': "Sesje po rozwinięciu",
@@ -2102,7 +2234,11 @@ window.__ModuleLoader__.load({
2102
2234
  'wsPreset.title': 'Alterar o preset padrão do workspace',
2103
2235
  'wsPreset.current': 'Preset padrão atual',
2104
2236
  'wsPreset.currentUnknown': 'Preset atual desconhecido',
2105
- 'wsPreset.noBlank': 'Nenhuma nova sessão em espera neste workspace; vale o padrão global',
2237
+ 'presetStandardName': 'Modo padrão',
2238
+ 'presetPtcName': 'Modo PTC',
2239
+ 'presetMinimalName': 'Modo mínimo',
2240
+ 'presetCordisName': 'Modo criador',
2241
+ 'wsPreset.createHint': 'Nenhuma nova sessão em espera neste workspace; ao confirmar, uma nova sessão com o predefinido escolhido será preparada como padrão do workspace',
2106
2242
  'wsPreset.empty': 'Nenhum preset disponível',
2107
2243
  'wsPreset.loadFailed': 'Falha ao carregar a lista de presets',
2108
2244
  'wsPreset.done': 'Preset padrão do workspace atualizado',
@@ -2121,6 +2257,13 @@ window.__ModuleLoader__.load({
2121
2257
  'settings.compactChains.hint': 'Níveis com um único filho viram uma linha; com vários filhos a árvore se expande; ao arrastar um espaço de trabalho, as cadeias voltam temporariamente a pastas, permitindo soltar em qualquer nível; o estado de expansão e o estilo personalizado ficam neste navegador.',
2122
2258
  'settings.statusPulse': 'Luz de status pulsante',
2123
2259
  'settings.statusPulse.hint': 'As luzes escondidas pelo recolhimento (concluído verde / em execução azul / aguardando âmbar) sobem pela hierarquia: linhas de espaço de trabalho e pasta pulsam o ícone na cor do status (títulos com brilho personalizado pulsam junto), linhas de grupo de sessões mostram um ponto pulsante; ativado por padrão, pode ser desligado aqui.',
2260
+ 'settings.density': 'Densidade das linhas',
2261
+ 'settings.density.compact': 'Compacto',
2262
+ 'settings.density.comfortable': 'Confortável (padrão)',
2263
+ 'settings.density.roomy': 'Espaçoso',
2264
+ 'settings.density.hint': 'As alturas das linhas seguem a barra lateral oficial: Confortável é o padrão oficial; Compacto economiza espaço vertical, Espaçoso dá áreas de clique maiores. Aplica na hora, salvo neste navegador.',
2265
+ 'settings.showCount': 'Contador de sessões',
2266
+ 'settings.showCount.hint': 'A barra oficial removeu a contagem de sessões; ative para restaurá-la no fim da linha (cede aos botões da linha ao passar o mouse). Desativado por padrão.',
2124
2267
  'settings.workspaceGroup': "Agrupamento de espaços",
2125
2268
  'settings.workspaceGroup.hint': "Pastas do disco: aninhar apenas pelas pastas oficiais (diretórios de disco). Disco + nomes: acrescenta grupos de nomes sobre a árvore de disco. Apenas nomes: agrupa só pela barra nos títulos, ignorando o aninhamento de disco. O estado do interruptor antigo migra automaticamente.",
2126
2269
  'settings.sessionLimit': "Sessões ao expandir",
@@ -2269,7 +2412,11 @@ window.__ModuleLoader__.load({
2269
2412
  'wsPreset.title': 'Изменить пресет воркспейса по умолчанию',
2270
2413
  'wsPreset.current': 'Текущий пресет по умолчанию',
2271
2414
  'wsPreset.currentUnknown': 'Текущий пресет неизвестен',
2272
- 'wsPreset.noBlank': 'В этом воркспейсе нет ожидающей новой сессии; действует глобальный пресет по умолчанию',
2415
+ 'presetStandardName': 'Стандартный режим',
2416
+ 'presetPtcName': 'Режим PTC',
2417
+ 'presetMinimalName': 'Минимальный режим',
2418
+ 'presetCordisName': 'Режим создателя',
2419
+ 'wsPreset.createHint': 'В этом воркспейсе нет ожидающей новой сессии; после подтверждения будет подготовлена новая сессия с выбранным пресетом в качестве дефолта воркспейса',
2273
2420
  'wsPreset.empty': 'Нет доступных пресетов',
2274
2421
  'wsPreset.loadFailed': 'Не удалось загрузить список пресетов',
2275
2422
  'wsPreset.done': 'Пресет воркспейса по умолчанию обновлён',
@@ -2288,6 +2435,13 @@ window.__ModuleLoader__.load({
2288
2435
  'settings.compactChains.hint': 'Уровни с единственным потомком сливаются в одну строку; при нескольких потомках дерево разворачивается; во время перетаскивания рабочей области цепочки временно разворачиваются в папки, чтобы можно было положить элемент на любой уровень; состояние сворачивания и оформление хранятся в этом браузере.',
2289
2436
  'settings.statusPulse': 'Пульсирующий индикатор состояния',
2290
2437
  'settings.statusPulse.hint': 'Индикаторы, скрытые сворачиванием (завершено — зелёный / выполняется — синий / ожидает — янтарный), всплывают вверх по иерархии: строки рабочих областей и папок пульсируют значком в цвете состояния (заголовки со своим свечением пульсируют вместе с ними), строки групп сессий показывают пульсирующую точку; включено по умолчанию, здесь можно отключить.',
2438
+ 'settings.density': 'Плотность строк',
2439
+ 'settings.density.compact': 'Компактно',
2440
+ 'settings.density.comfortable': 'Обычно (по умолчанию)',
2441
+ 'settings.density.roomy': 'Просторно',
2442
+ 'settings.density.hint': 'Высоты строк соответствуют официальной боковой панели: «Обычно» — официальный стандарт; «Компактно» экономит место по вертикали, «Просторно» увеличивает зоны нажатия. Применяется сразу, хранится в этом браузере.',
2443
+ 'settings.showCount': 'Счётчик сессий',
2444
+ 'settings.showCount.hint': 'Официальная панель убрала счётчик сессий; включите, чтобы вернуть его в конец строки (уступает кнопкам строки при наведении). По умолчанию выключено.',
2291
2445
  'settings.workspaceGroup': "Группировка рабочих областей",
2292
2446
  'settings.workspaceGroup.hint': "Папки диска: вложенность только по официальным папкам (каталогам диска). Диск + имена: добавляет группы имён поверх дерева диска. Только имена: группирует лишь по косой черте в названиях, игнорируя вложенность диска. Состояние старого переключателя переносится автоматически.",
2293
2447
  'settings.sessionLimit': "Сессий при разворачивании",
@@ -2436,7 +2590,11 @@ window.__ModuleLoader__.load({
2436
2590
  'wsPreset.title': 'Ändra standardförinställning för arbetsyta',
2437
2591
  'wsPreset.current': 'Aktuell standardförinställning',
2438
2592
  'wsPreset.currentUnknown': 'Aktuell förinställning okänd',
2439
- 'wsPreset.noBlank': 'Ingen väntande ny session i denna arbetsyta; den globala standarden gäller',
2593
+ 'presetStandardName': 'Standardläge',
2594
+ 'presetPtcName': 'PTC-läge',
2595
+ 'presetMinimalName': 'Minimalt läge',
2596
+ 'presetCordisName': 'Skaparläge',
2597
+ 'wsPreset.createHint': 'Ingen väntande ny session i denna arbetsyta; vid bekräftelse förbereds en ny session med den valda förinställningen som arbetsytans standard',
2440
2598
  'wsPreset.empty': 'Inga förinställningar tillgängliga',
2441
2599
  'wsPreset.loadFailed': 'Kunde inte läsa in förinställningslistan',
2442
2600
  'wsPreset.done': 'Standardförinställning för arbetsyta uppdaterad',
@@ -2455,6 +2613,13 @@ window.__ModuleLoader__.load({
2455
2613
  'settings.compactChains.hint': 'Nivåer med ett enda barn slås ihop till en rad; med flera barn fälls trädet ut; medan du drar en arbetsyta fälls kedjorna tillfälligt ut till mappar så att du kan släppa på vilken nivå som helst; utfällt läge och eget utseende sparas i den här webbläsaren.',
2456
2614
  'settings.statusPulse': 'Pulserande statuslampa',
2457
2615
  'settings.statusPulse.hint': 'Statuslampor som döljs av ihopfällningen (klart grön / körs blå / väntar bärnsten) bubblar uppåt i hierarkin: rader för arbetsytor och mappar pulserar ikonen i statusfärgen (titlar med egen glöd pulserar med), sessionsgrupper visar en pulserande prick; på som standard, stängs av här.',
2616
+ 'settings.density': 'Radtäthet',
2617
+ 'settings.density.compact': 'Kompakt',
2618
+ 'settings.density.comfortable': 'Bekväm (standard)',
2619
+ 'settings.density.roomy': 'Rymlig',
2620
+ 'settings.density.hint': 'Radhöjder följer det officiella sidofältet: Bekväm är officiell standard; Kompakt sparar vertikalt utrymme, Rymlig ger större klickytor. Verkar direkt, sparas i webbläsaren.',
2621
+ 'settings.showCount': 'Sessionsräknare',
2622
+ 'settings.showCount.hint': 'Det officiella sidofältet tog bort sessionsräknaren; slå på den för att återställa den i radens slut (den väjer för radknapparna vid hover). Av som standard.',
2458
2623
  'settings.workspaceGroup': "Arbetsytegruppering",
2459
2624
  'settings.workspaceGroup.hint': "Skivmappar: kapslar bara enligt de officiella mapparna (skivkataloger). Skiva + namn: lägger namngrupper ovanpå skivträdet. Endast namn: grupperar rent efter snedstreck i titlar och ignorerar skivkapsling. Den gamla reglagestatusen migreras automatiskt.",
2460
2625
  'settings.sessionLimit': "Sessioner vid utfällning",
@@ -2603,7 +2768,11 @@ window.__ModuleLoader__.load({
2603
2768
  'wsPreset.title': 'เปลี่ยนพรีเซ็ตเริ่มต้นของพื้นที่ทำงาน',
2604
2769
  'wsPreset.current': 'พรีเซ็ตเริ่มต้นปัจจุบัน',
2605
2770
  'wsPreset.currentUnknown': 'ไม่ทราบพรีเซ็ตปัจจุบัน',
2606
- 'wsPreset.noBlank': 'ไม่มีเซสชันใหม่ที่รออยู่ในพื้นที่ทำงานนี้; ใช้ค่าเริ่มต้นส่วนกลาง',
2771
+ 'presetStandardName': 'โหมดมาตรฐาน',
2772
+ 'presetPtcName': 'โหมด PTC',
2773
+ 'presetMinimalName': 'โหมดมินิมอล',
2774
+ 'presetCordisName': 'โหมดครีเอเตอร์',
2775
+ 'wsPreset.createHint': 'ยังไม่มีเซสชันใหม่ที่รออยู่ในพื้นที่ทำงานนี้; เมื่อยืนยันจะเตรียมเซสชันใหม่ที่ใช้พรีเซ็ตที่เลือกเป็นค่าเริ่มต้นของพื้นที่ทำงาน',
2607
2776
  'wsPreset.empty': 'ไม่มีพรีเซ็ตที่ใช้ได้',
2608
2777
  'wsPreset.loadFailed': 'โหลดรายการพรีเซ็ตไม่สำเร็จ',
2609
2778
  'wsPreset.done': 'อัปเดตพรีเซ็ตเริ่มต้นของพื้นที่ทำงานแล้ว',
@@ -2622,6 +2791,13 @@ window.__ModuleLoader__.load({
2622
2791
  'settings.compactChains.hint': 'ระดับที่มีลูกเพียงหนึ่งจะถูกรวมเป็นบรรทัดเดียว เมื่อมีลูกหลายตัวต้นไม้จะขยายออก ขณะลากพื้นที่ทำงาน สายจะขยายกลับเป็นโฟลเดอร์ชั่วคราวเพื่อให้วางได้ทุกระดับ สถานะการขยายและรูปลักษณ์ที่กำหนดเองจะถูกเก็บไว้ในเบราว์เซอร์นี้',
2623
2792
  'settings.statusPulse': 'ไฟสถานะหายใจ',
2624
2793
  'settings.statusPulse.hint': 'ไฟสถานะที่ถูกย่อซ่อนไว้ (เสร็จแล้วสีเขียว / กำลังทำงานสีน้ำเงิน / รอตอบสนองสีเหลืองอำพัน) จะลอยขึ้นตามลำดับชั้น: แถวพื้นที่ทำงานและโฟลเดอร์จะให้ไอคอนหายใจเป็นสีตามสถานะ (ชื่อที่มีการเรืองแสงกำหนดเองก็หายใจตาม) แถวกลุ่มเซสชันจะแสดงจุดสถานะที่หายใจ เปิดไว้เป็นค่าเริ่มต้น ปิดได้ที่นี่',
2794
+ 'settings.density': 'ความหนาแน่นของแถว',
2795
+ 'settings.density.compact': 'กระชับ',
2796
+ 'settings.density.comfortable': 'ปกติ (ค่าเริ่มต้น)',
2797
+ 'settings.density.roomy': 'กว้าง',
2798
+ 'settings.density.hint': 'ความสูงของแถวเป็นไปตามแถบข้างทางการ: ปกติคือค่าเริ่มต้นทางการ; กระชับประหยัดพื้นที่แนวตั้ง กว้างให้พื้นที่คลิกใหญ่ขึ้น ใช้ได้ทันทีและบันทึกในเบราว์เซอร์นี้',
2799
+ 'settings.showCount': 'ป้ายจำนวนเซสชัน',
2800
+ 'settings.showCount.hint': 'แถบข้างทางการได้ลบตัวนับเซสชันออก; เปิดเพื่อแสดงอีกครั้งที่ท้ายแถว (เว้นให้ปุ่มการทำงานของแถวเมื่อชี้เมาส์) ปิดอยู่เป็นค่าเริ่มต้น',
2625
2801
  'settings.workspaceGroup': "การจัดกลุ่มพื้นที่ทำงาน",
2626
2802
  'settings.workspaceGroup.hint': "โฟลเดอร์ดิสก์: ซ้อนตามโฟลเดอร์ทางการ (ไดเรกทอรีดิสก์) เท่านั้น ดิสก์ + ชื่อ: เพิ่มกลุ่มชื่อแบบเส้นทับทับต้นไม้ดิสก์ เฉพาะชื่อ: จัดกลุ่มจากเครื่องหมายทับในชื่อเท่านั้นโดยไม่สนการซ้อนดิสก์ สถานะสวิตช์เวอร์ชันเก่าจะย้ายมาโดยอัตโนมัติ",
2627
2803
  'settings.sessionLimit': "จำนวนเซสชันเมื่อกาง",
@@ -2770,7 +2946,11 @@ window.__ModuleLoader__.load({
2770
2946
  'wsPreset.title': 'Çalışma alanı varsayılan ön ayarını değiştir',
2771
2947
  'wsPreset.current': 'Geçerli varsayılan ön ayar',
2772
2948
  'wsPreset.currentUnknown': 'Geçerli ön ayar bilinmiyor',
2773
- 'wsPreset.noBlank': 'Bu çalışma alanında bekleyen yeni oturum yok; genel varsayılan izlenir',
2949
+ 'presetStandardName': 'Standart mod',
2950
+ 'presetPtcName': 'PTC modu',
2951
+ 'presetMinimalName': 'Minimal mod',
2952
+ 'presetCordisName': 'Oluşturucu modu',
2953
+ 'wsPreset.createHint': 'Bu çalışma alanında bekleyen yeni oturum yok; onaylayınca seçilen ön ayarla yeni bir oturum çalışma alanı varsayılanı olarak hazırlanır',
2774
2954
  'wsPreset.empty': 'Kullanılabilir ön ayar yok',
2775
2955
  'wsPreset.loadFailed': 'Ön ayar listesi yüklenemedi',
2776
2956
  'wsPreset.done': 'Çalışma alanı varsayılan ön ayarı güncellendi',
@@ -2789,6 +2969,13 @@ window.__ModuleLoader__.load({
2789
2969
  'settings.compactChains.hint': 'Tek çocuklu düzeyler tek satırda birleşir; birden çok çocuk olduğunda ağaç açılır; bir çalışma alanını sürüklerken zincirler geçici olarak klasörlere açılır, böylece her düzeye bırakabilirsiniz; açılma durumu ve özel görünüm bu tarayıcıda saklanır.',
2790
2970
  'settings.statusPulse': 'Nefes alan durum ışığı',
2791
2971
  'settings.statusPulse.hint': 'Daraltmayla gizlenen durum ışıkları (tamamlandı yeşil / çalışıyor mavi / bekliyor amber) hiyerarşide yukarı taşar: çalışma alanı ve klasör satırlarının simgesi durum renginde nefes alır (özel ışıması olan başlıklar da birlikte nefes alır), oturum grubu satırları nefes alan bir durum noktası gösterir; varsayılan olarak açık, buradan kapatılabilir.',
2972
+ 'settings.density': 'Satır yoğunluğu',
2973
+ 'settings.density.compact': 'Sıkı',
2974
+ 'settings.density.comfortable': 'Rahat (varsayılan)',
2975
+ 'settings.density.roomy': 'Geniş',
2976
+ 'settings.density.hint': 'Satır yükseklikleri resmi kenar çubuğunu izler: Rahat resmi varsayılan; Sıkı dikey alan tasarrufu sağlar, Geniş daha büyük tıklama alanı verir. Anında uygulanır, bu tarayıcıda saklanır.',
2977
+ 'settings.showCount': 'Oturum sayacı',
2978
+ 'settings.showCount.hint': 'Resmi kenar çubuğu oturum sayacını kaldırdı; satır sonunda geri getirmek için açın (üzerine gelince satır düğmelerine yer verir). Varsayılan olarak kapalı.',
2792
2979
  'settings.workspaceGroup': "Çalışma alanı gruplaması",
2793
2980
  'settings.workspaceGroup.hint': "Disk klasörleri: yalnızca resmî klasörlere (disk dizinleri) göre iç içe. Disk + adlar: disk ağacının üzerine ad grupları ekler. Yalnız adlar: yalnızca başlıklardaki bölü işaretiyle gruplar, disk iç içeliğini yok sayar. Eski anahtar durumu otomatik taşınır.",
2794
2981
  'settings.sessionLimit': "Açıldığında gösterilecek oturum",
@@ -2937,7 +3124,11 @@ window.__ModuleLoader__.load({
2937
3124
  'wsPreset.title': 'Thay đổi preset mặc định của không gian làm việc',
2938
3125
  'wsPreset.current': 'Preset mặc định hiện tại',
2939
3126
  'wsPreset.currentUnknown': 'Không rõ preset hiện tại',
2940
- 'wsPreset.noBlank': 'Không gian làm việc này chưa có phiên mới đang chờ; đang theo mặc định toàn cục',
3127
+ 'presetStandardName': 'Chế độ chuẩn',
3128
+ 'presetPtcName': 'Chế độ PTC',
3129
+ 'presetMinimalName': 'Chế độ tối giản',
3130
+ 'presetCordisName': 'Chế độ người sáng tạo',
3131
+ 'wsPreset.createHint': 'Không gian làm việc này chưa có phiên mới đang chờ; xác nhận sẽ chuẩn bị một phiên mới dùng cấu hình đã chọn làm mặc định của không gian làm việc',
2941
3132
  'wsPreset.empty': 'Không có preset khả dụng',
2942
3133
  'wsPreset.loadFailed': 'Không tải được danh sách preset',
2943
3134
  'wsPreset.done': 'Đã cập nhật preset mặc định của không gian làm việc',
@@ -2956,6 +3147,13 @@ window.__ModuleLoader__.load({
2956
3147
  'settings.compactChains.hint': 'Các tầng chỉ có một nhánh con được gộp thành một dòng; khi có nhiều nhánh con, cây tự mở ra; khi kéo không gian làm việc, chuỗi tạm mở lại thành thư mục để có thể thả vào bất kỳ tầng nào; trạng thái mở và giao diện tùy chỉnh được lưu trong trình duyệt này.',
2957
3148
  'settings.statusPulse': 'Đèn trạng thái nhấp nháy',
2958
3149
  'settings.statusPulse.hint': 'Những đèn trạng thái bị thu gọn che đi (xong màu xanh lá / đang chạy màu xanh dương / đang chờ màu hổ phách) sẽ nổi dần lên các tầng trên: hàng không gian làm việc và thư mục cho biểu tượng nhấp nháy theo màu trạng thái (tiêu đề có đặt phát sáng cũng nhấp nháy theo), hàng nhóm phiên hiện một chấm trạng thái nhấp nháy; mặc định bật, có thể tắt tại đây.',
3150
+ 'settings.density': 'Mật độ hàng',
3151
+ 'settings.density.compact': 'Gọn',
3152
+ 'settings.density.comfortable': 'Thoải mái (mặc định)',
3153
+ 'settings.density.roomy': 'Rộng',
3154
+ 'settings.density.hint': 'Chiều cao hàng theo thanh bên chính thức: Thoải mái là mặc định chính thức; Gọn tiết kiệm không gian dọc, Rộng cho vùng nhấp lớn hơn. Áp dụng ngay, lưu trong trình duyệt này.',
3155
+ 'settings.showCount': 'Nhãn số phiên',
3156
+ 'settings.showCount.hint': 'Thanh bên chính thức đã bỏ bộ đếm phiên; bật để hiện lại ở cuối hàng (nhường chỗ cho các nút hàng khi rê chuột). Mặc định tắt.',
2959
3157
  'settings.workspaceGroup': "Nhóm không gian làm việc",
2960
3158
  'settings.workspaceGroup.hint': "Thư mục đĩa: lồng theo thư mục chính thức (thư mục đĩa) mà thôi. Đĩa + tên: thêm nhóm tên lên trên cây đĩa. Chỉ tên: nhóm chỉ theo dấu gạch chéo trong tiêu đề, bỏ qua lồng đĩa. Trạng thái công tắc bản cũ tự động chuyển đổi.",
2961
3159
  'settings.sessionLimit': "Số phiên khi mở rộng",
@@ -3104,7 +3302,11 @@ window.__ModuleLoader__.load({
3104
3302
  'wsPreset.title': '修改工作區預設',
3105
3303
  'wsPreset.current': '目前預設',
3106
3304
  'wsPreset.currentUnknown': '目前預設未知',
3107
- 'wsPreset.noBlank': '此工作區暫無待命的新會話,目前跟隨全域預設',
3305
+ 'presetStandardName': '標準模式',
3306
+ 'presetPtcName': 'PTC 模式',
3307
+ 'presetMinimalName': '極簡模式',
3308
+ 'presetCordisName': '創造模式',
3309
+ 'wsPreset.createHint': '該工作區暫無待命的新對話,確認後將預備一個使用所選預設的新對話作為工作區預設',
3108
3310
  'wsPreset.empty': '沒有可用的預設',
3109
3311
  'wsPreset.loadFailed': '預設清單讀取失敗',
3110
3312
  'wsPreset.done': '已更新工作區預設',
@@ -3123,6 +3325,13 @@ window.__ModuleLoader__.load({
3123
3325
  'settings.compactChains.hint': '單層鏈會合併成一行,出現多個子級就會自動展開成樹狀;拖拽工作區期間單鏈會臨時展開返做文件夾樹,可以放入任何一級;展開狀態同自訂外觀會喺呢個瀏覽器保存。',
3124
3326
  'settings.statusPulse': '狀態呼吸燈',
3125
3327
  'settings.statusPulse.hint': '被摺疊遮住嘅狀態燈(完成綠 / 運行藍 / 待互動琥珀)會沿住層級向外冒泡:工作區同分組行會以圖示呼吸發光(顏色跟狀態,自訂過發光嘅標題一齊呼吸),對話分組行就會顯示呼吸狀態燈;預設開啟,可以喺度閂咗佢。',
3328
+ 'settings.density': '行密度',
3329
+ 'settings.density.compact': '緊湊',
3330
+ 'settings.density.comfortable': '舒適(預設)',
3331
+ 'settings.density.roomy': '寬鬆',
3332
+ 'settings.density.hint': '行高跟隨官方側欄:舒適 = 官方預設行高;緊湊更省縱向空間,寬鬆有更大的點擊範圍。變更立即生效,儲存在當前瀏覽器。',
3333
+ 'settings.showCount': '對話數角標',
3334
+ 'settings.showCount.hint': '官方側欄已移除工作區行尾的對話計數;開啟後在本插件行尾恢復顯示(懸停時讓位給操作按鈕)。預設關閉。',
3126
3335
  'settings.workspaceGroup': "工作區分組",
3127
3336
  'settings.workspaceGroup.hint': "磁碟目錄 = 只按官方資料夾(磁碟目錄)巢狀;磁碟 + 名稱 = 在磁碟層之上再按標題裏的 / 分組;僅名稱 = 只按標題裏的 / 分組,完全不理會磁碟目錄的巢狀關係。舊版本的開關狀態會自動遷移。",
3128
3337
  'settings.sessionLimit': "展開時顯示會話數",
@@ -3271,7 +3480,11 @@ window.__ModuleLoader__.load({
3271
3480
  'wsPreset.title': '修改工作區預設',
3272
3481
  'wsPreset.current': '目前預設',
3273
3482
  'wsPreset.currentUnknown': '目前預設未知',
3274
- 'wsPreset.noBlank': '此工作區暫無待命的新會話,目前跟隨全域預設',
3483
+ 'presetStandardName': '標準模式',
3484
+ 'presetPtcName': 'PTC 模式',
3485
+ 'presetMinimalName': '極簡模式',
3486
+ 'presetCordisName': '創造模式',
3487
+ 'wsPreset.createHint': '該工作區暫無待命的新對話,確認後將預備一個使用所選預設的新對話作為工作區預設',
3275
3488
  'wsPreset.empty': '沒有可用的預設',
3276
3489
  'wsPreset.loadFailed': '預設清單讀取失敗',
3277
3490
  'wsPreset.done': '已更新工作區預設',
@@ -3290,6 +3503,13 @@ window.__ModuleLoader__.load({
3290
3503
  'settings.compactChains.hint': '單層鏈會合併成一行,出現多個子級就會自動展開成樹狀;拖拽工作區期間單鏈會臨時展開返做文件夾樹,可以放入任何一級;展開狀態同自訂外觀會喺呢個瀏覽器保存。',
3291
3504
  'settings.statusPulse': '狀態呼吸燈',
3292
3505
  'settings.statusPulse.hint': '被摺疊遮住嘅狀態燈(完成綠 / 運行藍 / 待互動琥珀)會沿住層級向外冒泡:工作區同分組行會以圖示呼吸發光(顏色跟狀態,自訂過發光嘅標題一齊呼吸),對話分組行就會顯示呼吸狀態燈;預設開啟,可以喺度閂咗佢。',
3506
+ 'settings.density': '行密度',
3507
+ 'settings.density.compact': '緊湊',
3508
+ 'settings.density.comfortable': '舒適(預設)',
3509
+ 'settings.density.roomy': '寬鬆',
3510
+ 'settings.density.hint': '行高跟隨官方側欄:舒適 = 官方預設行高;緊湊慳位,寬鬆有更大嘅點擊範圍。改動即時生效,儲存喺當前瀏覽器。',
3511
+ 'settings.showCount': '對話數角標',
3512
+ 'settings.showCount.hint': '官方側欄已移除工作區行尾嘅對話計數;開啟後喺本插件行尾恢復顯示(懸停時讓位畀操作按鈕)。預設關閉。',
3293
3513
  'settings.workspaceGroup': "工作區分組",
3294
3514
  'settings.workspaceGroup.hint': "磁碟目錄 = 只按官方資料夾(磁碟目錄)巢狀;磁碟 + 名稱 = 在磁碟層之上再按標題裏的 / 分組;僅名稱 = 只按標題裏的 / 分組,完全不理會磁碟目錄的巢狀關係。舊版本的開關狀態會自動遷移。",
3295
3515
  'settings.sessionLimit': "展開時顯示會話數",
@@ -3438,7 +3658,11 @@ window.__ModuleLoader__.load({
3438
3658
  'wsPreset.title': '修改工作區預設',
3439
3659
  'wsPreset.current': '目前預設',
3440
3660
  'wsPreset.currentUnknown': '目前預設未知',
3441
- 'wsPreset.noBlank': '此工作區暫無待命的新會話,目前跟隨全域預設',
3661
+ 'presetStandardName': '標準模式',
3662
+ 'presetPtcName': 'PTC 模式',
3663
+ 'presetMinimalName': '極簡模式',
3664
+ 'presetCordisName': '創造模式',
3665
+ 'wsPreset.createHint': '此工作區暫無待命的新工作階段,確認後將準備一個使用所選預設的新工作階段作為工作區預設',
3442
3666
  'wsPreset.empty': '沒有可用的預設',
3443
3667
  'wsPreset.loadFailed': '預設清單讀取失敗',
3444
3668
  'wsPreset.done': '已更新工作區預設',
@@ -3457,6 +3681,13 @@ window.__ModuleLoader__.load({
3457
3681
  'settings.compactChains.hint': '單層鏈會合併成一行,出現多個子層級時自動展開為樹狀;拖曳工作區期間單鏈會暫時展開回資料夾樹,可放入任一層級;展開狀態與自訂外觀會保存在本瀏覽器。',
3458
3682
  'settings.statusPulse': '狀態呼吸燈',
3459
3683
  'settings.statusPulse.hint': '被收合藏起的狀態燈(完成綠 / 執行藍 / 待互動琥珀)會沿著層級向外冒出:工作區與群組列以圖示呼吸發光(顏色隨狀態,自訂過發光的標題一起呼吸),工作階段群組列則顯示呼吸狀態燈;預設開啟,可在此關閉。',
3684
+ 'settings.density': '列密度',
3685
+ 'settings.density.compact': '緊湊',
3686
+ 'settings.density.comfortable': '舒適 (預設)',
3687
+ 'settings.density.roomy': '寬鬆',
3688
+ 'settings.density.hint': '列高跟隨官方側邊欄:舒適 = 官方預設列高;緊湊更省垂直空間,寬鬆有較大的點擊範圍。變更立即生效,儲存在目前瀏覽器。',
3689
+ 'settings.showCount': '工作階段數標記',
3690
+ 'settings.showCount.hint': '官方側邊欄已移除工作區列尾的工作階段計數;開啟後在本外掛列尾恢復顯示 (暫留時讓位給動作按鈕)。預設關閉。',
3460
3691
  'settings.workspaceGroup': "工作區分組",
3461
3692
  'settings.workspaceGroup.hint': "磁碟資料夾 = 只按官方資料夾(磁碟目錄)巢狀;磁碟 + 名稱 = 在磁碟層之上再按標題裏的 / 分組;僅名稱 = 只按標題裏的 / 分組,完全不理會磁碟目錄的巢狀關係。舊版本的開關狀態會自動遷移。",
3462
3693
  'settings.sessionLimit': "展開時顯示會話數",
@@ -3931,9 +4162,14 @@ window.__ModuleLoader__.load({
3931
4162
  * React discipline — the occupant mounts once per row and keeps its own
3932
4163
  * state across our re-renders).
3933
4164
  */
3934
- function ShippedSeatOccupant({ seat, ownerProps, t }) {
4165
+ function ShippedSeatOccupant({ seat, ownerProps, t, useMenuOpenState }) {
3935
4166
  const bound = React.useMemo(() => bindOccupantFace(seat.face), [seat])
3936
- return E(seat.component, Object.assign({}, ownerProps, bound, t ? { t } : null))
4167
+ return E(seat.component, Object.assign({}, ownerProps, bound, t ? { t } : null,
4168
+ // The menu.item slot's hookContext factory (ui-slots) hands its
4169
+ // occupants the menu open-state pair through a useMenuOpenState
4170
+ // hook; the slot renderer would inject it, our in-row menu
4171
+ // reproduces the same shape here.
4172
+ typeof useMenuOpenState === 'function' ? { useMenuOpenState } : null))
3937
4173
  }
3938
4174
 
3939
4175
  /**
@@ -4304,6 +4540,12 @@ window.__ModuleLoader__.load({
4304
4540
  '.bw-header-title{flex:1;font-size:14px;font-weight:400;line-height:20px;color:var(--dsw-alias-label-tertiary,#9a9a9a);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
4305
4541
  '.bw-icon-btn{flex:none;width:24px;height:24px;border:none;background:transparent;border-radius:var(--dsw-radius-sm,6px);display:grid;place-items:center;color:var(--dsw-alias-label-secondary,#b8b8b8);cursor:pointer;padding:0}',
4306
4542
  '.bw-icon-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.15));color:var(--dsw-alias-label-primary,#e6e6e6)}',
4543
+ // Official row iconButton (Rows.module.css): a bare 16px tertiary
4544
+ // square — the shipped strip buttons ride this, not the 24px
4545
+ // toolbox button above.
4546
+ '.bw-row-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border:none;border-radius:var(--dsw-radius-xs,4px);padding:0;background:transparent;color:var(--dsw-alias-label-tertiary,#9a9a9a);cursor:pointer}',
4547
+ '.bw-row-btn:hover{color:var(--dsw-alias-label-primary,#e6e6e6)}',
4548
+ '.bw-row-btn:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary,#5b8def));outline-offset:-2px}',
4307
4549
  // Focus ring parity with the shipped icon buttons (dsh 0.1.7-rc.2
4308
4550
  // focus.css + WorkspaceBrowser.module.css): the host publishes the ring
4309
4551
  // width/colour as tokens and keeps offsets inside controls that clip an
@@ -4327,10 +4569,12 @@ window.__ModuleLoader__.load({
4327
4569
  // as the chevron-only button beside an input.
4328
4570
  '.bw-menu-block{display:block;width:100%}',
4329
4571
  '.bw-menu-inline{display:inline-block;width:auto;flex:none}',
4330
- '.bw-tree{flex:1;overflow-y:auto;overflow-x:hidden;padding:2px 6px 12px;min-height:0}',
4331
- '.bw-row{display:flex;align-items:center;gap:6px;min-height:28px;padding:0 6px;border-radius:var(--dsw-radius-sm,6px);cursor:pointer;user-select:none;font-size:14px;line-height:20px;color:var(--dsw-alias-label-primary,#e6e6e6);position:relative}',
4332
- '.bw-drop-before::after{content:"";position:absolute;left:8px;right:8px;top:-1px;height:2px;border-radius:1px;background:var(--dsw-alias-brand-primary,#5b8def);pointer-events:none}',
4333
- '.bw-drop-after::after{content:"";position:absolute;left:8px;right:8px;bottom:-1px;height:2px;border-radius:1px;background:var(--dsw-alias-brand-primary,#5b8def);pointer-events:none}',
4572
+ '.bw-tree{--bw-row-h:34px;--bw-session-h:32px;flex:1;overflow-y:auto;overflow-x:hidden;padding:2px 6px 12px;min-height:0}',
4573
+ '.bw-tree[data-bw-density="compact"]{--bw-row-h:28px;--bw-session-h:26px}',
4574
+ '.bw-tree[data-bw-density="roomy"]{--bw-row-h:40px;--bw-session-h:38px}',
4575
+ '.bw-row{display:flex;align-items:center;gap:6px;height:var(--bw-row-h,34px);padding:0 8px;padding-inline-start:calc(8px + var(--bw-indent,0px));border-radius:var(--dsw-radius-md,8px);cursor:pointer;user-select:none;font-size:14px;line-height:20px;color:var(--dsw-alias-label-primary,#e6e6e6);position:relative}',
4576
+ '.bw-drop-before::before{content:"";position:absolute;z-index:1;left:0;right:4px;top:-7px;height:12px;pointer-events:none;background:linear-gradient(55deg,transparent 0,var(--dsw-alias-state-business-primary,#5b8def) 50%,transparent 100%) 0 0/5px 7px no-repeat,linear-gradient(125deg,transparent 0,var(--dsw-alias-state-business-primary,#5b8def) 50%,transparent 100%) 0 5px/5px 7px no-repeat,linear-gradient(var(--dsw-alias-state-business-primary,#5b8def),var(--dsw-alias-state-business-primary,#5b8def)) 4px 5px/calc(100% - 4px) 2px no-repeat}',
4577
+ '.bw-drop-after::after{content:"";position:absolute;z-index:1;left:0;right:4px;bottom:-7px;height:12px;pointer-events:none;background:linear-gradient(55deg,transparent 0,var(--dsw-alias-state-business-primary,#5b8def) 50%,transparent 100%) 0 0/5px 7px no-repeat,linear-gradient(125deg,transparent 0,var(--dsw-alias-state-business-primary,#5b8def) 50%,transparent 100%) 0 5px/5px 7px no-repeat,linear-gradient(var(--dsw-alias-state-business-primary,#5b8def),var(--dsw-alias-state-business-primary,#5b8def)) 4px 5px/calc(100% - 4px) 2px no-repeat}',
4334
4578
  '.bw-drop-into{outline:1.5px dashed var(--dsw-alias-brand-primary,#5b8def);outline-offset:-1.5px}',
4335
4579
  // Grouping drop lands on the header button / a group row: solid brand
4336
4580
  // rim + a tinted wash so "this drop joins a group" reads stronger than
@@ -4368,13 +4612,26 @@ window.__ModuleLoader__.load({
4368
4612
  '.bw-gn-icon{flex:none;display:grid;place-items:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary,#b8b8b8)}',
4369
4613
  '.bw-gn-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--dsw-alias-label-primary,#e6e6e6)}',
4370
4614
  '.bw-gn-input{width:100%;box-sizing:border-box}',
4371
- '.bw-row:hover{background:var(--dsw-specific-sidebar-nav-item-hover,var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12)))}',
4372
- '.bw-row:hover{background:color-mix(in srgb,var(--dsw-specific-sidebar-nav-item-hover,rgba(127,127,127,.14)) 50%,transparent)}',
4373
- '.bw-row-current{background:var(--dsw-specific-sidebar-nav-item-active,rgba(91,141,239,.15))}',
4374
- '.bw-row-current{background:color-mix(in srgb,var(--dsw-specific-sidebar-nav-item-active,rgba(91,141,239,.16)) 40%,transparent)}',
4375
- '.bw-row-icon{flex:none;display:grid;place-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
4376
- '.bw-chevron{flex:none;display:grid;place-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a);transition:transform .15s ease}',
4377
- '.bw-chevron-open{transform:rotate(90deg)}',
4615
+ // Row hover/selection follow the background plugin's interface scheme
4616
+ // (body[data-ds-dark-theme], flipped by dsh-any-background): dark
4617
+ // surfaces tint BLACK — every label token is rewritten toward bright ink
4618
+ // on dark wallpapers, so any label-derived mix still reads as white.
4619
+ // Light surfaces keep the white mix.
4620
+ '.bw-row:hover,.bw-row.menu-open{background:color-mix(in srgb,#000 12%,transparent)}',
4621
+ 'body:not([data-ds-dark-theme]) .bw-row:hover,body:not([data-ds-dark-theme]) .bw-row.menu-open{background:color-mix(in srgb,#fff 12%,transparent)}',
4622
+ '',
4623
+ '.bw-row-current{background:color-mix(in srgb,#000 18%,transparent)}',
4624
+ 'body:not([data-ds-dark-theme]) .bw-row-current{background:color-mix(in srgb,#fff 20%,transparent)}',
4625
+ '',
4626
+ '.bw-row-icon{flex:none;width:16px;height:20px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
4627
+ '.bw-chevron{flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-caption,#adb2b8)}',
4628
+ '.bw-chevron-open .bw-arrow{transform:rotate(90deg)}',
4629
+ '.bw-container-row .bw-chevron{display:none}',
4630
+ '.bw-container-row:hover .bw-chevron,.bw-container-row.menu-open .bw-chevron{display:inline-flex}',
4631
+ '.bw-container-row:hover .bw-folder-cell,.bw-container-row.menu-open .bw-folder-cell{display:none}',
4632
+ '.bw-arrow{flex:none;display:inline-flex;transition:transform 150ms var(--ds-ease-in-out,ease-in-out)}',
4633
+ '@media (prefers-reduced-motion:reduce){.bw-arrow{transition:none}}',
4634
+ '.bw-folder-active{color:var(--dsw-alias-state-business-primary,#5b8def)}',
4378
4635
  // Hero picker rows (v0.22.0): hierarchy rides the row itself — an
4379
4636
  // indentation gutter, a folder chevron or workspace glyph, and the
4380
4637
  // trailing check. The row mirrors the primitives menu cell (same hover
@@ -4393,23 +4650,28 @@ window.__ModuleLoader__.load({
4393
4650
  // padding covers the widest rim (2px at the top of the slider). margin
4394
4651
  // cancels it, so text still starts and truncates where it did.
4395
4652
  '.bw-row-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:3px;margin:-3px}',
4396
- '.bw-row-count{flex:none;font-size:12px;color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
4397
- '.bw-row-time{flex:none;font-size:10px;color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
4653
+ '.bw-session-row .bw-row-label{padding:0;margin:0 6px 0 4px}',
4654
+ '.bw-session-row .bw-row-label[data-scrolled]{mask-image:linear-gradient(to right,transparent,#000 12px);-webkit-mask-image:linear-gradient(to right,transparent,#000 12px)}',
4655
+ '.bw-session-row .bw-row-label[data-clipped]{mask-image:linear-gradient(to left,transparent,#000 12px);-webkit-mask-image:linear-gradient(to left,transparent,#000 12px)}',
4656
+ '.bw-session-row .bw-row-label[data-scrolled][data-clipped]{mask-image:linear-gradient(to right,transparent,#000 12px,#000 calc(100% - 12px),transparent);-webkit-mask-image:linear-gradient(to right,transparent,#000 12px,#000 calc(100% - 12px),transparent)}',
4657
+ '.bw-row-count{flex:none;display:none;font-size:12px;color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
4658
+ '.bw-tree[data-bw-count] .bw-row-count{display:inline}',
4659
+ '.bw-row-time{flex:none;font-size:10px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
4398
4660
  // The outline is inherited from the row: the 10-12px meta column (session
4399
4661
  // count / relative time) opts out — stroked meta text outshines the title
4400
4662
  // it is supposed to support.
4401
4663
  '.bw-row-count,.bw-row-time{-webkit-text-stroke-width:0}',
4402
4664
  '.bw-schedule-badge{flex:none;display:inline-flex;align-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a);margin:0 6px}',
4403
- '.bw-row-actions{flex:none;display:none;align-items:center;gap:2px}',
4404
- '.bw-row:hover .bw-row-actions{display:flex}',
4405
- '.bw-row:hover .bw-row-time,.bw-row:hover .bw-row-count,.bw-row:hover .bw-schedule-badge{display:none}',
4406
- '.bw-row-archived{opacity:.55}',
4407
- '.bw-pin-indicator{flex:none;display:inline-flex;align-items:center;color:var(--dsw-alias-brand-primary,#5b8def);margin:0 4px}',
4665
+ '.bw-row-actions{flex:none;display:none;align-items:center;gap:15px}',
4666
+ '.bw-row:hover .bw-row-actions,.bw-row.menu-open .bw-row-actions{display:flex}',
4667
+ '.bw-row:hover .bw-row-time,.bw-row:hover .bw-row-count,.bw-row:hover .bw-schedule-badge,.bw-row:hover .bw-pin-indicator,.bw-row.menu-open .bw-row-time,.bw-row.menu-open .bw-row-count,.bw-row.menu-open .bw-schedule-badge,.bw-row.menu-open .bw-pin-indicator{display:none}',
4668
+ '.bw-row-archived .bw-row-label{color:var(--dsw-alias-label-caption,#8a8a8a)}',
4669
+ '.bw-pin-indicator{flex:none;display:inline-flex;align-items:center;justify-content:center;width:16px;height:20px;color:var(--dsw-alias-label-caption,#adb2b8);margin:0 0 0 6px}',
4408
4670
  '.bw-overflow-row{display:block;width:100%;text-align:left;border:0;background:transparent;color:var(--dsw-alias-label-tertiary,#9a9a9a);font-size:12px;cursor:pointer;padding:4px 6px;font-family:inherit}',
4409
4671
  '.bw-overflow-row:hover{color:var(--dsw-alias-label-secondary,#b8b8b8)}',
4410
4672
  '.bw-dot{flex:none;width:6px;height:6px;border-radius:50%;background:transparent}',
4411
- '.bw-session-row{font-size:14px;line-height:20px;color:var(--dsw-alias-label-primary,#e6e6e6);min-height:26px}',
4412
- '.bw-sgroup-row{font-size:14px;line-height:20px;color:var(--dsw-alias-label-tertiary,#9a9a9a);min-height:24px}',
4673
+ '.bw-session-row{font-size:14px;line-height:20px;color:var(--dsw-alias-label-primary,#e6e6e6);height:var(--bw-session-h,32px)}',
4674
+ '.bw-sgroup-row{font-size:14px;line-height:20px;color:var(--dsw-alias-label-tertiary,#9a9a9a);height:var(--bw-session-h,32px)}',
4413
4675
  '.bw-sgroup-row:hover{color:var(--dsw-alias-label-secondary,#b8b8b8)}',
4414
4676
  '.bw-session-row:hover{color:var(--dsw-alias-label-primary,#e6e6e6)}',
4415
4677
  '.bw-empty{padding:28px 12px;text-align:center;font-size:13px;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
@@ -4421,6 +4683,14 @@ window.__ModuleLoader__.load({
4421
4683
  // Row toast for the shipped fork shortcut's async failure (the shipped
4422
4684
  // notice rides the shadowed overlay). Token chain kept transparent-safe
4423
4685
  // like every other self-drawn surface.
4686
+ // Session/workspace hover-card body (official Rows.module.css palette:
4687
+ // fixed colors, independent of theme aliases).
4688
+ '.bw-hover-card{display:flex;flex-direction:column;gap:8px}',
4689
+ '.bw-hover-title{font-size:14px;line-height:20px;color:#FFFFFF;overflow-wrap:break-word}',
4690
+ '.bw-hover-path{font-size:12px;line-height:16px;color:#CFD3D6;word-break:break-all}',
4691
+ '.bw-hover-time{font-size:12px;line-height:16px;color:#CFD3D6}',
4692
+ '.bw-hover-status{display:flex;align-items:center;gap:8px;font-size:12px;line-height:20px;color:#ADB2B8}',
4693
+ '.bw-hover-archived svg{flex-shrink:0;margin:0 -4px}',
4424
4694
  '.bw-toast{position:absolute;left:10px;right:10px;bottom:12px;z-index:30;display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3));border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-toast-bg,var(--dsw-specific-menu,rgba(28,28,32,.92)));color:var(--dsw-alias-toast-label,var(--dsw-alias-label-primary,#e6e6e6));font-size:12px;box-shadow:0 8px 24px rgba(0,0,0,.35)}',
4425
4695
  '.bw-toast-text{flex:1;min-width:0}',
4426
4696
  '.bw-toast-close{flex:none;width:20px;height:20px;border:none;background:transparent;color:inherit;border-radius:var(--dsw-radius-xs,4px);display:grid;place-items:center;cursor:pointer;padding:0;opacity:.75}',
@@ -4479,7 +4749,8 @@ window.__ModuleLoader__.load({
4479
4749
  '.bw-plugin-body .bw-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
4480
4750
  '.bw-rail{display:flex;flex-direction:column;align-items:center;gap:6px;padding:6px 0}',
4481
4751
  '.bw-rail-btn{width:36px;height:36px;border:none;background:transparent;border-radius:var(--dsw-radius-sm,8px);display:grid;place-items:center;color:var(--dsw-alias-label-secondary,#b8b8b8);cursor:pointer;padding:0}',
4482
- '.bw-rail-btn:hover{background:var(--dsw-specific-sidebar-nav-item-hover,var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12)));color:var(--dsw-alias-label-primary,#e6e6e6)}',
4752
+ '.bw-rail-btn:hover{background:color-mix(in srgb,#000 12%,transparent);color:var(--dsw-alias-label-primary,#e6e6e6)}',
4753
+ 'body:not([data-ds-dark-theme]) .bw-rail-btn:hover{background:color-mix(in srgb,#fff 12%,transparent)}',
4483
4754
  '.bw-modal-body{display:flex;flex-direction:column;gap:10px;min-width:300px;max-width:380px;box-sizing:border-box}',
4484
4755
  // issue #10: Modal 高度契约 — 卡片按 Modal 的 padding box 限高,超高内容滚 contentClassName 区
4485
4756
  // (官方 RiskConfirmation 同款范式;.dialog 为 overflow:hidden,不限高则上下被裁)
@@ -5250,8 +5521,25 @@ window.__ModuleLoader__.load({
5250
5521
  * applyPreset is null exactly when no blank exists (or the face is
5251
5522
  * absent); the roster is a feature-probed plugin-side read.
5252
5523
  */
5253
- function WorkspacePresetDialog({ applyPreset, loadRoster, current, onApplied, onClose, t }) {
5254
- const hasBlank = typeof applyPreset === 'function'
5524
+ /* BUILT-IN preset id → this plugin's dictionary key. Mirrors the official
5525
+ * display fold (agent-preset-registry display.ts isBuiltInPreset: built-ins
5526
+ * publish NO name) because that registry package is not on the client boot
5527
+ * graph and cannot be required here. Custom presets keep their authored
5528
+ * name; the id itself is the last-resort fallback. Host-follow: delete this
5529
+ * fold and require the official one once it ships on the boot graph. */
5530
+ const BUILT_IN_PRESET_LABEL_KEYS = {
5531
+ standard: 'presetStandardName',
5532
+ ptc: 'presetPtcName',
5533
+ minimal: 'presetMinimalName',
5534
+ cordis: 'presetCordisName',
5535
+ }
5536
+ const presetDisplayLabel = (row, t) => {
5537
+ if (row && row.name === undefined && BUILT_IN_PRESET_LABEL_KEYS[row.id] !== undefined) return t(BUILT_IN_PRESET_LABEL_KEYS[row.id])
5538
+ return (row && row.name) || (row && row.id) || ''
5539
+ }
5540
+
5541
+ function WorkspacePresetDialog({ applyPreset, hasBlank, loadRoster, current, onApplied, onClose, t }) {
5542
+ const editable = typeof applyPreset === 'function' && hasBlank !== false
5255
5543
  const menuReady = typeof ui.Menu === 'function'
5256
5544
  const [status, setStatus] = React.useState('loading') // loading | ready | error
5257
5545
  const [rows, setRows] = React.useState([])
@@ -5285,12 +5573,12 @@ window.__ModuleLoader__.load({
5285
5573
  return () => { alive = false }
5286
5574
  }, [])
5287
5575
  const nameOf = (id) => {
5288
- for (const row of rows) { if (row.id === id) return row.name || row.id }
5576
+ for (const row of rows) { if (row.id === id) return presetDisplayLabel(row, t) }
5289
5577
  return id
5290
5578
  }
5291
5579
  const options = rows.map((row) => ({
5292
5580
  value: row.id,
5293
- label: (row.name || row.id) + (row.isDefault ? ' (' + t('wsPreset.globalDefault') + ')' : ''),
5581
+ label: presetDisplayLabel(row, t) + (row.isDefault ? ' (' + t('wsPreset.globalDefault') + ')' : ''),
5294
5582
  }))
5295
5583
  const commit = () => {
5296
5584
  if (busy || !picked) { onClose(); return }
@@ -5307,7 +5595,9 @@ window.__ModuleLoader__.load({
5307
5595
  setErrorText(reason instanceof Error ? reason.message : String(reason || t('wsPreset.loadFailed')))
5308
5596
  })
5309
5597
  }
5310
- const currentLine = !hasBlank ? t('wsPreset.noBlank')
5598
+ // No standing draft → the confirm PREPARES one with the picked preset
5599
+ // (see the ws-preset menu branch); the hint line spells that out.
5600
+ const currentLine = !editable ? t('wsPreset.createHint')
5311
5601
  : current ? t('wsPreset.current') + ': ' + nameOf(current)
5312
5602
  : t('wsPreset.currentUnknown')
5313
5603
  return E(ui.Modal, {
@@ -5316,15 +5606,13 @@ window.__ModuleLoader__.load({
5316
5606
  closeLabel: t('close'),
5317
5607
  title: t('wsPreset.title'),
5318
5608
  footer: E('div', { className: 'bw-modal-actions' },
5319
- hasBlank ? E(BTN, { variant: 'outline', onClick: onClose, disabled: busy }, t('cancel')) : null,
5320
- hasBlank
5321
- ? E(BTN, { variant: 'primary', onClick: commit, disabled: busy || !picked || picked === current }, t('confirm'))
5322
- : E(BTN, { variant: 'primary', onClick: onClose }, t('close')),
5609
+ E(BTN, { variant: 'outline', onClick: onClose, disabled: busy }, t('cancel')),
5610
+ E(BTN, { variant: 'primary', onClick: commit, disabled: busy || !picked || picked === current }, t('confirm')),
5323
5611
  ),
5324
5612
  },
5325
5613
  E('div', { className: 'bw-modal-body' },
5326
5614
  status === 'error' ? E('div', { className: 'bw-error-text', role: 'alert' }, errorText) : null,
5327
- status === 'ready' && hasBlank ? (
5615
+ status === 'ready' ? (
5328
5616
  rows.length === 0
5329
5617
  ? E('div', { className: 'bw-hint' }, t('wsPreset.empty'))
5330
5618
  : E('div', { className: 'bw-field' },
@@ -5341,7 +5629,7 @@ window.__ModuleLoader__.load({
5341
5629
  )
5342
5630
  ) : null,
5343
5631
  E('div', { className: 'bw-hint' }, currentLine),
5344
- status === 'ready' && hasBlank && errorText !== ''
5632
+ status === 'ready' && errorText !== ''
5345
5633
  ? E('div', { className: 'bw-error-text', role: 'alert' }, errorText)
5346
5634
  : null,
5347
5635
  ),
@@ -5584,7 +5872,7 @@ window.__ModuleLoader__.load({
5584
5872
  }
5585
5873
  }
5586
5874
 
5587
- function FolderRow({ node, depth, expanded, onToggle, onContextMenu, dropInto, dragEvents, custStyle, iconMode, pulse, t }) {
5875
+ function FolderRow({ node, depth, expanded, onToggle, onContextMenu, dropInto, dragEvents, custStyle, iconMode, pulse, active, t }) {
5588
5876
  const total = countWorkspaces(node)
5589
5877
  const iconEl = iconOf(iconMode, expanded)
5590
5878
  // Hidden status dots breathe on the icon; with the icon hidden (mode
@@ -5593,44 +5881,58 @@ window.__ModuleLoader__.load({
5593
5881
  ? E(PulseGlow, { state: pulse }, iconEl || (typeof ui.StateDot === 'function' ? E(ui.StateDot, { state: pulse, size: 10 }) : null))
5594
5882
  : iconEl
5595
5883
  return E('div', {
5596
- className: cls('bw-row', dropInto && 'bw-drop-into', dropInto && 'bw-drop-into-strong'),
5597
- style: { paddingLeft: 4 + depth * 12, ...(custStyle || {}), ...(pulse ? { '--bw-pulse-color': PULSE_COLORS[pulse] || PULSE_COLORS.ongoing } : null) },
5884
+ className: cls('bw-row', 'bw-container-row', dropInto && 'bw-drop-into', dropInto && 'bw-drop-into-strong'),
5885
+ style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}), ...(pulse ? { '--bw-pulse-color': PULSE_COLORS[pulse] || PULSE_COLORS.ongoing } : null) },
5598
5886
  onClick: onToggle,
5599
5887
  onContextMenu: onContextMenu,
5600
5888
  role: 'treeitem',
5601
5889
  'aria-expanded': expanded,
5602
5890
  ...(dragEvents || {}),
5603
5891
  },
5604
- E('span', { className: cls('bw-chevron', expanded && 'bw-chevron-open') }, icon('IconTriangleRightFill14', 14)),
5605
- E('span', { className: 'bw-row-icon' }, iconChild),
5892
+ // Official projectRow duality (Rows.module.css): the chevron hides
5893
+ // until hover, swapping in for the folder glyph; the arrow spins
5894
+ // open with the row.
5895
+ E('span', { className: cls('bw-chevron', expanded && 'bw-chevron-open') }, E('span', { className: 'bw-arrow' }, icon('IconTriangleRightFill14', 14))),
5896
+ E('span', { className: cls('bw-row-icon', 'bw-folder-cell', active && !pulse && 'bw-folder-active') }, iconChild),
5606
5897
  // A row that already carries a custom label glow breathes in sync.
5607
5898
  E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle.textShadow && 'bw-pulse-text') }, node.name),
5608
5899
  total > 0 ? E('span', { className: 'bw-row-count' }, String(total)) : null,
5609
5900
  )
5610
5901
  }
5611
5902
 
5612
- function WorkspaceRow({ workspace, depth, count, sessionsOpen, onToggle, onStart, onContextMenu, currentInside, dropHalf, dropInto, dragEvents, custStyle, iconMode, pulse, newSessionLabel, newSessionAria, t }) {
5903
+ function WorkspaceRow({ workspace, depth, count, sessionsOpen, onToggle, onStart, onMenu, onContextMenu, currentInside, menuOpen, dropHalf, dropInto, dragEvents, custStyle, iconMode, pulse, newSessionLabel, newSessionAria, menuAria, hoverBody, t }) {
5613
5904
  const iconEl = iconOf(iconMode, sessionsOpen)
5614
5905
  const iconChild = pulse
5615
5906
  ? E(PulseGlow, { state: pulse }, iconEl || (typeof ui.StateDot === 'function' ? E(ui.StateDot, { state: pulse, size: 10 }) : null))
5616
5907
  : iconEl
5617
- return E('div', {
5618
- className: cls('bw-row', currentInside && 'bw-row-current', dropHalf === 'before' && 'bw-drop-before', dropHalf === 'after' && 'bw-drop-after', dropInto && 'bw-drop-into', dropInto && 'bw-drop-into-strong'),
5619
- style: { paddingLeft: 6 + depth * 12, ...(custStyle || {}), ...(pulse ? { '--bw-pulse-color': PULSE_COLORS[pulse] || PULSE_COLORS.ongoing } : null) },
5908
+ let row = E('div', {
5909
+ className: cls('bw-row', 'bw-container-row', currentInside && 'bw-row-current', menuOpen && 'menu-open', dropHalf === 'before' && 'bw-drop-before', dropHalf === 'after' && 'bw-drop-after', dropInto && 'bw-drop-into', dropInto && 'bw-drop-into-strong'),
5910
+ style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}), ...(pulse ? { '--bw-pulse-color': PULSE_COLORS[pulse] || PULSE_COLORS.ongoing } : null) },
5620
5911
  onClick: onToggle,
5621
5912
  onContextMenu: onContextMenu,
5622
5913
  role: 'treeitem',
5623
5914
  'aria-expanded': sessionsOpen,
5624
5915
  ...(dragEvents || {}),
5625
5916
  },
5626
- E('span', { className: cls('bw-chevron', sessionsOpen && 'bw-chevron-open') }, icon('IconTriangleRightFill14', 14)),
5627
- E('span', { className: 'bw-row-icon' }, iconChild),
5917
+ // Official projectRow duality (Rows.module.css): the chevron hides
5918
+ // until hover, swapping in for the folder glyph; the arrow spins
5919
+ // open with the row; a subtree holding the current session tints
5920
+ // the folder glyph blue (folderActive), not the whole row.
5921
+ E('span', { className: cls('bw-chevron', sessionsOpen && 'bw-chevron-open') }, E('span', { className: 'bw-arrow' }, icon('IconTriangleRightFill14', 14))),
5922
+ E('span', { className: cls('bw-row-icon', 'bw-folder-cell', currentInside && !pulse && 'bw-folder-active') }, iconChild),
5628
5923
  E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle.textShadow && 'bw-pulse-text'), title: workspace.title || workspace.leaf }, workspace.leaf),
5629
5924
  count > 0 ? E('span', { className: 'bw-row-count' }, String(count)) : null,
5630
5925
  E('span', { className: 'bw-row-actions', onClick: (e) => e.stopPropagation() },
5631
5926
  E('button', {
5632
5927
  type: 'button',
5633
- className: 'bw-icon-btn',
5928
+ className: 'bw-row-btn',
5929
+ 'aria-label': menuAria || '...',
5930
+ title: menuAria || undefined,
5931
+ onClick: (e) => { e.stopPropagation(); if (onMenu) onMenu(e) },
5932
+ }, icon('IconEllipsisOutline16', 15)),
5933
+ E('button', {
5934
+ type: 'button',
5935
+ className: 'bw-row-btn',
5634
5936
  'aria-label': newSessionLabel || t('session.new'),
5635
5937
  'aria-keyshortcuts': newSessionAria,
5636
5938
  title: newSessionLabel || t('session.new'),
@@ -5638,6 +5940,13 @@ window.__ModuleLoader__.load({
5638
5940
  }, icon('IconPlusOutline16')),
5639
5941
  ),
5640
5942
  )
5943
+ // Official workspace hover card (Rows.tsx WorkspaceHoverContent): the
5944
+ // full label plus the abbreviated path. Workspace rows carry no
5945
+ // createdAt here, so the created line is absent.
5946
+ if (typeof ui.HoverCard === 'function' && typeof hoverBody === 'function') {
5947
+ row = E(ui.HoverCard, { key: 'hover', anchor: row, content: hoverBody(workspace), openDelayMs: 800, grouped: true })
5948
+ }
5949
+ return row
5641
5950
  }
5642
5951
 
5643
5952
  // Official status priority: pending interaction > running > running
@@ -5678,6 +5987,60 @@ window.__ModuleLoader__.load({
5678
5987
  return s === 'ongoing' ? null : s
5679
5988
  }
5680
5989
 
5990
+ // Official title marquee (Rows.tsx useTitleMarquee): on hover an
5991
+ // overflowing title scrolls at the shipped constant speed so the hidden
5992
+ // tail becomes readable; scroll progress is exposed through data
5993
+ // attributes that drive the CSS edge-fade masks. Reduced motion jumps
5994
+ // straight to the end; leaving the row resets instantly.
5995
+ const MIN_TITLE_REVEAL_PX = 8
5996
+ const TITLE_MARQUEE_PX_PER_MS = 0.03
5997
+ const marqueePlace = (title, left, range) => {
5998
+ if (!title) return
5999
+ if (typeof title.scrollTo === 'function') title.scrollTo({ left, behavior: 'instant' })
6000
+ else title.scrollLeft = left
6001
+ if (left > 0) title.dataset.scrolled = ''
6002
+ else delete title.dataset.scrolled
6003
+ if (left < range) title.dataset.clipped = ''
6004
+ else delete title.dataset.clipped
6005
+ }
6006
+ function useTitleMarquee() {
6007
+ const titleRef = React.useRef(null)
6008
+ const frameRef = React.useRef(0)
6009
+ React.useEffect(() => () => {
6010
+ if (frameRef.current) cancelAnimationFrame(frameRef.current)
6011
+ frameRef.current = 0
6012
+ }, [])
6013
+ const onPointerEnter = () => {
6014
+ const title = titleRef.current
6015
+ if (!title) return
6016
+ const range = title.scrollWidth - title.clientWidth
6017
+ if (!(range > MIN_TITLE_REVEAL_PX)) return
6018
+ if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
6019
+ marqueePlace(title, range, range)
6020
+ return
6021
+ }
6022
+ let position = 0
6023
+ let previous = performance.now()
6024
+ const step = (timestamp) => {
6025
+ position = Math.min(position + (timestamp - previous) * TITLE_MARQUEE_PX_PER_MS, range)
6026
+ previous = timestamp
6027
+ marqueePlace(titleRef.current, position, range)
6028
+ if (position < range) frameRef.current = requestAnimationFrame(step)
6029
+ }
6030
+ frameRef.current = requestAnimationFrame(step)
6031
+ }
6032
+ const onPointerLeave = () => {
6033
+ if (frameRef.current) cancelAnimationFrame(frameRef.current)
6034
+ frameRef.current = 0
6035
+ const title = titleRef.current
6036
+ if (!title) return
6037
+ marqueePlace(title, 0, 0)
6038
+ delete title.dataset.scrolled
6039
+ delete title.dataset.clipped
6040
+ }
6041
+ return { titleRef, onPointerEnter, onPointerLeave }
6042
+ }
6043
+
5681
6044
  function SessionRow({ node, depth, current, onOpen, onContextMenu, now, dropHalf, dragEvents, custStyle, breathing, sessionActions, scheduleSeat, t }) {
5682
6045
  const state = sessionStateOf(node)
5683
6046
  const status = state === null ? null : {
@@ -5718,23 +6081,65 @@ window.__ModuleLoader__.load({
5718
6081
  }
5719
6082
  const archivedTitle = official('row.archived')
5720
6083
  const pinnedTitle = official('row.pinned')
5721
- const strip = !node.blank && sessionActions && (sessionActions.trigger || sessionActions.quick)
6084
+ // In-row "..." menu (official Rows.tsx): the ellipsis button anchors a
6085
+ // primitives Menu whose entries are the SAME shared list the
6086
+ // right-click menu builds (sessionActions.menuItems) and whose trailing
6087
+ // children are the shipped menu.item seat occupants; picks dispatch
6088
+ // through the SAME handler (sessionActions.pick), so the two surfaces
6089
+ // cannot drift. Hosts without the primitives Menu keep the old
6090
+ // open-context-menu trigger button.
6091
+ const inlineMenu = !node.blank && sessionActions && typeof ui.Menu === 'function'
6092
+ && typeof sessionActions.menuItems === 'function' && typeof sessionActions.pick === 'function'
6093
+ const [menuOpen, setMenuOpen] = React.useState(false)
6094
+ const menuOpenPair = React.useMemo(() => [menuOpen, setMenuOpen], [menuOpen])
6095
+ const menuSeatChildren = inlineMenu && Array.isArray(sessionActions.menuSeat)
6096
+ ? sessionActions.menuSeat.map((seat, index) => E(ShippedSeatOccupant, {
6097
+ key: 'menu-seat-' + index,
6098
+ seat,
6099
+ ownerProps: { sessionId: node.id, displayTitle: node.leaf || node.title },
6100
+ t: seat.t,
6101
+ // The menu.item hook-context factory (ui-slots) hands occupants
6102
+ // the open-state pair; reproduced here for the in-row menu.
6103
+ useMenuOpenState: () => menuOpenPair,
6104
+ }))
6105
+ : null
6106
+ // The shipped row.action seat renders the OFFICIAL pin/archive quick
6107
+ // buttons; our own fallback buttons step aside so the row never shows
6108
+ // two sets (same rule as the schedule mark's leading cell).
6109
+ const quickSeatUsed = Array.isArray(sessionActions.rowActionSeat) && sessionActions.rowActionSeat.length > 0
6110
+ const strip = !node.blank && sessionActions && (inlineMenu || sessionActions.trigger || (sessionActions.quick && !quickSeatUsed))
5722
6111
  ? E('span', { className: 'bw-row-actions', onClick: (e) => e.stopPropagation() },
5723
- sessionActions.trigger ? E('button', {
6112
+ inlineMenu ? E(ui.Menu, {
6113
+ open: menuOpen,
6114
+ onClose: () => setMenuOpen(false),
6115
+ portal: true,
6116
+ closeOnPointerLeave: true,
6117
+ dense: true,
6118
+ anchor: E('button', {
6119
+ type: 'button',
6120
+ className: 'bw-row-btn',
6121
+ 'aria-label': official('actions.session.aria', { name: node.leaf || node.title }) || '...',
6122
+ title: sessionActions.triggerTitle,
6123
+ onClick: () => setMenuOpen((open) => !open),
6124
+ }, icon('IconEllipsisOutline16', 15)),
6125
+ items: sessionActions.menuItems(node),
6126
+ onSelect: (id) => { setMenuOpen(false); sessionActions.pick(node, id) },
6127
+ }, menuSeatChildren)
6128
+ : sessionActions.trigger ? E('button', {
5724
6129
  type: 'button',
5725
6130
  className: 'bw-icon-btn',
5726
6131
  'aria-label': official('actions.session.aria', { name: node.leaf || node.title }) || '...',
5727
6132
  title: sessionActions.triggerTitle,
5728
6133
  onClick: (e) => sessionActions.openMenu(node, e),
5729
6134
  }, icon('IconEllipsisOutline16', 15)) : null,
5730
- sessionActions.quick && sessionActions.pin && !node.archived ? E('button', {
6135
+ !quickSeatUsed && sessionActions.quick && sessionActions.pin && !node.archived ? E('button', {
5731
6136
  type: 'button',
5732
6137
  className: 'bw-icon-btn',
5733
6138
  'aria-label': official(node.pinned ? 'menu.unpinSession' : 'menu.pinSession') || '',
5734
6139
  title: official(node.pinned ? 'actions.unpin' : 'actions.pin') || undefined,
5735
6140
  onClick: (e) => { e.stopPropagation(); sessionActions.pin(node.id, !node.pinned) },
5736
6141
  }, icon(node.pinned ? 'IconPinFillRegular' : 'IconPinOutlineRegular', 14)) : null,
5737
- sessionActions.quick && sessionActions.archive ? E('button', {
6142
+ !quickSeatUsed && sessionActions.quick && sessionActions.archive ? E('button', {
5738
6143
  type: 'button',
5739
6144
  className: 'bw-icon-btn',
5740
6145
  'aria-label': official(node.archived ? 'menu.unarchiveSession' : 'menu.archiveSession') || '',
@@ -5743,11 +6148,18 @@ window.__ModuleLoader__.load({
5743
6148
  }, icon(node.archived ? 'IconUnarchiveOutlineRegular' : 'IconArchiveOutline20', 14)) : null,
5744
6149
  )
5745
6150
  : null
5746
- return E('div', {
6151
+ const marquee = useTitleMarquee()
6152
+ const title = node.leaf || node.title
6153
+ // Official HoverCard (Rows.tsx 693-703): the row is its own anchor; the
6154
+ // card yields while the row menu is open. Title copy rides the official
6155
+ // copy/copyLabels; a blank row copies nothing (official rule).
6156
+ const hoverCard = typeof ui.HoverCard === 'function' && sessionActions && typeof sessionActions.hoverBody === 'function'
6157
+ let row = E('div', {
5747
6158
  className: cls('bw-row', 'bw-session-row', current && 'bw-row-current',
5748
6159
  node.archived && 'bw-row-archived',
6160
+ menuOpen && 'menu-open',
5749
6161
  dropHalf === 'before' && 'bw-drop-before', dropHalf === 'after' && 'bw-drop-after'),
5750
- style: { paddingLeft: 8 + depth * 12, ...(custStyle || {}) },
6162
+ style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}) },
5751
6163
  onClick: () => {
5752
6164
  if (node.archived) {
5753
6165
  // Official semantics (guardedOpen): an archived row is not
@@ -5757,6 +6169,14 @@ window.__ModuleLoader__.load({
5757
6169
  }
5758
6170
  onOpen(node.id)
5759
6171
  },
6172
+ onDoubleClick: node.blank ? undefined : (e) => {
6173
+ // Official rename affordance (Rows.tsx title onDoubleClick): the
6174
+ // double click never reaches the row's open handler.
6175
+ e.stopPropagation()
6176
+ if (sessionActions && typeof sessionActions.rename === 'function') sessionActions.rename(node)
6177
+ },
6178
+ onPointerEnter: marquee.onPointerEnter,
6179
+ onPointerLeave: marquee.onPointerLeave,
5760
6180
  onContextMenu: onContextMenu,
5761
6181
  role: 'treeitem',
5762
6182
  'aria-description': node.archived ? archivedTitle || undefined : undefined,
@@ -5766,21 +6186,41 @@ window.__ModuleLoader__.load({
5766
6186
  status && typeof ui.StateDot === 'function'
5767
6187
  ? (state === 'ongoing' && breathing
5768
6188
  // Running breathes blue ON the session row only (never relays);
5769
- // the settings toggle covers this breathing too.
5770
- ? E(PulseGlow, { state: 'ongoing' }, E(ui.StateDot, { state: status.state, size: 10 }))
5771
- : E(ui.StateDot, { state: status.state, size: 10 }))
6189
+ // the settings toggle covers this breathing too. The dot takes
6190
+ // its official size (14px spinner / 10px lamp), inside the
6191
+ // fixed 16px leading cell.
6192
+ ? E(PulseGlow, { state: 'ongoing' }, E(ui.StateDot, { state: status.state }))
6193
+ : E(ui.StateDot, { state: status.state }))
5772
6194
  : (markNode || E('span', { className: 'bw-dot' })),
5773
6195
  ),
5774
- E('span', { className: 'bw-row-label' }, node.leaf || node.title),
6196
+ E('span', { className: 'bw-row-label', ref: marquee.titleRef }, title),
5775
6197
  // Legacy projection mark (see markNode): only ever renders where the
5776
6198
  // shipped seat does not exist, so the row never shows two marks.
5777
6199
  node.hasActiveSchedule && !markNode
5778
6200
  ? E('span', { className: 'bw-schedule-badge', role: 'img', 'aria-label': t('schedule.active'), title: t('schedule.active') }, icon('IconAlarmClockOutline16', 14))
5779
6201
  : null,
5780
- E('span', { className: 'bw-row-time' }, timeLabel(node.updatedAt, now, t)),
5781
- node.pinned ? E('span', { className: 'bw-pin-indicator', role: 'img', 'aria-label': pinnedTitle || undefined, title: pinnedTitle || undefined }, icon('IconPinFillRegular', 13)) : null,
6202
+ // Official blank rows carry no time, no pin, no actions (a standing
6203
+ // "new session" placeholder, not a session).
6204
+ !node.blank ? E('span', { className: 'bw-row-time' }, timeLabel(node.updatedAt, now, t)) : null,
6205
+ !node.blank && node.pinned ? E('span', { className: 'bw-pin-indicator', role: 'img', 'aria-label': pinnedTitle || undefined, title: pinnedTitle || undefined }, icon('IconPinFillRegular', 13)) : null,
5782
6206
  strip,
5783
6207
  )
6208
+ if (hoverCard) {
6209
+ const copyText = node.blank || title === '' ? undefined : title
6210
+ const copyLabel = official('copy')
6211
+ const copiedLabel = official('hover.copied')
6212
+ row = E(ui.HoverCard, Object.assign({
6213
+ key: 'hover',
6214
+ anchor: row,
6215
+ content: sessionActions.hoverBody(node, status, state),
6216
+ openDelayMs: 800,
6217
+ grouped: true,
6218
+ disabled: menuOpen,
6219
+ // copyText present REQUIRES both labels (primitives HoverCard
6220
+ // contract): copy only renders where the official copy survives.
6221
+ }, copyText && copyLabel && copiedLabel ? { copyText, copyLabel, copiedLabel } : {}))
6222
+ }
6223
+ return row
5784
6224
  }
5785
6225
 
5786
6226
  /**
@@ -5791,14 +6231,16 @@ window.__ModuleLoader__.load({
5791
6231
  function SessionGroupRow({ name, depth, expanded, count, onToggle, onContextMenu, dropInto, dragEvents, custStyle, pulse, t }) {
5792
6232
  return E('div', {
5793
6233
  className: cls('bw-row', 'bw-sgroup-row', dropInto && 'bw-drop-into', dropInto && 'bw-drop-into-strong'),
5794
- style: { paddingLeft: 10 + depth * 12, ...(custStyle || {}) },
6234
+ style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}) },
5795
6235
  onClick: onToggle,
5796
6236
  onContextMenu: onContextMenu,
5797
6237
  role: 'treeitem',
5798
6238
  'aria-expanded': expanded,
5799
6239
  ...(dragEvents || {}),
5800
6240
  },
5801
- E('span', { className: cls('bw-chevron', expanded && 'bw-chevron-open') }, icon('IconTriangleRightFill14', 14)),
6241
+ // Session groups are a plugin concept: their chevron stays visible
6242
+ // (only the official container rows hide it until hover).
6243
+ E('span', { className: cls('bw-chevron', expanded && 'bw-chevron-open') }, E('span', { className: 'bw-arrow' }, icon('IconTriangleRightFill14', 14))),
5802
6244
  // Session groups have no icon slot: a collapsed group with hidden
5803
6245
  // status relays through a breathing official StateDot instead.
5804
6246
  pulse ? E(PulseGlow, { state: pulse }, typeof ui.StateDot === 'function' ? E(ui.StateDot, { state: pulse, size: 10 }) : null) : null,
@@ -6395,6 +6837,12 @@ window.__ModuleLoader__.load({
6395
6837
  // Progressive listing quota (v0.21.0, issue #8): 0 = unlimited.
6396
6838
  const rawLimit = Math.floor(Number(prefs.sessionLimit))
6397
6839
  const sessionLimit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, 500) : 0
6840
+ // Row density (v0.34.0): Comfortable rides the shipped sidebar row
6841
+ // heights (the default); Compact/Roomy drive the --bw-row-h dials.
6842
+ const densityRaw = String(prefs.density || 'comfortable')
6843
+ const density = densityRaw === 'compact' || densityRaw === 'roomy' ? densityRaw : 'comfortable'
6844
+ // Session count badge: the shipped sidebar retired it; default OFF.
6845
+ const showCount = prefs.showCount === true
6398
6846
  // Grouping strategy (v0.21.0, issue #7): rides the view store top-level
6399
6847
  // key next to groupBy/orderBy and stays browser-local, like every other
6400
6848
  // view option. The legacy workspaceTitleSlash boolean is consulted only
@@ -6576,6 +7024,32 @@ window.__ModuleLoader__.load({
6576
7024
  }, E('span', { className: 'bw-switch-thumb' })),
6577
7025
  ),
6578
7026
  E('div', { className: 'bw-hint' }, t('settings.statusPulse.hint')),
7027
+ E('div', { className: 'bw-setting-row' },
7028
+ E('div', { className: 'bw-setting-label' }, t('settings.density')),
7029
+ E('div', { className: 'bw-seg' },
7030
+ ['compact', 'comfortable', 'roomy'].map((mode) =>
7031
+ E('button', {
7032
+ type: 'button',
7033
+ key: mode,
7034
+ className: cls('bw-seg-btn', density === mode && 'bw-seg-btn-active'),
7035
+ onClick: () => { setPref('density', mode) },
7036
+ }, t('settings.density.' + mode)),
7037
+ ),
7038
+ ),
7039
+ ),
7040
+ E('div', { className: 'bw-hint' }, t('settings.density.hint')),
7041
+ E('div', { className: 'bw-setting-row' },
7042
+ E('div', { className: 'bw-setting-label' }, t('settings.showCount')),
7043
+ E('button', {
7044
+ type: 'button',
7045
+ role: 'switch',
7046
+ 'aria-checked': showCount,
7047
+ 'aria-label': t('settings.showCount'),
7048
+ className: cls('bw-switch', showCount && 'bw-switch-on'),
7049
+ onClick: () => { setPref('showCount', !showCount) },
7050
+ }, E('span', { className: 'bw-switch-thumb' })),
7051
+ ),
7052
+ E('div', { className: 'bw-hint' }, t('settings.showCount.hint')),
6579
7053
  E('div', { className: 'bw-setting-row' },
6580
7054
  E('div', { className: 'bw-setting-label' }, t('settings.autoStyle')),
6581
7055
  E('button', {
@@ -6935,7 +7409,7 @@ window.__ModuleLoader__.load({
6935
7409
  useDirectoryFlow,
6936
7410
  pickDirectory, listDirectory, createDirectory,
6937
7411
  sidebarFlowEntry,
6938
- shippedBrowserFace, scheduleSeatReader,
7412
+ shippedBrowserFace, scheduleSeatReader, seatEntriesReader,
6939
7413
  officialT,
6940
7414
  presetFaceOf,
6941
7415
  t,
@@ -7006,6 +7480,10 @@ window.__ModuleLoader__.load({
7006
7480
  // Progressive listing (v0.21.0, issue #8): 0 means unlimited (default).
7007
7481
  const rawLimit = Math.floor(Number(prefsMap.sessionLimit))
7008
7482
  const sessionLimit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, 500) : 0
7483
+ // Tree density + count badge (v0.34.0), read fresh every render.
7484
+ const densityRaw = String(prefsMap.density || 'comfortable')
7485
+ const density = densityRaw === 'compact' || densityRaw === 'roomy' ? densityRaw : 'comfortable'
7486
+ const showCount = prefsMap.showCount === true
7009
7487
  // Dual-write wrappers: every preference mutation lands in the local
7010
7488
  // store (immediate echo + scope-less fallback) AND the host settings
7011
7489
  // store (durable cross-device copy for the manual pull on the other
@@ -7149,6 +7627,15 @@ window.__ModuleLoader__.load({
7149
7627
  // The shipped schedule mark's seat occupant (see scheduleSeatOf); absent
7150
7628
  // whenever the Schedule overlay is off, which is the shipped default.
7151
7629
  const scheduleSeat = typeof scheduleSeatReader === 'function' ? scheduleSeatReader() : null
7630
+ // The shipped session-row seats (official Rows.tsx): menu.item
7631
+ // occupants become the in-row menu's trailing children, row.action
7632
+ // occupants become the official pin/archive quick buttons (our own
7633
+ // fallback buttons step aside), session.row.hover decorates the hover
7634
+ // card. All read off the ledger like the schedule mark.
7635
+ const seatEntriesOf = typeof seatEntriesReader === 'function' ? seatEntriesReader : null
7636
+ const rowMenuSeat = seatEntriesOf ? seatEntriesOf('sidebar.workspaces.session.menu.item') : []
7637
+ const rowActionSeat = seatEntriesOf ? seatEntriesOf('sidebar.workspaces.session.row.action') : []
7638
+ const hoverSeat = seatEntriesOf ? seatEntriesOf('sidebar.session.row.hover') : []
7152
7639
  /** Effective catalog row of one shipped command (current keybinding, label). */
7153
7640
  const shortcutRowOf = (id) => {
7154
7641
  const rows = Array.isArray(shortcutRows) ? shortcutRows : EMPTY_LIST
@@ -7275,6 +7762,34 @@ window.__ModuleLoader__.load({
7275
7762
  }
7276
7763
  }, [])
7277
7764
 
7765
+ // Modal surface handoff for dsh-any-background: that plugin sorts dialog
7766
+ // surfaces by [aria-labelledby] (settings panels have it; confirm dialogs
7767
+ // do not and are pinned OPAQUE by design, exemptDefaultRule). The official
7768
+ // ui-primitives Modal never emits aria-labelledby (Modal.tsx: root gets
7769
+ // aria-label only, the <h2> has no id), so every Modal-built dialog lands
7770
+ // in the confirm bucket and stays solid no matter the sliders. Patch ONLY
7771
+ // our own dialogs (recognizable by .bw-modal-body/.bw-modal-actions):
7772
+ // point aria-labelledby at the header <h2>. Host confirm dialogs keep
7773
+ // their intentional opaque treatment.
7774
+ React.useEffect(() => {
7775
+ if (typeof MutationObserver !== 'function' || !document.body) return undefined
7776
+ let seq = 0
7777
+ const sweep = () => {
7778
+ const dialogs = document.querySelectorAll('[role="dialog"][aria-modal="true"][aria-label]:not([aria-labelledby])')
7779
+ for (const dlg of dialogs) {
7780
+ if (!dlg.querySelector('.bw-modal-body, .bw-modal-actions')) continue
7781
+ const heading = dlg.querySelector('h2')
7782
+ if (!heading) continue
7783
+ if (!heading.id) { seq += 1; heading.id = 'bw-dialog-label-' + seq }
7784
+ dlg.setAttribute('aria-labelledby', heading.id)
7785
+ }
7786
+ }
7787
+ const observer = new MutationObserver(sweep)
7788
+ observer.observe(document.body, { childList: true, subtree: true })
7789
+ sweep()
7790
+ return () => observer.disconnect()
7791
+ }, [])
7792
+
7278
7793
  const [query, setQuery] = React.useState('')
7279
7794
  const [searchOpen, setSearchOpen] = React.useState(false)
7280
7795
  const [flowBusy, setFlowBusy] = React.useState(false) // createWorkspace in flight after a pick
@@ -7698,6 +8213,41 @@ window.__ModuleLoader__.load({
7698
8213
  const value = typeof officialT === 'function' ? officialT(key, params) : null
7699
8214
  return typeof value === 'string' && value !== '' && value !== key ? value : fallback
7700
8215
  }
8216
+
8217
+ // Official hover-card bodies (Rows.tsx WorkspaceHoverContent /
8218
+ // SessionHoverContent): the palette is FIXED in the shipped card
8219
+ // (#FFFFFF title, #CFD3D6 meta, #ADB2B8 status), and the copy rides
8220
+ // the official 'workspace' dictionary; a missing key degrades to the
8221
+ // aligned bw time label.
8222
+ const workspaceHoverBody = (workspace) => E('div', { className: 'bw-hover-card' },
8223
+ E('div', { className: 'bw-hover-title' }, workspace.title || workspace.leaf),
8224
+ workspace.path ? E('div', { className: 'bw-hover-path' }, workspace.path) : null,
8225
+ )
8226
+ const sessionHoverTime = (node) => {
8227
+ if (typeof ui.relativeTime !== 'function') return timeLabel(node.updatedAt, now, t)
8228
+ const r = ui.relativeTime(node.updatedAt, now)
8229
+ if (!r) return ''
8230
+ if (r.unit === 'now') return officialText('time.now', undefined, t('time.now'))
8231
+ const inner = officialText('time.' + r.unit, { n: r.n }, timeLabel(node.updatedAt, now, t))
8232
+ const ago = officialText('time.ago', { t: inner }, null)
8233
+ return ago || inner
8234
+ }
8235
+ const sessionHoverBody = (node, status) => E('div', { className: 'bw-hover-card' },
8236
+ E('div', { className: 'bw-hover-title' }, node.leaf || node.title),
8237
+ !node.blank ? E('div', { className: 'bw-hover-time' }, sessionHoverTime(node)) : null,
8238
+ // The shipped session.hover decorations render inside the card,
8239
+ // like the official renderSlot('sidebar.session.row.hover').
8240
+ ...(Array.isArray(hoverSeat) ? hoverSeat.map((seat, index) => E(QuietBoundary, { key: 'hover-seat-' + index }, E(ShippedSeatOccupant, {
8241
+ seat,
8242
+ ownerProps: { sessionId: node.id },
8243
+ t: seat.t,
8244
+ }))) : []),
8245
+ status && typeof ui.StateDot === 'function' ? E('div', { className: 'bw-hover-status' },
8246
+ E(ui.StateDot, { state: status.state }), E('span', null, status.title)) : null,
8247
+ node.archived && officialText('row.archived', undefined, '')
8248
+ ? E('div', { className: 'bw-hover-status' }, icon('IconArchiveOutline20', 14), E('span', null, officialText('row.archived', undefined, '')))
8249
+ : null,
8250
+ )
7701
8251
  const pinAvailable = typeof pinSession === 'function' && typeof unpinSession === 'function'
7702
8252
  const unarchiveAvailable = typeof unarchiveSession === 'function'
7703
8253
  const sessionActions = {
@@ -7734,6 +8284,24 @@ window.__ModuleLoader__.load({
7734
8284
  }
7735
8285
  : undefined,
7736
8286
  openMenu: (node, e) => openCtx('session', node, e),
8287
+ // In-row "..." menu (official Rows.tsx): the SAME shared entry list
8288
+ // (sessionMenuEntriesFor) and the SAME pick dispatch the right-click
8289
+ // menu uses, so the two surfaces cannot drift. menuSeat carries the
8290
+ // shipped menu.item occupants (rendered as Menu children);
8291
+ // rowActionSeat the official quick buttons (our own buttons step
8292
+ // aside while it has entries). An id the shared dispatcher does not
8293
+ // own (bw extensions: move-group / customize) falls back to the
8294
+ // ctx surface's handler through a transient payload.
8295
+ menuItems: (node) => (typeof sessionMenuEntriesFor === 'function' ? sessionMenuEntriesFor(node) : []),
8296
+ menuSeat: rowMenuSeat,
8297
+ rowActionSeat,
8298
+ pick: (node, id) => {
8299
+ if (typeof pickSessionAction === 'function' && pickSessionAction(node, id)) return
8300
+ setCtx({ kind: 'session', payload: node, x: 0, y: 0 })
8301
+ if (typeof handleCtxPick === 'function') handleCtxPick(id)
8302
+ },
8303
+ rename: (node) => setDialog({ kind: 'sess-rename', session: node }),
8304
+ hoverBody: (node, status) => sessionHoverBody(node, status),
7737
8305
  explainArchived: () => {
7738
8306
  const text = officialText('toast.archivedNotOpenable', undefined, '') || officialText('row.archived', undefined, '')
7739
8307
  if (text) setErrorText(text)
@@ -8541,12 +9109,20 @@ window.__ModuleLoader__.load({
8541
9109
  depth,
8542
9110
  count,
8543
9111
  sessionsOpen: searching ? true : sessionsOpenOf(workspace.workspaceId),
8544
- currentInside: !!(currentId && (workspace.sessionIds || []).includes(currentId)),
9112
+ currentInside: wsHasCurrent(workspace),
8545
9113
  onToggle: () => { if (!searching) actions.setSessionsExpanded(workspace.workspaceId, !sessionsOpenOf(workspace.workspaceId)) },
8546
9114
  // Starting a session force-expands the workspace row: the user must
8547
9115
  // SEE the new session appear, even if the row was collapsed.
8548
9116
  onStart: () => { actions.setSessionsExpanded(workspace.workspaceId, true); startSession(workspace.workspaceId) },
8549
9117
  onContextMenu: (e) => openCtx('workspace', workspace, e),
9118
+ // Official Rows.tsx: the ellipsis button anchors the row's own
9119
+ // menu; the ctx surface stays the right-click path. While the
9120
+ // menu is open the row pins (hover fill + visible actions),
9121
+ // matching the official menuOpen state.
9122
+ onMenu: (e) => openCtx('workspace', workspace, e),
9123
+ menuOpen: !!(ctx && ctx.kind === 'workspace' && ctx.payload && ctx.payload.workspaceId === workspace.workspaceId),
9124
+ menuAria: officialText('actions.workspace.aria', { name: workspace.title || workspace.leaf }) || undefined,
9125
+ hoverBody: workspaceHoverBody,
8550
9126
  dropHalf: wsDropHalf(workspace.workspaceId),
8551
9127
  dropInto: wsRootDropActive(workspace.workspaceId),
8552
9128
  dragEvents: workspaceDragEvents(workspace),
@@ -8570,6 +9146,26 @@ window.__ModuleLoader__.load({
8570
9146
  return rows
8571
9147
  }
8572
9148
 
9149
+ // Official folderActive semantics (Rows.tsx ProjectRow): a folder or
9150
+ // workspace whose SUBTREE holds the current session tints its folder
9151
+ // glyph blue, regardless of expansion. The plugin tree is path-folded,
9152
+ // so ancestor folders match by disk-path prefix, and disk-nested
9153
+ // workspace chains are walked through the sub levels.
9154
+ const pathHoldsCurrent = (ancestor, child) => !!ancestor && !!child
9155
+ && (child === ancestor || child.startsWith(ancestor.endsWith('/') ? ancestor : ancestor + '/'))
9156
+ const subHasCurrent = (level) => {
9157
+ if (!level || !currentId) return false
9158
+ for (const w of level.workspaces || []) if (wsHasCurrent(w)) return true
9159
+ for (const f of level.folders || []) {
9160
+ if (f.kind === 'ws') { if (wsHasCurrent(f.workspace)) return true } else if (subHasCurrent(f)) return true
9161
+ }
9162
+ return false
9163
+ }
9164
+ const wsHasCurrent = (w) => !!currentId && !!w
9165
+ && ((w.sessionIds || []).includes(currentId) || subHasCurrent(w.sub))
9166
+ const currentWsPath = currentId
9167
+ ? ((items || []).find((w) => (w.sessionIds || []).includes(currentId)) || {}).path
9168
+ : undefined
8573
9169
  const renderLevelRows = (level, depth) => {
8574
9170
  const rows = []
8575
9171
  for (const folder of level.folders) rows.push(...renderPlainFolder(folder, depth))
@@ -8598,6 +9194,7 @@ window.__ModuleLoader__.load({
8598
9194
  custStyle: rowStyleOf('folder:' + idPath),
8599
9195
  iconMode: (styleEntry('folder:' + idPath) || {}).icon || 'solid',
8600
9196
  pulse: expanded ? null : folderPulseOf(node),
9197
+ active: pathHoldsCurrent(currentWsPath, node.path),
8601
9198
  t,
8602
9199
  })]
8603
9200
  if (expanded) {
@@ -8627,12 +9224,12 @@ window.__ModuleLoader__.load({
8627
9224
  rows.push(E('div', {
8628
9225
  key: 'ungrouped-label',
8629
9226
  className: 'bw-row bw-sgroup-row',
8630
- style: { paddingLeft: 8 },
9227
+ style: { '--bw-indent': '0px' },
8631
9228
  onClick: () => { if (!searching && actions && typeof actions.setUngroupedOpen === 'function') actions.setUngroupedOpen(!open) },
8632
9229
  role: 'treeitem',
8633
9230
  'aria-expanded': open,
8634
9231
  },
8635
- E('span', { className: cls('bw-chevron', open && 'bw-chevron-open') }, icon('IconTriangleRightFill14', 14)),
9232
+ E('span', { className: cls('bw-chevron', open && 'bw-chevron-open') }, E('span', { className: 'bw-arrow' }, icon('IconTriangleRightFill14', 14))),
8636
9233
  E('span', { className: 'bw-row-label' }, t('group.ungrouped')),
8637
9234
  E('span', { className: 'bw-row-count' }, String(ungrouped.length)),
8638
9235
  ))
@@ -8688,6 +9285,102 @@ window.__ModuleLoader__.load({
8688
9285
  // provide is the bw-only surface: name-group / session-group rename
8689
9286
  // and Customize appearance, appended after a separator.
8690
9287
  const ot = typeof officialT === 'function' ? officialT : null
9288
+ // Shared session-menu builder + dispatcher: the right-click menu AND
9289
+ // the row's own "..." menu render the SAME entry list (official
9290
+ // mirror + bw extensions + trailing customize) and dispatch through
9291
+ // the SAME handler, so the two surfaces cannot drift. menuEntries
9292
+ // feeds it the ctx payload; sessionActions feeds it the row node.
9293
+ const sessionMenuEntriesFor = (payload) => {
9294
+ const customizeEntry = { id: 'customize', label: t('custom.title'), icon: icon('IconPersonalizationOutline16', 16) }
9295
+ const groupItem = { id: 'move-group', label: t('menu.moveToGroup'), icon: icon('IconFolderOpenOutline16', 16) }
9296
+ const ungroupItem = { id: 'move-out', label: t('menu.moveOutGroup'), icon: icon('IconRightUpOutline16', 16) }
9297
+ const sgroupItems = sessionSlash ? [groupItem] : []
9298
+ const sessionUngroupItems = sessionSlash && payload && groupPrefixOf(String(payload.title || '')) !== '' ? [ungroupItem] : []
9299
+ const official = (key, fallback) => {
9300
+ const value = typeof ot === 'function' ? ot(key) : null
9301
+ return typeof value === 'string' && value !== '' && value !== key ? value : fallback
9302
+ }
9303
+ const archived = !!(payload.archived || (archivedSet && archivedSet.has(payload.id)))
9304
+ const pinned = !!(payload.pinned || (pinnedSet && pinnedSet.has(payload.id)))
9305
+ // Official 0.1.7 mirror (Rows.tsx session menu + the shipped slot
9306
+ // entries): pin, rename, fork, archive — same order, same icons,
9307
+ // same archived-state behavior (pin hides; archive becomes
9308
+ // unarchive). Copy Session ID (order 450) rides the official
9309
+ // dictionary, so upstream rewording AND future item additions land
9310
+ // without a plugin release; every verb is gated on its service
9311
+ // method so older hosts simply never offer what they cannot do.
9312
+ return [
9313
+ ...(pinAvailable && !archived ? [{
9314
+ id: pinned ? 'unpin' : 'pin',
9315
+ label: official(pinned ? 'menu.unpinSession' : 'menu.pinSession', t('menu.archive')),
9316
+ icon: icon(pinned ? 'IconPinFillRegular' : 'IconPinOutlineRegular', 16),
9317
+ }] : []),
9318
+ { id: 'rename', label: official('rename', t('menu.rename')), icon: icon('IconEditOutline16', 16) },
9319
+ { id: 'fork', label: official('menu.fork', t('menu.fork')), icon: icon('IconBranchOutline16', 16) },
9320
+ // Official semantics: archive hides the row through the
9321
+ // registry-global archive set and never touches the session log
9322
+ // — deliberately NOT styled destructive. An archived row gets
9323
+ // the restore entry instead.
9324
+ archived
9325
+ ? { id: 'unarchive', label: official('menu.unarchiveSession', t('menu.archive')), icon: icon('IconUnarchiveOutlineRegular', 16) }
9326
+ : { id: 'archive', label: official('menu.archiveSession', t('menu.archive')), icon: icon('IconArchiveOutline20', 16) },
9327
+ ...(typeof ui.writeClipboard === 'function' ? [{
9328
+ id: 'copy-id',
9329
+ label: official('menu.sessionId', t('menu.sessionId')),
9330
+ icon: icon('IconCopyOutlineRegular', 16),
9331
+ }] : []),
9332
+ { type: 'separator', id: 'bw-sep' },
9333
+ ...sessionUngroupItems,
9334
+ ...sgroupItems,
9335
+ customizeEntry,
9336
+ ]
9337
+ }
9338
+ const pickSessionAction = (payload, id) => {
9339
+ if (id === 'rename') { setDialog({ kind: 'sess-rename', session: payload }); return true }
9340
+ if (id === 'fork') { forkSession(payload.id); return true }
9341
+ if ((id === 'pin' || id === 'unpin') && pinAvailable) {
9342
+ Promise.resolve().then(() => (id === 'pin' ? pinSession : unpinSession)(payload.id)).catch((reason) => {
9343
+ fail(officialText(id === 'pin' ? 'toast.pinFailed' : 'toast.unpinFailed', undefined, reason instanceof Error ? reason.message : String(reason || 'pin failed')))
9344
+ })
9345
+ return true
9346
+ }
9347
+ if (id === 'unarchive' && unarchiveAvailable) {
9348
+ Promise.resolve().then(() => unarchiveSession(payload.id)).catch((reason) => { console.warn('session unarchive rejected:', reason) })
9349
+ return true
9350
+ }
9351
+ if (id === 'archive' && typeof archiveSession === 'function') {
9352
+ Promise.resolve().then(() => archiveSession(payload.id)).catch((reason) => {
9353
+ // Official refusal flow: running work refuses the plain archive
9354
+ // (WorkspaceArchiveError / workspace/session-active) and the
9355
+ // confirmation offers to stop it first. Anything else reports
9356
+ // through the error modal.
9357
+ if (reason && reason.name === 'WorkspaceArchiveError') {
9358
+ setDialog({ kind: 'sess-archive-confirm', sessionId: payload.id })
9359
+ return
9360
+ }
9361
+ fail(reason)
9362
+ })
9363
+ return true
9364
+ }
9365
+ if (id === 'copy-id' && typeof ui.writeClipboard === 'function') {
9366
+ // Official mirror: copied → success notice, anything else → the
9367
+ // failure notice (official sessionIdInjected wiring).
9368
+ Promise.resolve(ui.writeClipboard(payload.id)).then((copied) => {
9369
+ setCopyNotice((prev) => ({ seq: (prev ? prev.seq : 0) + 1, ok: copied === true }))
9370
+ }, () => {
9371
+ setCopyNotice((prev) => ({ seq: (prev ? prev.seq : 0) + 1, ok: false }))
9372
+ })
9373
+ return true
9374
+ }
9375
+ // Plain retitle back to the leaf: the row leaves its group and
9376
+ // returns to the top level of its workspace (v0.18.0).
9377
+ if (id === 'move-out') {
9378
+ const leaf = leafOf(String(payload.title || ''))
9379
+ if (leaf !== payload.title) Promise.resolve().then(() => renameByUser(payload.id, leaf)).catch(fail)
9380
+ return true
9381
+ }
9382
+ return false
9383
+ }
8691
9384
  const menuEntries = () => {
8692
9385
  if (ctx === null) return []
8693
9386
  const customizeEntry = { id: 'customize', label: t('custom.title'), icon: icon('IconPersonalizationOutline16', 16) }
@@ -8701,14 +9394,8 @@ window.__ModuleLoader__.load({
8701
9394
  // probing it silently yielded a glyph-less row. IconFolderOpenOutline16
8702
9395
  // is present and reads as "move into a group" just as well.
8703
9396
  const groupItem = { id: 'move-group', label: t('menu.moveToGroup'), icon: icon('IconFolderOpenOutline16', 16) }
8704
- // "Leave the group" (v0.18.0) — the counterpart of move-group, offered
8705
- // only where a group actually exists to leave: a SESSION row whose own
8706
- // title carries a "/" prefix, with the session name layer on. A
8707
- // top-level row gets no entry (there is nothing to leave).
8708
- const ungroupItem = { id: 'move-out', label: t('menu.moveOutGroup'), icon: icon('IconRightUpOutline16', 16) }
8709
9397
  const wsGroupItems = workspaceSlash ? [groupItem] : []
8710
9398
  const sgroupItems = sessionSlash ? [groupItem] : []
8711
- const sessionUngroupItems = sessionSlash && ctx.kind === 'session' && ctx.payload && groupPrefixOf(String(ctx.payload.title || '')) !== '' ? [ungroupItem] : []
8712
9399
  // "Delete group" only makes sense for a DECLARED group that holds
8713
9400
  // nothing (v0.17.0): a group with members is a projection of their
8714
9401
  // titles, so removing the declaration would change nothing on screen
@@ -8735,49 +9422,10 @@ window.__ModuleLoader__.load({
8735
9422
  ...(presetFace ? [{ id: 'ws-preset', label: t('menu.workspacePreset'), icon: icon('IconAgentPresetOutlineRegular', 16) }] : []),
8736
9423
  ]
8737
9424
  if (ctx.kind === 'session') {
8738
- // Official 0.1.7 mirror (Rows.tsx session menu + the shipped slot
8739
- // entries): pin, rename, fork, archive — same order, same icons,
8740
- // same archived-state behavior (pin hides; archive becomes
8741
- // unarchive). dsh 0.2.1-alpha.5 adds a fifth official entry
8742
- // (Copy Session ID, order 450), mirrored below the archive row.
8743
- // Copy rides the official dictionary, so upstream
8744
- // rewording AND future item additions land without a plugin
8745
- // release; every verb is gated on its service method so older
8746
- // hosts simply never offer what they cannot do.
8747
- const payload = ctx.payload || {}
8748
- const archived = !!(payload.archived || (archivedSet && archivedSet.has(payload.id)))
8749
- const pinned = !!(payload.pinned || (pinnedSet && pinnedSet.has(payload.id)))
8750
- const official = (key, fallback) => {
8751
- const value = typeof ot === 'function' ? ot(key) : null
8752
- return typeof value === 'string' && value !== '' && value !== key ? value : fallback
8753
- }
8754
- return [
8755
- ...(pinAvailable && !archived ? [{
8756
- id: pinned ? 'unpin' : 'pin',
8757
- label: official(pinned ? 'menu.unpinSession' : 'menu.pinSession', t('menu.archive')),
8758
- icon: icon(pinned ? 'IconPinFillRegular' : 'IconPinOutlineRegular', 16),
8759
- }] : []),
8760
- { id: 'rename', label: official('rename', t('menu.rename')), icon: icon('IconEditOutline16', 16) },
8761
- { id: 'fork', label: official('menu.fork', t('menu.fork')), icon: icon('IconBranchOutline16', 16) },
8762
- // Official semantics: archive hides the row through the
8763
- // registry-global archive set and never touches the session log
8764
- // — deliberately NOT styled destructive. An archived row gets
8765
- // the restore entry instead.
8766
- archived
8767
- ? { id: 'unarchive', label: official('menu.unarchiveSession', t('menu.archive')), icon: icon('IconUnarchiveOutlineRegular', 16) }
8768
- : { id: 'archive', label: official('menu.archiveSession', t('menu.archive')), icon: icon('IconArchiveOutline20', 16) },
8769
- // Official order 450: clipboard write rides the host primitive,
8770
- // so the whole row hides where the primitives lack it.
8771
- ...(typeof ui.writeClipboard === 'function' ? [{
8772
- id: 'copy-id',
8773
- label: official('menu.sessionId', t('menu.sessionId')),
8774
- icon: icon('IconCopyOutlineRegular', 16),
8775
- }] : []),
8776
- { type: 'separator', id: 'bw-sep' },
8777
- ...sessionUngroupItems,
8778
- ...sgroupItems,
8779
- customizeEntry,
8780
- ]
9425
+ // The shared builder owns the session list (see
9426
+ // sessionMenuEntriesFor): the row's own "..." menu renders the
9427
+ // exact same entries, so the two surfaces cannot drift.
9428
+ return sessionMenuEntriesFor(ctx.payload || {})
8781
9429
  }
8782
9430
  return [
8783
9431
  { id: 'rename-sgroup', label: t('menu.renameSgroup'), icon: icon('IconEditOutline16', 16) },
@@ -8818,9 +9466,13 @@ window.__ModuleLoader__.load({
8818
9466
  // Freeze the target NOW (the dialog outlives the menu ctx): the
8819
9467
  // blank lookup mirrors the official createInWorkspace rule — a
8820
9468
  // standing blank IS the workspace default (its preset projection
8821
- // is what the hero chip shows); none → the workspace follows the
8822
- // GLOBAL default and the dialog says so instead of promising an
8823
- // edit. No session is ever created for the edit.
9469
+ // is what the hero chip shows). With NO standing blank the edit
9470
+ // still applies: ClientSessions.create prepares one WITH the
9471
+ // picked preset (the same channel as the official new-session
9472
+ // button), which makes it the workspace default. Creating a
9473
+ // session here is deliberate, not a side effect — the host has
9474
+ // no other per-workspace default store (agent-preset-registry
9475
+ // selectedDefault is GLOBAL; the standing blank IS the carrier).
8824
9476
  const raw = (storedItems || []).find((item) => item.workspaceId === payload.workspaceId)
8825
9477
  const blankIds = raw && Array.isArray(raw.sessionIds) ? raw.sessionIds : []
8826
9478
  const blankId = raw && raw.path
@@ -8834,44 +9486,23 @@ window.__ModuleLoader__.load({
8834
9486
  const projection = blankSummary ? blankSummary.projectionValues : undefined
8835
9487
  setDialog({
8836
9488
  kind: 'ws-preset',
8837
- applyPreset: blankId !== undefined ? (presetId) => presetFace.select(blankId, presetId) : null,
9489
+ hasBlank: blankId !== undefined,
9490
+ applyPreset: blankId !== undefined
9491
+ ? (presetId) => presetFace.select(blankId, presetId)
9492
+ // Reuse declined / no draft to reuse: create WITH the preset.
9493
+ // Shape matches select's { ok } contract; a SessionCreateError
9494
+ // lands in the dialog as an inline refusal line.
9495
+ : (presetId) => Promise.resolve()
9496
+ .then(() => sessions.create({ cwd: raw.path, agentPreset: presetId }))
9497
+ .then(() => ({ ok: true }), (reason) => ({ ok: false, refusal: reason instanceof Error ? reason.message : String(reason || 'session create failed') })),
8838
9498
  current: projection && typeof projection.agentPreset === 'string' ? projection.agentPreset : undefined,
8839
9499
  })
8840
9500
  }
8841
9501
  else if (kind === 'sgroup' && id === 'rename-sgroup') setDialog({ kind: 'sgroup-rename', target: payload })
8842
- else if (kind === 'session' && id === 'rename') setDialog({ kind: 'sess-rename', session: payload })
8843
- else if (kind === 'session' && id === 'fork') forkSession(payload.id)
8844
- else if (kind === 'session' && (id === 'pin' || id === 'unpin') && pinAvailable) {
8845
- Promise.resolve().then(() => (id === 'pin' ? pinSession : unpinSession)(payload.id)).catch((reason) => {
8846
- fail(officialText(id === 'pin' ? 'toast.pinFailed' : 'toast.unpinFailed', undefined, reason instanceof Error ? reason.message : String(reason || 'pin failed')))
8847
- })
8848
- }
8849
- else if (kind === 'session' && id === 'unarchive' && unarchiveAvailable) {
8850
- Promise.resolve().then(() => unarchiveSession(payload.id)).catch((reason) => { console.warn('session unarchive rejected:', reason) })
8851
- }
8852
- else if (kind === 'session' && id === 'archive' && typeof archiveSession === 'function') {
8853
- Promise.resolve().then(() => archiveSession(payload.id)).catch((reason) => {
8854
- if (reason && reason.name === 'WorkspaceArchiveError') {
8855
- setDialog({ kind: 'sess-archive-confirm', sessionId: payload.id })
8856
- return
8857
- }
8858
- fail(reason)
8859
- })
8860
- }
8861
- else if (kind === 'session' && id === 'copy-id' && typeof ui.writeClipboard === 'function') {
8862
- // Official mirror: copied → success notice, anything else → the
8863
- // failure notice (official sessionIdInjected wiring).
8864
- Promise.resolve(ui.writeClipboard(payload.id)).then((copied) => {
8865
- setCopyNotice((prev) => ({ seq: (prev ? prev.seq : 0) + 1, ok: copied === true }))
8866
- }, () => {
8867
- setCopyNotice((prev) => ({ seq: (prev ? prev.seq : 0) + 1, ok: false }))
8868
- })
8869
- }
8870
- // Plain retitle back to the leaf: the row leaves its group and returns
8871
- // to the top level of its workspace (v0.18.0).
8872
- else if (kind === 'session' && id === 'move-out') {
8873
- const leaf = leafOf(String(payload.title || ''))
8874
- if (leaf !== payload.title) Promise.resolve().then(() => renameByUser(payload.id, leaf)).catch(fail)
9502
+ else if (kind === 'session') {
9503
+ // The shared dispatcher owns every session verb (see
9504
+ // pickSessionAction); an unknown id falls through silently.
9505
+ if (!pickSessionAction(payload, id)) return
8875
9506
  }
8876
9507
  }
8877
9508
 
@@ -9255,7 +9886,14 @@ window.__ModuleLoader__.load({
9255
9886
  disabled: flowBusy,
9256
9887
  }, icon('IconProjectAddOutline16')) : null,
9257
9888
  ),
9258
- E('div', { ref: treeRef, className: 'bw-tree', role: 'tree', 'aria-label': t('title') }, bodyRows),
9889
+ E('div', {
9890
+ ref: treeRef,
9891
+ className: 'bw-tree',
9892
+ role: 'tree',
9893
+ 'aria-label': t('title'),
9894
+ 'data-bw-density': density,
9895
+ ...(showCount ? { 'data-bw-count': '' } : {}),
9896
+ }, bodyRows),
9259
9897
  dialogElement,
9260
9898
  (ctx !== null && typeof ui.Menu === 'function') ? E(ui.Menu, {
9261
9899
  // Right-click opens the SAME menu surface the official ⋯ button
@@ -9540,6 +10178,25 @@ window.__ModuleLoader__.load({
9540
10178
  }
9541
10179
  return { component: seat.component, face: seat.face, t: seatT }
9542
10180
  },
10181
+ // The shipped session-row seat occupants, read off the ledger in a
10182
+ // LIST (same shape as scheduleSeatReader): one reader serves every
10183
+ // seat name (menu.item / row.action / session.row.hover).
10184
+ seatEntriesReader: (name) => {
10185
+ try {
10186
+ const entries = slots.entries(name)
10187
+ if (!Array.isArray(entries)) return []
10188
+ return entries.map((entry) => {
10189
+ if (!entry || !entry.component || typeof entry.inject !== 'function') return null
10190
+ let seatT = null
10191
+ if (entry.locale && ctx.locale && typeof ctx.locale.bind === 'function') {
10192
+ try { seatT = ctx.locale.bind(entry.locale) } catch (error) { seatT = null }
10193
+ }
10194
+ return { component: entry.component, face: entry.inject(), t: seatT }
10195
+ }).filter(Boolean)
10196
+ } catch (error) {
10197
+ return []
10198
+ }
10199
+ },
9543
10200
  startSession: (workspaceId) => { uiWorkspace.startSession(workspaceId) },
9544
10201
  open: (sessionId) => { uiWorkspace.openSession(sessionId) },
9545
10202
  searchSessions,