dsh-better-workspace 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dsh.plugin.json +1 -1
- package/icon.svg +5 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +10 -3
- package/src/client.js +501 -59
- package/src/index.js +43 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-better-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);添加工作区由官方目录流承载。Workspace tree for the DSH sidebar workspace list, nested by official disk folders with optional \"/\"-name grouping.",
|
|
6
6
|
"engines": {
|
package/icon.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" fill="none">
|
|
2
|
+
<path d="M4 10.5A2.5 2.5 0 0 1 6.5 8h6l2.4 2.8H29.5A2.5 2.5 0 0 1 32 13.3V26a2.5 2.5 0 0 1-2.5 2.5h-23A2.5 2.5 0 0 1 4 26V10.5z" stroke="#4493f8" stroke-width="1.7" stroke-linejoin="round"/>
|
|
3
|
+
<path d="M11 17.5h9M11 21.5h14" stroke="#4493f8" stroke-width="1.5" stroke-linecap="round"/>
|
|
4
|
+
<circle cx="24.5" cy="17.5" r="1.3" fill="#f0883e"/>
|
|
5
|
+
</svg>
|
package/locale/en.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "Better Workspace",
|
|
4
|
+
"description": "Upgrades the sidebar workspace list into a two-layer hierarchy tree (disk nesting by the official owningParentFolder semantics, optional name-slash grouping), explicit empty folders, appearance customization with live preview, and the official add-workspace flow for every backend."
|
|
5
|
+
}
|
|
6
|
+
}
|
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);添加工作区走官方目录流。Workspace tree for the DSH sidebar: disk-path nesting (official workspace-tree semantics), with optional \"/\"-name grouping (off by default); add-workspace rides the official directory flow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"main": "src/index.js",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": "./src/index.js",
|
|
16
|
-
"./client": "./src/client.js"
|
|
16
|
+
"./client": "./src/client.js",
|
|
17
|
+
"./locale/*.json": "./locale/*.json"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
|
19
20
|
"node": ">=18",
|
|
@@ -41,7 +42,9 @@
|
|
|
41
42
|
"dsh.plugin.json",
|
|
42
43
|
"README.md",
|
|
43
44
|
"README_EN.md",
|
|
44
|
-
"LICENSE"
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"locale",
|
|
47
|
+
"icon.svg"
|
|
45
48
|
],
|
|
46
49
|
"scripts": {
|
|
47
50
|
"test": "node --test tests/smoke.mjs",
|
|
@@ -64,5 +67,9 @@
|
|
|
64
67
|
"homepage": "https://github.com/KannaKuron/dsh-better-workspace",
|
|
65
68
|
"bugs": {
|
|
66
69
|
"url": "https://github.com/KannaKuron/dsh-better-workspace/issues"
|
|
70
|
+
},
|
|
71
|
+
"icon": "./icon.svg",
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
67
74
|
}
|
|
68
75
|
}
|
package/src/client.js
CHANGED
|
@@ -83,6 +83,10 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
'settings.workspaceSlash.hint': '关闭时工作区只按官方文件夹(磁盘目录)嵌套,标题原样显示;开启后标题里的 / 重新成为分组层级(组名/名字)。默认关闭,只影响工作区;会话分组另有独立开关。',
|
|
84
84
|
'settings.statusPulse': '状态呼吸灯',
|
|
85
85
|
'settings.statusPulse.hint': '被折叠藏起的状态灯(完成绿 / 运行蓝 / 待交互琥珀)沿层级向外冒泡:工作区与分组行以图标呼吸发光(颜色随状态,自定义过发光的标题一起呼吸),会话分组行显示呼吸状态灯;默认开启,可在此关闭。',
|
|
86
|
+
'settings.sessionMenu': "会话右键菜单",
|
|
87
|
+
'settings.sessionMenu.hint': "开启时右键点击会话行即可打开操作菜单(包含官方的置顶、归档、停止并归档等);关闭后改为在行尾悬停显示官方样式的 ⋯ 按钮,点击打开同一个菜单。默认开启。",
|
|
88
|
+
'settings.rowActions': "会话行悬停按钮",
|
|
89
|
+
'settings.rowActions.hint': "悬停会话行时在行尾显示置顶 / 归档快捷按钮,与官方行保持一致;关闭后这些动作只保留在菜单里。默认开启。",
|
|
86
90
|
'settings.appearance': '默认外观',
|
|
87
91
|
'settings.appearance.hint': '没有单独自定义过的行使用这套外观;字体描边默认开启——背景画面下不描边文字常常看不清。字体颜色留空即跟随主题。',
|
|
88
92
|
'settings.appearance.reset': '恢复默认外观',
|
|
@@ -213,6 +217,10 @@ window.__ModuleLoader__.load({
|
|
|
213
217
|
'settings.workspaceSlash.hint': 'Off: workspaces nest purely by the official folders (disk directories) and titles render whole. On: "/" in a title becomes a group level again ("group/name"). Off by default, workspaces only — session grouping has its own switch.',
|
|
214
218
|
'settings.statusPulse': 'Status breathing light',
|
|
215
219
|
'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.',
|
|
220
|
+
'settings.sessionMenu': "Session context menu",
|
|
221
|
+
'settings.sessionMenu.hint': "On: right-click a session row to open its action menu (including the official pin, archive, and stop-and-archive). Off: the row shows the official-style ⋯ button on hover instead, opening the same menu. On by default.",
|
|
222
|
+
'settings.rowActions': "Session row hover buttons",
|
|
223
|
+
'settings.rowActions.hint': "Show pin / archive quick buttons at the end of a hovered session row, matching the official rows. Off: these actions stay in the menu only. On by default.",
|
|
216
224
|
'settings.appearance': 'Default appearance',
|
|
217
225
|
'settings.appearance.hint': 'Rows that were never customized use this appearance; the outline is on by default — text without it is often unreadable over a background image. Leave the color empty to follow the theme.',
|
|
218
226
|
'settings.appearance.reset': 'Reset to default',
|
|
@@ -351,6 +359,10 @@ window.__ModuleLoader__.load({
|
|
|
351
359
|
'settings.workspaceSlash.hint': 'عند الإيقاف تتداخل مساحات العمل حسب المجلدات الرسمية (مجلدات القرص) فقط وتظهر العناوين كاملة؛ وعند التشغيل تعود "/" في العنوان مستوى تجميع («المجموعة/الاسم»). معطّل افتراضياً، ويخص مساحات العمل وحدها — لتجميع الجلسات مفتاحه المستقل.',
|
|
352
360
|
'settings.statusPulse': 'مؤشر حالة نابض',
|
|
353
361
|
'settings.statusPulse.hint': 'مؤشرات الحالة التي يخفيها الطيّ (مكتمل أخضر / قيد التشغيل أزرق / بانتظار تفاعل كهرماني) تتصاعد عبر المستويات: تصدر أسطر مساحات العمل والمجلدات وميضاً نابضاً للأيقونة بلون الحالة (وتنبض معها العناوين ذات التوهج المخصص)، وتعرض أسطر مجموعات الجلسات نقطة حالة نابضة؛ مفعّل افتراضياً ويمكن إيقافه هنا.',
|
|
362
|
+
'settings.sessionMenu': "قائمة النقر الأيمن للجلسة",
|
|
363
|
+
'settings.sessionMenu.hint': "عند التفعيل: انقر بزر الفأرة الأيمن على سطر الجلسة لفتح قائمة الإجراءات (بما في ذلك التثبيت والأرشفة الرسمية). عند الإيقاف: يظهر زر ⋯ بأسلوب رسمي عند تمرير المؤشر يفتح القائمة نفسها. مفعّل افتراضياً.",
|
|
364
|
+
'settings.rowActions': "أزرار تمرير سطر الجلسة",
|
|
365
|
+
'settings.rowActions.hint': "إظهار أزرار التثبيت / الأرشفة السريعة في نهاية سطر الجلسة عند تمرير المؤشر، مطابقة للأسطر الرسمية. عند الإيقاف: تبقى هذه الإجراءات في القائمة فقط. مفعّل افتراضياً.",
|
|
354
366
|
'settings.appearance': 'المظهر الافتراضي',
|
|
355
367
|
'settings.appearance.hint': 'تستخدم الأسطر غير المخصصة هذا المظهر؛ حدّ النص مفعّل افتراضياً — فبدونه يصعب قراءة النص فوق صورة خلفية. اترك اللون فارغاً لاتباع السمة.',
|
|
356
368
|
'settings.appearance.reset': 'استعادة المظهر الافتراضي',
|
|
@@ -481,6 +493,10 @@ window.__ModuleLoader__.load({
|
|
|
481
493
|
'settings.workspaceSlash.hint': 'Aus: Arbeitsbereiche verschachteln nur nach den offiziellen Ordnern (Verzeichnisse auf der Platte), Titel bleiben vollständig. An: "/" im Titel wird wieder eine Gruppenebene ("Gruppe/Name"). Standardmäßig aus, gilt nur für Arbeitsbereiche — Sitzungsgruppen haben einen eigenen Schalter.',
|
|
482
494
|
'settings.statusPulse': 'Status-Pulslicht',
|
|
483
495
|
'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.',
|
|
496
|
+
'settings.sessionMenu': "Sitzungs-Kontextmenü",
|
|
497
|
+
'settings.sessionMenu.hint': "Ein: Rechtsklick auf eine Sitzungszeile öffnet das Aktionsmenü (inklusive offiziellem Anheften, Archivieren und Stoppen-und-Archivieren). Aus: beim Daraufzeigen erscheint stattdessen der offizielle ⋯-Knopf am Zeilenende und öffnet dasselbe Menü. Standardmäßig ein.",
|
|
498
|
+
'settings.rowActions': "Sitzungszeilen-Schnellknöpfe",
|
|
499
|
+
'settings.rowActions.hint': "Zeigt beim Daraufzeigen Schnellknöpfe zum Anheften / Archivieren am Zeilenende – wie die offiziellen Zeilen. Aus: diese Aktionen bleiben nur im Menü. Standardmäßig ein.",
|
|
484
500
|
'settings.appearance': 'Standardaussehen',
|
|
485
501
|
'settings.appearance.hint': 'Zeilen ohne eigene Anpassung nutzen dieses Aussehen; die Textkontur ist standardmäßig an – ohne sie ist Text über einem Hintergrundbild oft kaum lesbar. Bleibt die Farbe leer, folgt sie dem Thema.',
|
|
486
502
|
'settings.appearance.reset': 'Standard wiederherstellen',
|
|
@@ -611,6 +627,10 @@ window.__ModuleLoader__.load({
|
|
|
611
627
|
'settings.workspaceSlash.hint': 'Désactivé : les espaces s\'imbriquent uniquement selon les dossiers officiels (répertoires du disque) et les titres restent entiers. Activé : le « / » d\'un titre redevient un niveau de groupe (« groupe/nom »). Désactivé par défaut, espaces uniquement — les groupes de sessions ont leur propre interrupteur.',
|
|
612
628
|
'settings.statusPulse': 'Voyant d\'état pulsant',
|
|
613
629
|
'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.',
|
|
630
|
+
'settings.sessionMenu': "Menu contextuel des sessions",
|
|
631
|
+
'settings.sessionMenu.hint': "Activé : un clic droit sur une ligne de session ouvre son menu d’actions (épinglage, archivage et arrêt-avec-archivage officiels inclus). Désactivé : la ligne affiche plutôt le bouton ⋯ officiel au survol, ouvrant le même menu. Activé par défaut.",
|
|
632
|
+
'settings.rowActions': "Boutons de survol des sessions",
|
|
633
|
+
'settings.rowActions.hint': "Affiche des boutons rapides d’épinglage / d’archivage en fin de ligne au survol, comme les lignes officielles. Désactivé : ces actions restent dans le menu uniquement. Activé par défaut.",
|
|
614
634
|
'settings.appearance': 'Apparence par défaut',
|
|
615
635
|
'settings.appearance.hint': 'Les lignes sans personnalisation utilisent cette apparence; le contour du texte est activé par défaut — sans lui, le texte reste souvent illisible sur une image de fond. Laissez la couleur vide pour suivre le thème.',
|
|
616
636
|
'settings.appearance.reset': 'Rétablir l\'apparence par défaut',
|
|
@@ -741,6 +761,10 @@ window.__ModuleLoader__.load({
|
|
|
741
761
|
'settings.workspaceSlash.hint': 'बंद: कार्यस्थान केवल आधिकारिक फ़ोल्डरों (डिस्क निर्देशिकाओं) के अनुसार नेस्ट होते हैं और शीर्षक पूरे दिखते हैं। चालू: शीर्षक में "/" फिर से समूह स्तर बन जाता है ("समूह/नाम")। डिफ़ॉल्ट रूप से बंद, केवल कार्यस्थानों पर लागू — सत्र समूह का अपना स्विच है।',
|
|
742
762
|
'settings.statusPulse': 'स्थिति की साँस लेती बत्ती',
|
|
743
763
|
'settings.statusPulse.hint': 'समेटने से छिपी स्थिति बत्तियाँ (पूर्ण हरी / चल रहा नीली / प्रतीक्षारत अंबर) पदानुक्रम में ऊपर उठती हैं: कार्यस्थान और फ़ोल्डर पंक्तियों का चिह्न स्थिति के रंग में साँस लेता है (अनुकूलित चमक वाले शीर्षक भी साथ साँस लेते हैं), सत्र समूह पंक्तियों पर साँस लेता स्थिति बिंदु दिखता है; डिफ़ॉल्ट रूप से चालू, यहाँ बंद किया जा सकता है।',
|
|
764
|
+
'settings.sessionMenu': "सेशन संदर्भ मेनू",
|
|
765
|
+
'settings.sessionMenu.hint': "चालू: सेशन पंक्ति पर राइट-क्लिक करने से उसका क्रिया मेनू खुलता है (आधिकारिक पिन, आर्काइव और रोककर-आर्काइव सहित)। बंद: पंक्ति पर होवर करने पर आधिकारिक शैली का ⋯ बटन दिखता है, जो वही मेनू खोलता है। डिफ़ॉल्ट रूप से चालू।",
|
|
766
|
+
'settings.rowActions': "सेशन पंक्ति होवर बटन",
|
|
767
|
+
'settings.rowActions.hint': "होवर करने पर सेशन पंक्ति के अंत में पिन / आर्काइव क्विक बटन दिखाता है, आधिकारिक पंक्तियों जैसा। बंद: ये क्रियाएँ केवल मेनू में रहती हैं। डिफ़ॉल्ट रूप से चालू।",
|
|
744
768
|
'settings.appearance': 'डिफ़ॉल्ट रूप',
|
|
745
769
|
'settings.appearance.hint': 'जिन पंक्तियों को अलग से अनुकूलित नहीं किया गया वे यह रूप इस्तेमाल करती हैं; पाठ की रूपरेखा डिफ़ॉल्ट रूप से चालू है — पृष्ठभूमि छवि पर बिना रूपरेखा वाला पाठ अक्सर पढ़ा नहीं जाता। रंग खाली छोड़ने पर वह थीम का अनुसरण करता है।',
|
|
746
770
|
'settings.appearance.reset': 'डिफ़ॉल्ट रूप पर लौटें',
|
|
@@ -871,6 +895,10 @@ window.__ModuleLoader__.load({
|
|
|
871
895
|
'settings.workspaceSlash.hint': 'Mati: ruang kerja bersarang hanya menurut folder resmi (direktori disk) dan judul tampil utuh. Nyala: "/" pada judul kembali menjadi tingkat grup ("grup/nama"). Default mati, hanya ruang kerja — pengelompokan sesi punya sakelarnya sendiri.',
|
|
872
896
|
'settings.statusPulse': 'Lampu status bernapas',
|
|
873
897
|
'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.',
|
|
898
|
+
'settings.sessionMenu': "Menu klik kanan sesi",
|
|
899
|
+
'settings.sessionMenu.hint': "Aktif: klik kanan pada baris sesi untuk membuka menu aksinya (termasuk pin, arsip, dan henti-dan-arsip resmi). Nonaktif: baris menampilkan tombol ⋯ gaya resmi saat dihover, membuka menu yang sama. Aktif secara bawaan.",
|
|
900
|
+
'settings.rowActions': "Tombol hover baris sesi",
|
|
901
|
+
'settings.rowActions.hint': "Menampilkan tombol cepat pin / arsip di ujung baris sesi saat dihover, mengikuti baris resmi. Nonaktif: aksi ini hanya ada di menu. Aktif secara bawaan.",
|
|
874
902
|
'settings.appearance': 'Tampilan bawaan',
|
|
875
903
|
'settings.appearance.hint': 'Baris yang belum disesuaikan memakai tampilan ini; garis luar teks aktif secara bawaan — tanpa itu teks sering tak terbaca di atas gambar latar. Biarkan warna kosong agar mengikuti tema.',
|
|
876
904
|
'settings.appearance.reset': 'Kembalikan tampilan bawaan',
|
|
@@ -1001,6 +1029,10 @@ window.__ModuleLoader__.load({
|
|
|
1001
1029
|
'settings.workspaceSlash.hint': 'Off: le aree di lavoro si annidano solo secondo le cartelle ufficiali (directory del disco) e i titoli restano interi. On: la "/" nel titolo torna a essere un livello di gruppo ("gruppo/nome"). Disattivato per impostazione predefinita, solo aree di lavoro — le sessioni hanno il proprio interruttore.',
|
|
1002
1030
|
'settings.statusPulse': 'Spia di stato pulsante',
|
|
1003
1031
|
'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.',
|
|
1032
|
+
'settings.sessionMenu': "Menu contestuale sessione",
|
|
1033
|
+
'settings.sessionMenu.hint': "Attivo: il clic destro su una riga di sessione apre il menu azioni (con pin, archiviazione e ferma-e-archivia ufficiali). Disattivo: la riga mostra il pulsante ⋯ ufficiale al passaggio, aprendo lo stesso menu. Attivo per impostazione predefinita.",
|
|
1034
|
+
'settings.rowActions': "Pulsanti al passaggio della sessione",
|
|
1035
|
+
'settings.rowActions.hint': "Mostra pulsanti rapidi di pin / archiviazione in fondo alla riga al passaggio, come le righe ufficiali. Disattivo: queste azioni restano solo nel menu. Attivo per impostazione predefinita.",
|
|
1004
1036
|
'settings.appearance': 'Aspetto predefinito',
|
|
1005
1037
|
'settings.appearance.hint': 'Le righe mai personalizzate usano questo aspetto; il contorno del testo è attivo per impostazione predefinita — senza di esso il testo è spesso illeggibile sopra un\'immagine di sfondo. Lascia il colore vuoto per seguire il tema.',
|
|
1006
1038
|
'settings.appearance.reset': 'Ripristina l\'aspetto predefinito',
|
|
@@ -1131,6 +1163,10 @@ window.__ModuleLoader__.load({
|
|
|
1131
1163
|
'settings.workspaceSlash.hint': 'オフ:ワークスペースは公式フォルダー(ディスクのディレクトリ)だけで入れ子になり、タイトルはそのまま表示されます。オン:タイトル内の「/」が再びグループ階層になります(「グループ/名前」)。既定はオフ、対象はワークスペースのみ — セッションのグループ化には専用のスイッチがあります。',
|
|
1132
1164
|
'settings.statusPulse': 'ステータス表示灯',
|
|
1133
1165
|
'settings.statusPulse.hint': '折りたたみで隠れたステータス表示(完了は緑 / 実行中は青 / 操作待ちは琥珀)が上位の階層へ伝わります:ワークスペースとフォルダーの行はアイコンがステータス色で明滅し(グローを設定したタイトルも連動),セッショングループの行には明滅するステータスドットが出ます;既定はオンで,ここでオフにできます。',
|
|
1166
|
+
'settings.sessionMenu': "セッション右クリックメニュー",
|
|
1167
|
+
'settings.sessionMenu.hint': "オン:セッション行を右クリックすると操作メニューを開きます(公式のピン留め・アーカイブ・停止してアーカイブを含む)。オフ:行にカーソルを合わせると公式スタイルの ⋯ ボタンが表示され、同じメニューを開きます。既定はオン。",
|
|
1168
|
+
'settings.rowActions': "セッション行のホバーボタン",
|
|
1169
|
+
'settings.rowActions.hint': "セッション行にカーソルを合わせると行末にピン / アーカイブのクイックボタンを表示し、公式の行と揃えます。オフ:これらの操作はメニュー内のみ。既定はオン。",
|
|
1134
1170
|
'settings.appearance': '既定の外観',
|
|
1135
1171
|
'settings.appearance.hint': '個別にカスタマイズしていない行に適用される外観です;文字の縁取りは既定でオンです——背景画像の上では縁取りのない文字が読みにくいためです。色を空にするとテーマに従います。',
|
|
1136
1172
|
'settings.appearance.reset': '既定の外観に戻す',
|
|
@@ -1261,6 +1297,10 @@ window.__ModuleLoader__.load({
|
|
|
1261
1297
|
'settings.workspaceSlash.hint': '끄면: 작업 공간은 공식 폴더(디스크 디렉터리)로만 중첩되고 제목은 그대로 표시됩니다. 켜면: 제목의 "/"가 다시 그룹 단계가 됩니다("그룹/이름"). 기본값은 끔이며 작업 공간에만 적용됩니다 — 세션 그룹화에는 별도 스위치가 있습니다.',
|
|
1262
1298
|
'settings.statusPulse': '상태 표시등',
|
|
1263
1299
|
'settings.statusPulse.hint': '접혀서 가려진 상태 표시(완료는 초록 / 실행은 파랑 / 대기는 호박색)가 상위 단계로 번집니다: 워크스페이스와 폴더 행은 아이콘이 상태 색으로 깜빡이고(글로우를 지정한 제목도 함께 깜빡임), 세션 그룹 행에는 깜빡이는 상태 점이 표시됩니다; 기본값은 켜짐이며 여기서 끌 수 있습니다.',
|
|
1300
|
+
'settings.sessionMenu': "세션 우클릭 메뉴",
|
|
1301
|
+
'settings.sessionMenu.hint': "켬: 세션 행을 우클릭하면 작업 메뉴가 열립니다(공식 고정, 보관, 중지 후 보관 포함). 끔: 행에 마우스를 올리면 공식 스타일의 ⋯ 버튼이 표시되어 같은 메뉴를 엽니다. 기본값은 켬.",
|
|
1302
|
+
'settings.rowActions': "세션 행 호버 버튼",
|
|
1303
|
+
'settings.rowActions.hint': "세션 행에 마우스를 올리면 행 끝에 고정 / 보관 빠른 버튼을 표시하여 공식 행과 일치시킵니다. 끔: 이 작업들은 메뉴에만 남습니다. 기본값은 켬.",
|
|
1264
1304
|
'settings.appearance': '기본 모양',
|
|
1265
1305
|
'settings.appearance.hint': '따로 지정하지 않은 행에 적용되는 모양입니다; 글자 외곽선은 기본으로 켜져 있습니다——배경 이미지 위에서는 외곽선 없는 글자가 잘 보이지 않기 때문입니다. 색을 비우면 테마를 따릅니다.',
|
|
1266
1306
|
'settings.appearance.reset': '기본 모양으로 되돌리기',
|
|
@@ -1391,6 +1431,10 @@ window.__ModuleLoader__.load({
|
|
|
1391
1431
|
'settings.workspaceSlash.hint': 'Uit: werkruimten nesten alleen volgens de officiële mappen (schijfmappen) en titels blijven volledig. Aan: "/" in een titel wordt weer een groepsniveau ("groep/naam"). Standaard uit, alleen werkruimten — sessiegroepen hebben hun eigen schakelaar.',
|
|
1392
1432
|
'settings.statusPulse': 'Pulserend statuslampje',
|
|
1393
1433
|
'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.',
|
|
1434
|
+
'settings.sessionMenu': "Sessie-contextmenu",
|
|
1435
|
+
'settings.sessionMenu.hint': "Aan: rechtermuisklik op een sessierij opent het actiemenu (inclusief officieel vastmaken, archiveren en stoppen-en-archiveren). Uit: bij aanwijzen verschijnt in plaats daarvan de officiële ⋯-knop aan het rij-einde, die hetzelfde menu opent. Standaard aan.",
|
|
1436
|
+
'settings.rowActions': "Sessierij-snelknoppen",
|
|
1437
|
+
'settings.rowActions.hint': "Toont snelknoppen voor vastmaken / archiveren aan het einde van een aangewezen sessierij, gelijk aan de officiële rijen. Uit: deze acties blijven alleen in het menu. Standaard aan.",
|
|
1394
1438
|
'settings.appearance': 'Standaarduiterlijk',
|
|
1395
1439
|
'settings.appearance.hint': 'Regels zonder eigen aanpassing gebruiken dit uiterlijk; de tekstomtrek staat standaard aan — zonder omtrek is tekst op een achtergrondafbeelding vaak slecht leesbaar. Laat de kleur leeg om het thema te volgen.',
|
|
1396
1440
|
'settings.appearance.reset': 'Standaarduiterlijk herstellen',
|
|
@@ -1521,6 +1565,10 @@ window.__ModuleLoader__.load({
|
|
|
1521
1565
|
'settings.workspaceSlash.hint': 'Wył.: obszary robocze zagnieżdżają się wyłącznie według oficjalnych folderów (katalogów dysku), a tytuły są pełne. Wł.: "/" w tytule znów staje się poziomem grupy ("grupa/nazwa"). Domyślnie wył., dotyczy tylko obszarów roboczych — grupy sesji mają własny przełącznik.',
|
|
1522
1566
|
'settings.statusPulse': 'Pulsująca lampka stanu',
|
|
1523
1567
|
'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ć.',
|
|
1568
|
+
'settings.sessionMenu': "Menu kontekstowe sesji",
|
|
1569
|
+
'settings.sessionMenu.hint': "Włączone: kliknięcie prawym przyciskiem wiersza sesji otwiera jego menu akcji (w tym oficjalne przypinanie, archiwizację i zatrzymanie z archiwizacją). Wyłączone: przy najechaniu wiersz pokazuje oficjalny przycisk ⋯ otwierający to samo menu. Domyślnie włączone.",
|
|
1570
|
+
'settings.rowActions': "Przyciski najechania wiersza sesji",
|
|
1571
|
+
'settings.rowActions.hint': "Pokazuje szybkie przyciski przypnij / archiwizuj na końcu najechanego wiersza sesji, jak w oficjalnych wierszach. Wyłączone: te akcje zostają tylko w menu. Domyślnie włączone.",
|
|
1524
1572
|
'settings.appearance': 'Domyślny wygląd',
|
|
1525
1573
|
'settings.appearance.hint': 'Wiersze bez własnych ustawień używają tego wyglądu; obrys tekstu jest domyślnie włączony — bez niego tekst na obrazie tła bywa nieczytelny. Puste pole koloru oznacza podążanie za motywem.',
|
|
1526
1574
|
'settings.appearance.reset': 'Przywróć domyślny wygląd',
|
|
@@ -1651,6 +1699,10 @@ window.__ModuleLoader__.load({
|
|
|
1651
1699
|
'settings.workspaceSlash.hint': 'Desligado: as áreas de trabalho aninham-se apenas pelas pastas oficiais (diretórios do disco) e os títulos ficam inteiros. Ligado: a "/" no título volta a ser um nível de grupo ("grupo/nome"). Desligado por padrão, só áreas de trabalho — os grupos de sessões têm o próprio interruptor.',
|
|
1652
1700
|
'settings.statusPulse': 'Luz de status pulsante',
|
|
1653
1701
|
'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.',
|
|
1702
|
+
'settings.sessionMenu': "Menu de contexto da sessão",
|
|
1703
|
+
'settings.sessionMenu.hint': "Ligado: clicar com o botão direito numa linha de sessão abre o menu de ações (incluindo fixar, arquivar e parar-e-arquivar oficiais). Desligado: a linha mostra o botão ⋯ oficial ao passar o cursor, abrindo o mesmo menu. Ligado por predefinição.",
|
|
1704
|
+
'settings.rowActions': "Botões de passagem da sessão",
|
|
1705
|
+
'settings.rowActions.hint': "Mostra botões rápidos de fixar / arquivar no fim da linha ao passar o cursor, como nas linhas oficiais. Desligado: essas ações ficam apenas no menu. Ligado por predefinição.",
|
|
1654
1706
|
'settings.appearance': 'Aparência padrão',
|
|
1655
1707
|
'settings.appearance.hint': 'Linhas sem personalização usam esta aparência; o contorno do texto vem ativado por padrão — sem ele, o texto costuma ficar ilegível sobre uma imagem de fundo. Deixe a cor vazia para seguir o tema.',
|
|
1656
1708
|
'settings.appearance.reset': 'Restaurar aparência padrão',
|
|
@@ -1781,6 +1833,10 @@ window.__ModuleLoader__.load({
|
|
|
1781
1833
|
'settings.workspaceSlash.hint': 'Выкл.: рабочие области вкладываются только по официальным папкам (каталогам диска), заголовки показываются целиком. Вкл.: «/» в заголовке снова становится уровнем группы («группа/имя»). По умолчанию выкл., только рабочие области — у групп сессий свой переключатель.',
|
|
1782
1834
|
'settings.statusPulse': 'Пульсирующий индикатор состояния',
|
|
1783
1835
|
'settings.statusPulse.hint': 'Индикаторы, скрытые сворачиванием (завершено — зелёный / выполняется — синий / ожидает — янтарный), всплывают вверх по иерархии: строки рабочих областей и папок пульсируют значком в цвете состояния (заголовки со своим свечением пульсируют вместе с ними), строки групп сессий показывают пульсирующую точку; включено по умолчанию, здесь можно отключить.',
|
|
1836
|
+
'settings.sessionMenu': "Контекстное меню сессии",
|
|
1837
|
+
'settings.sessionMenu.hint': "Вкл: правый клик по строке сессии открывает меню действий (включая официальные закрепление, архивацию и «остановить и архивировать»). Выкл: при наведении в строке появляется официальная кнопка ⋯, открывающая то же меню. Включено по умолчанию.",
|
|
1838
|
+
'settings.rowActions': "Кнопки наведения строки сессии",
|
|
1839
|
+
'settings.rowActions.hint': "Показывает быстрые кнопки закрепить / архивировать в конце строки при наведении, как в официальных строках. Выкл: эти действия остаются только в меню. Включено по умолчанию.",
|
|
1784
1840
|
'settings.appearance': 'Оформление по умолчанию',
|
|
1785
1841
|
'settings.appearance.hint': 'Строки без своей настройки используют это оформление; обводка текста включена по умолчанию — без неё текст на фоновом изображении часто нечитаем. Пустой цвет означает следование теме.',
|
|
1786
1842
|
'settings.appearance.reset': 'Вернуть оформление по умолчанию',
|
|
@@ -1911,6 +1967,10 @@ window.__ModuleLoader__.load({
|
|
|
1911
1967
|
'settings.workspaceSlash.hint': 'Av: arbetsytor kapslas in enbart efter de officiella mapparna (diskmappar) och titlar visas hela. På: "/" i en titel blir åter en gruppnivå ("grupp/namn"). Av som standard, endast arbetsytor — sessionsgrupper har sin egen växlare.',
|
|
1912
1968
|
'settings.statusPulse': 'Pulserande statuslampa',
|
|
1913
1969
|
'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.',
|
|
1970
|
+
'settings.sessionMenu': "Sessionens högerklicksmeny",
|
|
1971
|
+
'settings.sessionMenu.hint': "På: högerklick på en sessionsrad öppnar dess åtgärdsmeny (inklusive officiell fästning, arkivering och stoppa-och-arkivera). Av: raden visar istället den officiella ⋯-knappen vid hovring, som öppnar samma meny. På som standard.",
|
|
1972
|
+
'settings.rowActions': "Hovringsknappar för sessionsraden",
|
|
1973
|
+
'settings.rowActions.hint': "Visar snabbknappar för fäst / arkivera i radens slut vid hovring, som de officiella raderna. Av: åtgärderna finns bara i menyn. På som standard.",
|
|
1914
1974
|
'settings.appearance': 'Standardutseende',
|
|
1915
1975
|
'settings.appearance.hint': 'Rader utan egen anpassning använder detta utseende; textkonturen är på som standard — utan den är texten ofta oläslig ovanpå en bakgrundsbild. Lämna färgen tom för att följa temat.',
|
|
1916
1976
|
'settings.appearance.reset': 'Återställ standardutseende',
|
|
@@ -2041,6 +2101,10 @@ window.__ModuleLoader__.load({
|
|
|
2041
2101
|
'settings.workspaceSlash.hint': 'ปิด: พื้นที่ทำงานซ้อนกันตามโฟลเดอร์ทางการ (ไดเรกทอรีบนดิสก์) เท่านั้น และชื่อเรื่องแสดงเต็ม ปิดเป็นค่าเริ่มต้น ใช้กับพื้นที่ทำงานเท่านั้น — การจัดกลุ่มเซสชันมีสวิตช์แยก',
|
|
2042
2102
|
'settings.statusPulse': 'ไฟสถานะหายใจ',
|
|
2043
2103
|
'settings.statusPulse.hint': 'ไฟสถานะที่ถูกย่อซ่อนไว้ (เสร็จแล้วสีเขียว / กำลังทำงานสีน้ำเงิน / รอตอบสนองสีเหลืองอำพัน) จะลอยขึ้นตามลำดับชั้น: แถวพื้นที่ทำงานและโฟลเดอร์จะให้ไอคอนหายใจเป็นสีตามสถานะ (ชื่อที่มีการเรืองแสงกำหนดเองก็หายใจตาม) แถวกลุ่มเซสชันจะแสดงจุดสถานะที่หายใจ เปิดไว้เป็นค่าเริ่มต้น ปิดได้ที่นี่',
|
|
2104
|
+
'settings.sessionMenu': "เมนูคลิกขวาของเซสชัน",
|
|
2105
|
+
'settings.sessionMenu.hint': "เปิด: คลิกขวาที่แถวเซสชันเพื่อเปิดเมนูการกระทำ (รวมปักหมุด, เก็บถาวร และหยุด-แล้ว-เก็บถาวรแบบทางการ) ปิด: เมื่อชี้ที่แถวจะแสดงปุ่ม ⋯ สไตล์ทางการเปิดเมนูเดียวกันแทน เปิดตามค่าเริ่มต้น",
|
|
2106
|
+
'settings.rowActions': "ปุ่มเมื่อชี้แถวเซสชัน",
|
|
2107
|
+
'settings.rowActions.hint': "แสดงปุ่มด่วนปักหมุด / เก็บถาวรท้ายแถวเมื่อชี้ ให้ตรงกับแถวทางการ ปิด: การกระทำเหล่านี้มีเฉพาะในเมนู เปิดตามค่าเริ่มต้น",
|
|
2044
2108
|
'settings.appearance': 'รูปลักษณ์เริ่มต้น',
|
|
2045
2109
|
'settings.appearance.hint': 'แถวที่ยังไม่ได้กำหนดเองจะใช้รูปลักษณ์นี้ เส้นขอบตัวอักษรเปิดไว้เป็นค่าเริ่มต้น — บนภาพพื้นหลัง ตัวอักษรที่ไม่มีเส้นขอบมักอ่านยาก เว้นสีว่างไว้เพื่อตามธีม',
|
|
2046
2110
|
'settings.appearance.reset': 'คืนค่ารูปลักษณ์เริ่มต้น',
|
|
@@ -2171,6 +2235,10 @@ window.__ModuleLoader__.load({
|
|
|
2171
2235
|
'settings.workspaceSlash.hint': 'Kapalı: çalışma alanları yalnızca resmî klasörlere (disk dizinleri) göre iç içe geçer ve başlıklar tam görünür. Açık: başlıktaki "/" yeniden bir grup düzeyi olur ("grup/ad"). Varsayılan kapalı, yalnızca çalışma alanları — oturum gruplamasının kendi anahtarı var.',
|
|
2172
2236
|
'settings.statusPulse': 'Nefes alan durum ışığı',
|
|
2173
2237
|
'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.',
|
|
2238
|
+
'settings.sessionMenu': "Oturum sağ tık menüsü",
|
|
2239
|
+
'settings.sessionMenu.hint': "Açık: oturum satırına sağ tıklayınca eylem menüsü açılır (resmî sabitleme, arşivleme ve durdurup-arşivleme dahil). Kapalı: satırın üzerine gelince resmî ⋯ düğmesi görünür ve aynı menüyü açar. Varsayılan açık.",
|
|
2240
|
+
'settings.rowActions': "Oturum satırı düğmeleri",
|
|
2241
|
+
'settings.rowActions.hint': "Üzerine gelince satır sonunda sabitle / arşivle hızlı düğmelerini gösterir, resmî satırlarla aynı. Kapalı: bu eylemler yalnızca menüde kalır. Varsayılan açık.",
|
|
2174
2242
|
'settings.appearance': 'Varsayılan görünüm',
|
|
2175
2243
|
'settings.appearance.hint': 'Özel olarak uyarlanmamış satırlar bu görünümü kullanır; metin konturu varsayılan olarak açıktır — arka plan görseli üzerinde kontursuz metin çoğu zaman okunmaz. Rengi boş bırakırsanız temayı izler.',
|
|
2176
2244
|
'settings.appearance.reset': 'Varsayılan görünüme dön',
|
|
@@ -2301,6 +2369,10 @@ window.__ModuleLoader__.load({
|
|
|
2301
2369
|
'settings.workspaceSlash.hint': 'Tắt: không gian làm việc lồng nhau chỉ theo thư mục chính thức (thư mục trên đĩa) và tiêu đề hiển thị nguyên vẹn. Bật: "/" trong tiêu đề lại trở thành một cấp nhóm ("nhóm/tên"). Mặc định tắt, chỉ áp dụng cho không gian làm việc — nhóm phiên có công tắc riêng.',
|
|
2302
2370
|
'settings.statusPulse': 'Đèn trạng thái nhấp nháy',
|
|
2303
2371
|
'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.',
|
|
2372
|
+
'settings.sessionMenu': "Menu chuột phải phiên",
|
|
2373
|
+
'settings.sessionMenu.hint': "Bật: nhấp chuột phải vào dòng phiên để mở menu thao tác (bao gồm ghim, lưu trữ và dừng-rồi-lưu trữ chính thức). Tắt: dòng hiển thị nút ⋯ kiểu chính thức khi di chuột, mở cùng menu đó. Bật theo mặc định.",
|
|
2374
|
+
'settings.rowActions': "Nút hover dòng phiên",
|
|
2375
|
+
'settings.rowActions.hint': "Hiện nút nhanh ghim / lưu trữ ở cuối dòng khi di chuột, giống các dòng chính thức. Tắt: các thao tác này chỉ còn trong menu. Bật theo mặc định.",
|
|
2304
2376
|
'settings.appearance': 'Giao diện mặc định',
|
|
2305
2377
|
'settings.appearance.hint': 'Những hàng chưa tùy chỉnh riêng dùng giao diện này; viền chữ mặc định bật — trên ảnh nền, chữ không viền thường khó đọc. Để trống màu để theo chủ đề.',
|
|
2306
2378
|
'settings.appearance.reset': 'Khôi phục giao diện mặc định',
|
|
@@ -2431,6 +2503,10 @@ window.__ModuleLoader__.load({
|
|
|
2431
2503
|
'settings.workspaceSlash.hint': '關閉時工作區只按官方資料夾(磁碟目錄)嵌套,標題原樣顯示;開啟後標題裡的 / 重新成為分組層級(組名/名字)。預設關閉;只影響工作區,對話分組另有獨立開關。',
|
|
2432
2504
|
'settings.statusPulse': '狀態呼吸燈',
|
|
2433
2505
|
'settings.statusPulse.hint': '被摺疊遮住嘅狀態燈(完成綠 / 運行藍 / 待互動琥珀)會沿住層級向外冒泡:工作區同分組行會以圖示呼吸發光(顏色跟狀態,自訂過發光嘅標題一齊呼吸),對話分組行就會顯示呼吸狀態燈;預設開啟,可以喺度閂咗佢。',
|
|
2506
|
+
'settings.sessionMenu': "會話右鍵選單",
|
|
2507
|
+
'settings.sessionMenu.hint': "開啟時右鍵點擊會話行即可開啟操作選單(包含官方的置頂、封存、停止並封存等);關閉後改為在行尾懸停顯示官方樣式的 ⋯ 按鈕,點擊開啟同一個選單。預設開啟。",
|
|
2508
|
+
'settings.rowActions': "會話行懸停按鈕",
|
|
2509
|
+
'settings.rowActions.hint': "懸停會話行時在行尾顯示置頂 / 封存快捷按鈕,與官方行保持一致;關閉後這些動作只保留在選單裡。預設開啟。",
|
|
2434
2510
|
'settings.appearance': '預設外觀',
|
|
2435
2511
|
'settings.appearance.hint': '未單獨自訂過嘅行會用呢套外觀;字體描邊預設開啟——有背景圖嗰陣唔描邊嘅字經常睇唔清。字體顏色留空就會跟主題。',
|
|
2436
2512
|
'settings.appearance.reset': '還原預設外觀',
|
|
@@ -2561,6 +2637,10 @@ window.__ModuleLoader__.load({
|
|
|
2561
2637
|
'settings.workspaceSlash.hint': '關閉時工作區只按官方資料夾(磁碟目錄)嵌套,標題原樣顯示;開啟後標題裡的 / 重新成為分組層級(組名/名字)。預設關閉;只影響工作區,對話分組另有獨立開關。',
|
|
2562
2638
|
'settings.statusPulse': '狀態呼吸燈',
|
|
2563
2639
|
'settings.statusPulse.hint': '被摺疊遮住嘅狀態燈(完成綠 / 運行藍 / 待互動琥珀)會沿住層級向外冒泡:工作區同分組行會以圖示呼吸發光(顏色跟狀態,自訂過發光嘅標題一齊呼吸),對話分組行就會顯示呼吸狀態燈;預設開啟,可以喺度閂咗佢。',
|
|
2640
|
+
'settings.sessionMenu': "會話右鍵選單",
|
|
2641
|
+
'settings.sessionMenu.hint': "開啟時右鍵點擊會話行即可開啟操作選單(包含官方的置頂、歸檔、停止並歸檔等);關閉後改為在行尾懸停顯示官方樣式的 ⋯ 按鈕,點擊開啟同一個選單。預設開啟。",
|
|
2642
|
+
'settings.rowActions': "會話行懸停按鈕",
|
|
2643
|
+
'settings.rowActions.hint': "懸停會話行時在行尾顯示置頂 / 歸檔快捷按鈕,與官方行保持一致;關閉後這些動作只保留在選單裡。預設開啟。",
|
|
2564
2644
|
'settings.appearance': '預設外觀',
|
|
2565
2645
|
'settings.appearance.hint': '未單獨自訂過嘅行會用呢套外觀;字體描邊預設開啟——有背景圖嗰陣唔描邊嘅字經常睇唔清。字體顏色留空就會跟主題。',
|
|
2566
2646
|
'settings.appearance.reset': '還原預設外觀',
|
|
@@ -2691,6 +2771,10 @@ window.__ModuleLoader__.load({
|
|
|
2691
2771
|
'settings.workspaceSlash.hint': '關閉時工作區只按官方資料夾(磁碟目錄)巢狀,標題原樣顯示;開啟後標題裡的 / 重新成為群組層級(群組名/名字)。預設關閉;只影響工作區,工作階段分組另有獨立開關。',
|
|
2692
2772
|
'settings.statusPulse': '狀態呼吸燈',
|
|
2693
2773
|
'settings.statusPulse.hint': '被收合藏起的狀態燈(完成綠 / 執行藍 / 待互動琥珀)會沿著層級向外冒出:工作區與群組列以圖示呼吸發光(顏色隨狀態,自訂過發光的標題一起呼吸),工作階段群組列則顯示呼吸狀態燈;預設開啟,可在此關閉。',
|
|
2774
|
+
'settings.sessionMenu': "會話右鍵選單",
|
|
2775
|
+
'settings.sessionMenu.hint': "開啟時右鍵點擊會話列即可開啟動作選單(包含官方的釘選、封存、停止並封存等);關閉後改為在列尾懸停顯示官方樣式的 ⋯ 按鈕,點擊開啟同一個選單。預設開啟。",
|
|
2776
|
+
'settings.rowActions': "會話列懸停按鈕",
|
|
2777
|
+
'settings.rowActions.hint': "懸停會話列時在列尾顯示釘選 / 封存快捷按鈕,與官方列保持一致;關閉後這些動作只保留在選單裡。預設開啟。",
|
|
2694
2778
|
'settings.appearance': '預設外觀',
|
|
2695
2779
|
'settings.appearance.hint': '未個別自訂過的列會使用這套外觀;字型外框預設開啟——在背景圖片上沒有外框的文字常常看不清楚。字型顏色留空即跟隨佈景主題。',
|
|
2696
2780
|
'settings.appearance.reset': '還原預設外觀',
|
|
@@ -2863,12 +2947,22 @@ window.__ModuleLoader__.load({
|
|
|
2863
2947
|
if (ui[name]) return name
|
|
2864
2948
|
const aliases = ICON_ALIASES[name]
|
|
2865
2949
|
if (aliases) for (const alias of aliases) if (ui[alias]) return alias
|
|
2950
|
+
// Icon-set generation fallback (dsh 0.1.7): the primitives renamed the
|
|
2951
|
+
// whole product set from pixel-size suffixes (…16/…14/…20) to stroke
|
|
2952
|
+
// weights (…Regular/…Medium). Strip the trailing size and try the new
|
|
2953
|
+
// spellings so pre-0.1.7 names keep rendering on current hosts — the
|
|
2954
|
+
// header/menus call icon() with historical names directly.
|
|
2955
|
+
const base = typeof name === 'string' ? name.replace(/\d+$/, '') : ''
|
|
2956
|
+
if (base !== '' && base !== name) {
|
|
2957
|
+
if (ui[base + 'Regular']) return base + 'Regular'
|
|
2958
|
+
if (ui[base + 'Medium']) return base + 'Medium'
|
|
2959
|
+
}
|
|
2866
2960
|
return ''
|
|
2867
2961
|
}
|
|
2868
2962
|
|
|
2869
2963
|
/** Render a primitives icon by name; unknown names degrade to null, never crash. */
|
|
2870
2964
|
const icon = (name, size) => {
|
|
2871
|
-
const C = ui[name]
|
|
2965
|
+
const C = ui[resolveIconName(name)]
|
|
2872
2966
|
return C ? E(C, { size: size || 16 }) : null
|
|
2873
2967
|
}
|
|
2874
2968
|
|
|
@@ -2891,6 +2985,35 @@ window.__ModuleLoader__.load({
|
|
|
2891
2985
|
return p.kind || p.status || p.type || 'pending'
|
|
2892
2986
|
}
|
|
2893
2987
|
|
|
2988
|
+
// Official visiblePendingKind (dsh 0.1.7 tree.ts): only the three
|
|
2989
|
+
// navigation kinds light the warning lamp; anything else a domain
|
|
2990
|
+
// publishes stays off the sidebar.
|
|
2991
|
+
const visiblePendingKind = (kind) => (kind === 'approval' || kind === 'plan-review' || kind === 'question' ? kind : undefined)
|
|
2992
|
+
|
|
2993
|
+
// In-place flavor for arrays built by the caller (same bucket order).
|
|
2994
|
+
const pinnedPartitionInPlace = (rows) => {
|
|
2995
|
+
const arranged = pinnedPartition(rows)
|
|
2996
|
+
if (arranged !== rows) rows.splice(0, rows.length, ...arranged)
|
|
2997
|
+
return rows
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
// Official pinned-block partition (sectionMembers): placeholders first,
|
|
3001
|
+
// then the non-archived pinned rows, then everything else — preserving
|
|
3002
|
+
// each caller-side order inside every bucket.
|
|
3003
|
+
const pinnedPartition = (rows) => {
|
|
3004
|
+
if (!rows || rows.length === 0) return rows
|
|
3005
|
+
const blanks = []
|
|
3006
|
+
const pins = []
|
|
3007
|
+
const rest = []
|
|
3008
|
+
for (const row of rows) {
|
|
3009
|
+
if (row.pinned) pins.push(row)
|
|
3010
|
+
else if (row.blank) blanks.push(row)
|
|
3011
|
+
else rest.push(row)
|
|
3012
|
+
}
|
|
3013
|
+
if (pins.length === 0) return rows
|
|
3014
|
+
return [...blanks, ...pins, ...rest]
|
|
3015
|
+
}
|
|
3016
|
+
|
|
2894
3017
|
const sessionTitleOf = (summary, t, rememberedTitle) => {
|
|
2895
3018
|
if (!summary) return ''
|
|
2896
3019
|
if (summary.blank) return t('session.new')
|
|
@@ -2906,14 +3029,21 @@ window.__ModuleLoader__.load({
|
|
|
2906
3029
|
}
|
|
2907
3030
|
|
|
2908
3031
|
/**
|
|
2909
|
-
* Official visibility rule (dsh tree.ts sessionVisible): subagent
|
|
2910
|
-
* live in their parent's catalog,
|
|
2911
|
-
*
|
|
3032
|
+
* Official visibility rule (dsh 0.1.7 tree.ts sessionVisible): subagent
|
|
3033
|
+
* children live in their parent's catalog, a blank row is the provisional
|
|
3034
|
+
* New Session of the current selection, and archived rows follow the
|
|
3035
|
+
* archived filter — 'default' hides them, 'show' mixes them into their
|
|
3036
|
+
* kept slots, 'only' restricts the view to them. Pre-filter hosts only
|
|
3037
|
+
* ever pass 'default' (the historical hide-archived behavior).
|
|
2912
3038
|
*/
|
|
2913
|
-
const sessionVisible = (summary, current, archivedSet) =>
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
&& (
|
|
3039
|
+
const sessionVisible = (summary, current, archivedSet, archivedFilter) => {
|
|
3040
|
+
if (!summary || summary.origin === 'subagent') return false
|
|
3041
|
+
if (summary.blank && summary.id !== current) return false
|
|
3042
|
+
const archived = archivedSet && archivedSet.has(summary.id)
|
|
3043
|
+
if (archivedFilter === 'show') return true
|
|
3044
|
+
if (archivedFilter === 'only') return !!archived
|
|
3045
|
+
return !archived
|
|
3046
|
+
}
|
|
2917
3047
|
|
|
2918
3048
|
/**
|
|
2919
3049
|
* The "open" session, across BOTH host generations.
|
|
@@ -3388,6 +3518,8 @@ window.__ModuleLoader__.load({
|
|
|
3388
3518
|
'.bw-row-actions{flex:none;display:none;align-items:center;gap:2px}',
|
|
3389
3519
|
'.bw-row:hover .bw-row-actions{display:flex}',
|
|
3390
3520
|
'.bw-row:hover .bw-row-time,.bw-row:hover .bw-row-count,.bw-row:hover .bw-schedule-badge{display:none}',
|
|
3521
|
+
'.bw-row-archived{opacity:.55}',
|
|
3522
|
+
'.bw-pin-indicator{flex:none;display:inline-flex;align-items:center;color:var(--dsw-alias-brand-primary,#5b8def);margin:0 4px}',
|
|
3391
3523
|
'.bw-dot{flex:none;width:6px;height:6px;border-radius:50%;background:transparent}',
|
|
3392
3524
|
'.bw-session-row{font-size:12.5px;color:var(--dsw-alias-label-secondary,#b8b8b8);min-height:26px}',
|
|
3393
3525
|
'.bw-sgroup-row{font-size:12.5px;color:var(--dsw-alias-label-tertiary,#9a9a9a);min-height:24px}',
|
|
@@ -3509,7 +3641,7 @@ window.__ModuleLoader__.load({
|
|
|
3509
3641
|
.split('/').map((s) => s.trim()).filter((s) => s !== '').join('/')
|
|
3510
3642
|
|
|
3511
3643
|
const createViewStore = () => storeKit.defineStore({
|
|
3512
|
-
init: () => ({ expanded: {}, sessionsExpanded: {}, sessionGroups: {}, sessionOrder: {}, prefs: { compactChains: true }, styling: {}, groupBy: 'workspace-tree', orderBy: 'manual', sessionTitleSlash: true, workspaceTitleSlash: false, folders: { ws: {}, sess: {} } }),
|
|
3644
|
+
init: () => ({ expanded: {}, sessionsExpanded: {}, sessionGroups: {}, sessionOrder: {}, prefs: { compactChains: true }, styling: {}, groupBy: 'workspace-tree', orderBy: 'manual', archivedFilter: 'default', sessionTitleSlash: true, workspaceTitleSlash: false, folders: { ws: {}, sess: {} } }),
|
|
3513
3645
|
// NOTE: hydration REPLACES the state with the persisted whole value —
|
|
3514
3646
|
// init defaults never merge. Every action must tolerate a missing key
|
|
3515
3647
|
// (states persisted by older plugin versions lack sessionGroups), and
|
|
@@ -3558,6 +3690,7 @@ window.__ModuleLoader__.load({
|
|
|
3558
3690
|
// (tree + both slash layers on + manual host order).
|
|
3559
3691
|
setGroupBy: (d, value) => { d.groupBy = value },
|
|
3560
3692
|
setOrderBy: (d, value) => { d.orderBy = value },
|
|
3693
|
+
setArchivedFilter: (d, value) => { d.archivedFilter = value === 'show' || value === 'only' ? value : 'default' },
|
|
3561
3694
|
setSessionTitleSlash: (d, value) => { d.sessionTitleSlash = value !== false },
|
|
3562
3695
|
setWorkspaceTitleSlash: (d, value) => { d.workspaceTitleSlash = value !== false },
|
|
3563
3696
|
// Explicit (possibly EMPTY) groups, v0.17.0. A group has always been a
|
|
@@ -3683,6 +3816,10 @@ window.__ModuleLoader__.load({
|
|
|
3683
3816
|
// simply never mirror and never sync, which is exactly the pre-0.9.5
|
|
3684
3817
|
// browser-local behavior.
|
|
3685
3818
|
let prefsScopeRef = null
|
|
3819
|
+
// 'settingsScope' (dsh <= 0.1.6, the shared ~/.dsh/settings.yaml home —
|
|
3820
|
+
// the only world where CROSS-DEVICE sync makes sense) or 'configForms'
|
|
3821
|
+
// (dsh >= 0.1.7, per-profile patch persistence). null = browser-local.
|
|
3822
|
+
let prefsScopeVia = null
|
|
3686
3823
|
const HOST_SNAP_UNAVAILABLE = { status: 'unavailable', value: undefined }
|
|
3687
3824
|
const hostPrefsOf = (snap) => (snap && snap.status === 'ready' && snap.value && typeof snap.value === 'object'
|
|
3688
3825
|
? snap.value
|
|
@@ -3842,7 +3979,7 @@ window.__ModuleLoader__.load({
|
|
|
3842
3979
|
)
|
|
3843
3980
|
}
|
|
3844
3981
|
|
|
3845
|
-
function ConfirmDialog({ title, body, onConfirm, onClose, t }) {
|
|
3982
|
+
function ConfirmDialog({ title, body, confirmLabel, onConfirm, onClose, t }) {
|
|
3846
3983
|
return E(ui.Modal, {
|
|
3847
3984
|
open: true,
|
|
3848
3985
|
onClose,
|
|
@@ -3850,7 +3987,7 @@ window.__ModuleLoader__.load({
|
|
|
3850
3987
|
title,
|
|
3851
3988
|
footer: E('div', { className: 'bw-modal-actions' },
|
|
3852
3989
|
E(BTN, { variant: 'outline', onClick: onClose }, t('cancel')),
|
|
3853
|
-
E(BTN, { variant: 'primary', onClick: onConfirm }, t('confirm')),
|
|
3990
|
+
E(BTN, { variant: 'primary', onClick: onConfirm }, confirmLabel || t('confirm')),
|
|
3854
3991
|
),
|
|
3855
3992
|
}, E('div', { className: 'bw-modal-body' }, E('div', { className: 'bw-hint' }, body)), StyleNode())
|
|
3856
3993
|
}
|
|
@@ -4333,7 +4470,7 @@ window.__ModuleLoader__.load({
|
|
|
4333
4470
|
return s === 'ongoing' ? null : s
|
|
4334
4471
|
}
|
|
4335
4472
|
|
|
4336
|
-
function SessionRow({ node, depth, current, onOpen, onContextMenu, now, dropHalf, dragEvents, custStyle, breathing, t }) {
|
|
4473
|
+
function SessionRow({ node, depth, current, onOpen, onContextMenu, now, dropHalf, dragEvents, custStyle, breathing, sessionActions, t }) {
|
|
4337
4474
|
const state = sessionStateOf(node)
|
|
4338
4475
|
const status = state === null ? null : {
|
|
4339
4476
|
state,
|
|
@@ -4343,12 +4480,61 @@ window.__ModuleLoader__.load({
|
|
|
4343
4480
|
? (node.running ? t('status.running') : t('status.subagents', { n: node.subagents }))
|
|
4344
4481
|
: t('status.completed'),
|
|
4345
4482
|
}
|
|
4483
|
+
// Official mirror surface (dsh 0.1.7 Rows.tsx): pinned rows carry the
|
|
4484
|
+
// pin marker; archived rows render grayed and never open; the hover
|
|
4485
|
+
// strip hosts the "..." trigger and the pin/archive quick buttons.
|
|
4486
|
+
// The strip replaces the time cell on hover exactly like the official
|
|
4487
|
+
// row, and its clicks never reach the row (stopPropagation).
|
|
4488
|
+
const ot = sessionActions ? sessionActions.ot : null
|
|
4489
|
+
const official = (key, params) => {
|
|
4490
|
+
if (typeof ot !== 'function') return null
|
|
4491
|
+
const value = ot(key, params)
|
|
4492
|
+
return typeof value === 'string' && value !== '' && value !== key ? value : null
|
|
4493
|
+
}
|
|
4494
|
+
const archivedTitle = official('row.archived')
|
|
4495
|
+
const pinnedTitle = official('row.pinned')
|
|
4496
|
+
const strip = !node.blank && sessionActions && (sessionActions.trigger || sessionActions.quick)
|
|
4497
|
+
? E('span', { className: 'bw-row-actions', onClick: (e) => e.stopPropagation() },
|
|
4498
|
+
sessionActions.trigger ? E('button', {
|
|
4499
|
+
type: 'button',
|
|
4500
|
+
className: 'bw-icon-btn',
|
|
4501
|
+
'aria-label': official('actions.session.aria', { name: node.leaf || node.title }) || '...',
|
|
4502
|
+
title: sessionActions.triggerTitle,
|
|
4503
|
+
onClick: (e) => sessionActions.openMenu(node, e),
|
|
4504
|
+
}, icon('IconEllipsisOutline16', 15)) : null,
|
|
4505
|
+
sessionActions.quick && sessionActions.pin && !node.archived ? E('button', {
|
|
4506
|
+
type: 'button',
|
|
4507
|
+
className: 'bw-icon-btn',
|
|
4508
|
+
'aria-label': official(node.pinned ? 'menu.unpinSession' : 'menu.pinSession') || '',
|
|
4509
|
+
title: official(node.pinned ? 'actions.unpin' : 'actions.pin') || undefined,
|
|
4510
|
+
onClick: (e) => { e.stopPropagation(); sessionActions.pin(node.id, !node.pinned) },
|
|
4511
|
+
}, icon(node.pinned ? 'IconPinFillRegular' : 'IconPinOutlineRegular', 14)) : null,
|
|
4512
|
+
sessionActions.quick && sessionActions.archive ? E('button', {
|
|
4513
|
+
type: 'button',
|
|
4514
|
+
className: 'bw-icon-btn',
|
|
4515
|
+
'aria-label': official(node.archived ? 'menu.unarchiveSession' : 'menu.archiveSession') || '',
|
|
4516
|
+
title: official(node.archived ? 'actions.unarchive' : 'actions.archive') || undefined,
|
|
4517
|
+
onClick: (e) => { e.stopPropagation(); sessionActions.archive(node.id, !node.archived) },
|
|
4518
|
+
}, icon(node.archived ? 'IconUnarchiveOutlineRegular' : 'IconArchiveOutline20', 14)) : null,
|
|
4519
|
+
)
|
|
4520
|
+
: null
|
|
4346
4521
|
return E('div', {
|
|
4347
|
-
className: cls('bw-row', 'bw-session-row', current && 'bw-row-current',
|
|
4522
|
+
className: cls('bw-row', 'bw-session-row', current && 'bw-row-current',
|
|
4523
|
+
node.archived && 'bw-row-archived',
|
|
4524
|
+
dropHalf === 'before' && 'bw-drop-before', dropHalf === 'after' && 'bw-drop-after'),
|
|
4348
4525
|
style: { paddingLeft: 8 + depth * 12, ...(custStyle || {}) },
|
|
4349
|
-
onClick: () =>
|
|
4526
|
+
onClick: () => {
|
|
4527
|
+
if (node.archived) {
|
|
4528
|
+
// Official semantics (guardedOpen): an archived row is not
|
|
4529
|
+
// openable — the click explains instead of navigating.
|
|
4530
|
+
if (sessionActions && typeof sessionActions.explainArchived === 'function') sessionActions.explainArchived()
|
|
4531
|
+
return
|
|
4532
|
+
}
|
|
4533
|
+
onOpen(node.id)
|
|
4534
|
+
},
|
|
4350
4535
|
onContextMenu: onContextMenu,
|
|
4351
4536
|
role: 'treeitem',
|
|
4537
|
+
'aria-description': node.archived ? archivedTitle || undefined : undefined,
|
|
4352
4538
|
...(dragEvents || {}),
|
|
4353
4539
|
},
|
|
4354
4540
|
E('span', { className: 'bw-row-icon', title: status ? status.title : undefined },
|
|
@@ -4365,6 +4551,8 @@ window.__ModuleLoader__.load({
|
|
|
4365
4551
|
? E('span', { className: 'bw-schedule-badge', role: 'img', 'aria-label': t('schedule.active'), title: t('schedule.active') }, icon('IconAlarmClockOutline16', 14))
|
|
4366
4552
|
: null,
|
|
4367
4553
|
E('span', { className: 'bw-row-time' }, timeLabel(node.updatedAt, now, t)),
|
|
4554
|
+
node.pinned ? E('span', { className: 'bw-pin-indicator', role: 'img', 'aria-label': pinnedTitle || undefined, title: pinnedTitle || undefined }, icon('IconPinFillRegular', 13)) : null,
|
|
4555
|
+
strip,
|
|
4368
4556
|
)
|
|
4369
4557
|
}
|
|
4370
4558
|
|
|
@@ -4405,9 +4593,25 @@ window.__ModuleLoader__.load({
|
|
|
4405
4593
|
* name layer). The primitives Menu is feature-probed like every
|
|
4406
4594
|
* component — without it the button simply never renders.
|
|
4407
4595
|
*/
|
|
4408
|
-
function ViewOptionsMenu({ groupBy, orderBy, sessionSlash, onPick, onToggle, t }) {
|
|
4596
|
+
function ViewOptionsMenu({ groupBy, orderBy, sessionSlash, archivedFilter, onPick, onToggle, onFilterPick, ot, t }) {
|
|
4409
4597
|
const [open, setOpen] = React.useState(false)
|
|
4410
4598
|
if (typeof ui.Menu !== 'function') return null
|
|
4599
|
+
// Archived filter (dsh 0.1.7 official ViewOptionsMenu mirror): the
|
|
4600
|
+
// section rides the OFFICIAL dictionary — when the host's 'workspace'
|
|
4601
|
+
// dictionary lacks the keys (pre-filter hosts) the whole section stays
|
|
4602
|
+
// hidden, matching that host's native menu. ot(key) returns the key
|
|
4603
|
+
// itself for a missing entry, which is the detection.
|
|
4604
|
+
const official = (key) => {
|
|
4605
|
+
if (typeof ot !== 'function') return null
|
|
4606
|
+
const value = ot(key)
|
|
4607
|
+
return typeof value === 'string' && value !== '' && value !== key ? value : null
|
|
4608
|
+
}
|
|
4609
|
+
const filterLabel = official('filterBy.label')
|
|
4610
|
+
const showLabel = official('viewOptions.showArchived')
|
|
4611
|
+
const onlyLabel = official('viewOptions.onlyArchived')
|
|
4612
|
+
const hasFilter = filterLabel !== null && showLabel !== null && onlyLabel !== null
|
|
4613
|
+
&& typeof onFilterPick === 'function'
|
|
4614
|
+
const filter = archivedFilter === 'show' || archivedFilter === 'only' ? archivedFilter : 'default'
|
|
4411
4615
|
return E(ui.Menu, {
|
|
4412
4616
|
open,
|
|
4413
4617
|
onClose: () => { setOpen(false) },
|
|
@@ -4420,6 +4624,14 @@ window.__ModuleLoader__.load({
|
|
|
4420
4624
|
{ type: 'label', id: 'bw-order-by', text: t('viewOptions.orderBy') },
|
|
4421
4625
|
{ id: 'manual', label: t('viewOptions.manualOrder') },
|
|
4422
4626
|
{ id: 'updated', label: t('viewOptions.updatedOrder') },
|
|
4627
|
+
...(hasFilter ? [
|
|
4628
|
+
{ type: 'separator', id: 'bw-filter-sep' },
|
|
4629
|
+
{ type: 'label', id: 'bw-filter-by', text: filterLabel },
|
|
4630
|
+
// Both items are exclusive toggles (official semantics):
|
|
4631
|
+
// re-picking the selected one returns to the default view.
|
|
4632
|
+
{ id: 'show-archived', label: showLabel, icon: icon('IconArchiveOutline20', 16) },
|
|
4633
|
+
{ id: 'only-archived', label: onlyLabel, icon: icon('IconArchiveCheckOutlineRegular', 16) },
|
|
4634
|
+
] : []),
|
|
4423
4635
|
{ type: 'separator', id: 'bw-slash-sep' },
|
|
4424
4636
|
// The workspace name layer moved OUT of this menu in v0.15: it is
|
|
4425
4637
|
// opt-in now and lives in the settings card, where "why is my tree
|
|
@@ -4427,10 +4639,14 @@ window.__ModuleLoader__.load({
|
|
|
4427
4639
|
{ id: 'session-slash', label: t('viewOptions.sessionSlash'), disabled: groupBy === 'flat' },
|
|
4428
4640
|
],
|
|
4429
4641
|
selectedIds: [groupBy, orderBy]
|
|
4430
|
-
.concat(sessionSlash ? ['session-slash'] : [])
|
|
4642
|
+
.concat(sessionSlash ? ['session-slash'] : [])
|
|
4643
|
+
.concat(filter === 'show' ? ['show-archived'] : [])
|
|
4644
|
+
.concat(filter === 'only' ? ['only-archived'] : []),
|
|
4431
4645
|
onSelect: (id) => {
|
|
4432
4646
|
if (id === 'workspace' || id === 'workspace-tree' || id === 'flat') { onPick('groupBy', id); setOpen(false); return }
|
|
4433
4647
|
if (id === 'manual' || id === 'updated') { onPick('orderBy', id); setOpen(false); return }
|
|
4648
|
+
if (id === 'show-archived') { onFilterPick(filter === 'show' ? 'default' : 'show'); setOpen(false); return }
|
|
4649
|
+
if (id === 'only-archived') { onFilterPick(filter === 'only' ? 'default' : 'only'); setOpen(false); return }
|
|
4434
4650
|
if (id === 'session-slash') { onToggle('sessionTitleSlash', !sessionSlash); return }
|
|
4435
4651
|
},
|
|
4436
4652
|
align: 'end',
|
|
@@ -4478,6 +4694,18 @@ window.__ModuleLoader__.load({
|
|
|
4478
4694
|
// filters them out (see ICON_PICKER_CHOICES).
|
|
4479
4695
|
'IconPaperPlaneOutline14', 'IconShieldOutline16', 'IconPlanOutline14',
|
|
4480
4696
|
'IconWrapLinesOutline16', 'IconCompactOutline16',
|
|
4697
|
+
// dsh 0.1.7 generation (resolveIconName filters them out on older
|
|
4698
|
+
// hosts): the renamed set added pin/archive-check/tree/list glyphs the
|
|
4699
|
+
// native browser now ships. Historical names above resolve through the
|
|
4700
|
+
// Regular/Medium generation fallback, so both spellings coexist here.
|
|
4701
|
+
'IconPinOutlineRegular', 'IconPinFillRegular', 'IconSlidersTwoOutlineRegular',
|
|
4702
|
+
'IconArchiveCheckOutlineRegular', 'IconUnarchiveOutlineRegular',
|
|
4703
|
+
'IconWorkspaceTreeOutlineRegular', 'IconFlatListOutlineRegular',
|
|
4704
|
+
'IconChevronsUpDownOutlineRegular', 'IconGaugeOutlineRegular',
|
|
4705
|
+
'IconInfoOutlineRegular', 'IconWarningTriangleOutlineRegular',
|
|
4706
|
+
'IconCompareSplitOutlineRegular', 'IconPluginPinwheelOutlineRegular',
|
|
4707
|
+
'IconMicrophoneOutlineRegular', 'IconSendOutlineRegular',
|
|
4708
|
+
'IconTreeCornerRegular', 'IconDeliverDocRegular', 'IconWrapFillRegular',
|
|
4481
4709
|
]
|
|
4482
4710
|
|
|
4483
4711
|
/**
|
|
@@ -4767,6 +4995,8 @@ window.__ModuleLoader__.load({
|
|
|
4767
4995
|
const localStyling = useStore ? (useStore(s => s.styling) || {}) : {}
|
|
4768
4996
|
const compactChains = prefs.compactChains !== false
|
|
4769
4997
|
const statusPulse = prefs.statusPulse !== false
|
|
4998
|
+
const sessionMenu = prefs.sessionMenu !== false
|
|
4999
|
+
const rowActions = prefs.rowActions !== false
|
|
4770
5000
|
// Opt-in workspace name layer (v0.15): the same tri-state read the tree
|
|
4771
5001
|
// uses — see workspaceSlashOf. Deliberately NOT a `prefs` entry: it rides
|
|
4772
5002
|
// the view store's top-level key next to groupBy/orderBy and stays
|
|
@@ -4851,6 +5081,30 @@ window.__ModuleLoader__.load({
|
|
|
4851
5081
|
}, E('span', { className: 'bw-switch-thumb' })),
|
|
4852
5082
|
),
|
|
4853
5083
|
E('div', { className: 'bw-hint' }, t('settings.statusPulse.hint')),
|
|
5084
|
+
E('div', { className: 'bw-setting-row', style: { marginTop: 10 } },
|
|
5085
|
+
E('div', { className: 'bw-setting-label' }, t('settings.sessionMenu')),
|
|
5086
|
+
E('button', {
|
|
5087
|
+
type: 'button',
|
|
5088
|
+
role: 'switch',
|
|
5089
|
+
'aria-checked': sessionMenu,
|
|
5090
|
+
'aria-label': t('settings.sessionMenu'),
|
|
5091
|
+
className: cls('bw-switch', sessionMenu && 'bw-switch-on'),
|
|
5092
|
+
onClick: () => { setPref('sessionMenu', !sessionMenu) },
|
|
5093
|
+
}, E('span', { className: 'bw-switch-thumb' })),
|
|
5094
|
+
),
|
|
5095
|
+
E('div', { className: 'bw-hint' }, t('settings.sessionMenu.hint')),
|
|
5096
|
+
E('div', { className: 'bw-setting-row', style: { marginTop: 10 } },
|
|
5097
|
+
E('div', { className: 'bw-setting-label' }, t('settings.rowActions')),
|
|
5098
|
+
E('button', {
|
|
5099
|
+
type: 'button',
|
|
5100
|
+
role: 'switch',
|
|
5101
|
+
'aria-checked': rowActions,
|
|
5102
|
+
'aria-label': t('settings.rowActions'),
|
|
5103
|
+
className: cls('bw-switch', rowActions && 'bw-switch-on'),
|
|
5104
|
+
onClick: () => { setPref('rowActions', !rowActions) },
|
|
5105
|
+
}, E('span', { className: 'bw-switch-thumb' })),
|
|
5106
|
+
),
|
|
5107
|
+
E('div', { className: 'bw-hint' }, t('settings.rowActions.hint')),
|
|
4854
5108
|
E('div', { className: 'bw-setting-label', style: { marginTop: 16 } }, t('settings.appearance')),
|
|
4855
5109
|
E('div', { className: 'bw-hint' }, t('settings.appearance.hint')),
|
|
4856
5110
|
E('div', { className: 'bw-appearance-box' },
|
|
@@ -4864,19 +5118,24 @@ window.__ModuleLoader__.load({
|
|
|
4864
5118
|
E(BTN, { variant: 'outline', onClick: () => setAppearance(DEFAULT_APPEARANCE) }, t('settings.appearance.reset')),
|
|
4865
5119
|
),
|
|
4866
5120
|
),
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
5121
|
+
// Manual cross-device sync exists only where a SHARED host home does
|
|
5122
|
+
// (dsh <= 0.1.6 settings.yaml; both surfaces read one file). On
|
|
5123
|
+
// dsh >= 0.1.7 settings persist per profile, so the section hides.
|
|
5124
|
+
prefsScopeVia === 'settingsScope' ? E(React.Fragment, null,
|
|
5125
|
+
E('div', { className: 'bw-setting-label', style: { marginTop: 16 } }, t('sync.title')),
|
|
5126
|
+
E('div', { className: 'bw-hint' }, t('sync.desc')),
|
|
5127
|
+
E('div', { className: 'bw-sync-modes' },
|
|
5128
|
+
modeButton('overwrite', t('sync.mode.overwrite')),
|
|
5129
|
+
modeButton('merge', t('sync.mode.merge')),
|
|
5130
|
+
),
|
|
5131
|
+
E('div', { className: 'bw-sync-actions' },
|
|
5132
|
+
syncActionButton(t(IS_DESKTOP_SURFACE ? 'sync.pull.web' : 'sync.pull.desktop'), onPull, true, !scopeLive || snap.status === 'loading'),
|
|
5133
|
+
syncActionButton(t('sync.push'), onPush, false, !scopeLive),
|
|
5134
|
+
),
|
|
5135
|
+
syncMsg !== null ? E('div', { className: 'bw-hint' }, syncMsg)
|
|
5136
|
+
: (scopeLive ? null : E('div', { className: 'bw-hint' }, t('sync.off'))),
|
|
5137
|
+
scopeLive && snap.status === 'loading' ? E('div', { className: 'bw-hint' }, t('sync.loading')) : null,
|
|
5138
|
+
) : null,
|
|
4880
5139
|
)
|
|
4881
5140
|
}
|
|
4882
5141
|
|
|
@@ -4928,10 +5187,10 @@ window.__ModuleLoader__.load({
|
|
|
4928
5187
|
function BetterBrowser(props) {
|
|
4929
5188
|
const {
|
|
4930
5189
|
wide, expandSidebar,
|
|
4931
|
-
useSessions, useSessionPendingInteraction, useWorkspaces,
|
|
5190
|
+
useSessions, useSessionPendingInteraction, useSessionStatus, useWorkspaces,
|
|
4932
5191
|
useStore, actions,
|
|
4933
5192
|
startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace,
|
|
4934
|
-
archiveSession, createWorkspace,
|
|
5193
|
+
archiveSession, createWorkspace, pinSession, unpinSession, unarchiveSession,
|
|
4935
5194
|
insertWorkspaceBefore, insertSessionBefore,
|
|
4936
5195
|
useDirectoryFlow,
|
|
4937
5196
|
pickDirectory, listDirectory, createDirectory,
|
|
@@ -4947,8 +5206,20 @@ window.__ModuleLoader__.load({
|
|
|
4947
5206
|
const items = useWorkspaces(s => s.items)
|
|
4948
5207
|
const phase = useWorkspaces(s => s.phase)
|
|
4949
5208
|
const archivedSessionIds = useWorkspaces(s => s.archivedSessionIds) || []
|
|
5209
|
+
// Registry-global pin set (dsh 0.1.7): absent on older hosts, which
|
|
5210
|
+
// simply never see pinned rows or pin actions.
|
|
5211
|
+
const pinnedSessionIds = useWorkspaces(s => s.pinnedSessionIds) || []
|
|
4950
5212
|
const list = useSessions(s => s)
|
|
5213
|
+
// Session UI status, BOTH eras: dsh 0.1.7 replaced the pending-only
|
|
5214
|
+
// hook with the unified status map (running / pendingInteraction /
|
|
5215
|
+
// completionUnread). Prefer it when bound; the legacy hook stays the
|
|
5216
|
+
// pre-0.1.7 source, and summary fields fill whatever neither carries.
|
|
4951
5217
|
const pending = useSessionPendingInteraction ? useSessionPendingInteraction(s => s) : null
|
|
5218
|
+
const statusMap = typeof useSessionStatus === 'function' ? useSessionStatus(s => s) : null
|
|
5219
|
+
const statusOf = (id) => {
|
|
5220
|
+
if (!statusMap || typeof statusMap.get !== 'function') return undefined
|
|
5221
|
+
return statusMap.get(id)
|
|
5222
|
+
}
|
|
4952
5223
|
const expandedMap = useStore ? (useStore(s => s.expanded) || {}) : {}
|
|
4953
5224
|
const sessionsExpandedMap = useStore ? (useStore(s => s.sessionsExpanded) || {}) : {}
|
|
4954
5225
|
const sessionGroupsMap = useStore ? (useStore(s => s.sessionGroups) || {}) : {}
|
|
@@ -4975,7 +5246,16 @@ window.__ModuleLoader__.load({
|
|
|
4975
5246
|
const shared = makeSharedWrites(actions, stylingMap)
|
|
4976
5247
|
const compactChains = prefsMap.compactChains !== false
|
|
4977
5248
|
const statusPulse = prefsMap.statusPulse !== false
|
|
5249
|
+
// Session-action surface gates (v0.20.0): the right-click menu and the
|
|
5250
|
+
// official-style hover strip can each be turned off; defaults follow
|
|
5251
|
+
// the plugin's historical behavior (right-click on, quick buttons on).
|
|
5252
|
+
const sessionMenuEnabled = prefsMap.sessionMenu !== false
|
|
5253
|
+
const rowActionsEnabled = prefsMap.rowActions !== false
|
|
5254
|
+
// Archived-row visibility (v0.20.0, official ArchivedFilter mirror);
|
|
5255
|
+
// persisted snapshots from older plugin versions lack the key.
|
|
5256
|
+
const archivedFilter = useStore ? (useStore(s => s.archivedFilter) || 'default') : 'default'
|
|
4978
5257
|
const archivedSet = React.useMemo(() => new Set(archivedSessionIds), [archivedSessionIds])
|
|
5258
|
+
const pinnedSet = React.useMemo(() => new Set(pinnedSessionIds), [pinnedSessionIds])
|
|
4979
5259
|
const subCounts = React.useMemo(() => subagentRunningCounts(list ? list.byId : {}), [list ? list.byId : null])
|
|
4980
5260
|
// Current session across both host generations (see mainSessionIdOf).
|
|
4981
5261
|
// `current` belongs in the deps: 0.1.5-rc.x switches it without
|
|
@@ -5201,21 +5481,25 @@ window.__ModuleLoader__.load({
|
|
|
5201
5481
|
if (list && Array.isArray(list.ids)) {
|
|
5202
5482
|
for (const id of list.ids) {
|
|
5203
5483
|
const summary = list.byId[id]
|
|
5204
|
-
if (accounted.has(id) || !sessionVisible(summary, currentId, archivedSet)) continue
|
|
5484
|
+
if (accounted.has(id) || !sessionVisible(summary, currentId, archivedSet, archivedFilter)) continue
|
|
5485
|
+
const st = statusOf(id)
|
|
5205
5486
|
ungrouped.push({
|
|
5206
5487
|
id,
|
|
5207
5488
|
title: sessionTitleOf(summary, t, rememberedTitleOf(id)),
|
|
5208
5489
|
leaf: sessionTitleOf(summary, t, rememberedTitleOf(id)),
|
|
5209
5490
|
blank: !!summary.blank,
|
|
5210
|
-
running: !!summary.running,
|
|
5211
|
-
completed: summary.completed === true,
|
|
5491
|
+
running: (st && st.running === true) || !!summary.running,
|
|
5492
|
+
completed: summary.completed === true || (st && st.completionUnread === true),
|
|
5212
5493
|
hasActiveSchedule: hasActiveScheduleOf(summary),
|
|
5213
5494
|
subagents: subCounts.get(id) || 0,
|
|
5214
5495
|
updatedAt: summary.updatedAt || 0,
|
|
5215
|
-
pending: pendingKindOf(pending, id),
|
|
5496
|
+
pending: visiblePendingKind(st && st.pendingInteraction && st.pendingInteraction.kind) || pendingKindOf(pending, id),
|
|
5497
|
+
pinned: pinnedSet.has(id) && !archivedSet.has(id),
|
|
5498
|
+
archived: archivedSet.has(id),
|
|
5216
5499
|
})
|
|
5217
5500
|
}
|
|
5218
5501
|
ungrouped.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
5502
|
+
pinnedPartitionInPlace(ungrouped)
|
|
5219
5503
|
}
|
|
5220
5504
|
|
|
5221
5505
|
// While a workspace drag is active, single-child chains render UNCOMPRESSED:
|
|
@@ -5252,18 +5536,21 @@ window.__ModuleLoader__.load({
|
|
|
5252
5536
|
const rows = []
|
|
5253
5537
|
for (const id of workspace.sessionIds || []) {
|
|
5254
5538
|
const summary = list && list.byId ? list.byId[id] : undefined
|
|
5255
|
-
if (!sessionVisible(summary, currentId, archivedSet)) continue
|
|
5539
|
+
if (!sessionVisible(summary, currentId, archivedSet, archivedFilter)) continue
|
|
5540
|
+
const st = statusOf(id)
|
|
5256
5541
|
rows.push({
|
|
5257
5542
|
id,
|
|
5258
5543
|
title: sessionTitleOf(summary, t, rememberedTitleOf(id)),
|
|
5259
5544
|
leaf: sessionTitleOf(summary, t, rememberedTitleOf(id)),
|
|
5260
5545
|
blank: !!summary.blank,
|
|
5261
|
-
running: !!summary.running,
|
|
5262
|
-
completed: summary.completed === true,
|
|
5546
|
+
running: (st && st.running === true) || !!summary.running,
|
|
5547
|
+
completed: summary.completed === true || (st && st.completionUnread === true),
|
|
5263
5548
|
hasActiveSchedule: hasActiveScheduleOf(summary),
|
|
5264
5549
|
subagents: subCounts.get(id) || 0,
|
|
5265
5550
|
updatedAt: summary.updatedAt || 0,
|
|
5266
|
-
pending: pendingKindOf(pending, id),
|
|
5551
|
+
pending: visiblePendingKind(st && st.pendingInteraction && st.pendingInteraction.kind) || pendingKindOf(pending, id),
|
|
5552
|
+
pinned: pinnedSet.has(id) && !archivedSet.has(id),
|
|
5553
|
+
archived: archivedSet.has(id),
|
|
5267
5554
|
})
|
|
5268
5555
|
}
|
|
5269
5556
|
// Browser-local reorder fallback (dsh 0.1.6-alpha.1 stopped injecting
|
|
@@ -5284,10 +5571,10 @@ window.__ModuleLoader__.load({
|
|
|
5284
5571
|
const at = sorted.indexOf(pin)
|
|
5285
5572
|
if (at > 0) { sorted.splice(at, 1); sorted.unshift(pin) }
|
|
5286
5573
|
}
|
|
5287
|
-
return sorted
|
|
5574
|
+
return pinnedPartition(sorted)
|
|
5288
5575
|
}
|
|
5289
5576
|
const local = typeof insertSessionBefore === 'function' ? [] : sessionOrderOf(workspace.workspaceId)
|
|
5290
|
-
if (local.length === 0) return rows
|
|
5577
|
+
if (local.length === 0) return pinnedPartition(rows)
|
|
5291
5578
|
const remaining = new Map(rows.map((row) => [row.id, row]))
|
|
5292
5579
|
const out = []
|
|
5293
5580
|
for (const id of local) {
|
|
@@ -5295,7 +5582,7 @@ window.__ModuleLoader__.load({
|
|
|
5295
5582
|
if (row) { out.push(row); remaining.delete(id) }
|
|
5296
5583
|
}
|
|
5297
5584
|
for (const row of rows) if (remaining.has(row.id)) out.push(row)
|
|
5298
|
-
return out
|
|
5585
|
+
return pinnedPartition(out)
|
|
5299
5586
|
}
|
|
5300
5587
|
|
|
5301
5588
|
// Search flattens BOTH layers depth-first: every workspace (top level,
|
|
@@ -5383,6 +5670,60 @@ window.__ModuleLoader__.load({
|
|
|
5383
5670
|
setCtx({ kind, payload, x: e.clientX, y: e.clientY })
|
|
5384
5671
|
}
|
|
5385
5672
|
|
|
5673
|
+
// Session-action surface (v0.20.0, official mirror): the right-click
|
|
5674
|
+
// menu, the hover "..." trigger, and the pin/archive quick buttons all
|
|
5675
|
+
// drive the same calls the official row entries make — pin/unpin and
|
|
5676
|
+
// unarchive go straight to the uiWorkspace service; archive raises the
|
|
5677
|
+
// stop-and-archive confirmation when the Host refuses a running
|
|
5678
|
+
// session (WorkspaceArchiveError, workspace/session-active). Copy rides
|
|
5679
|
+
// the official 'workspace' dictionary (auto-rewording), with graceful
|
|
5680
|
+
// degradation on hosts that lack a key.
|
|
5681
|
+
const officialText = (key, params, fallback) => {
|
|
5682
|
+
const value = typeof officialT === 'function' ? officialT(key, params) : null
|
|
5683
|
+
return typeof value === 'string' && value !== '' && value !== key ? value : fallback
|
|
5684
|
+
}
|
|
5685
|
+
const pinAvailable = typeof pinSession === 'function' && typeof unpinSession === 'function'
|
|
5686
|
+
const unarchiveAvailable = typeof unarchiveSession === 'function'
|
|
5687
|
+
const sessionActions = {
|
|
5688
|
+
ot: officialT,
|
|
5689
|
+
// Right-click is the default trigger; turning the setting off moves
|
|
5690
|
+
// the trigger to the official-style "..." hover button.
|
|
5691
|
+
trigger: !sessionMenuEnabled,
|
|
5692
|
+
quick: rowActionsEnabled,
|
|
5693
|
+
pin: pinAvailable
|
|
5694
|
+
? (id, pinning) => {
|
|
5695
|
+
Promise.resolve().then(() => (pinning ? pinSession : unpinSession)(id)).catch((reason) => {
|
|
5696
|
+
fail(officialText(pinning ? 'toast.pinFailed' : 'toast.unpinFailed', undefined, reason instanceof Error ? reason.message : String(reason || 'pin failed')))
|
|
5697
|
+
})
|
|
5698
|
+
}
|
|
5699
|
+
: undefined,
|
|
5700
|
+
archive: typeof archiveSession === 'function'
|
|
5701
|
+
? (id, archiving) => {
|
|
5702
|
+
if (!archiving) {
|
|
5703
|
+
if (!unarchiveAvailable) return
|
|
5704
|
+
Promise.resolve().then(() => unarchiveSession(id)).catch((reason) => { console.warn('session unarchive rejected:', reason) })
|
|
5705
|
+
return
|
|
5706
|
+
}
|
|
5707
|
+
Promise.resolve().then(() => archiveSession(id)).catch((reason) => {
|
|
5708
|
+
// Official refusal flow: running work refuses the plain archive
|
|
5709
|
+
// (WorkspaceArchiveError / workspace/session-active) and the
|
|
5710
|
+
// confirmation offers to stop it first. Anything else reports
|
|
5711
|
+
// through the error modal.
|
|
5712
|
+
if (reason && reason.name === 'WorkspaceArchiveError') {
|
|
5713
|
+
setDialog({ kind: 'sess-archive-confirm', sessionId: id })
|
|
5714
|
+
return
|
|
5715
|
+
}
|
|
5716
|
+
fail(reason)
|
|
5717
|
+
})
|
|
5718
|
+
}
|
|
5719
|
+
: undefined,
|
|
5720
|
+
openMenu: (node, e) => openCtx('session', node, e),
|
|
5721
|
+
explainArchived: () => {
|
|
5722
|
+
const text = officialText('toast.archivedNotOpenable', undefined, '') || officialText('row.archived', undefined, '')
|
|
5723
|
+
if (text) setErrorText(text)
|
|
5724
|
+
},
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5386
5727
|
/* ----------------------- group move helpers --------------------- */
|
|
5387
5728
|
// Grouping is a NAME PROJECTION (v0.12.0 retired explicit empty
|
|
5388
5729
|
// groups): a "group" is nothing but the slash-prefix of a title, so
|
|
@@ -5884,6 +6225,13 @@ window.__ModuleLoader__.load({
|
|
|
5884
6225
|
// the same thing.
|
|
5885
6226
|
const target = flat.find(s => s.id === targetSessionId)
|
|
5886
6227
|
const nextTitle = sessionSlash && target ? titleMovedToGroup(source.title, target.title) : null
|
|
6228
|
+
// Official pinned-block rule: a pinned row reorders only among pinned
|
|
6229
|
+
// rows. A cross-block drop that would be a pure reorder is ignored;
|
|
6230
|
+
// group moves keep working (the pin fronts the row in its new group).
|
|
6231
|
+
if (target && nextTitle === null) {
|
|
6232
|
+
const sourceRow = flat.find(s => s.id === source.sessionId)
|
|
6233
|
+
if (sourceRow && !!sourceRow.pinned !== !!target.pinned) return
|
|
6234
|
+
}
|
|
5887
6235
|
const reorder = () => {
|
|
5888
6236
|
if (typeof insertSessionBefore === 'function') {
|
|
5889
6237
|
return (anchor !== undefined ? insertSessionBefore(workspaceId, source.sessionId, anchor) : insertSessionBefore(workspaceId, source.sessionId))
|
|
@@ -6115,9 +6463,10 @@ window.__ModuleLoader__.load({
|
|
|
6115
6463
|
now,
|
|
6116
6464
|
onOpen: (id) => open(id),
|
|
6117
6465
|
onContextMenu: (e) => openCtx('session', session, e),
|
|
6118
|
-
dropHalf: workspaceId ? sessDropHalf(session.id) : null,
|
|
6119
|
-
dragEvents: workspaceId ? sessionDragEvents(session, workspaceId) : undefined,
|
|
6466
|
+
dropHalf: workspaceId && !session.archived ? sessDropHalf(session.id) : null,
|
|
6467
|
+
dragEvents: workspaceId && !session.archived ? sessionDragEvents(session, workspaceId) : undefined,
|
|
6120
6468
|
custStyle: rowStyleOf('session:' + session.id),
|
|
6469
|
+
sessionActions,
|
|
6121
6470
|
breathing: statusPulse,
|
|
6122
6471
|
t,
|
|
6123
6472
|
})
|
|
@@ -6286,18 +6635,42 @@ window.__ModuleLoader__.load({
|
|
|
6286
6635
|
...wsGroupItems,
|
|
6287
6636
|
customizeEntry,
|
|
6288
6637
|
]
|
|
6289
|
-
if (ctx.kind === 'session')
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
//
|
|
6293
|
-
//
|
|
6294
|
-
//
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6638
|
+
if (ctx.kind === 'session') {
|
|
6639
|
+
// Official 0.1.7 mirror (Rows.tsx session menu + the shipped slot
|
|
6640
|
+
// entries): pin, rename, fork, archive — same order, same icons,
|
|
6641
|
+
// same archived-state behavior (pin hides; archive becomes
|
|
6642
|
+
// unarchive). Copy rides the official dictionary, so upstream
|
|
6643
|
+
// rewording AND future item additions land without a plugin
|
|
6644
|
+
// release; every verb is gated on its service method so older
|
|
6645
|
+
// hosts simply never offer what they cannot do.
|
|
6646
|
+
const payload = ctx.payload || {}
|
|
6647
|
+
const archived = !!(payload.archived || (archivedSet && archivedSet.has(payload.id)))
|
|
6648
|
+
const pinned = !!(payload.pinned || (pinnedSet && pinnedSet.has(payload.id)))
|
|
6649
|
+
const official = (key, fallback) => {
|
|
6650
|
+
const value = typeof ot === 'function' ? ot(key) : null
|
|
6651
|
+
return typeof value === 'string' && value !== '' && value !== key ? value : fallback
|
|
6652
|
+
}
|
|
6653
|
+
return [
|
|
6654
|
+
...(pinAvailable && !archived ? [{
|
|
6655
|
+
id: pinned ? 'unpin' : 'pin',
|
|
6656
|
+
label: official(pinned ? 'menu.unpinSession' : 'menu.pinSession', t('menu.archive')),
|
|
6657
|
+
icon: icon(pinned ? 'IconPinFillRegular' : 'IconPinOutlineRegular', 16),
|
|
6658
|
+
}] : []),
|
|
6659
|
+
{ id: 'rename', label: official('rename', t('menu.rename')), icon: icon('IconEditOutline16', 16) },
|
|
6660
|
+
{ id: 'fork', label: official('menu.fork', t('menu.fork')), icon: icon('IconBranchOutline16', 16) },
|
|
6661
|
+
// Official semantics: archive hides the row through the
|
|
6662
|
+
// registry-global archive set and never touches the session log
|
|
6663
|
+
// — deliberately NOT styled destructive. An archived row gets
|
|
6664
|
+
// the restore entry instead.
|
|
6665
|
+
archived
|
|
6666
|
+
? { id: 'unarchive', label: official('menu.unarchiveSession', t('menu.archive')), icon: icon('IconUnarchiveOutlineRegular', 16) }
|
|
6667
|
+
: { id: 'archive', label: official('menu.archiveSession', t('menu.archive')), icon: icon('IconArchiveOutline20', 16) },
|
|
6668
|
+
{ type: 'separator', id: 'bw-sep' },
|
|
6669
|
+
...sessionUngroupItems,
|
|
6670
|
+
...sgroupItems,
|
|
6671
|
+
customizeEntry,
|
|
6672
|
+
]
|
|
6673
|
+
}
|
|
6301
6674
|
return [
|
|
6302
6675
|
{ id: 'rename-sgroup', label: t('menu.renameSgroup'), icon: icon('IconEditOutline16', 16) },
|
|
6303
6676
|
...(ctx.payload && ctx.payload.empty ? [deleteSgroupEntry] : []),
|
|
@@ -6336,7 +6709,23 @@ window.__ModuleLoader__.load({
|
|
|
6336
6709
|
else if (kind === 'sgroup' && id === 'rename-sgroup') setDialog({ kind: 'sgroup-rename', target: payload })
|
|
6337
6710
|
else if (kind === 'session' && id === 'rename') setDialog({ kind: 'sess-rename', session: payload })
|
|
6338
6711
|
else if (kind === 'session' && id === 'fork') forkSession(payload.id)
|
|
6339
|
-
else if (kind === 'session' && id === '
|
|
6712
|
+
else if (kind === 'session' && (id === 'pin' || id === 'unpin') && pinAvailable) {
|
|
6713
|
+
Promise.resolve().then(() => (id === 'pin' ? pinSession : unpinSession)(payload.id)).catch((reason) => {
|
|
6714
|
+
fail(officialText(id === 'pin' ? 'toast.pinFailed' : 'toast.unpinFailed', undefined, reason instanceof Error ? reason.message : String(reason || 'pin failed')))
|
|
6715
|
+
})
|
|
6716
|
+
}
|
|
6717
|
+
else if (kind === 'session' && id === 'unarchive' && unarchiveAvailable) {
|
|
6718
|
+
Promise.resolve().then(() => unarchiveSession(payload.id)).catch((reason) => { console.warn('session unarchive rejected:', reason) })
|
|
6719
|
+
}
|
|
6720
|
+
else if (kind === 'session' && id === 'archive' && typeof archiveSession === 'function') {
|
|
6721
|
+
Promise.resolve().then(() => archiveSession(payload.id)).catch((reason) => {
|
|
6722
|
+
if (reason && reason.name === 'WorkspaceArchiveError') {
|
|
6723
|
+
setDialog({ kind: 'sess-archive-confirm', sessionId: payload.id })
|
|
6724
|
+
return
|
|
6725
|
+
}
|
|
6726
|
+
fail(reason)
|
|
6727
|
+
})
|
|
6728
|
+
}
|
|
6340
6729
|
// Plain retitle back to the leaf: the row leaves its group and returns
|
|
6341
6730
|
// to the top level of its workspace (v0.18.0).
|
|
6342
6731
|
else if (kind === 'session' && id === 'move-out') {
|
|
@@ -6408,6 +6797,27 @@ window.__ModuleLoader__.load({
|
|
|
6408
6797
|
onClose: () => setDialog(null),
|
|
6409
6798
|
t,
|
|
6410
6799
|
})
|
|
6800
|
+
if (dialog.kind === 'sess-archive-confirm') {
|
|
6801
|
+
// Official stop-and-archive confirmation (dsh 0.1.7
|
|
6802
|
+
// SessionArchiveConfirmDialog mirror): the Host refused the plain
|
|
6803
|
+
// archive because this session still runs; confirming asks the
|
|
6804
|
+
// Host to stop that work first. Copy rides the official dictionary.
|
|
6805
|
+
const summary = list && list.byId ? list.byId[dialog.sessionId] : undefined
|
|
6806
|
+
const displayTitle = summary ? sessionTitleOf(summary, t, rememberedTitleOf(dialog.sessionId)) : ''
|
|
6807
|
+
return E(ConfirmDialog, {
|
|
6808
|
+
key: 'sess-archive-confirm',
|
|
6809
|
+
title: officialText('archive.confirm.title', undefined, t('menu.archive')),
|
|
6810
|
+
body: officialText('archive.confirm.desc', { title: displayTitle }, ''),
|
|
6811
|
+
confirmLabel: officialText('archive.confirm.action', undefined, undefined),
|
|
6812
|
+
onConfirm: () => {
|
|
6813
|
+
Promise.resolve().then(() => archiveSession(dialog.sessionId, { stopActivity: true }))
|
|
6814
|
+
.then(() => setDialog(null))
|
|
6815
|
+
.catch(fail)
|
|
6816
|
+
},
|
|
6817
|
+
onClose: () => setDialog(null),
|
|
6818
|
+
t,
|
|
6819
|
+
})
|
|
6820
|
+
}
|
|
6411
6821
|
if (dialog.kind === 'sgroup-rename') return E(TextDialog, {
|
|
6412
6822
|
key: 'sgroup-rename',
|
|
6413
6823
|
title: t('menu.renameSgroup'),
|
|
@@ -6556,7 +6966,10 @@ window.__ModuleLoader__.load({
|
|
|
6556
6966
|
groupBy,
|
|
6557
6967
|
orderBy,
|
|
6558
6968
|
sessionSlash,
|
|
6969
|
+
archivedFilter,
|
|
6559
6970
|
onPick: (kind, value) => { if (kind === 'groupBy') actions.setGroupBy(value); else actions.setOrderBy(value) },
|
|
6971
|
+
onFilterPick: (value) => { if (actions && typeof actions.setArchivedFilter === 'function') actions.setArchivedFilter(value) },
|
|
6972
|
+
ot: officialT,
|
|
6560
6973
|
onToggle: (key, value) => { if (key === 'sessionTitleSlash') actions.setSessionTitleSlash(value) },
|
|
6561
6974
|
t,
|
|
6562
6975
|
}),
|
|
@@ -6740,7 +7153,12 @@ window.__ModuleLoader__.load({
|
|
|
6740
7153
|
insertSessionBefore: typeof workspaces.insertSessionBefore === 'function'
|
|
6741
7154
|
? (workspaceId, sessionId, beforeSessionId) => workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId)
|
|
6742
7155
|
: undefined,
|
|
6743
|
-
|
|
7156
|
+
// options rides through for the 0.1.7 stop-and-archive hop; older
|
|
7157
|
+
// services simply never receive a second argument.
|
|
7158
|
+
archiveSession: (sessionId, options) => uiWorkspace.archiveSession(sessionId, options),
|
|
7159
|
+
pinSession: typeof uiWorkspace.pinSession === 'function' ? (sessionId) => uiWorkspace.pinSession(sessionId) : undefined,
|
|
7160
|
+
unpinSession: typeof uiWorkspace.unpinSession === 'function' ? (sessionId) => uiWorkspace.unpinSession(sessionId) : undefined,
|
|
7161
|
+
unarchiveSession: typeof uiWorkspace.unarchiveSession === 'function' ? (sessionId) => uiWorkspace.unarchiveSession(sessionId) : undefined,
|
|
6744
7162
|
createWorkspace: (input) => workspaces.create(input),
|
|
6745
7163
|
pickDirectory: () => uiWorkspace.pickDirectory(),
|
|
6746
7164
|
// Browse-side primitives: the in-app dialog needs them whenever the
|
|
@@ -6788,14 +7206,38 @@ window.__ModuleLoader__.load({
|
|
|
6788
7206
|
// block). Binding is best-effort: a missing/failed bind leaves the
|
|
6789
7207
|
// plugin fully browser-local — the pre-0.9.5 behavior.
|
|
6790
7208
|
prefsScopeRef = null
|
|
7209
|
+
prefsScopeVia = null
|
|
6791
7210
|
try {
|
|
6792
7211
|
if (ctx.settingsScope && typeof ctx.settingsScope.bind === 'function') {
|
|
6793
7212
|
prefsScopeRef = ctx.settingsScope.bind({ namespace: 'better-workspace' })
|
|
7213
|
+
prefsScopeVia = 'settingsScope'
|
|
6794
7214
|
}
|
|
6795
7215
|
} catch (error) {
|
|
6796
7216
|
console.warn('[dsh-better-workspace] settings scope bind failed; sync stays local', error)
|
|
6797
7217
|
prefsScopeRef = null
|
|
6798
7218
|
}
|
|
7219
|
+
// dsh >= 0.1.7: the settings service is gone; one ConfigForm per live
|
|
7220
|
+
// profile entry replaces it (same getSnapshot/set/subscribe contract).
|
|
7221
|
+
// Values then persist in THIS profile's patch — there is no shared home
|
|
7222
|
+
// any more, so the manual cross-device sync section stays hidden.
|
|
7223
|
+
try {
|
|
7224
|
+
if (ctx.inject) {
|
|
7225
|
+
ctx.inject(['configForms'], (fctx) => {
|
|
7226
|
+
try {
|
|
7227
|
+
if (prefsScopeVia === 'settingsScope') return
|
|
7228
|
+
const forms = fctx && fctx.configForms
|
|
7229
|
+
if (forms && typeof forms.get === 'function') {
|
|
7230
|
+
prefsScopeRef = forms.get('better-workspace')
|
|
7231
|
+
prefsScopeVia = 'configForms'
|
|
7232
|
+
}
|
|
7233
|
+
} catch (error) {
|
|
7234
|
+
console.warn('[dsh-better-workspace] configForms acquisition failed', error)
|
|
7235
|
+
}
|
|
7236
|
+
})
|
|
7237
|
+
}
|
|
7238
|
+
} catch (error) {
|
|
7239
|
+
console.warn('[dsh-better-workspace] configForms wiring failed', error)
|
|
7240
|
+
}
|
|
6799
7241
|
|
|
6800
7242
|
// Settings → Plugins card only (the tab dispatches the intersection of
|
|
6801
7243
|
// served namespaces — registered host-side — and settings.plugin.item
|
|
@@ -6849,7 +7291,7 @@ window.__ModuleLoader__.load({
|
|
|
6849
7291
|
|
|
6850
7292
|
return {
|
|
6851
7293
|
name: 'dsh-better-workspace',
|
|
6852
|
-
inject: ['slots', 'sessions', 'workspaces', 'locale', 'uiWorkspace'
|
|
7294
|
+
inject: ['slots', 'sessions', 'workspaces', 'locale', 'uiWorkspace'],
|
|
6853
7295
|
apply,
|
|
6854
7296
|
}
|
|
6855
7297
|
},
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Schema from '@deepseek-ai/schemastery'
|
|
1
2
|
/**
|
|
2
3
|
* dsh-better-workspace — host half (plain JavaScript, no build step).
|
|
3
4
|
*
|
|
@@ -10,6 +11,36 @@
|
|
|
10
11
|
*/
|
|
11
12
|
export const name = 'dsh-better-workspace'
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* dsh >= 0.1.7 marks a Config field live-editable without remount; a
|
|
16
|
+
* 0.1.6-era schemastery predates the method and the guard keeps this module
|
|
17
|
+
* loadable there (values then behave as ordinary config, read through the
|
|
18
|
+
* registered settings namespace instead).
|
|
19
|
+
*/
|
|
20
|
+
function live(schema) {
|
|
21
|
+
return typeof schema.volatile === 'function' ? schema.volatile() : schema
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Row Config = the settings surface on dsh >= 0.1.7 (values persist under the
|
|
26
|
+
* row id 'better-workspace' — the same string as the old settings namespace,
|
|
27
|
+
* so the one-shot legacy settings.yaml import maps old user values onto the
|
|
28
|
+
* new home). Inert metadata on older hosts. NOTE: settings are per-profile
|
|
29
|
+
* on the new host; the client hides the manual cross-device sync section
|
|
30
|
+
* accordingly (the shared settings.yaml home no longer exists).
|
|
31
|
+
*/
|
|
32
|
+
export const Config = Schema.object({
|
|
33
|
+
compactChains: live(Schema.boolean().default(true)),
|
|
34
|
+
statusPulse: live(Schema.boolean().default(true)),
|
|
35
|
+
styling: live(Schema.dict(Schema.any()).default({})),
|
|
36
|
+
appearance: live(Schema.dict(Schema.any()).default({})),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/** Read one Config value across eras: Volatile ref (>= 0.1.7) or plain value. */
|
|
40
|
+
export function valueOf(value) {
|
|
41
|
+
return value && typeof value.get === 'function' ? value.get() : value
|
|
42
|
+
}
|
|
43
|
+
|
|
13
44
|
export function apply(ctx) {
|
|
14
45
|
const log = ctx && ctx.logger && typeof ctx.logger.info === 'function'
|
|
15
46
|
? (msg) => ctx.logger.info(msg)
|
|
@@ -24,7 +55,18 @@ export function apply(ctx) {
|
|
|
24
55
|
// The dsh-settings and zod modules resolve only through the dsh Loader; the
|
|
25
56
|
// smoke test imports this file in plain Node with a logger-only ctx and
|
|
26
57
|
// never reaches this path.
|
|
27
|
-
|
|
58
|
+
// OLD-era namespace registration (dsh <= 0.1.6): the row Config above IS
|
|
59
|
+
// the surface on dsh >= 0.1.7 — register() no longer exists there and this
|
|
60
|
+
// block is skipped entirely.
|
|
61
|
+
const legacySettings = (() => {
|
|
62
|
+
try {
|
|
63
|
+
const settings = ctx.get('settings')
|
|
64
|
+
return !!(settings && typeof settings.register === 'function')
|
|
65
|
+
} catch {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
})()
|
|
69
|
+
if (legacySettings) ctx.inject(['settings'], (sctx) => {
|
|
28
70
|
log('[dsh-better-workspace] settings inject fired')
|
|
29
71
|
// The settings service calls the schema AS A FUNCTION to resolve a value
|
|
30
72
|
// (schema(mergeLayers(...))) — that is @deepseek-ai/schemastery's contract:
|