dsh-better-workspace 0.22.0 → 0.23.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/README.md +2 -1
- package/README_EN.md +2 -1
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +602 -28
package/README.md
CHANGED
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
|
|
71
71
|
- **文件夹式收起**:点工作区行 = 收起/展开该工作区的全部会话(收起后行上保留会话数角标);会话分组同样可收起;搜索时自动全部展开。
|
|
72
72
|
- **拖拽还原原版能力**:工作区行可拖拽——拖到另一工作区行上/下半即调整顺序(写回宿主 registry 序),拖到分组行则移动进该分组(改写名称前缀);会话行可拖拽——同组内拖到另一会话上/下即调整顺序,拖到**别组的会话行**会把名字前缀改写到那个组(拖到根层的行 = 移出分组),拖到会话子分组行即移动进该分组,拖到**自己的工作区行**或**树里任何组外的地方**(含空白区域)同样移出分组 —— 拖拽期间工作区行整行高亮、树底部有一条淡虚线提示这个出口;带分组前缀的会话行右键还多一项「移出分组」。显示顺序 = 宿主手动序,拖拽结果立即生效。**磁盘层是文件系统事实,不随拖拽改变**:跨磁盘层的放置只重组名称分组(工作区落回自身磁盘层的末尾),同层放置才走锚点序。拖拽工作区期间,单链合并显示的行(如 工作/A)临时展开回文件夹树——路径里的每一级都是可投放的分组行,松手后自动合并回去。
|
|
73
|
-
- **不丢原生能力**:会话行(打开/重命名/分叉/归档)、每工作区「新会话」(点 + 总是先展开该工作区,让新会话立刻可见)、当前会话高亮、官方状态灯(蓝色运行环 / 琥珀待交互 / 绿色完成提醒)、**状态呼吸灯**(折叠藏起的状态灯向外冒泡:工作区/分组行图标按状态色呼吸发光——琥珀待交互/绿完成,自定义过发光的标题一起呼吸;运行中只在会话行本层蓝色呼吸,不外渗;可在设置关闭)、**活跃定时任务角标**(有活动 Schedule
|
|
73
|
+
- **不丢原生能力**:会话行(打开/重命名/分叉/归档)、每工作区「新会话」(点 + 总是先展开该工作区,让新会话立刻可见)、当前会话高亮、官方状态灯(蓝色运行环 / 琥珀待交互 / 绿色完成提醒)、**状态呼吸灯**(折叠藏起的状态灯向外冒泡:工作区/分组行图标按状态色呼吸发光——琥珀待交互/绿完成,自定义过发光的标题一起呼吸;运行中只在会话行本层蓝色呼吸,不外渗;可在设置关闭)、**活跃定时任务角标**(有活动 Schedule 的会话显示闹钟图标;v0.23.0 起直接渲染官方 `sidebar.session.row.leading` 座位的占用者,与官方行同源)、未分组会话兜底、界面文案跟随 DSH 的语言设置(内置 21 种语言,含简繁中文)。
|
|
74
|
+
- **官方新语义与快捷键都接上(v0.23.0,对齐 dsh 0.1.7-rc.2)**:① 归档筛选是官方新版三态直选(**隐藏已归档** / **全部对话(显示已归档)** / **仅显示已归档**);选「仅显示已归档」时**没有归档会话的工作区整行隐藏**,空列表显示「暂无已归档会话」+「查看其他会话」一键回退;② 官方快捷键 **⌥⌘K 搜索 / ⌥⌘O 添加工作区 / ⌥⌘N 新会话**(桌面端为 ⌘K / ⌘O / ⌘N)在本插件的自绘侧栏里**真的生效**——搜索键打开并聚焦本插件的搜索框、添加键走同一个官方目录流(原生宿主弹系统选择器,browse 宿主弹应用内入口提示),按钮文案还会带上宿主当前的键位;③ 首次使用的工作区显示为本地化的「默认工作区」,不再露出磁盘目录名。
|
|
74
75
|
- **URL/斜杠标题不拆层**:宿主自动生成的会话标题落地时若含 `/`(如复述了 URL),整串自动包上 `“”` 原样显示,绝不解析成层级——只对本次启动后**新建的会话**生效,并等命名稳定(约 20 秒,让原生 AI 命名先落地)才处理;存量会话与你手动命名的 `/` 分组标题永不被改动。引号内(含手动添加)的 `/` 永不切分,孤立引号只当普通字符;无引号但含 URL 的旧标题也有平铺兜底。
|
|
75
76
|
- **新会话页的工作区选择器 = 侧栏那棵树(v0.22.0)**:会话空态页「工作区」按钮的弹层原本是官方的一层平铺标题列表(连磁盘目录嵌套都没有);本插件接管该座位后,弹层绘制与侧栏**同一棵**树——磁盘目录嵌套、「/」名称分组三态、单链合并全部读同一个视图 store,所以侧栏的视图选项一改,**这边立刻跟着变**;当前工作区带勾选标记,分组行可开合(选择器内本地记忆,默认全展开),标题按侧栏的层级叶子显示。底部「添加工作区」仍由**官方目录拾取占用者**承载(原生选择器 / 官方应用内浏览器),不是仿制品。
|
|
76
77
|
|
package/README_EN.md
CHANGED
|
@@ -70,7 +70,8 @@ Session titles nest on `/` too (e.g. `test1/plugin maintenance`). Session groups
|
|
|
70
70
|
|
|
71
71
|
- **Folder-style collapse**: clicking a workspace row collapses/expands all of its sessions (the row keeps a session counter when collapsed); session groups collapse too; search force-expands everything.
|
|
72
72
|
- **Native drag restored**: drag a workspace row above/below another to reorder (writes back the host registry order) or onto a folder row to move it into that group (rewrites the title prefix); drag session rows to reorder inside their group, onto a row of ANOTHER group to move into that group (onto a root-level row to leave the group), onto a session-group row to move into it, or onto their OWN workspace row / anywhere outside a group (empty space included) to leave it — the workspace row highlights and the tree shows a faint dashed hint while such a drag is in flight; a grouped session row also offers "Move out of group" in its context menu. Display order = host manual order, drag results are visible immediately. **The disk layer is a filesystem fact and never changes through dragging**: a cross-level drop only re-groups the name prefix (the workspace lands at the end of its own disk level), same-level drops take the anchor order. While dragging a workspace, merged single-chain rows (e.g. work/A) temporarily re-expand into folder rows — every level of the path becomes a drop target, and chains merge back when the drag ends.
|
|
73
|
-
- **Native capabilities kept**: open/rename/fork/archive sessions, per-workspace new-session (the + button always expands the workspace first so the new session is immediately visible), current-session highlight, official status dots (blue running ring / amber pending / green done), **status breathing lights** (dots hidden by collapse bubble outward: workspace/folder rows breathe on their icon in the status color — amber pending / green done, custom-glow labels breathe along; running breathes blue only on the session row itself and never relays; off-able in settings), **active-schedule badge** (alarm clock icon for sessions with active Schedule records,
|
|
73
|
+
- **Native capabilities kept**: open/rename/fork/archive sessions, per-workspace new-session (the + button always expands the workspace first so the new session is immediately visible), current-session highlight, official status dots (blue running ring / amber pending / green done), **status breathing lights** (dots hidden by collapse bubble outward: workspace/folder rows breathe on their icon in the status color — amber pending / green done, custom-glow labels breathe along; running breathes blue only on the session row itself and never relays; off-able in settings), **active-schedule badge** (alarm clock icon for sessions with active Schedule records; since v0.23.0 it renders the occupant of the shipped `sidebar.session.row.leading` seat, so it is the same mark the official row would show), ungrouped-session fallback, UI copy in 21 languages following the DSH language preference (Simplified and Traditional Chinese included).
|
|
74
|
+
- **Official semantics and shortcuts wired up (v0.23.0, aligned with dsh 0.1.7-rc.2)**: ① the archived filter is the shipped three-way pick (**Hide archived** / **All conversations (show archived)** / **Archived only**); under "Archived only" a workspace without archived sessions disappears entirely, and an empty list offers "No archived sessions yet" + **View other sessions** as the way back. ② The shipped shortcuts **⌥⌘K search / ⌥⌘O add workspace / ⌥⌘N new session** (⌘K / ⌘O / ⌘N on desktop) really work in this plugin's own sidebar — the search key opens and focuses this sidebar's filter field, the add key runs the very same official directory flow (native chooser on a native host, the in-app pointer on a browse host), and the buttons show the host's current keybindings. ③ A first-use workspace reads as the localized "Default workspace" instead of the on-disk directory name.
|
|
74
75
|
- **Slash-bearing titles never nest from URLs**: when a host-generated session title lands containing `/` (e.g. echoing a pasted URL), it is auto-wrapped in `“”` and shown verbatim — only for sessions **created after this launch**, and only once the name stays stable for ~20s (letting the native AI naming land first); pre-existing sessions and your manually named `/` groupings are never touched. Quotes (manual ones too) never split on `/`, and a lone quote is just an ordinary character; unquoted URL tails still fall back flat.
|
|
75
76
|
- **The New Session workspace picker IS the sidebar tree (v0.22.0)**: the empty-state "Workspace" chip used to open the shipped one-level list of flat titles (no disk nesting at all). This plugin shadows that seat (lowest priority renders) and draws the **same** tree the sidebar draws — disk nesting, the tri-state "/" name layer, chain compression — all from the one view store, so changing a view option on the sidebar moves this popover immediately. The current workspace carries a check, folder rows fold (picker-local, fully expanded by default), and the pinned "Add workspace" action still renders the **shipped directory-picking occupant** (native chooser / official in-app browser) instead of a copy.
|
|
76
77
|
|
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.23.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; the New Session workspace picker shows the same tree.",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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), and the same tree in the New Session workspace picker; add-workspace rides the official directory flow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client.js
CHANGED
|
@@ -39,6 +39,11 @@ window.__ModuleLoader__.load({
|
|
|
39
39
|
'rail.add': '添加工作区',
|
|
40
40
|
'empty': '暂无工作区',
|
|
41
41
|
'empty.search': '没有匹配的结果',
|
|
42
|
+
'empty.noneArchived': '暂无已归档会话',
|
|
43
|
+
'empty.viewOthers': '查看其他会话',
|
|
44
|
+
'shortcut.forkFailed': '无法分叉会话,请重试',
|
|
45
|
+
'shortcut.noCompletedTurn': '当前会话没有已结束的轮次',
|
|
46
|
+
'workspace.defaultName': '默认工作区',
|
|
42
47
|
'session.new': '新会话',
|
|
43
48
|
'viewOptions.label': '视图选项',
|
|
44
49
|
'viewOptions.groupBy': '分组方式',
|
|
@@ -182,6 +187,11 @@ window.__ModuleLoader__.load({
|
|
|
182
187
|
'rail.add': 'Add workspace',
|
|
183
188
|
'empty': 'No workspaces yet',
|
|
184
189
|
'empty.search': 'No matches',
|
|
190
|
+
'empty.noneArchived': 'No archived sessions yet',
|
|
191
|
+
'empty.viewOthers': 'View other sessions',
|
|
192
|
+
'shortcut.forkFailed': 'Could not fork the session. Try again.',
|
|
193
|
+
'shortcut.noCompletedTurn': 'This session has no completed turn',
|
|
194
|
+
'workspace.defaultName': 'Default workspace',
|
|
185
195
|
'session.new': 'New session',
|
|
186
196
|
'viewOptions.label': 'View options',
|
|
187
197
|
'viewOptions.groupBy': 'Group by',
|
|
@@ -333,6 +343,11 @@ window.__ModuleLoader__.load({
|
|
|
333
343
|
'rail.add': 'إضافة مساحة عمل',
|
|
334
344
|
'empty': 'لا توجد مساحات عمل بعد',
|
|
335
345
|
'empty.search': 'لا توجد نتائج مطابقة',
|
|
346
|
+
'empty.noneArchived': 'لا توجد جلسات مؤرشفة بعد',
|
|
347
|
+
'empty.viewOthers': 'عرض الجلسات الأخرى',
|
|
348
|
+
'shortcut.forkFailed': 'تعذّر تفريع الجلسة. أعد المحاولة.',
|
|
349
|
+
'shortcut.noCompletedTurn': 'لا توجد جولة مكتملة في هذه الجلسة',
|
|
350
|
+
'workspace.defaultName': 'مساحة العمل الافتراضية',
|
|
336
351
|
'session.new': 'جلسة جديدة',
|
|
337
352
|
'viewOptions.label': 'خيارات العرض',
|
|
338
353
|
'viewOptions.groupBy': 'التجميع حسب',
|
|
@@ -476,6 +491,11 @@ window.__ModuleLoader__.load({
|
|
|
476
491
|
'rail.add': 'Arbeitsbereich hinzufügen',
|
|
477
492
|
'empty': 'Noch keine Arbeitsbereiche',
|
|
478
493
|
'empty.search': 'Keine Treffer',
|
|
494
|
+
'empty.noneArchived': 'Noch keine archivierten Sitzungen',
|
|
495
|
+
'empty.viewOthers': 'Andere Sitzungen anzeigen',
|
|
496
|
+
'shortcut.forkFailed': 'Sitzung konnte nicht geforkt werden. Erneut versuchen.',
|
|
497
|
+
'shortcut.noCompletedTurn': 'Diese Sitzung hat keinen abgeschlossenen Durchlauf',
|
|
498
|
+
'workspace.defaultName': 'Standard-Arbeitsbereich',
|
|
479
499
|
'session.new': 'Neue Sitzung',
|
|
480
500
|
'viewOptions.label': 'Ansichtsoptionen',
|
|
481
501
|
'viewOptions.groupBy': 'Gruppieren',
|
|
@@ -619,6 +639,11 @@ window.__ModuleLoader__.load({
|
|
|
619
639
|
'rail.add': 'Ajouter un espace de travail',
|
|
620
640
|
'empty': 'Aucun espace de travail',
|
|
621
641
|
'empty.search': 'Aucun résultat',
|
|
642
|
+
'empty.noneArchived': 'Aucune session archivée',
|
|
643
|
+
'empty.viewOthers': 'Voir les autres sessions',
|
|
644
|
+
'shortcut.forkFailed': 'Impossible de dupliquer la session. Réessayez.',
|
|
645
|
+
'shortcut.noCompletedTurn': "Cette session n'a aucun tour terminé",
|
|
646
|
+
'workspace.defaultName': 'Espace de travail par défaut',
|
|
622
647
|
'session.new': 'Nouvelle session',
|
|
623
648
|
'viewOptions.label': 'Options d’affichage',
|
|
624
649
|
'viewOptions.groupBy': 'Regrouper par',
|
|
@@ -762,6 +787,11 @@ window.__ModuleLoader__.load({
|
|
|
762
787
|
'rail.add': 'कार्यस्थान जोड़ें',
|
|
763
788
|
'empty': 'अभी कोई कार्यस्थान नहीं',
|
|
764
789
|
'empty.search': 'कोई मेल खाता परिणाम नहीं',
|
|
790
|
+
'empty.noneArchived': 'अभी कोई संग्रहीत सत्र नहीं',
|
|
791
|
+
'empty.viewOthers': 'अन्य सत्र देखें',
|
|
792
|
+
'shortcut.forkFailed': 'सत्र को फ़ोर्क नहीं किया जा सका। पुनः प्रयास करें।',
|
|
793
|
+
'shortcut.noCompletedTurn': 'इस सत्र में कोई पूर्ण चक्र नहीं है',
|
|
794
|
+
'workspace.defaultName': 'डिफ़ॉल्ट कार्यस्थान',
|
|
765
795
|
'session.new': 'नया सत्र',
|
|
766
796
|
'viewOptions.label': 'दृश्य विकल्प',
|
|
767
797
|
'viewOptions.groupBy': 'समूहीकरण',
|
|
@@ -905,6 +935,11 @@ window.__ModuleLoader__.load({
|
|
|
905
935
|
'rail.add': 'Tambah ruang kerja',
|
|
906
936
|
'empty': 'Belum ada ruang kerja',
|
|
907
937
|
'empty.search': 'Tidak ada hasil yang cocok',
|
|
938
|
+
'empty.noneArchived': 'Belum ada sesi yang diarsipkan',
|
|
939
|
+
'empty.viewOthers': 'Lihat sesi lain',
|
|
940
|
+
'shortcut.forkFailed': 'Tidak dapat menyalin sesi. Coba lagi.',
|
|
941
|
+
'shortcut.noCompletedTurn': 'Sesi ini tidak memiliki putaran yang selesai',
|
|
942
|
+
'workspace.defaultName': 'Ruang kerja default',
|
|
908
943
|
'session.new': 'Sesi baru',
|
|
909
944
|
'viewOptions.label': 'Opsi tampilan',
|
|
910
945
|
'viewOptions.groupBy': 'Kelompokkan berdasarkan',
|
|
@@ -1048,6 +1083,11 @@ window.__ModuleLoader__.load({
|
|
|
1048
1083
|
'rail.add': 'Aggiungi area di lavoro',
|
|
1049
1084
|
'empty': 'Nessuna area di lavoro',
|
|
1050
1085
|
'empty.search': 'Nessun risultato',
|
|
1086
|
+
'empty.noneArchived': 'Nessuna sessione archiviata',
|
|
1087
|
+
'empty.viewOthers': 'Vedi altre sessioni',
|
|
1088
|
+
'shortcut.forkFailed': 'Impossibile creare una copia della sessione. Riprova.',
|
|
1089
|
+
'shortcut.noCompletedTurn': 'Questa sessione non ha turni completati',
|
|
1090
|
+
'workspace.defaultName': 'Area di lavoro predefinita',
|
|
1051
1091
|
'session.new': 'Nuova sessione',
|
|
1052
1092
|
'viewOptions.label': 'Opzioni vista',
|
|
1053
1093
|
'viewOptions.groupBy': 'Raggruppa per',
|
|
@@ -1191,6 +1231,11 @@ window.__ModuleLoader__.load({
|
|
|
1191
1231
|
'rail.add': 'ワークスペースを追加',
|
|
1192
1232
|
'empty': 'ワークスペースがありません',
|
|
1193
1233
|
'empty.search': '一致する結果がありません',
|
|
1234
|
+
'empty.noneArchived': 'アーカイブされたセッションはありません',
|
|
1235
|
+
'empty.viewOthers': '他のセッションを表示',
|
|
1236
|
+
'shortcut.forkFailed': 'セッションを分岐できませんでした。もう一度お試しください。',
|
|
1237
|
+
'shortcut.noCompletedTurn': 'このセッションには完了したターンがありません',
|
|
1238
|
+
'workspace.defaultName': '既定のワークスペース',
|
|
1194
1239
|
'session.new': '新しいセッション',
|
|
1195
1240
|
'viewOptions.label': '表示オプション',
|
|
1196
1241
|
'viewOptions.groupBy': 'グループ化',
|
|
@@ -1334,6 +1379,11 @@ window.__ModuleLoader__.load({
|
|
|
1334
1379
|
'rail.add': '워크스페이스 추가',
|
|
1335
1380
|
'empty': '워크스페이스가 없습니다',
|
|
1336
1381
|
'empty.search': '일치하는 결과가 없습니다',
|
|
1382
|
+
'empty.noneArchived': '보관된 세션이 없습니다',
|
|
1383
|
+
'empty.viewOthers': '다른 세션 보기',
|
|
1384
|
+
'shortcut.forkFailed': '세션을 분기할 수 없습니다. 다시 시도하세요.',
|
|
1385
|
+
'shortcut.noCompletedTurn': '이 세션에는 완료된 턴이 없습니다',
|
|
1386
|
+
'workspace.defaultName': '기본 워크스페이스',
|
|
1337
1387
|
'session.new': '새 세션',
|
|
1338
1388
|
'viewOptions.label': '보기 옵션',
|
|
1339
1389
|
'viewOptions.groupBy': '그룹화',
|
|
@@ -1477,6 +1527,11 @@ window.__ModuleLoader__.load({
|
|
|
1477
1527
|
'rail.add': 'Werkruimte toevoegen',
|
|
1478
1528
|
'empty': 'Nog geen werkruimten',
|
|
1479
1529
|
'empty.search': 'Geen resultaten',
|
|
1530
|
+
'empty.noneArchived': 'Nog geen gearchiveerde sessies',
|
|
1531
|
+
'empty.viewOthers': 'Andere sessies bekijken',
|
|
1532
|
+
'shortcut.forkFailed': 'Kan de sessie niet forken. Probeer opnieuw.',
|
|
1533
|
+
'shortcut.noCompletedTurn': 'Deze sessie heeft geen voltooide ronde',
|
|
1534
|
+
'workspace.defaultName': 'Standaardwerkruimte',
|
|
1480
1535
|
'session.new': 'Nieuwe sessie',
|
|
1481
1536
|
'viewOptions.label': 'Weergaveopties',
|
|
1482
1537
|
'viewOptions.groupBy': 'Groeperen op',
|
|
@@ -1620,6 +1675,11 @@ window.__ModuleLoader__.load({
|
|
|
1620
1675
|
'rail.add': 'Dodaj obszar roboczy',
|
|
1621
1676
|
'empty': 'Brak obszarów roboczych',
|
|
1622
1677
|
'empty.search': 'Brak wyników',
|
|
1678
|
+
'empty.noneArchived': 'Brak zarchiwizowanych sesji',
|
|
1679
|
+
'empty.viewOthers': 'Zobacz inne sesje',
|
|
1680
|
+
'shortcut.forkFailed': 'Nie udało się rozgałęzić sesji. Spróbuj ponownie.',
|
|
1681
|
+
'shortcut.noCompletedTurn': 'Ta sesja nie ma ukończonej tury',
|
|
1682
|
+
'workspace.defaultName': 'Domyślna przestrzeń robocza',
|
|
1623
1683
|
'session.new': 'Nowa sesja',
|
|
1624
1684
|
'viewOptions.label': 'Opcje widoku',
|
|
1625
1685
|
'viewOptions.groupBy': 'Grupuj według',
|
|
@@ -1763,6 +1823,11 @@ window.__ModuleLoader__.load({
|
|
|
1763
1823
|
'rail.add': 'Adicionar espaço de trabalho',
|
|
1764
1824
|
'empty': 'Nenhum espaço de trabalho',
|
|
1765
1825
|
'empty.search': 'Nenhum resultado',
|
|
1826
|
+
'empty.noneArchived': 'Nenhuma sessão arquivada',
|
|
1827
|
+
'empty.viewOthers': 'Ver outras sessões',
|
|
1828
|
+
'shortcut.forkFailed': 'Não foi possível ramificar a sessão. Tente novamente.',
|
|
1829
|
+
'shortcut.noCompletedTurn': 'Esta sessão não tem nenhum turno concluído',
|
|
1830
|
+
'workspace.defaultName': 'Espaço de trabalho predefinido',
|
|
1766
1831
|
'session.new': 'Nova sessão',
|
|
1767
1832
|
'viewOptions.label': 'Opções de vista',
|
|
1768
1833
|
'viewOptions.groupBy': 'Agrupar por',
|
|
@@ -1906,6 +1971,11 @@ window.__ModuleLoader__.load({
|
|
|
1906
1971
|
'rail.add': 'Добавить рабочую область',
|
|
1907
1972
|
'empty': 'Рабочих областей пока нет',
|
|
1908
1973
|
'empty.search': 'Ничего не найдено',
|
|
1974
|
+
'empty.noneArchived': 'Нет архивных сеансов',
|
|
1975
|
+
'empty.viewOthers': 'Показать другие сеансы',
|
|
1976
|
+
'shortcut.forkFailed': 'Не удалось ответвить сеанс. Повторите попытку.',
|
|
1977
|
+
'shortcut.noCompletedTurn': 'В этом сеансе нет завершённых циклов',
|
|
1978
|
+
'workspace.defaultName': 'Рабочая область по умолчанию',
|
|
1909
1979
|
'session.new': 'Новая сессия',
|
|
1910
1980
|
'viewOptions.label': 'Параметры вида',
|
|
1911
1981
|
'viewOptions.groupBy': 'Группировка',
|
|
@@ -2049,6 +2119,11 @@ window.__ModuleLoader__.load({
|
|
|
2049
2119
|
'rail.add': 'Lägg till arbetsyta',
|
|
2050
2120
|
'empty': 'Inga arbetsytor ännu',
|
|
2051
2121
|
'empty.search': 'Inga träffar',
|
|
2122
|
+
'empty.noneArchived': 'Inga arkiverade sessioner',
|
|
2123
|
+
'empty.viewOthers': 'Visa andra sessioner',
|
|
2124
|
+
'shortcut.forkFailed': 'Kunde inte förgrena sessionen. Försök igen.',
|
|
2125
|
+
'shortcut.noCompletedTurn': 'Den här sessionen har ingen avslutad omgång',
|
|
2126
|
+
'workspace.defaultName': 'Standardarbetsyta',
|
|
2052
2127
|
'session.new': 'Ny session',
|
|
2053
2128
|
'viewOptions.label': 'Visningsalternativ',
|
|
2054
2129
|
'viewOptions.groupBy': 'Gruppera efter',
|
|
@@ -2192,6 +2267,11 @@ window.__ModuleLoader__.load({
|
|
|
2192
2267
|
'rail.add': 'เพิ่มพื้นที่ทำงาน',
|
|
2193
2268
|
'empty': 'ยังไม่มีพื้นที่ทำงาน',
|
|
2194
2269
|
'empty.search': 'ไม่พบผลลัพธ์ที่ตรงกัน',
|
|
2270
|
+
'empty.noneArchived': 'ยังไม่มีเซสชันที่เก็บถาวร',
|
|
2271
|
+
'empty.viewOthers': 'ดูเซสชันอื่น',
|
|
2272
|
+
'shortcut.forkFailed': 'ไม่สามารถแยกเซสชันได้ ลองอีกครั้ง',
|
|
2273
|
+
'shortcut.noCompletedTurn': 'เซสชันนี้ไม่มีรอบที่เสร็จสิ้น',
|
|
2274
|
+
'workspace.defaultName': 'พื้นที่ทำงานเริ่มต้น',
|
|
2195
2275
|
'session.new': 'เซสชันใหม่',
|
|
2196
2276
|
'viewOptions.label': 'ตัวเลือกมุมมอง',
|
|
2197
2277
|
'viewOptions.groupBy': 'จัดกลุ่มตาม',
|
|
@@ -2335,6 +2415,11 @@ window.__ModuleLoader__.load({
|
|
|
2335
2415
|
'rail.add': 'Çalışma alanı ekle',
|
|
2336
2416
|
'empty': 'Henüz çalışma alanı yok',
|
|
2337
2417
|
'empty.search': 'Eşleşen sonuç yok',
|
|
2418
|
+
'empty.noneArchived': 'Arşivlenmiş oturum yok',
|
|
2419
|
+
'empty.viewOthers': 'Diğer oturumları görüntüle',
|
|
2420
|
+
'shortcut.forkFailed': 'Oturum çatallanamadı. Yeniden deneyin.',
|
|
2421
|
+
'shortcut.noCompletedTurn': 'Bu oturumda tamamlanmış tur yok',
|
|
2422
|
+
'workspace.defaultName': 'Varsayılan çalışma alanı',
|
|
2338
2423
|
'session.new': 'Yeni oturum',
|
|
2339
2424
|
'viewOptions.label': 'Görünüm seçenekleri',
|
|
2340
2425
|
'viewOptions.groupBy': 'Gruplandır',
|
|
@@ -2478,6 +2563,11 @@ window.__ModuleLoader__.load({
|
|
|
2478
2563
|
'rail.add': 'Thêm không gian làm việc',
|
|
2479
2564
|
'empty': 'Chưa có không gian làm việc',
|
|
2480
2565
|
'empty.search': 'Không có kết quả phù hợp',
|
|
2566
|
+
'empty.noneArchived': 'Chưa có phiên nào được lưu trữ',
|
|
2567
|
+
'empty.viewOthers': 'Xem các phiên khác',
|
|
2568
|
+
'shortcut.forkFailed': 'Không thể tách phiên. Hãy thử lại.',
|
|
2569
|
+
'shortcut.noCompletedTurn': 'Phiên này chưa có lượt nào hoàn thành',
|
|
2570
|
+
'workspace.defaultName': 'Không gian làm việc mặc định',
|
|
2481
2571
|
'session.new': 'Phiên mới',
|
|
2482
2572
|
'viewOptions.label': 'Tùy chọn hiển thị',
|
|
2483
2573
|
'viewOptions.groupBy': 'Nhóm theo',
|
|
@@ -2621,6 +2711,11 @@ window.__ModuleLoader__.load({
|
|
|
2621
2711
|
'rail.add': '新增工作區',
|
|
2622
2712
|
'empty': '未有工作區',
|
|
2623
2713
|
'empty.search': '搵唔到符合嘅結果',
|
|
2714
|
+
'empty.noneArchived': '未有已封存嘅會話',
|
|
2715
|
+
'empty.viewOthers': '睇其他會話',
|
|
2716
|
+
'shortcut.forkFailed': '無法分叉會話,請重試',
|
|
2717
|
+
'shortcut.noCompletedTurn': '呢個會話未有已完成嘅回合',
|
|
2718
|
+
'workspace.defaultName': '預設工作區',
|
|
2624
2719
|
'session.new': '新對話',
|
|
2625
2720
|
'viewOptions.label': '檢視選項',
|
|
2626
2721
|
'viewOptions.groupBy': '分組方式',
|
|
@@ -2764,6 +2859,11 @@ window.__ModuleLoader__.load({
|
|
|
2764
2859
|
'rail.add': '新增工作區',
|
|
2765
2860
|
'empty': '未有工作區',
|
|
2766
2861
|
'empty.search': '搵唔到符合嘅結果',
|
|
2862
|
+
'empty.noneArchived': '未有已封存嘅會話',
|
|
2863
|
+
'empty.viewOthers': '睇其他會話',
|
|
2864
|
+
'shortcut.forkFailed': '無法分叉會話,請重試',
|
|
2865
|
+
'shortcut.noCompletedTurn': '呢個會話未有已完成嘅回合',
|
|
2866
|
+
'workspace.defaultName': '預設工作區',
|
|
2767
2867
|
'session.new': '新對話',
|
|
2768
2868
|
'viewOptions.label': '檢視選項',
|
|
2769
2869
|
'viewOptions.groupBy': '分組方式',
|
|
@@ -2907,6 +3007,11 @@ window.__ModuleLoader__.load({
|
|
|
2907
3007
|
'rail.add': '新增工作區',
|
|
2908
3008
|
'empty': '尚無工作區',
|
|
2909
3009
|
'empty.search': '沒有相符的結果',
|
|
3010
|
+
'empty.noneArchived': '尚無已封存的會話',
|
|
3011
|
+
'empty.viewOthers': '查看其他工作階段',
|
|
3012
|
+
'shortcut.forkFailed': '無法分岔工作階段,請重試',
|
|
3013
|
+
'shortcut.noCompletedTurn': '此工作階段沒有已完成的回合',
|
|
3014
|
+
'workspace.defaultName': '預設工作區',
|
|
2910
3015
|
'session.new': '新工作階段',
|
|
2911
3016
|
'viewOptions.label': '檢視選項',
|
|
2912
3017
|
'viewOptions.groupBy': '分組方式',
|
|
@@ -3267,6 +3372,168 @@ window.__ModuleLoader__.load({
|
|
|
3267
3372
|
&& Array.isArray(summary.projectionValues.schedule)
|
|
3268
3373
|
&& summary.projectionValues.schedule.length > 0)
|
|
3269
3374
|
|
|
3375
|
+
/**
|
|
3376
|
+
* First-use Workspace naming, official semantics (dsh 0.1.7-rc.2:
|
|
3377
|
+
* api workspace-controller default-workspace.ts). The registry derives the
|
|
3378
|
+
* automatic first-use title from the on-disk directory name
|
|
3379
|
+
* ('default-workspace', language-neutral so one installation keeps one path
|
|
3380
|
+
* across language switches), and every shipped browser surface labels
|
|
3381
|
+
* exactly that title with the LOCALIZED default name — the stored title
|
|
3382
|
+
* itself never changes. rc.2 dropped the old `defaultWorkspace.title`
|
|
3383
|
+
* dictionary path and resolves the label at the display site instead;
|
|
3384
|
+
* mirrored here so the tree, the hero picker and the rename prefill agree
|
|
3385
|
+
* with the shipped browser. The copy comes from this plugin's dictionaries,
|
|
3386
|
+
* which cover the 19 languages the official common dictionary leaves on its
|
|
3387
|
+
* English fallback. A host without the key (pre-rc.2) simply keeps the
|
|
3388
|
+
* stored title — the substitution is a display layer, never a write.
|
|
3389
|
+
*/
|
|
3390
|
+
const DEFAULT_WORKSPACE_TITLE = 'default-workspace'
|
|
3391
|
+
const withDisplayTitles = (items, localizedDefault) => {
|
|
3392
|
+
const list = items || []
|
|
3393
|
+
if (!localizedDefault) return list
|
|
3394
|
+
let replaced = false
|
|
3395
|
+
const out = list.map((workspace) => {
|
|
3396
|
+
if (!workspace || workspace.title !== DEFAULT_WORKSPACE_TITLE) return workspace
|
|
3397
|
+
replaced = true
|
|
3398
|
+
return Object.assign({}, workspace, { title: String(localizedDefault) })
|
|
3399
|
+
})
|
|
3400
|
+
return replaced ? out : list
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
/**
|
|
3404
|
+
* The SHIPPED sidebar's own business face, read off the slot ledger (dsh
|
|
3405
|
+
* 0.1.7-rc.2). The shipped shortcut layer routes session search and
|
|
3406
|
+
* add-workspace into a private request store whose ONLY consumer is the
|
|
3407
|
+
* shipped browser's own effect; a seat shadowed at priority -1 never mounts
|
|
3408
|
+
* that consumer, so this plugin consumes the same channel instead of
|
|
3409
|
+
* leaving Cmd/Ctrl+K and Cmd/Ctrl+O consumed-but-dead. Same ledger-read
|
|
3410
|
+
* technique as the hero picker's directory-flow occupant. Every read is
|
|
3411
|
+
* best-effort and version-gated by the face's own shape: an older host has
|
|
3412
|
+
* no requestSearch at all (no channel, no consumption), and a host that
|
|
3413
|
+
* hides the ledger costs the channel only.
|
|
3414
|
+
*/
|
|
3415
|
+
const shippedBrowserFaceOf = (slots, ownComponent) => {
|
|
3416
|
+
try {
|
|
3417
|
+
const entries = slots.entries('sidebar.workspaces')
|
|
3418
|
+
for (const entry of entries || []) {
|
|
3419
|
+
if (!entry || entry.component === ownComponent) continue
|
|
3420
|
+
if (typeof entry.inject !== 'function') continue
|
|
3421
|
+
const face = entry.inject()
|
|
3422
|
+
if (face && typeof face === 'object' && typeof face.requestSearch === 'function') return face
|
|
3423
|
+
}
|
|
3424
|
+
} catch (error) {
|
|
3425
|
+
return null
|
|
3426
|
+
}
|
|
3427
|
+
return null
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
/**
|
|
3431
|
+
* The shipped schedule mark's seat occupant (dsh 0.1.7-rc.2:
|
|
3432
|
+
* `sidebar.session.row.leading`, registered by
|
|
3433
|
+
* @deepseek-ai/dsh-client-ui-schedule). rc.2 removed the list projection
|
|
3434
|
+
* this plugin used to mirror the mark from and moved the mark into a seat
|
|
3435
|
+
* DECLARED BY THE SHIPPED BROWSER — a shadowing entry cannot render another
|
|
3436
|
+
* entry's child seat, so the occupant is read off the ledger and rendered
|
|
3437
|
+
* inside our own row, exactly like the hero picker's directory-flow
|
|
3438
|
+
* occupant. No occupant (the Schedule overlay is off, or an older host has
|
|
3439
|
+
* no such seat) means no mark, which is the shipped default anyway.
|
|
3440
|
+
*/
|
|
3441
|
+
const scheduleSeatOf = (slots) => {
|
|
3442
|
+
try {
|
|
3443
|
+
const entries = slots.entries('sidebar.session.row.leading')
|
|
3444
|
+
const entry = entries && entries.length > 0 ? entries[0] : null
|
|
3445
|
+
if (!entry || !entry.component || typeof entry.inject !== 'function') return null
|
|
3446
|
+
return {
|
|
3447
|
+
component: entry.component,
|
|
3448
|
+
face: entry.inject(),
|
|
3449
|
+
locale: typeof entry.locale === 'string' ? entry.locale : null,
|
|
3450
|
+
}
|
|
3451
|
+
} catch (error) {
|
|
3452
|
+
return null
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
/**
|
|
3457
|
+
* Bind one ledger occupant's `hooks` compartment into the `use<Name>`
|
|
3458
|
+
* selector-hook props the slot renderer would synthesize (ui-slots
|
|
3459
|
+
* PropsHooks): the occupant was written against that contract, so it is
|
|
3460
|
+
* reproduced here — subscribe/getSnapshot captured once per source, the
|
|
3461
|
+
* selected value cached across renders with the optional equality — the
|
|
3462
|
+
* semantics of the shipped bindSnapshotSelector, whose
|
|
3463
|
+
* use-sync-external-store shim is not on the client baseline whitelist.
|
|
3464
|
+
*/
|
|
3465
|
+
const EMPTY_SELECTION = { empty: true }
|
|
3466
|
+
const NOOP_SUBSCRIBE = () => () => {}
|
|
3467
|
+
const NOOP_SNAPSHOT = () => null
|
|
3468
|
+
/**
|
|
3469
|
+
* Subscribe one bare observable source (HostObservable: getSnapshot +
|
|
3470
|
+
* subscribe) without a selector. The capture is memoized so a component
|
|
3471
|
+
* never resubscribes across renders, and a missing source degrades to a
|
|
3472
|
+
* constant null instead of a conditional hook.
|
|
3473
|
+
*/
|
|
3474
|
+
const useObservableSnapshot = (observable) => {
|
|
3475
|
+
const subscribe = React.useMemo(
|
|
3476
|
+
() => (observable && typeof observable.subscribe === 'function' ? (notify) => observable.subscribe(notify) : NOOP_SUBSCRIBE),
|
|
3477
|
+
[observable],
|
|
3478
|
+
)
|
|
3479
|
+
const getSnapshot = React.useMemo(
|
|
3480
|
+
() => (observable && typeof observable.getSnapshot === 'function' ? () => observable.getSnapshot() : NOOP_SNAPSHOT),
|
|
3481
|
+
[observable],
|
|
3482
|
+
)
|
|
3483
|
+
return React.useSyncExternalStore(subscribe, getSnapshot)
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
const bindSelectorHook = (observable) => {
|
|
3487
|
+
const subscribe = (notify) => observable.subscribe(notify)
|
|
3488
|
+
const getSnapshot = () => observable.getSnapshot()
|
|
3489
|
+
return function useSelector(selector, isEqual) {
|
|
3490
|
+
const selectorRef = React.useRef(selector)
|
|
3491
|
+
selectorRef.current = selector
|
|
3492
|
+
const isEqualRef = React.useRef(isEqual)
|
|
3493
|
+
isEqualRef.current = isEqual
|
|
3494
|
+
const cached = React.useRef(EMPTY_SELECTION)
|
|
3495
|
+
const getSelection = React.useCallback(() => {
|
|
3496
|
+
const next = selectorRef.current(getSnapshot())
|
|
3497
|
+
const previous = cached.current
|
|
3498
|
+
if (previous !== EMPTY_SELECTION && typeof isEqualRef.current === 'function' && isEqualRef.current(previous, next)) {
|
|
3499
|
+
return previous
|
|
3500
|
+
}
|
|
3501
|
+
cached.current = next
|
|
3502
|
+
return next
|
|
3503
|
+
}, [])
|
|
3504
|
+
return React.useSyncExternalStore(subscribe, getSelection)
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
/** Split an inject face into plain props + `use<Name>` selector hooks. */
|
|
3509
|
+
const bindOccupantFace = (face) => {
|
|
3510
|
+
const props = {}
|
|
3511
|
+
if (!face || typeof face !== 'object') return props
|
|
3512
|
+
for (const key of Object.keys(face)) {
|
|
3513
|
+
if (key === 'hooks' || key === 'keyedHooks') continue
|
|
3514
|
+
props[key] = face[key]
|
|
3515
|
+
}
|
|
3516
|
+
const hooks = face.hooks
|
|
3517
|
+
if (hooks && typeof hooks === 'object') {
|
|
3518
|
+
for (const key of Object.keys(hooks)) {
|
|
3519
|
+
const observable = hooks[key]
|
|
3520
|
+
if (!observable || typeof observable.subscribe !== 'function' || typeof observable.getSnapshot !== 'function') continue
|
|
3521
|
+
props['use' + key.charAt(0).toUpperCase() + key.slice(1)] = bindSelectorHook(observable)
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
return props
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
/**
|
|
3528
|
+
* Render one occupant the SHIPPED browser seats (module level on purpose:
|
|
3529
|
+
* React discipline — the occupant mounts once per row and keeps its own
|
|
3530
|
+
* state across our re-renders).
|
|
3531
|
+
*/
|
|
3532
|
+
function ShippedSeatOccupant({ seat, ownerProps, t }) {
|
|
3533
|
+
const bound = React.useMemo(() => bindOccupantFace(seat.face), [seat])
|
|
3534
|
+
return E(seat.component, Object.assign({}, ownerProps, bound, t ? { t } : null))
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3270
3537
|
/**
|
|
3271
3538
|
* Running subagent descendants per session (light lineage walk over
|
|
3272
3539
|
* parent links) — a parent row keeps its "ongoing" ring while a spawned
|
|
@@ -3633,10 +3900,17 @@ window.__ModuleLoader__.load({
|
|
|
3633
3900
|
'.bw-root{height:100%;display:flex;flex-direction:column;min-height:0;position:relative;color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3634
3901
|
'.bw-header{display:flex;align-items:center;gap:2px;padding:10px 10px 4px;flex:none}',
|
|
3635
3902
|
'.bw-header-title{flex:1;font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-secondary,#b8b8b8);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
3636
|
-
'.bw-icon-btn{flex:none;width:24px;height:24px;border:none;background:transparent;border-radius:6px;display:grid;place-items:center;color:var(--dsw-alias-label-secondary,#b8b8b8);cursor:pointer;padding:0}',
|
|
3903
|
+
'.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}',
|
|
3637
3904
|
'.bw-icon-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.15));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3905
|
+
// Focus ring parity with the shipped icon buttons (dsh 0.1.7-rc.2
|
|
3906
|
+
// focus.css + WorkspaceBrowser.module.css): the host publishes the ring
|
|
3907
|
+
// width/colour as tokens and keeps offsets inside controls that clip an
|
|
3908
|
+
// outer ring — ours sit in dense 28px rows, so they do the same. The
|
|
3909
|
+
// tokens are absent before rc.2, where the literal fallbacks keep the
|
|
3910
|
+
// pre-existing look.
|
|
3911
|
+
'.bw-icon-btn:focus-visible,.bw-rail-btn:focus-visible,.bw-ctx-item:focus-visible,.bw-seg-btn:focus-visible,.bw-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}',
|
|
3638
3912
|
'.bw-search-row{flex:none;padding:0 10px 6px}',
|
|
3639
|
-
'.bw-input{width:100%;box-sizing:border-box;height:26px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));border-radius:6px;color:inherit;padding:0 8px;font-size:12px;outline:none;font-family:inherit}',
|
|
3913
|
+
'.bw-input{width:100%;box-sizing:border-box;height:26px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));border-radius:var(--dsw-radius-sm,6px);color:inherit;padding:0 8px;font-size:12px;outline:none;font-family:inherit}',
|
|
3640
3914
|
'.bw-input:focus{border-color:var(--dsw-alias-brand-primary,#5b8def)}',
|
|
3641
3915
|
'.bw-input::placeholder{color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
|
|
3642
3916
|
// Dropdown triggers (MenuPicker): the same surface as .bw-input so a
|
|
@@ -3652,7 +3926,7 @@ window.__ModuleLoader__.load({
|
|
|
3652
3926
|
'.bw-menu-block{display:block;width:100%}',
|
|
3653
3927
|
'.bw-menu-inline{display:inline-block;width:auto;flex:none}',
|
|
3654
3928
|
'.bw-tree{flex:1;overflow-y:auto;overflow-x:hidden;padding:2px 6px 12px;min-height:0}',
|
|
3655
|
-
'.bw-row{display:flex;align-items:center;gap:6px;min-height:28px;padding:0 6px;border-radius:6px;cursor:pointer;user-select:none;font-size:13px;color:var(--dsw-alias-label-primary,#e6e6e6);position:relative}',
|
|
3929
|
+
'.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:13px;color:var(--dsw-alias-label-primary,#e6e6e6);position:relative}',
|
|
3656
3930
|
'.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}',
|
|
3657
3931
|
'.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}',
|
|
3658
3932
|
'.bw-drop-into{outline:1.5px dashed var(--dsw-alias-brand-primary,#5b8def);outline-offset:-1.5px}',
|
|
@@ -3704,7 +3978,7 @@ window.__ModuleLoader__.load({
|
|
|
3704
3978
|
// trailing check. The row mirrors the primitives menu cell (same hover
|
|
3705
3979
|
// and focus fill) because the fixed 14px icon cell of that row crops any
|
|
3706
3980
|
// indentation nested inside it.
|
|
3707
|
-
'.bw-pick-row{display:flex;align-items:center;gap:6px;width:100%;min-height:30px;padding:4px 8px;border:none;border-radius:8px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer;text-align:left}',
|
|
3981
|
+
'.bw-pick-row{display:flex;align-items:center;gap:6px;width:100%;min-height:30px;padding:4px 8px;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer;text-align:left}',
|
|
3708
3982
|
'.bw-pick-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.15))}',
|
|
3709
3983
|
'.bw-pick-row:focus-visible{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.15));outline:none}',
|
|
3710
3984
|
'.bw-pick-indent{flex:none}',
|
|
@@ -3737,12 +4011,24 @@ window.__ModuleLoader__.load({
|
|
|
3737
4011
|
'.bw-sgroup-row:hover{color:var(--dsw-alias-label-secondary,#b8b8b8)}',
|
|
3738
4012
|
'.bw-session-row:hover{color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3739
4013
|
'.bw-empty{padding:28px 12px;text-align:center;font-size:12px;color:var(--dsw-alias-label-dimmed,#7a7a7a)}',
|
|
4014
|
+
// Archived-only empty state (dsh 0.1.7-rc.2 official EmptySessions): the
|
|
4015
|
+
// filter is a mode the user can get stuck in, so the body names it and
|
|
4016
|
+
// offers the way back.
|
|
4017
|
+
'.bw-empty-action{margin:8px auto 0;display:block;padding:0;border:none;background:none;cursor:pointer;font:inherit;font-size:12px;color:var(--dsw-alias-link,#5b8def)}',
|
|
4018
|
+
'.bw-empty-action:hover{text-decoration:underline}',
|
|
4019
|
+
// Row toast for the shipped fork shortcut's async failure (the shipped
|
|
4020
|
+
// notice rides the shadowed overlay). Token chain kept transparent-safe
|
|
4021
|
+
// like every other self-drawn surface.
|
|
4022
|
+
'.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)}',
|
|
4023
|
+
'.bw-toast-text{flex:1;min-width:0}',
|
|
4024
|
+
'.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}',
|
|
4025
|
+
'.bw-toast-close:hover{opacity:1;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.15))}',
|
|
3740
4026
|
'.bw-swatch{width:20px;height:20px;border-radius:6px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));cursor:pointer;flex:none;background:transparent;padding:0}',
|
|
3741
4027
|
'.bw-swatch-wide{width:auto;min-width:38px;padding:0 8px;font-size:11px;color:var(--dsw-alias-label-secondary,#b8b8b8)}',
|
|
3742
4028
|
'.bw-swatch-active{outline:2px solid var(--dsw-alias-brand-primary,#5b8def);outline-offset:1px}',
|
|
3743
4029
|
'.bw-color-input{width:36px;height:26px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));border-radius:6px;background:transparent;cursor:pointer;padding:0}',
|
|
3744
4030
|
'.bw-seg{display:flex;gap:6px;flex-wrap:wrap}',
|
|
3745
|
-
'.bw-seg-btn{height:24px;padding:0 10px;border-radius:6px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font-size:12px;cursor:pointer;font-family:inherit}',
|
|
4031
|
+
'.bw-seg-btn{height:24px;padding:0 10px;border-radius:var(--dsw-radius-sm,6px);border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font-size:12px;cursor:pointer;font-family:inherit}',
|
|
3746
4032
|
'.bw-seg-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
|
|
3747
4033
|
'.bw-seg-btn-active{background:var(--dsw-alias-brand-primary,#5b8def);border-color:transparent;color:var(--dsw-alias-brand-text,#fff)}',
|
|
3748
4034
|
'.bw-seg-btn-active:hover{background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-brand-primary,#5b8def));border-color:transparent}',
|
|
@@ -3755,13 +4041,13 @@ window.__ModuleLoader__.load({
|
|
|
3755
4041
|
'.bw-rgb-label{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
|
|
3756
4042
|
'.bw-rgb-input{width:52px;height:26px;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));border-radius:6px;color:inherit;font-size:12px;padding:0 6px;font-family:inherit}',
|
|
3757
4043
|
'.bw-ctx-overlay{position:fixed;inset:0;z-index:40}',
|
|
3758
|
-
'.bw-ctx-menu{position:fixed;min-width:170px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-overlay,rgba(28,28,32,.72)));-webkit-backdrop-filter:var(--dsh-any-blur-card-panels,blur(12px) saturate(1.15));backdrop-filter:var(--dsh-any-blur-card-panels,blur(12px) saturate(1.15));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3));border-radius:8px;padding:4px;box-shadow:0 8px 24px rgba(0,0,0,.35);display:flex;flex-direction:column}',
|
|
3759
|
-
'.bw-ctx-item{display:flex;align-items:center;gap:8px;height:28px;padding:0 10px;border:none;background:transparent;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:12.5px;border-radius:6px;cursor:pointer;text-align:left;font-family:inherit}',
|
|
4044
|
+
'.bw-ctx-menu{position:fixed;min-width:170px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-overlay,rgba(28,28,32,.72)));-webkit-backdrop-filter:var(--dsh-any-blur-card-panels,blur(12px) saturate(1.15));backdrop-filter:var(--dsh-any-blur-card-panels,blur(12px) saturate(1.15));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3));border-radius:var(--dsw-radius-sm,8px);padding:4px;box-shadow:0 8px 24px rgba(0,0,0,.35);display:flex;flex-direction:column}',
|
|
4045
|
+
'.bw-ctx-item{display:flex;align-items:center;gap:8px;height:28px;padding:0 10px;border:none;background:transparent;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:12.5px;border-radius:var(--dsw-radius-sm,6px);cursor:pointer;text-align:left;font-family:inherit}',
|
|
3760
4046
|
'.bw-ctx-item:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14))}',
|
|
3761
4047
|
'.bw-ctx-danger{color:var(--dsw-alias-state-error-primary,#f85149)}',
|
|
3762
4048
|
'.bw-ctx-sep{height:1px;background:var(--dsw-alias-border-l1,rgba(127,127,127,.2));margin:4px 6px}',
|
|
3763
4049
|
'.bw-settings{display:flex;flex-direction:column;gap:6px;max-width:640px}',
|
|
3764
|
-
'.bw-plugin-card{list-style:none;border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.18));border-radius:12px;background:var(--dsw-alias-bg-layer-3,rgba(127,127,127,.05));transition:border-color .16s,background .16s}',
|
|
4050
|
+
'.bw-plugin-card{list-style:none;border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.18));border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-bg-layer-3,rgba(127,127,127,.05));transition:border-color .16s,background .16s}',
|
|
3765
4051
|
'.bw-plugin-card:hover{border-color:var(--dsw-alias-label-dimmed,#7a7a7a)}',
|
|
3766
4052
|
'.bw-plugin-card-open{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1));border-color:var(--dsw-alias-label-dimmed,#7a7a7a)}',
|
|
3767
4053
|
'.bw-plugin-head{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:12px}',
|
|
@@ -3782,7 +4068,7 @@ window.__ModuleLoader__.load({
|
|
|
3782
4068
|
'.bw-switch-on .bw-switch-thumb{transform:translateX(16px)}',
|
|
3783
4069
|
'.bw-plugin-body .bw-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
|
|
3784
4070
|
'.bw-rail{display:flex;flex-direction:column;align-items:center;gap:6px;padding:6px 0}',
|
|
3785
|
-
'.bw-rail-btn{width:36px;height:36px;border:none;background:transparent;border-radius:8px;display:grid;place-items:center;color:var(--dsw-alias-label-secondary,#b8b8b8);cursor:pointer;padding:0}',
|
|
4071
|
+
'.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}',
|
|
3786
4072
|
'.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)}',
|
|
3787
4073
|
'.bw-modal-body{display:flex;flex-direction:column;gap:10px;min-width:300px;max-width:380px;box-sizing:border-box}',
|
|
3788
4074
|
'.bw-field{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#b8b8b8)}',
|
|
@@ -4655,7 +4941,7 @@ window.__ModuleLoader__.load({
|
|
|
4655
4941
|
)
|
|
4656
4942
|
}
|
|
4657
4943
|
|
|
4658
|
-
function WorkspaceRow({ workspace, depth, count, sessionsOpen, onToggle, onStart, onContextMenu, currentInside, dropHalf, dropInto, dragEvents, custStyle, iconMode, pulse, t }) {
|
|
4944
|
+
function WorkspaceRow({ workspace, depth, count, sessionsOpen, onToggle, onStart, onContextMenu, currentInside, dropHalf, dropInto, dragEvents, custStyle, iconMode, pulse, newSessionLabel, newSessionAria, t }) {
|
|
4659
4945
|
const iconEl = iconOf(iconMode, sessionsOpen)
|
|
4660
4946
|
const iconChild = pulse
|
|
4661
4947
|
? E(PulseGlow, { state: pulse }, iconEl || (typeof ui.StateDot === 'function' ? E(ui.StateDot, { state: pulse, size: 10 }) : null))
|
|
@@ -4674,7 +4960,14 @@ window.__ModuleLoader__.load({
|
|
|
4674
4960
|
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle.textShadow && 'bw-pulse-text'), title: workspace.title || workspace.leaf }, workspace.leaf),
|
|
4675
4961
|
count > 0 ? E('span', { className: 'bw-row-count' }, String(count)) : null,
|
|
4676
4962
|
E('span', { className: 'bw-row-actions', onClick: (e) => e.stopPropagation() },
|
|
4677
|
-
E('button', {
|
|
4963
|
+
E('button', {
|
|
4964
|
+
type: 'button',
|
|
4965
|
+
className: 'bw-icon-btn',
|
|
4966
|
+
'aria-label': newSessionLabel || t('session.new'),
|
|
4967
|
+
'aria-keyshortcuts': newSessionAria,
|
|
4968
|
+
title: newSessionLabel || t('session.new'),
|
|
4969
|
+
onClick: (e) => { e.stopPropagation(); onStart() },
|
|
4970
|
+
}, icon('IconPlusOutline16')),
|
|
4678
4971
|
),
|
|
4679
4972
|
)
|
|
4680
4973
|
}
|
|
@@ -4717,7 +5010,7 @@ window.__ModuleLoader__.load({
|
|
|
4717
5010
|
return s === 'ongoing' ? null : s
|
|
4718
5011
|
}
|
|
4719
5012
|
|
|
4720
|
-
function SessionRow({ node, depth, current, onOpen, onContextMenu, now, dropHalf, dragEvents, custStyle, breathing, sessionActions, t }) {
|
|
5013
|
+
function SessionRow({ node, depth, current, onOpen, onContextMenu, now, dropHalf, dragEvents, custStyle, breathing, sessionActions, scheduleSeat, t }) {
|
|
4721
5014
|
const state = sessionStateOf(node)
|
|
4722
5015
|
const status = state === null ? null : {
|
|
4723
5016
|
state,
|
|
@@ -4727,6 +5020,23 @@ window.__ModuleLoader__.load({
|
|
|
4727
5020
|
? (node.running ? t('status.running') : t('status.subagents', { n: node.subagents }))
|
|
4728
5021
|
: t('status.completed'),
|
|
4729
5022
|
}
|
|
5023
|
+
// Ambient Schedule mark, two host generations, exactly one of them live:
|
|
5024
|
+
// · dsh 0.1.7-rc.2+ — the shipped row's leading seat
|
|
5025
|
+
// (`sidebar.session.row.leading`), declared by an entry this seat
|
|
5026
|
+
// shadows, so its occupant is rendered here (see scheduleSeatOf). The
|
|
5027
|
+
// shipped rule holds: the cell belongs to the mark only while the row's
|
|
5028
|
+
// own primary state is idle — a status dot and a seat occupant never
|
|
5029
|
+
// share it — and an archived or blank row keeps the cell empty.
|
|
5030
|
+
// · up to 0.1.7-rc.1 — the active-Schedule list projection, which rc.2
|
|
5031
|
+
// deleted; the legacy badge below still serves those hosts, and its
|
|
5032
|
+
// `!markNode` gate is what keeps the two marks from ever doubling up.
|
|
5033
|
+
const markNode = scheduleSeat && !status && !node.archived && !node.blank
|
|
5034
|
+
? E(QuietBoundary, { key: 'schedule-seat' }, E(ShippedSeatOccupant, {
|
|
5035
|
+
seat: scheduleSeat,
|
|
5036
|
+
ownerProps: { sessionId: node.id },
|
|
5037
|
+
t: scheduleSeat.t,
|
|
5038
|
+
}))
|
|
5039
|
+
: null
|
|
4730
5040
|
// Official mirror surface (dsh 0.1.7 Rows.tsx): pinned rows carry the
|
|
4731
5041
|
// pin marker; archived rows render grayed and never open; the hover
|
|
4732
5042
|
// strip hosts the "..." trigger and the pin/archive quick buttons.
|
|
@@ -4791,10 +5101,12 @@ window.__ModuleLoader__.load({
|
|
|
4791
5101
|
// the settings toggle covers this breathing too.
|
|
4792
5102
|
? E(PulseGlow, { state: 'ongoing' }, E(ui.StateDot, { state: status.state, size: 10 }))
|
|
4793
5103
|
: E(ui.StateDot, { state: status.state, size: 10 }))
|
|
4794
|
-
: E('span', { className: 'bw-dot' }),
|
|
5104
|
+
: (markNode || E('span', { className: 'bw-dot' })),
|
|
4795
5105
|
),
|
|
4796
5106
|
E('span', { className: 'bw-row-label' }, node.leaf || node.title),
|
|
4797
|
-
|
|
5107
|
+
// Legacy projection mark (see markNode): only ever renders where the
|
|
5108
|
+
// shipped seat does not exist, so the row never shows two marks.
|
|
5109
|
+
node.hasActiveSchedule && !markNode
|
|
4798
5110
|
? E('span', { className: 'bw-schedule-badge', role: 'img', 'aria-label': t('schedule.active'), title: t('schedule.active') }, icon('IconAlarmClockOutline16', 14))
|
|
4799
5111
|
: null,
|
|
4800
5112
|
E('span', { className: 'bw-row-time' }, timeLabel(node.updatedAt, now, t)),
|
|
@@ -4854,8 +5166,15 @@ window.__ModuleLoader__.load({
|
|
|
4854
5166
|
return typeof value === 'string' && value !== '' && value !== key ? value : null
|
|
4855
5167
|
}
|
|
4856
5168
|
const filterLabel = official('filterBy.label')
|
|
5169
|
+
const hideLabel = official('viewOptions.hideArchived')
|
|
4857
5170
|
const showLabel = official('viewOptions.showArchived')
|
|
4858
5171
|
const onlyLabel = official('viewOptions.onlyArchived')
|
|
5172
|
+
// rc.2 turns the filter into three explicit radio-style picks (hide /
|
|
5173
|
+
// show / only) and names the default one; pre-rc.2 hosts only ship the
|
|
5174
|
+
// two toggles, where re-picking the selected item returns to the default
|
|
5175
|
+
// view. The presence of the hide label is the era probe, and the icons
|
|
5176
|
+
// follow the same generation split (the rc.2 glyphs are absent earlier).
|
|
5177
|
+
const threeState = hideLabel !== null
|
|
4859
5178
|
const hasFilter = filterLabel !== null && showLabel !== null && onlyLabel !== null
|
|
4860
5179
|
&& typeof onFilterPick === 'function'
|
|
4861
5180
|
const filter = archivedFilter === 'show' || archivedFilter === 'only' ? archivedFilter : 'default'
|
|
@@ -4874,9 +5193,8 @@ window.__ModuleLoader__.load({
|
|
|
4874
5193
|
...(hasFilter ? [
|
|
4875
5194
|
{ type: 'separator', id: 'bw-filter-sep' },
|
|
4876
5195
|
{ type: 'label', id: 'bw-filter-by', text: filterLabel },
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
{ id: 'show-archived', label: showLabel, icon: icon('IconArchiveOutline20', 16) },
|
|
5196
|
+
...(threeState ? [{ id: 'hide-archived', label: hideLabel, icon: icon('IconArchiveOffOutlineRegular', 16) }] : []),
|
|
5197
|
+
{ id: 'show-archived', label: showLabel, icon: icon(threeState ? 'IconQueueOutlineRegular' : 'IconArchiveOutline20', 16) },
|
|
4880
5198
|
{ id: 'only-archived', label: onlyLabel, icon: icon('IconArchiveCheckOutlineRegular', 16) },
|
|
4881
5199
|
] : []),
|
|
4882
5200
|
{ type: 'separator', id: 'bw-wsgroup-sep' },
|
|
@@ -4894,16 +5212,18 @@ window.__ModuleLoader__.load({
|
|
|
4894
5212
|
],
|
|
4895
5213
|
selectedIds: [groupBy, orderBy]
|
|
4896
5214
|
.concat(sessionSlash ? ['session-slash'] : [])
|
|
4897
|
-
.
|
|
4898
|
-
.
|
|
5215
|
+
// rc.2 keeps exactly one filter pick selected (the default one is a
|
|
5216
|
+
// real item); the pre-rc.2 pair only shows a selection when it is on.
|
|
5217
|
+
.concat(filter === 'show' ? ['show-archived'] : filter === 'only' ? ['only-archived'] : (threeState ? ['hide-archived'] : []))
|
|
4899
5218
|
.concat(workspaceGroupMode === 'disk' ? ['ws-disk'] : [])
|
|
4900
5219
|
.concat(workspaceGroupMode === 'disk-slash' ? ['ws-disk-slash'] : [])
|
|
4901
5220
|
.concat(workspaceGroupMode === 'slash' ? ['ws-slash'] : []),
|
|
4902
5221
|
onSelect: (id) => {
|
|
4903
5222
|
if (id === 'workspace' || id === 'workspace-tree' || id === 'flat') { onPick('groupBy', id); setOpen(false); return }
|
|
4904
5223
|
if (id === 'manual' || id === 'updated') { onPick('orderBy', id); setOpen(false); return }
|
|
4905
|
-
if (id === '
|
|
4906
|
-
if (id === '
|
|
5224
|
+
if (id === 'hide-archived') { onFilterPick('default'); setOpen(false); return }
|
|
5225
|
+
if (id === 'show-archived') { onFilterPick(threeState ? 'show' : (filter === 'show' ? 'default' : 'show')); setOpen(false); return }
|
|
5226
|
+
if (id === 'only-archived') { onFilterPick(threeState ? 'only' : (filter === 'only' ? 'default' : 'only')); setOpen(false); return }
|
|
4907
5227
|
if (id === 'ws-disk' || id === 'ws-disk-slash' || id === 'ws-slash') { onWsGroupPick(id.slice(3)); setOpen(false); return }
|
|
4908
5228
|
if (id === 'session-slash') { onToggle('sessionTitleSlash', !sessionSlash); return }
|
|
4909
5229
|
},
|
|
@@ -5573,7 +5893,14 @@ window.__ModuleLoader__.load({
|
|
|
5573
5893
|
const [picking, setPicking] = React.useState(false)
|
|
5574
5894
|
const [errorText, setErrorText] = React.useState(null)
|
|
5575
5895
|
|
|
5576
|
-
|
|
5896
|
+
// Same resolved first-use name as the sidebar (see withDisplayTitles):
|
|
5897
|
+
// the picker must label a Workspace exactly like the tree does.
|
|
5898
|
+
const defaultWorkspaceName = t('workspace.defaultName')
|
|
5899
|
+
const rawItems = snapshot && Array.isArray(snapshot.items) ? snapshot.items : EMPTY_LIST
|
|
5900
|
+
const items = React.useMemo(
|
|
5901
|
+
() => withDisplayTitles(rawItems, defaultWorkspaceName),
|
|
5902
|
+
[rawItems, defaultWorkspaceName],
|
|
5903
|
+
)
|
|
5577
5904
|
// Same construction the sidebar runs (buildTree + optional chain
|
|
5578
5905
|
// compression), so both surfaces answer the same view options.
|
|
5579
5906
|
const tree = React.useMemo(() => {
|
|
@@ -5744,6 +6071,7 @@ window.__ModuleLoader__.load({
|
|
|
5744
6071
|
insertWorkspaceBefore, insertSessionBefore,
|
|
5745
6072
|
useDirectoryFlow,
|
|
5746
6073
|
pickDirectory, listDirectory, createDirectory,
|
|
6074
|
+
shippedBrowserFace, scheduleSeatReader,
|
|
5747
6075
|
officialT,
|
|
5748
6076
|
t,
|
|
5749
6077
|
} = props
|
|
@@ -5753,7 +6081,16 @@ window.__ModuleLoader__.load({
|
|
|
5753
6081
|
return null
|
|
5754
6082
|
}
|
|
5755
6083
|
|
|
5756
|
-
const
|
|
6084
|
+
const storedItems = useWorkspaces(s => s.items)
|
|
6085
|
+
// Official rc.2 first-use naming (see withDisplayTitles): every surface
|
|
6086
|
+
// reads the RESOLVED name, exactly like the shipped browser's own
|
|
6087
|
+
// workspaces projection, so the tree, the group labels and the rename
|
|
6088
|
+
// prefill agree with the rest of the shell.
|
|
6089
|
+
const defaultWorkspaceName = t('workspace.defaultName')
|
|
6090
|
+
const items = React.useMemo(
|
|
6091
|
+
() => withDisplayTitles(storedItems, defaultWorkspaceName),
|
|
6092
|
+
[storedItems, defaultWorkspaceName],
|
|
6093
|
+
)
|
|
5757
6094
|
const phase = useWorkspaces(s => s.phase)
|
|
5758
6095
|
const archivedSessionIds = useWorkspaces(s => s.archivedSessionIds) || []
|
|
5759
6096
|
// Registry-global pin set (dsh 0.1.7): absent on older hosts, which
|
|
@@ -5817,6 +6154,68 @@ window.__ModuleLoader__.load({
|
|
|
5817
6154
|
const archivedSet = React.useMemo(() => new Set(archivedSessionIds), [archivedSessionIds])
|
|
5818
6155
|
const pinnedSet = React.useMemo(() => new Set(pinnedSessionIds), [pinnedSessionIds])
|
|
5819
6156
|
const subCounts = React.useMemo(() => subagentRunningCounts(list ? list.byId : {}), [list ? list.byId : null])
|
|
6157
|
+
|
|
6158
|
+
/* --------------- shipped-browser channel (0.1.7-rc.2) --------------- */
|
|
6159
|
+
// Read per render, never memoized: the shipped entry registers beside the
|
|
6160
|
+
// slot declaration and the ledger must be re-read until it lands.
|
|
6161
|
+
const shippedFace = typeof shippedBrowserFace === 'function' ? shippedBrowserFace() : null
|
|
6162
|
+
const shortcutChannel = shippedFace && shippedFace.hooks ? shippedFace.hooks.workspaceShortcuts || null : null
|
|
6163
|
+
const shortcutCatalog = shippedFace && shippedFace.hooks ? shippedFace.hooks.shortcuts || null : null
|
|
6164
|
+
const shortcutState = useObservableSnapshot(shortcutChannel)
|
|
6165
|
+
const shortcutRows = useObservableSnapshot(shortcutCatalog)
|
|
6166
|
+
const shortcutStateOf = shortcutState && typeof shortcutState === 'object' ? shortcutState : null
|
|
6167
|
+
const searchRequest = shortcutStateOf ? shortcutStateOf.searchRequest : 0
|
|
6168
|
+
const addRequested = !!(shortcutStateOf && shortcutStateOf.addRequested)
|
|
6169
|
+
const forkError = shortcutStateOf ? shortcutStateOf.forkError : null
|
|
6170
|
+
const dismissForkError = shippedFace && typeof shippedFace.dismissForkError === 'function'
|
|
6171
|
+
? shippedFace.dismissForkError
|
|
6172
|
+
: null
|
|
6173
|
+
const closeAddWorkspace = shippedFace && typeof shippedFace.closeAddWorkspace === 'function'
|
|
6174
|
+
? shippedFace.closeAddWorkspace
|
|
6175
|
+
: null
|
|
6176
|
+
const reportDirectoryBusy = shippedFace && typeof shippedFace.setDirectoryBusy === 'function'
|
|
6177
|
+
? shippedFace.setDirectoryBusy
|
|
6178
|
+
: null
|
|
6179
|
+
// The shipped schedule mark's seat occupant (see scheduleSeatOf); absent
|
|
6180
|
+
// whenever the Schedule overlay is off, which is the shipped default.
|
|
6181
|
+
const scheduleSeat = typeof scheduleSeatReader === 'function' ? scheduleSeatReader() : null
|
|
6182
|
+
/** Effective catalog row of one shipped command (current keybinding, label). */
|
|
6183
|
+
const shortcutRowOf = (id) => {
|
|
6184
|
+
const rows = Array.isArray(shortcutRows) ? shortcutRows : EMPTY_LIST
|
|
6185
|
+
for (const row of rows) if (row && row.id === id) return row
|
|
6186
|
+
return null
|
|
6187
|
+
}
|
|
6188
|
+
/**
|
|
6189
|
+
* Feature copy with the command's CURRENT keys appended — the shipped
|
|
6190
|
+
* sidebar shows them in its tooltips (Tooltip shortcutKeys) and this
|
|
6191
|
+
* surface has no tooltip primitive, so the same information rides the
|
|
6192
|
+
* native title/aria-label. Unbound (or an older host without the catalog)
|
|
6193
|
+
* keeps the copy alone.
|
|
6194
|
+
*/
|
|
6195
|
+
const shortcutLabel = (label, id) => {
|
|
6196
|
+
const row = shortcutRowOf(id)
|
|
6197
|
+
const keys = row && Array.isArray(row.keys) ? row.keys : EMPTY_LIST
|
|
6198
|
+
return keys.length > 0 ? label + ' (' + keys.join('') + ')' : label
|
|
6199
|
+
}
|
|
6200
|
+
const shortcutAria = (id) => {
|
|
6201
|
+
const row = shortcutRowOf(id)
|
|
6202
|
+
return row && typeof row.aria === 'string' && row.aria !== '' ? row.aria : undefined
|
|
6203
|
+
}
|
|
6204
|
+
/**
|
|
6205
|
+
* Shipped-browser copy with this plugin's aligned fallback: the official
|
|
6206
|
+
* 'workspace' dictionary is the source of truth (upstream rewording lands
|
|
6207
|
+
* without a release), and a host that predates a key keeps the local copy
|
|
6208
|
+
* instead of printing the key itself.
|
|
6209
|
+
*/
|
|
6210
|
+
const shippedText = (key, fallback) => {
|
|
6211
|
+
if (typeof officialT !== 'function') return fallback
|
|
6212
|
+
try {
|
|
6213
|
+
const value = officialT(key)
|
|
6214
|
+
return typeof value === 'string' && value !== '' && value !== key ? value : fallback
|
|
6215
|
+
} catch (error) {
|
|
6216
|
+
return fallback
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
5820
6219
|
// Current session across both host generations (see mainSessionIdOf).
|
|
5821
6220
|
// `current` belongs in the deps: 0.1.5-rc.x switches it without
|
|
5822
6221
|
// necessarily handing back a new byId, so keying on byId alone would
|
|
@@ -5952,6 +6351,54 @@ window.__ModuleLoader__.load({
|
|
|
5952
6351
|
return renameSession(sessionId, title)
|
|
5953
6352
|
}
|
|
5954
6353
|
|
|
6354
|
+
/* ------------ shipped-browser requests (dsh 0.1.7-rc.2) ------------ */
|
|
6355
|
+
// The shipped shortcut layer resolves session.search / workspace.add into
|
|
6356
|
+
// the shipped browser's private request store, whose only consumer is that
|
|
6357
|
+
// component's effect — shadowed here, so this plugin consumes the same
|
|
6358
|
+
// channel (see shippedBrowserFaceOf). Behavior mirrors the shipped
|
|
6359
|
+
// consumer: the search request opens and focuses the field (this tree
|
|
6360
|
+
// filters locally, which is this plugin's documented search surface), and
|
|
6361
|
+
// the add request runs the same flow as the header button. The focus is
|
|
6362
|
+
// deferred one render because the field mounts with the state flip.
|
|
6363
|
+
const [focusPending, setFocusPending] = React.useState(false)
|
|
6364
|
+
const searchInputRef = React.useRef(null)
|
|
6365
|
+
const startAddFlowRef = React.useRef(null)
|
|
6366
|
+
React.useEffect(() => {
|
|
6367
|
+
if (searchRequest === 0) return
|
|
6368
|
+
if (typeof closeAddWorkspace === 'function') closeAddWorkspace()
|
|
6369
|
+
// The shipped consumer expands the rail first: the field only exists in
|
|
6370
|
+
// the wide header, so the request has to surface the sidebar to land.
|
|
6371
|
+
if (!wide && typeof expandSidebar === 'function') expandSidebar()
|
|
6372
|
+
setSearchOpen(true)
|
|
6373
|
+
setFocusPending(true)
|
|
6374
|
+
}, [searchRequest])
|
|
6375
|
+
React.useEffect(() => {
|
|
6376
|
+
if (!focusPending) return
|
|
6377
|
+
const field = searchInputRef.current
|
|
6378
|
+
if (!field) return
|
|
6379
|
+
try { field.focus({ preventScroll: true }) } catch (error) { field.focus() }
|
|
6380
|
+
setFocusPending(false)
|
|
6381
|
+
}, [focusPending, searchOpen])
|
|
6382
|
+
React.useEffect(() => {
|
|
6383
|
+
if (!addRequested) return
|
|
6384
|
+
// Clear the request first: the flow is asynchronous and a re-render
|
|
6385
|
+
// during it must not start a second one.
|
|
6386
|
+
if (typeof closeAddWorkspace === 'function') closeAddWorkspace()
|
|
6387
|
+
const flow = startAddFlowRef.current
|
|
6388
|
+
if (typeof flow === 'function') flow()
|
|
6389
|
+
}, [addRequested])
|
|
6390
|
+
// The fork notification belongs to the shipped browser's own overlay,
|
|
6391
|
+
// which is shadowed with it; without this the shortcut's async failure
|
|
6392
|
+
// would be silent. Dismissal rides the shipped channel so both surfaces
|
|
6393
|
+
// agree on the notice's lifetime.
|
|
6394
|
+
React.useEffect(() => {
|
|
6395
|
+
if (forkError === null || forkError === undefined) return
|
|
6396
|
+
const timer = setTimeout(() => {
|
|
6397
|
+
if (typeof dismissForkError === 'function') dismissForkError()
|
|
6398
|
+
}, 6000)
|
|
6399
|
+
return () => clearTimeout(timer)
|
|
6400
|
+
}, [forkError ? forkError.seq : null])
|
|
6401
|
+
|
|
5955
6402
|
React.useEffect(() => {
|
|
5956
6403
|
if (!list || !list.byId || typeof renameSession !== 'function') return
|
|
5957
6404
|
if (blankSeenRef.current === null) blankSeenRef.current = new Set()
|
|
@@ -7045,12 +7492,41 @@ window.__ModuleLoader__.load({
|
|
|
7045
7492
|
dragEvents: workspaceId && !session.archived ? sessionDragEvents(session, workspaceId) : undefined,
|
|
7046
7493
|
custStyle: rowStyleOf('session:' + session.id),
|
|
7047
7494
|
sessionActions,
|
|
7495
|
+
scheduleSeat,
|
|
7048
7496
|
breathing: statusPulse,
|
|
7049
7497
|
t,
|
|
7050
7498
|
})
|
|
7051
7499
|
|
|
7500
|
+
/**
|
|
7501
|
+
* rc.2 archived-only rule (official tree.ts groupByWorkspace): that view
|
|
7502
|
+
* lists archives, not the Workspace inventory, so a Workspace whose
|
|
7503
|
+
* visible Sessions are empty contributes no row at all — and a name group
|
|
7504
|
+
* left without one is dropped with it (the shipped browser re-parents a
|
|
7505
|
+
* surviving descendant onto the nearest rendered ancestor; our groups are
|
|
7506
|
+
* derived from the members, so an emptied one simply disappears).
|
|
7507
|
+
* `sessionsOf` already applies the SAME visibility rule the rows render
|
|
7508
|
+
* with, so this can never disagree with the session list below the row.
|
|
7509
|
+
*/
|
|
7510
|
+
const archivedOnly = archivedFilter === 'only'
|
|
7511
|
+
const workspaceVisible = (workspace) => !archivedOnly
|
|
7512
|
+
|| !!(workspace && Array.isArray(workspace.sessionIds) && sessionsOf(workspace).length > 0)
|
|
7513
|
+
const countVisibleAt = (level) => {
|
|
7514
|
+
let total = 0
|
|
7515
|
+
for (const folder of level.folders) {
|
|
7516
|
+
total += folder.kind === 'ws'
|
|
7517
|
+
? (workspaceVisible(folder.workspace) ? 1 : 0)
|
|
7518
|
+
: countVisibleAt(folder)
|
|
7519
|
+
}
|
|
7520
|
+
for (const entry of level.workspaces) {
|
|
7521
|
+
if (workspaceVisible(entry)) total += 1
|
|
7522
|
+
if (entry.sub) total += countVisibleAt(entry.sub)
|
|
7523
|
+
}
|
|
7524
|
+
return total
|
|
7525
|
+
}
|
|
7526
|
+
|
|
7052
7527
|
const renderWorkspaceEntry = (entry, depth, pulse) => {
|
|
7053
7528
|
const { workspace } = entry
|
|
7529
|
+
if (!workspaceVisible(workspace)) return []
|
|
7054
7530
|
const count = countSessionTree(buildSessionTree(sessionsOf(workspace), sessionSlash, sessFolders, workspace.workspaceId))
|
|
7055
7531
|
const rows = [E(WorkspaceRow, {
|
|
7056
7532
|
key: 'ws-' + workspace.workspaceId,
|
|
@@ -7069,6 +7545,10 @@ window.__ModuleLoader__.load({
|
|
|
7069
7545
|
dragEvents: workspaceDragEvents(workspace),
|
|
7070
7546
|
custStyle: rowStyleOf('workspace:' + workspace.workspaceId),
|
|
7071
7547
|
iconMode: (styleEntry('workspace:' + workspace.workspaceId) || {}).icon || 'solid',
|
|
7548
|
+
// Shipped sidebar parity: the row's new-session affordance names the
|
|
7549
|
+
// current binding (Tooltip shortcutKeys upstream, native title here).
|
|
7550
|
+
newSessionLabel: shortcutLabel(t('session.new'), 'session.new'),
|
|
7551
|
+
newSessionAria: shortcutAria('session.new'),
|
|
7072
7552
|
pulse,
|
|
7073
7553
|
t,
|
|
7074
7554
|
})]
|
|
@@ -7092,6 +7572,9 @@ window.__ModuleLoader__.load({
|
|
|
7092
7572
|
|
|
7093
7573
|
const renderPlainFolder = (node, depth) => {
|
|
7094
7574
|
if (node.kind === 'ws') return renderWorkspaceEntry({ workspace: node.workspace }, depth, wsPulseOf(node.workspace))
|
|
7575
|
+
// Archived-only view: a group whose Workspaces all lost their archived
|
|
7576
|
+
// Sessions is as empty as they are (see countVisibleAt).
|
|
7577
|
+
if (archivedOnly && countVisibleAt(node) === 0) return []
|
|
7095
7578
|
// idPath disambiguates same-named name groups living in different
|
|
7096
7579
|
// disk levels; path stays the semantic key (rename prefix logic).
|
|
7097
7580
|
const idPath = node.idPath || node.path
|
|
@@ -7173,7 +7656,19 @@ window.__ModuleLoader__.load({
|
|
|
7173
7656
|
}
|
|
7174
7657
|
const isEmpty = bodyRows.length === 0
|
|
7175
7658
|
if (isEmpty) {
|
|
7176
|
-
|
|
7659
|
+
// rc.2 names the archived-only state and offers the way back
|
|
7660
|
+
// (official EmptySessions): the filter is a mode the user can get
|
|
7661
|
+
// stuck in, so the empty body explains it and leaves in one click.
|
|
7662
|
+
bodyRows = archivedOnly && !searching
|
|
7663
|
+
? [E('div', { key: 'empty', className: 'bw-empty' },
|
|
7664
|
+
E('div', null, shippedText('empty.noneArchived', t('empty.noneArchived'))),
|
|
7665
|
+
E('button', {
|
|
7666
|
+
type: 'button',
|
|
7667
|
+
className: 'bw-empty-action',
|
|
7668
|
+
onClick: () => { if (actions && typeof actions.setArchivedFilter === 'function') actions.setArchivedFilter('default') },
|
|
7669
|
+
}, shippedText('empty.viewOthers', t('empty.viewOthers'))),
|
|
7670
|
+
)]
|
|
7671
|
+
: [E('div', { key: 'empty', className: 'bw-empty' }, searching ? t('empty.search') : (phase === 'pending' ? '…' : t('empty')))]
|
|
7177
7672
|
}
|
|
7178
7673
|
|
|
7179
7674
|
/* ------------------------- context menu ------------------------ */
|
|
@@ -7335,8 +7830,23 @@ window.__ModuleLoader__.load({
|
|
|
7335
7830
|
if (!wide) {
|
|
7336
7831
|
return E('div', { className: 'bw-rail' },
|
|
7337
7832
|
StyleNode(),
|
|
7338
|
-
E('button', {
|
|
7339
|
-
|
|
7833
|
+
E('button', {
|
|
7834
|
+
type: 'button',
|
|
7835
|
+
className: 'bw-rail-btn',
|
|
7836
|
+
'aria-label': shortcutLabel(t('rail.search'), 'session.search'),
|
|
7837
|
+
'aria-keyshortcuts': shortcutAria('session.search'),
|
|
7838
|
+
title: shortcutLabel(t('rail.search'), 'session.search'),
|
|
7839
|
+
onClick: () => { expandSidebar(); setSearchOpen(true); setFocusPending(true) },
|
|
7840
|
+
}, icon('IconSearchOutline16', 18)),
|
|
7841
|
+
flowAvailable ? E('button', {
|
|
7842
|
+
type: 'button',
|
|
7843
|
+
className: 'bw-rail-btn',
|
|
7844
|
+
'aria-label': shortcutLabel(t('rail.add'), 'workspace.add'),
|
|
7845
|
+
'aria-keyshortcuts': shortcutAria('workspace.add'),
|
|
7846
|
+
title: shortcutLabel(t('rail.add'), 'workspace.add'),
|
|
7847
|
+
onClick: () => { expandSidebar(); startAddFlow() },
|
|
7848
|
+
disabled: flowBusy,
|
|
7849
|
+
}, icon('IconProjectAddOutline16', 18)) : null,
|
|
7340
7850
|
)
|
|
7341
7851
|
}
|
|
7342
7852
|
|
|
@@ -7493,7 +8003,14 @@ window.__ModuleLoader__.load({
|
|
|
7493
8003
|
// A `browse` host refuses that pick by design, so the click explains where
|
|
7494
8004
|
// the official in-app flow lives instead of opening a plugin-built copy —
|
|
7495
8005
|
// the shipped dialog cannot be rendered from here (see the capability block).
|
|
8006
|
+
//
|
|
8007
|
+
// The shipped shortcut channel also reports this interaction
|
|
8008
|
+
// (setDirectoryBusy): a second Cmd/Ctrl+O while the chooser is open is
|
|
8009
|
+
// answered with the host's own "already adding" reason instead of
|
|
8010
|
+
// stacking a second picker.
|
|
7496
8011
|
const startAddFlow = () => {
|
|
8012
|
+
if (typeof reportDirectoryBusy === 'function') reportDirectoryBusy(true)
|
|
8013
|
+
const settle = () => { if (typeof reportDirectoryBusy === 'function') reportDirectoryBusy(false) }
|
|
7497
8014
|
Promise.resolve(pickerCapabilityNow())
|
|
7498
8015
|
.then((kind) => {
|
|
7499
8016
|
if (kind === 'browse') { setDialog({ kind: 'add-guide' }); return undefined }
|
|
@@ -7506,7 +8023,11 @@ window.__ModuleLoader__.load({
|
|
|
7506
8023
|
})
|
|
7507
8024
|
})
|
|
7508
8025
|
.catch((reason) => { fail(messageOf(reason)) })
|
|
8026
|
+
.then(settle, settle)
|
|
7509
8027
|
}
|
|
8028
|
+
// The shortcut consumer runs from an effect declared above, after this
|
|
8029
|
+
// definition: the ref is the one stable handle it can call.
|
|
8030
|
+
startAddFlowRef.current = startAddFlow
|
|
7510
8031
|
|
|
7511
8032
|
// v0.18.0 — "drag it OUT" must never land on nothing. The row level
|
|
7512
8033
|
// already covers the workspace row and the root-level session rows; this
|
|
@@ -7530,6 +8051,24 @@ window.__ModuleLoader__.load({
|
|
|
7530
8051
|
|
|
7531
8052
|
return E('div', { className: cls('bw-root', sessionCouldLeaveGroup && 'bw-root-drop-out'), ...rootDropEvents },
|
|
7532
8053
|
StyleNode(),
|
|
8054
|
+
// The shipped fork notice rides the shipped browser's overlay, which is
|
|
8055
|
+
// shadowed with it: the shortcut's async failure would otherwise be
|
|
8056
|
+
// silent. Copy follows the shipped dictionary, the lifetime follows the
|
|
8057
|
+
// shipped channel (the effect above dismisses it).
|
|
8058
|
+
(forkError && typeof forkError === 'object')
|
|
8059
|
+
? E('div', { className: 'bw-toast', role: 'status' },
|
|
8060
|
+
E('span', { className: 'bw-toast-text' }, forkError.reason === 'unavailable'
|
|
8061
|
+
? shippedText('shortcut.noCompletedTurn', t('shortcut.noCompletedTurn'))
|
|
8062
|
+
: shippedText('shortcut.forkFailed', t('shortcut.forkFailed'))),
|
|
8063
|
+
E('button', {
|
|
8064
|
+
type: 'button',
|
|
8065
|
+
className: 'bw-toast-close',
|
|
8066
|
+
'aria-label': t('close'),
|
|
8067
|
+
title: t('close'),
|
|
8068
|
+
onClick: () => { if (typeof dismissForkError === 'function') dismissForkError() },
|
|
8069
|
+
}, icon('IconCloseOutline16', 12)),
|
|
8070
|
+
)
|
|
8071
|
+
: null,
|
|
7533
8072
|
E('div', { className: 'bw-header' },
|
|
7534
8073
|
E('div', { className: 'bw-header-title' }, t('title')),
|
|
7535
8074
|
// Group discoverability: an ICON entry point (v0.17.0 — it used to be
|
|
@@ -7545,6 +8084,7 @@ window.__ModuleLoader__.load({
|
|
|
7545
8084
|
...groupDropEvents,
|
|
7546
8085
|
}, icon('IconFolderOpen16', 16)) : null,
|
|
7547
8086
|
(searchOpen || query !== '') ? E('input', {
|
|
8087
|
+
ref: searchInputRef,
|
|
7548
8088
|
className: 'bw-input',
|
|
7549
8089
|
style: { width: 130, flex: 'none' },
|
|
7550
8090
|
value: query,
|
|
@@ -7567,8 +8107,23 @@ window.__ModuleLoader__.load({
|
|
|
7567
8107
|
onToggle: (key, value) => { if (key === 'sessionTitleSlash') actions.setSessionTitleSlash(value) },
|
|
7568
8108
|
t,
|
|
7569
8109
|
}),
|
|
7570
|
-
E('button', {
|
|
7571
|
-
|
|
8110
|
+
E('button', {
|
|
8111
|
+
type: 'button',
|
|
8112
|
+
className: 'bw-icon-btn',
|
|
8113
|
+
'aria-label': shortcutLabel(t('search.placeholder'), 'session.search'),
|
|
8114
|
+
'aria-keyshortcuts': shortcutAria('session.search'),
|
|
8115
|
+
title: shortcutLabel(t('search.placeholder'), 'session.search'),
|
|
8116
|
+
onClick: () => setSearchOpen(v => !v),
|
|
8117
|
+
}, icon('IconSearchOutline16')),
|
|
8118
|
+
flowAvailable ? E('button', {
|
|
8119
|
+
type: 'button',
|
|
8120
|
+
className: 'bw-icon-btn',
|
|
8121
|
+
'aria-label': shortcutLabel(t('add'), 'workspace.add'),
|
|
8122
|
+
'aria-keyshortcuts': shortcutAria('workspace.add'),
|
|
8123
|
+
title: shortcutLabel(t('add'), 'workspace.add'),
|
|
8124
|
+
onClick: startAddFlow,
|
|
8125
|
+
disabled: flowBusy,
|
|
8126
|
+
}, icon('IconProjectAddOutline16')) : null,
|
|
7572
8127
|
),
|
|
7573
8128
|
E('div', { ref: treeRef, className: 'bw-tree', role: 'tree', 'aria-label': t('title') }, bodyRows),
|
|
7574
8129
|
dialogElement,
|
|
@@ -7733,6 +8288,25 @@ window.__ModuleLoader__.load({
|
|
|
7733
8288
|
// Official 'workspace' dictionary translate (menu copy follows upstream);
|
|
7734
8289
|
// null when the bind failed — the menu then uses the aligned bw copy.
|
|
7735
8290
|
officialT,
|
|
8291
|
+
// The shipped sidebar's OWN face (dsh 0.1.7-rc.2): the shortcut request
|
|
8292
|
+
// channel and the catalog of effective keybindings. Both belong to the
|
|
8293
|
+
// entry this seat shadows, so they are read off the ledger and consumed
|
|
8294
|
+
// here (see shippedBrowserFaceOf) — otherwise Cmd/Ctrl+K and Cmd/Ctrl+O
|
|
8295
|
+
// would be consumed by the shipped command with no consumer left.
|
|
8296
|
+
shippedBrowserFace: () => shippedBrowserFaceOf(slots, BetterBrowser),
|
|
8297
|
+
// The shipped schedule mark's seat occupant (dsh 0.1.7-rc.2), rendered
|
|
8298
|
+
// inside our own row (see scheduleSeatOf); its copy is bound to the
|
|
8299
|
+
// occupant's OWN dictionary namespace, like the slot renderer would.
|
|
8300
|
+
scheduleSeatReader: () => {
|
|
8301
|
+
if (typeof React.useSyncExternalStore !== 'function') return null
|
|
8302
|
+
const seat = scheduleSeatOf(slots)
|
|
8303
|
+
if (!seat) return null
|
|
8304
|
+
let seatT = null
|
|
8305
|
+
if (seat.locale && ctx.locale && typeof ctx.locale.bind === 'function') {
|
|
8306
|
+
try { seatT = ctx.locale.bind(seat.locale) } catch (error) { seatT = null }
|
|
8307
|
+
}
|
|
8308
|
+
return { component: seat.component, face: seat.face, t: seatT }
|
|
8309
|
+
},
|
|
7736
8310
|
startSession: (workspaceId) => { uiWorkspace.startSession(workspaceId) },
|
|
7737
8311
|
open: (sessionId) => { uiWorkspace.openSession(sessionId) },
|
|
7738
8312
|
searchSessions,
|