dsh-ide-git 0.12.1 → 0.13.1
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 +3 -2
- package/src/client.js +414 -44
- package/src/index.js +5 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
简体中文 | [English](README_EN.md)
|
|
6
6
|
|
|
7
|
-
> 给 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 的侧边栏装一个 **IDE 级的 Git 工具窗口**——左边分支树、中间提交图谱、右边变更与提交详情,操作方式对齐 JetBrains 系 IDE 的 Git 面板;以 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 原生 Tab
|
|
7
|
+
> 给 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 的侧边栏装一个 **IDE 级的 Git 工具窗口**——左边分支树、中间提交图谱、右边变更与提交详情,操作方式对齐 JetBrains 系 IDE 的 Git 面板;以 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 原生 Tab 的形式注册,右侧栏与底部面板都能用;v0.13.0 起还可挂在**主对话区**(「对话 / 轨迹」旁边的 Git 标签页,设置里可关)。
|
|
8
8
|
|
|
9
9
|
DSH 自带的 Git 面板覆盖「暂存 / 提交 / 还原 / 看历史」;`dsh-ide-git` 补齐 IDE 用户习惯的那一层:**分支树 + 右键分支操作 + 提交图谱 + 提交详情 + 变更列表 + 提交框**。
|
|
10
10
|
|
|
@@ -64,6 +64,7 @@ DSH 自带的 Git 面板覆盖「暂存 / 提交 / 还原 / 看历史」;`dsh-id
|
|
|
64
64
|
|---|---|---|
|
|
65
65
|
| **dsh-better-sidebar**(底座,可选) | 装了它 | 它的 Tab 系统:右侧栏里的原生 Tab **加上底部工作台**;它的设置页 `侧边卡片` 里能看到本插件的卡片 |
|
|
66
66
|
| **DSH 原生右侧栏** | 没装底座时自动接管 | 右侧栏 Guide 页里的 **Git** 胶囊 → 点开就是同一个面板 |
|
|
67
|
+
| **主对话区标签页**(v0.13.0 起) | 总是可用(可在设置里关闭) | 会话顶部「对话 / 轨迹」旁多一枚 **Git** 标签,点它整块主对话区变成 Git 工具窗口;开关在插件设置 →「面板放置」 |
|
|
67
68
|
|
|
68
69
|
底座在场时以它为准(它还带底部工作台);底座**晚到**会顶掉原生注册再挂到它那边。所以**只装本插件也能用**,没有任何前置要求。
|
|
69
70
|
|
package/README_EN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[简体中文](README.md) | English
|
|
6
6
|
|
|
7
|
-
> An **IDE-grade Git tool window** for the [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) sidebar — branch tree on the left, commit graph in the middle, changes and commit details on the right, with JetBrains-style actions. Registered as a native [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) tab, usable in both the native right sidebar and the bottom workbench.
|
|
7
|
+
> An **IDE-grade Git tool window** for the [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) sidebar — branch tree on the left, commit graph in the middle, changes and commit details on the right, with JetBrains-style actions. Registered as a native [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) tab, usable in both the native right sidebar and the bottom workbench; since v0.13.0 also as a **conversation-area tab** (a Git tab beside Chat/Trajectory, switchable off in settings).
|
|
8
8
|
|
|
9
9
|
DSH's built-in Git panel covers stage / commit / revert / history. `dsh-ide-git` adds the layer IDE users expect: a **branch tree with a full context menu**, a **commit graph**, **commit details with per-file diffs**, a **grouped changes list** and a commit box.
|
|
10
10
|
|
|
@@ -56,6 +56,7 @@ The plugin registers once; which surface it lands on is the **host's** decision,
|
|
|
56
56
|
|---|---|---|
|
|
57
57
|
| **dsh-better-sidebar** (optional) | whenever it is installed | its tab system: the tab in the right sidebar **plus the bottom workbench**; the plugin also shows up as a card in its settings page |
|
|
58
58
|
| **DSH's own right sidebar** | automatically, when better-sidebar is absent | a **Git** capsule on the sidebar's guide page — the very same panel |
|
|
59
|
+
| **Conversation-area tab** (since v0.13.0) | always on (switchable in settings) | a **Git** tab beside Chat/Trajectory at the top of the conversation — clicking it turns the whole conversation area into the Git tool window; the switch lives in the plugin settings under Placement |
|
|
59
60
|
|
|
60
61
|
better-sidebar wins while it is there (it also carries the bottom workbench); a late-arriving better-sidebar takes the native registration down and hosts the tab itself. Installing this plugin alone is fully supported — no prerequisite.
|
|
61
62
|
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-ide-git",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "IDE 级 Git 操作面板:分支树 + 提交图谱 + 变更/暂存/提交 + 提交详情。装了 dsh-better-sidebar 时挂成它的原生 tab(右侧栏 + 底部工作台);没装时直接挂 DSH 原生右侧栏,单独安装即可用。JetBrains 风格 Git 工具窗口 for DeepSeek Harness.",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ide-git",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "DSH plugin: an IDE-grade Git tool window as a native dsh-better-sidebar tab — branch tree, commit graph, changes, commit details, JetBrains-style actions, in the right sidebar and the bottom panel. | DSH 插件:IDE 级 Git 工具窗口,以 dsh-better-sidebar 原生 Tab 挂载——分支树 / 提交图谱 / 变更与提交详情 / JetBrains 风格操作,右侧栏与底部面板双布局自适应。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"inject": [
|
|
30
30
|
"@deepseek-ai/dsh-client-locale",
|
|
31
31
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
32
|
-
"@deepseek-ai/dsh-client-modules"
|
|
32
|
+
"@deepseek-ai/dsh-client-modules",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
33
34
|
],
|
|
34
35
|
"platform": "web"
|
|
35
36
|
}
|
package/src/client.js
CHANGED
|
@@ -27,6 +27,7 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const useRef = React.useRef
|
|
28
28
|
const useCallback = React.useCallback
|
|
29
29
|
const useLayoutEffect = React.useLayoutEffect
|
|
30
|
+
const memo = React.memo
|
|
30
31
|
|
|
31
32
|
/* API base is MOUNT-RELATIVE (no leading slash). dsh 0.1.7 serves the
|
|
32
33
|
shell with <base href="./">, so document.baseURI is the mount the page
|
|
@@ -160,7 +161,10 @@ window.__ModuleLoader__.load({
|
|
|
160
161
|
'rail.settings': '动作条设置',
|
|
161
162
|
'rail.openSettings': '打开插件设置',
|
|
162
163
|
'settings.card.title': 'Git 工具窗口设置',
|
|
163
|
-
'settings.card.desc': '
|
|
164
|
+
'settings.card.desc': '面板放置、动作条按钮与 AI 提交信息',
|
|
165
|
+
'settings.placement.title': '面板放置',
|
|
166
|
+
'settings.placement.tab': '在主对话区显示 Git 标签页',
|
|
167
|
+
'settings.placement.tabHint': '关闭后仍可从右侧栏或底部面板打开。',
|
|
164
168
|
'settings.rail.title': '动作条按钮',
|
|
165
169
|
'settings.rail.hint': '勾选要显示在动作条上的动作;顺序固定为默认顺序,取消勾选即隐藏。',
|
|
166
170
|
'settings.rail.unavailable': '当前宿主没有设置服务,请用面板内的「动作条设置」。',
|
|
@@ -348,7 +352,10 @@ window.__ModuleLoader__.load({
|
|
|
348
352
|
'rail.settings': 'Action bar settings',
|
|
349
353
|
'rail.openSettings': 'Open plugin settings',
|
|
350
354
|
'settings.card.title': 'Git tool window settings',
|
|
351
|
-
'settings.card.desc': '
|
|
355
|
+
'settings.card.desc': 'Placement, action rail buttons and AI commit message',
|
|
356
|
+
'settings.placement.title': 'Placement',
|
|
357
|
+
'settings.placement.tab': 'Show the Git tab in the conversation area',
|
|
358
|
+
'settings.placement.tabHint': 'Off, the panel stays reachable from the right sidebar and the bottom panel.',
|
|
352
359
|
'settings.rail.title': 'Action rail buttons',
|
|
353
360
|
'settings.rail.hint': 'Tick the actions shown on the rail. The order is fixed; unticking one hides it.',
|
|
354
361
|
'settings.rail.unavailable': 'This host has no settings service; use the in-panel “Action rail settings”.',
|
|
@@ -544,7 +551,10 @@ window.__ModuleLoader__.load({
|
|
|
544
551
|
'rail.settings': '動作列設定',
|
|
545
552
|
'rail.openSettings': '開啟外掛設定',
|
|
546
553
|
'settings.card.title': 'Git 工具視窗設定',
|
|
547
|
-
'settings.card.desc': '
|
|
554
|
+
'settings.card.desc': '面板放置、動作列按鈕與 AI 提交訊息',
|
|
555
|
+
'settings.placement.title': '面板放置',
|
|
556
|
+
'settings.placement.tab': '喺主對話區顯示 Git 分頁',
|
|
557
|
+
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
548
558
|
'settings.rail.title': '動作列按鈕',
|
|
549
559
|
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
550
560
|
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
@@ -732,7 +742,10 @@ window.__ModuleLoader__.load({
|
|
|
732
742
|
'rail.settings': '動作列設定',
|
|
733
743
|
'rail.openSettings': '開啟外掛設定',
|
|
734
744
|
'settings.card.title': 'Git 工具視窗設定',
|
|
735
|
-
'settings.card.desc': '
|
|
745
|
+
'settings.card.desc': '面板放置、動作列按鈕與 AI 提交訊息',
|
|
746
|
+
'settings.placement.title': '面板放置',
|
|
747
|
+
'settings.placement.tab': '在主對話區顯示 Git 分頁',
|
|
748
|
+
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
736
749
|
'settings.rail.title': '動作列按鈕',
|
|
737
750
|
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
738
751
|
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
@@ -920,7 +933,10 @@ window.__ModuleLoader__.load({
|
|
|
920
933
|
'rail.settings': '動作列設定',
|
|
921
934
|
'rail.openSettings': '開啟外掛設定',
|
|
922
935
|
'settings.card.title': 'Git 工具視窗設定',
|
|
923
|
-
'settings.card.desc': '
|
|
936
|
+
'settings.card.desc': '面板放置、動作列按鈕與 AI 提交訊息',
|
|
937
|
+
'settings.placement.title': '面板放置',
|
|
938
|
+
'settings.placement.tab': '喺主對話區顯示 Git 分頁',
|
|
939
|
+
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
924
940
|
'settings.rail.title': '動作列按鈕',
|
|
925
941
|
'settings.rail.hint': '勾選要顯示在動作列上的動作;順序固定,取消勾選即隱藏。',
|
|
926
942
|
'settings.rail.unavailable': '目前宿主沒有設定服務,請用面板內的「動作條設定」。',
|
|
@@ -1108,7 +1124,10 @@ window.__ModuleLoader__.load({
|
|
|
1108
1124
|
'rail.settings': 'アクションバーの設定',
|
|
1109
1125
|
'rail.openSettings': 'プラグイン設定を開く',
|
|
1110
1126
|
'settings.card.title': 'Gitツールウィンドウの設定',
|
|
1111
|
-
'settings.card.desc': '
|
|
1127
|
+
'settings.card.desc': 'パネル配置、アクションレールのボタンと AI コミットメッセージ',
|
|
1128
|
+
'settings.placement.title': 'パネル配置',
|
|
1129
|
+
'settings.placement.tab': '会話エリアに Git タブを表示',
|
|
1130
|
+
'settings.placement.tabHint': 'オフにしても右サイドバーと下部パネルから開けます。',
|
|
1112
1131
|
'settings.rail.title': 'アクションレールのボタン',
|
|
1113
1132
|
'settings.rail.hint': 'レールに表示する操作にチェックを入れてください。順序は固定で、チェックを外すと非表示になります。',
|
|
1114
1133
|
'settings.rail.unavailable': 'このホストには設定サービスがないため、パネル内の「アクションレール設定」を使ってください。',
|
|
@@ -1296,7 +1315,10 @@ window.__ModuleLoader__.load({
|
|
|
1296
1315
|
'rail.settings': '작업 표시줄 설정',
|
|
1297
1316
|
'rail.openSettings': '플러그인 설정 열기',
|
|
1298
1317
|
'settings.card.title': 'Git 도구 창 설정',
|
|
1299
|
-
'settings.card.desc': '액션 레일 버튼 및 AI 커밋 메시지',
|
|
1318
|
+
'settings.card.desc': '패널 배치, 액션 레일 버튼 및 AI 커밋 메시지',
|
|
1319
|
+
'settings.placement.title': '패널 배치',
|
|
1320
|
+
'settings.placement.tab': '대화 영역에 Git 탭 표시',
|
|
1321
|
+
'settings.placement.tabHint': '꺼도 오른쪽 사이드바와 하단 패널에서 열 수 있습니다.',
|
|
1300
1322
|
'settings.rail.title': '액션 레일 버튼',
|
|
1301
1323
|
'settings.rail.hint': '레일에 표시할 동작을 선택하세요. 순서는 고정이며 선택을 해제하면 숨겨집니다.',
|
|
1302
1324
|
'settings.rail.unavailable': '이 호스트에는 설정 서비스가 없으므로 패널의 "액션 레일 설정"을 사용하세요.',
|
|
@@ -1484,7 +1506,10 @@ window.__ModuleLoader__.load({
|
|
|
1484
1506
|
'rail.settings': 'Einstellungen der Aktionsleiste',
|
|
1485
1507
|
'rail.openSettings': 'Plugin-Einstellungen öffnen',
|
|
1486
1508
|
'settings.card.title': 'Git-Werkzeugfenster-Einstellungen',
|
|
1487
|
-
'settings.card.desc': 'Aktionsleisten-Schaltflächen und KI-Commit-Nachricht',
|
|
1509
|
+
'settings.card.desc': 'Panel-Platzierung, Aktionsleisten-Schaltflächen und KI-Commit-Nachricht',
|
|
1510
|
+
'settings.placement.title': 'Panel-Platzierung',
|
|
1511
|
+
'settings.placement.tab': 'Git-Tab im Gesprächsbereich anzeigen',
|
|
1512
|
+
'settings.placement.tabHint': 'Ausgeschaltet bleibt das Panel über rechte Seitenleiste und unteres Panel erreichbar.',
|
|
1488
1513
|
'settings.rail.title': 'Schaltflächen der Aktionsleiste',
|
|
1489
1514
|
'settings.rail.hint': 'Die auf der Leiste sichtbaren Aktionen ankreuzen. Die Reihenfolge ist fest; abwählen blendet eine aus.',
|
|
1490
1515
|
'settings.rail.unavailable': 'Dieser Host hat keinen Einstellungsdienst; nutze die „Aktionsleisten-Einstellungen“ im Panel.',
|
|
@@ -1672,7 +1697,10 @@ window.__ModuleLoader__.load({
|
|
|
1672
1697
|
'rail.settings': 'Réglages de la barre d\'actions',
|
|
1673
1698
|
'rail.openSettings': 'Ouvrir les réglages du plugin',
|
|
1674
1699
|
'settings.card.title': 'Paramètres de la fenêtre d’outils Git',
|
|
1675
|
-
'settings.card.desc': '
|
|
1700
|
+
'settings.card.desc': 'Placement du panneau, boutons de la barre d’actions et message de commit par IA',
|
|
1701
|
+
'settings.placement.title': 'Placement du panneau',
|
|
1702
|
+
'settings.placement.tab': 'Afficher l’onglet Git dans la zone de conversation',
|
|
1703
|
+
'settings.placement.tabHint': 'Désactivé, le panneau reste accessible depuis la barre latérale droite et le panneau inférieur.',
|
|
1676
1704
|
'settings.rail.title': 'Boutons de la barre d’actions',
|
|
1677
1705
|
'settings.rail.hint': 'Cochez les actions affichées sur la barre. L’ordre est fixe ; décocher masque une action.',
|
|
1678
1706
|
'settings.rail.unavailable': 'Cet hôte n’a pas de service de réglages ; utilisez « Réglages de la barre d’actions » dans le panneau.',
|
|
@@ -1860,7 +1888,10 @@ window.__ModuleLoader__.load({
|
|
|
1860
1888
|
'rail.settings': 'Настройки панели действий',
|
|
1861
1889
|
'rail.openSettings': 'Открыть настройки плагина',
|
|
1862
1890
|
'settings.card.title': 'Настройки окна инструментов Git',
|
|
1863
|
-
'settings.card.desc': '
|
|
1891
|
+
'settings.card.desc': 'Размещение панели, кнопки панели действий и сообщение коммита от ИИ',
|
|
1892
|
+
'settings.placement.title': 'Размещение панели',
|
|
1893
|
+
'settings.placement.tab': 'Показывать вкладку Git в области диалога',
|
|
1894
|
+
'settings.placement.tabHint': 'В выключенном виде панель доступна из правой боковой панели и нижней панели.',
|
|
1864
1895
|
'settings.rail.title': 'Кнопки панели действий',
|
|
1865
1896
|
'settings.rail.hint': 'Отметьте действия, показываемые на панели. Порядок фиксирован; снятие флажка скрывает действие.',
|
|
1866
1897
|
'settings.rail.unavailable': 'На этом хосте нет службы настроек; используйте «Настройки панели действий» в панели.',
|
|
@@ -2048,7 +2079,10 @@ window.__ModuleLoader__.load({
|
|
|
2048
2079
|
'rail.settings': 'Configurações da barra de ações',
|
|
2049
2080
|
'rail.openSettings': 'Abrir configurações do plugin',
|
|
2050
2081
|
'settings.card.title': 'Configurações da janela de ferramentas Git',
|
|
2051
|
-
'settings.card.desc': '
|
|
2082
|
+
'settings.card.desc': 'Posição do painel, botões da barra de ações e mensagem de commit por IA',
|
|
2083
|
+
'settings.placement.title': 'Posição do painel',
|
|
2084
|
+
'settings.placement.tab': 'Mostrar a aba Git na área de conversa',
|
|
2085
|
+
'settings.placement.tabHint': 'Desligado, o painel continua disponível na barra lateral direita e no painel inferior.',
|
|
2052
2086
|
'settings.rail.title': 'Botões da barra de ações',
|
|
2053
2087
|
'settings.rail.hint': 'Marque as ações exibidas na barra. A ordem é fixa; desmarcar oculta uma ação.',
|
|
2054
2088
|
'settings.rail.unavailable': 'Este host não tem serviço de configurações; use «Configurações da barra de ações» no painel.',
|
|
@@ -2236,7 +2270,10 @@ window.__ModuleLoader__.load({
|
|
|
2236
2270
|
'rail.settings': 'Impostazioni della barra delle azioni',
|
|
2237
2271
|
'rail.openSettings': 'Apri le impostazioni del plugin',
|
|
2238
2272
|
'settings.card.title': 'Impostazioni della finestra strumenti Git',
|
|
2239
|
-
'settings.card.desc': '
|
|
2273
|
+
'settings.card.desc': 'Posizione del pannello, pulsanti della barra azioni e messaggio di commit con IA',
|
|
2274
|
+
'settings.placement.title': 'Posizione del pannello',
|
|
2275
|
+
'settings.placement.tab': 'Mostra la scheda Git nell’area conversazione',
|
|
2276
|
+
'settings.placement.tabHint': 'Da spento, il pannello resta raggiungibile dalla barra laterale destra e dal pannello inferiore.',
|
|
2240
2277
|
'settings.rail.title': 'Pulsanti della barra azioni',
|
|
2241
2278
|
'settings.rail.hint': 'Seleziona le azioni mostrate sulla barra. L’ordine è fisso; deselezionando se ne nasconde una.',
|
|
2242
2279
|
'settings.rail.unavailable': 'Questo host non ha un servizio di impostazioni; usa «Impostazioni barra azioni» nel pannello.',
|
|
@@ -2424,7 +2461,10 @@ window.__ModuleLoader__.load({
|
|
|
2424
2461
|
'rail.settings': 'Instellingen van de actiebalk',
|
|
2425
2462
|
'rail.openSettings': 'Plugin-instellingen openen',
|
|
2426
2463
|
'settings.card.title': 'Instellingen van het Git-werkvenster',
|
|
2427
|
-
'settings.card.desc': '
|
|
2464
|
+
'settings.card.desc': 'Paneelplaatsing, knoppen van de actiebalk en AI-commitbericht',
|
|
2465
|
+
'settings.placement.title': 'Paneelplaatsing',
|
|
2466
|
+
'settings.placement.tab': 'Git-tabblad in het gespreksgebied tonen',
|
|
2467
|
+
'settings.placement.tabHint': 'Uitgeschakeld blijft het paneel bereikbaar via de rechterzijbalk en het onderpaneel.',
|
|
2428
2468
|
'settings.rail.title': 'Knoppen van de actiebalk',
|
|
2429
2469
|
'settings.rail.hint': 'Vink de acties aan die op de balk verschijnen. De volgorde ligt vast; uitvinken verbergt er een.',
|
|
2430
2470
|
'settings.rail.unavailable': 'Deze host heeft geen instellingenservice; gebruik "Actiebalk-instellingen" in het paneel.',
|
|
@@ -2612,7 +2652,10 @@ window.__ModuleLoader__.load({
|
|
|
2612
2652
|
'rail.settings': 'Ustawienia paska działań',
|
|
2613
2653
|
'rail.openSettings': 'Otwórz ustawienia wtyczki',
|
|
2614
2654
|
'settings.card.title': 'Ustawienia okna narzędzi Git',
|
|
2615
|
-
'settings.card.desc': '
|
|
2655
|
+
'settings.card.desc': 'Rozmieszczenie panelu, przyciski paska akcji i wiadomość commita od AI',
|
|
2656
|
+
'settings.placement.title': 'Rozmieszczenie panelu',
|
|
2657
|
+
'settings.placement.tab': 'Pokazuj kartę Git w obszarze rozmowy',
|
|
2658
|
+
'settings.placement.tabHint': 'Po wyłączeniu panel pozostaje dostępny z prawego paska bocznego i dolnego panelu.',
|
|
2616
2659
|
'settings.rail.title': 'Przyciski paska akcji',
|
|
2617
2660
|
'settings.rail.hint': 'Zaznacz akcje widoczne na pasku. Kolejność jest stała; odznaczenie ukrywa akcję.',
|
|
2618
2661
|
'settings.rail.unavailable': 'Ten host nie ma usługi ustawień; użyj „Ustawienia paska akcji” w panelu.',
|
|
@@ -2800,7 +2843,10 @@ window.__ModuleLoader__.load({
|
|
|
2800
2843
|
'rail.settings': 'Inställningar för åtgärdsfältet',
|
|
2801
2844
|
'rail.openSettings': 'Öppna plugininställningar',
|
|
2802
2845
|
'settings.card.title': 'Inställningar för Git-verktygsfönstret',
|
|
2803
|
-
'settings.card.desc': '
|
|
2846
|
+
'settings.card.desc': 'Panelplacering, knappar i åtgärdsfältet och AI-commitmeddelande',
|
|
2847
|
+
'settings.placement.title': 'Panelplacering',
|
|
2848
|
+
'settings.placement.tab': 'Visa Git-fliken i konversationsytan',
|
|
2849
|
+
'settings.placement.tabHint': 'Avstängd finns panelen kvar via höger sidopanel och nedre panelen.',
|
|
2804
2850
|
'settings.rail.title': 'Knappar i åtgärdsfältet',
|
|
2805
2851
|
'settings.rail.hint': 'Markera de åtgärder som visas i fältet. Ordningen är fast; avmarkering döljer en.',
|
|
2806
2852
|
'settings.rail.unavailable': 'Denna värd saknar inställningstjänst; använd ”Åtgärdsfältets inställningar” i panelen.',
|
|
@@ -2988,7 +3034,10 @@ window.__ModuleLoader__.load({
|
|
|
2988
3034
|
'rail.settings': 'Eylem çubuğu ayarları',
|
|
2989
3035
|
'rail.openSettings': 'Eklenti ayarlarını aç',
|
|
2990
3036
|
'settings.card.title': 'Git araç penceresi ayarları',
|
|
2991
|
-
'settings.card.desc': '
|
|
3037
|
+
'settings.card.desc': 'Panel yerleşimi, eylem çubuğu düğmeleri ve AI commit mesajı',
|
|
3038
|
+
'settings.placement.title': 'Panel yerleşimi',
|
|
3039
|
+
'settings.placement.tab': 'Konuşma alanında Git sekmesini göster',
|
|
3040
|
+
'settings.placement.tabHint': 'Kapatıldığında panel sağ kenar çubuğundan ve alt panelden açılabilir.',
|
|
2992
3041
|
'settings.rail.title': 'Eylem çubuğu düğmeleri',
|
|
2993
3042
|
'settings.rail.hint': 'Çubukta gösterilecek eylemleri işaretleyin. Sıra sabittir; işareti kaldırmak eylemi gizler.',
|
|
2994
3043
|
'settings.rail.unavailable': 'Bu ana bilgisayarda ayar hizmeti yok; paneldeki "Eylem çubuğu ayarları"nı kullanın.',
|
|
@@ -3176,7 +3225,10 @@ window.__ModuleLoader__.load({
|
|
|
3176
3225
|
'rail.settings': 'Pengaturan bilah tindakan',
|
|
3177
3226
|
'rail.openSettings': 'Buka pengaturan plugin',
|
|
3178
3227
|
'settings.card.title': 'Pengaturan jendela alat Git',
|
|
3179
|
-
'settings.card.desc': '
|
|
3228
|
+
'settings.card.desc': 'Penempatan panel, tombol bilah aksi dan pesan commit AI',
|
|
3229
|
+
'settings.placement.title': 'Penempatan panel',
|
|
3230
|
+
'settings.placement.tab': 'Tampilkan tab Git di area percakapan',
|
|
3231
|
+
'settings.placement.tabHint': 'Dimatikan, panel tetap bisa dibuka dari bilah sisi kanan dan panel bawah.',
|
|
3180
3232
|
'settings.rail.title': 'Tombol bilah aksi',
|
|
3181
3233
|
'settings.rail.hint': 'Centang aksi yang ditampilkan di bilah. Urutannya tetap; menghapus centang menyembunyikannya.',
|
|
3182
3234
|
'settings.rail.unavailable': 'Host ini tidak punya layanan pengaturan; gunakan "Pengaturan bilah aksi" di panel.',
|
|
@@ -3364,7 +3416,10 @@ window.__ModuleLoader__.load({
|
|
|
3364
3416
|
'rail.settings': 'Cài đặt thanh thao tác',
|
|
3365
3417
|
'rail.openSettings': 'Mở cài đặt plugin',
|
|
3366
3418
|
'settings.card.title': 'Cài đặt cửa sổ công cụ Git',
|
|
3367
|
-
'settings.card.desc': '
|
|
3419
|
+
'settings.card.desc': 'Vị trí bảng, nút thanh tác vụ và thông điệp commit do AI viết',
|
|
3420
|
+
'settings.placement.title': 'Vị trí bảng',
|
|
3421
|
+
'settings.placement.tab': 'Hiện thẻ Git trong vùng hội thoại',
|
|
3422
|
+
'settings.placement.tabHint': 'Tắt rồi, bảng vẫn mở được từ thanh bên phải và bảng dưới.',
|
|
3368
3423
|
'settings.rail.title': 'Nút thanh tác vụ',
|
|
3369
3424
|
'settings.rail.hint': 'Chọn các tác vụ hiển thị trên thanh. Thứ tự cố định; bỏ chọn sẽ ẩn tác vụ đó.',
|
|
3370
3425
|
'settings.rail.unavailable': 'Máy chủ này không có dịch vụ cài đặt; hãy dùng “Cài đặt thanh tác vụ” trong bảng điều khiển.',
|
|
@@ -3552,7 +3607,10 @@ window.__ModuleLoader__.load({
|
|
|
3552
3607
|
'rail.settings': 'إعدادات شريط الإجراءات',
|
|
3553
3608
|
'rail.openSettings': 'فتح إعدادات الإضافة',
|
|
3554
3609
|
'settings.card.title': 'إعدادات نافذة أدوات Git',
|
|
3555
|
-
'settings.card.desc': 'أزرار شريط الإجراءات ورسالة إيداع بالذكاء الاصطناعي',
|
|
3610
|
+
'settings.card.desc': 'موضع اللوحة، أزرار شريط الإجراءات ورسالة إيداع بالذكاء الاصطناعي',
|
|
3611
|
+
'settings.placement.title': 'موضع اللوحة',
|
|
3612
|
+
'settings.placement.tab': 'إظهار تبويب Git في منطقة المحادثة',
|
|
3613
|
+
'settings.placement.tabHint': 'عند الإيقاف تبقى اللوحة متاحة من الشريط الجانبي الأيمن واللوحة السفلية.',
|
|
3556
3614
|
'settings.rail.title': 'أزرار شريط الإجراءات',
|
|
3557
3615
|
'settings.rail.hint': 'حدّد الإجراءات المعروضة على الشريط. الترتيب ثابت، وإلغاء التحديد يخفي الإجراء.',
|
|
3558
3616
|
'settings.rail.unavailable': 'لا تتوفر خدمة إعدادات على هذا المضيف؛ استخدم «إعدادات شريط الإجراءات» في اللوحة.',
|
|
@@ -3740,7 +3798,10 @@ window.__ModuleLoader__.load({
|
|
|
3740
3798
|
'rail.settings': 'क्रिया पट्टी सेटिंग्स',
|
|
3741
3799
|
'rail.openSettings': 'प्लगइन सेटिंग खोलें',
|
|
3742
3800
|
'settings.card.title': 'Git टूल विंडो सेटिंग्स',
|
|
3743
|
-
'settings.card.desc': 'एक्शन रेल बटन और AI कमिट संदेश',
|
|
3801
|
+
'settings.card.desc': 'पैनल प्लेसमेंट, एक्शन रेल बटन और AI कमिट संदेश',
|
|
3802
|
+
'settings.placement.title': 'पैनल प्लेसमेंट',
|
|
3803
|
+
'settings.placement.tab': 'संवाद क्षेत्र में Git टैब दिखाएँ',
|
|
3804
|
+
'settings.placement.tabHint': 'बंद करने पर भी पैनल दाएँ साइडबार और निचले पैनल से खुलता है।',
|
|
3744
3805
|
'settings.rail.title': 'एक्शन रेल बटन',
|
|
3745
3806
|
'settings.rail.hint': 'रेल पर दिखने वाली क्रियाएँ चुनें। क्रम निश्चित है; चुनाव हटाने पर वह छिप जाती है।',
|
|
3746
3807
|
'settings.rail.unavailable': 'इस होस्ट में सेटिंग सेवा नहीं है; पैनल में «एक्शन रेल सेटिंग» का उपयोग करें।',
|
|
@@ -3928,7 +3989,10 @@ window.__ModuleLoader__.load({
|
|
|
3928
3989
|
'rail.settings': 'ตั้งค่าแถบการทำงาน',
|
|
3929
3990
|
'rail.openSettings': 'เปิดการตั้งค่าปลั๊กอิน',
|
|
3930
3991
|
'settings.card.title': 'การตั้งค่าหน้าต่างเครื่องมือ Git',
|
|
3931
|
-
'settings.card.desc': 'ปุ่มแถบการทำงานและข้อความคอมมิตโดย AI',
|
|
3992
|
+
'settings.card.desc': 'ตำแหน่งแผง, ปุ่มแถบการทำงานและข้อความคอมมิตโดย AI',
|
|
3993
|
+
'settings.placement.title': 'ตำแหน่งแผง',
|
|
3994
|
+
'settings.placement.tab': 'แสดงแท็บ Git ในพื้นที่บทสนทนา',
|
|
3995
|
+
'settings.placement.tabHint': 'ปิดแล้วยังเปิดแผงจากแถบข้างขวาหรือแผงล่างได้',
|
|
3932
3996
|
'settings.rail.title': 'ปุ่มแถบการทำงาน',
|
|
3933
3997
|
'settings.rail.hint': 'ทำเครื่องหมายการทำงานที่จะแสดงบนแถบ ลำดับคงที่; ยกเลิกเครื่องหมายเพื่อซ่อน',
|
|
3934
3998
|
'settings.rail.unavailable': 'โฮสต์นี้ไม่มีบริการตั้งค่า ให้ใช้ «ตั้งค่าแถบการทำงาน» ในแผง',
|
|
@@ -4088,12 +4152,76 @@ window.__ModuleLoader__.load({
|
|
|
4088
4152
|
|
|
4089
4153
|
/* ============================== api ============================== */
|
|
4090
4154
|
|
|
4155
|
+
/* ---- remote channel fallback core (pure: tests/smoke.mjs slices this
|
|
4156
|
+
block) ---- */
|
|
4157
|
+
/* dsh-remote-web-ui's browser channel rewrites only the host's own /api/,
|
|
4158
|
+
/sidebar/, /git/ and /pet/ prefixes onto its /remote/ proxy; a
|
|
4159
|
+
plugin-owned route like ours is not on that list, so from a paired
|
|
4160
|
+
remote client the direct call reaches the harness browser-auth gate and
|
|
4161
|
+
comes back 401/403 "forbidden" (issue #9: both sidebar and dock panels
|
|
4162
|
+
refused to open remotely). The proxy itself serves any loopback path
|
|
4163
|
+
under /remote/<full pathname> with the paired device's credentials, so
|
|
4164
|
+
the fallback is to retry the very same request through it — the exact
|
|
4165
|
+
URL shape the boot patch produces for the host prefixes ('/remote' +
|
|
4166
|
+
the request's full pathname, mount prefix included, query preserved). */
|
|
4167
|
+
function remoteFallbackUrl(ofPath) {
|
|
4168
|
+
/* Resolve through the SAME base the direct fetch used — document.baseURI
|
|
4169
|
+
(which honours <base href>; dsh 0.1.7 mounts the shell with one), so
|
|
4170
|
+
the retry lands on the proxy copy of the very URL that was rejected. */
|
|
4171
|
+
let resolved = null
|
|
4172
|
+
try {
|
|
4173
|
+
let base
|
|
4174
|
+
if (typeof document === 'object' && document !== null && typeof document.baseURI === 'string' && document.baseURI !== '') {
|
|
4175
|
+
base = document.baseURI
|
|
4176
|
+
} else if (typeof window === 'object' && window !== null && window.location !== undefined && window.location !== null) {
|
|
4177
|
+
base = window.location.href
|
|
4178
|
+
}
|
|
4179
|
+
if (base !== undefined) resolved = new URL(ofPath, base)
|
|
4180
|
+
} catch (error) { resolved = null }
|
|
4181
|
+
if (resolved === null) return '/remote/' + String(ofPath).replace(/^\//, '')
|
|
4182
|
+
return '/remote' + resolved.pathname + resolved.search
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
/* A rejection worth retrying through the remote channel: the auth gate
|
|
4186
|
+
answers 401 (unauthenticated) or 403 (forbidden); every other status —
|
|
4187
|
+
including this route's own JSON errors, which answer 200/4xx with a
|
|
4188
|
+
parsed body — is a real answer, not a transport fence. */
|
|
4189
|
+
function isAuthRejection(status) {
|
|
4190
|
+
return status === 401 || status === 403
|
|
4191
|
+
}
|
|
4192
|
+
/* ---- end remote channel fallback core ---- */
|
|
4193
|
+
|
|
4194
|
+
/* Sticky remote-channel mode: once the proxy has answered, later calls go
|
|
4195
|
+
there directly — otherwise every call from a remote client pays the
|
|
4196
|
+
401/403 round trip first. Loopback desktop browsing never trips the
|
|
4197
|
+
fallback at all (same-origin requests are always admitted). */
|
|
4198
|
+
let viaRemoteChannel = false
|
|
4199
|
+
|
|
4091
4200
|
async function request(method, payload) {
|
|
4092
|
-
const
|
|
4201
|
+
const init = {
|
|
4093
4202
|
method: 'POST',
|
|
4094
4203
|
headers: { 'content-type': 'application/json' },
|
|
4095
4204
|
body: JSON.stringify(payload === undefined ? {} : payload),
|
|
4096
|
-
}
|
|
4205
|
+
}
|
|
4206
|
+
const direct = API_BASE + '/' + method
|
|
4207
|
+
let response = null
|
|
4208
|
+
if (viaRemoteChannel === true) {
|
|
4209
|
+
response = await fetch(remoteFallbackUrl(direct), init)
|
|
4210
|
+
} else {
|
|
4211
|
+
response = await fetch(direct, init)
|
|
4212
|
+
if (isAuthRejection(response.status) === true) {
|
|
4213
|
+
/* Off the origin's trust path: try the pairing proxy once. With no
|
|
4214
|
+
remote-web-ui installed the proxy path 404s — the retry then also
|
|
4215
|
+
fails, and the ORIGINAL rejection is what the caller sees (a
|
|
4216
|
+
proxy answer that is itself an auth rejection is discarded the
|
|
4217
|
+
same way, so a genuine cross-site refusal can never be masked). */
|
|
4218
|
+
const retried = await fetch(remoteFallbackUrl(direct), init)
|
|
4219
|
+
if (isAuthRejection(retried.status) !== true) {
|
|
4220
|
+
viaRemoteChannel = true
|
|
4221
|
+
response = retried
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4097
4225
|
let body = null
|
|
4098
4226
|
try { body = await response.json() } catch (error) { void error }
|
|
4099
4227
|
if (body === null || typeof body !== 'object') throw new Error('HTTP ' + response.status)
|
|
@@ -4106,6 +4234,52 @@ window.__ModuleLoader__.load({
|
|
|
4106
4234
|
return body.data
|
|
4107
4235
|
}
|
|
4108
4236
|
|
|
4237
|
+
/* ============================== first-paint cache ============================== */
|
|
4238
|
+
|
|
4239
|
+
/* Session-scoped seats (the conversation view, and every sidebar seat that
|
|
4240
|
+
follows the active session) REMOUNT their panel on each session switch:
|
|
4241
|
+
the slot subtree belongs to the session. Re-running the whole discovery
|
|
4242
|
+
(repos scan) + first page (summary / branches / log — three git spawns)
|
|
4243
|
+
on every switch made the chrome re-appear piece by piece (reported
|
|
4244
|
+
against v0.13.0). Same workspace ⇒ same repository ⇒ the previous paint
|
|
4245
|
+
is still essentially correct, so it seeds the states and a background
|
|
4246
|
+
refresh lands the fresh data a moment later. Pure functions; the panel
|
|
4247
|
+
only ever reads them at effect time and writes after a fetch. */
|
|
4248
|
+
const PAINT_TTL_MS = 30_000
|
|
4249
|
+
const paintRepos = new Map() /* cwd -> { at, data } */
|
|
4250
|
+
const paintPage = new Map() /* repoRoot -> { at, summary, branches, commits, hasMore } */
|
|
4251
|
+
const paintRoot = new Map() /* cwd -> last repoRoot the user was looking at */
|
|
4252
|
+
|
|
4253
|
+
function readPaintRepos(cwd) {
|
|
4254
|
+
const entry = paintRepos.get(cwd)
|
|
4255
|
+
if (entry === undefined || Date.now() - entry.at > PAINT_TTL_MS) return undefined
|
|
4256
|
+
return entry.data
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
function writePaintRepos(cwd, data) {
|
|
4260
|
+
paintRepos.set(cwd, { at: Date.now(), data: data })
|
|
4261
|
+
if (paintRepos.size > 16) paintRepos.delete(paintRepos.keys().next().value)
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
function readPaintPage(repoRoot) {
|
|
4265
|
+
const entry = paintPage.get(repoRoot)
|
|
4266
|
+
if (entry === undefined || Date.now() - entry.at > PAINT_TTL_MS) return undefined
|
|
4267
|
+
return entry
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
function writePaintPage(repoRoot, page) {
|
|
4271
|
+
paintPage.set(repoRoot, Object.assign({ at: Date.now() }, page))
|
|
4272
|
+
if (paintPage.size > 16) paintPage.delete(paintPage.keys().next().value)
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
function rememberPaintRoot(cwd, repoRoot) {
|
|
4276
|
+
if (typeof cwd === 'string' && cwd !== '' && repoRoot !== null) paintRoot.set(cwd, repoRoot)
|
|
4277
|
+
}
|
|
4278
|
+
|
|
4279
|
+
function readPaintRoot(cwd) {
|
|
4280
|
+
return paintRoot.get(cwd)
|
|
4281
|
+
}
|
|
4282
|
+
|
|
4109
4283
|
/* ============================== storage ============================== */
|
|
4110
4284
|
|
|
4111
4285
|
/* ---- ai commit pickers (issue #7): the model catalog and per-model
|
|
@@ -5447,6 +5621,49 @@ window.__ModuleLoader__.load({
|
|
|
5447
5621
|
E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: spec.icon, size: 13 })),
|
|
5448
5622
|
E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t(spec.key)))
|
|
5449
5623
|
})),
|
|
5624
|
+
/* Placement (issue #8): one switch, the same optimistic-write contract
|
|
5625
|
+
the rail rows keep. The conversation tab registers/unregisters live
|
|
5626
|
+
(see door 3 in apply), so the flip is visible without a reload. */
|
|
5627
|
+
E('div', { className: 'dig-settings-sub' }, t('settings.placement.title')),
|
|
5628
|
+
(() => {
|
|
5629
|
+
const field = 'conversationTab'
|
|
5630
|
+
const inputId = 'dig-placement-' + field
|
|
5631
|
+
const shown = Object.prototype.hasOwnProperty.call(intent.current, field)
|
|
5632
|
+
? intent.current[field] === true
|
|
5633
|
+
: values[field] !== false
|
|
5634
|
+
const toggle = (event) => {
|
|
5635
|
+
if (readOnly === true) { setNote(t('settings.rail.readonly')); return }
|
|
5636
|
+
const fromControl = event !== undefined && event !== null && event.target !== null
|
|
5637
|
+
&& event.target !== undefined && typeof event.target.checked === 'boolean'
|
|
5638
|
+
? event.target.checked
|
|
5639
|
+
: null
|
|
5640
|
+
const wanted = fromControl !== null
|
|
5641
|
+
? fromControl
|
|
5642
|
+
: (Object.prototype.hasOwnProperty.call(intent.current, field)
|
|
5643
|
+
? intent.current[field] !== true
|
|
5644
|
+
: values[field] !== false) !== true
|
|
5645
|
+
intent.current[field] = wanted
|
|
5646
|
+
dirty.current.set(field, wanted)
|
|
5647
|
+
setPending((now) => Object.assign({}, now, { [field]: wanted }))
|
|
5648
|
+
setNote('')
|
|
5649
|
+
bump()
|
|
5650
|
+
scheduleFlush()
|
|
5651
|
+
}
|
|
5652
|
+
return E('div', { key: field },
|
|
5653
|
+
E('div', { className: 'dig-settings-row', 'data-placement-row': field },
|
|
5654
|
+
E('input', {
|
|
5655
|
+
id: inputId,
|
|
5656
|
+
type: 'checkbox',
|
|
5657
|
+
className: 'dig-settings-check',
|
|
5658
|
+
'aria-labelledby': inputId + '-label',
|
|
5659
|
+
checked: shown,
|
|
5660
|
+
disabled: readOnly === true,
|
|
5661
|
+
onChange: toggle,
|
|
5662
|
+
}),
|
|
5663
|
+
E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: 'commit', size: 13 })),
|
|
5664
|
+
E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.placement.tab'))),
|
|
5665
|
+
E('div', { className: 'dig-settings-subnote' }, t('settings.placement.tabHint')))
|
|
5666
|
+
})(),
|
|
5450
5667
|
E('div', { className: 'dig-settings-sub' }, t('settings.commit.title')),
|
|
5451
5668
|
(() => {
|
|
5452
5669
|
/* The model row is a picker over the host's provider catalog; while
|
|
@@ -6628,18 +6845,30 @@ window.__ModuleLoader__.load({
|
|
|
6628
6845
|
as "still loading" and would spin forever). */
|
|
6629
6846
|
if (cwd === undefined) { setRepoState({ repos: [], isRepo: false }); return undefined }
|
|
6630
6847
|
let cancelled = false
|
|
6848
|
+
/* First-paint cache: a session switch through the same workspace
|
|
6849
|
+
reuses the previous discovery synchronously (repo bar and pick land
|
|
6850
|
+
with the first paint), then the background scan re-proves it. */
|
|
6851
|
+
const apply = (data, cachedPick) => {
|
|
6852
|
+
setRepoState(data)
|
|
6853
|
+
const paths = data.repos.map((repo) => repo.path)
|
|
6854
|
+
const remembered = readRememberedRepo(sessionId)
|
|
6855
|
+
let pick = null
|
|
6856
|
+
if (remembered !== undefined && paths.indexOf(remembered) >= 0) pick = remembered
|
|
6857
|
+
else if (cachedPick !== undefined && paths.indexOf(cachedPick) >= 0) pick = cachedPick
|
|
6858
|
+
else if (data.isRepo === true) pick = data.cwd
|
|
6859
|
+
else if (paths.length > 0) pick = paths[0]
|
|
6860
|
+
setRepoRoot(pick)
|
|
6861
|
+
rememberPaintRoot(cwd, pick)
|
|
6862
|
+
return pick
|
|
6863
|
+
}
|
|
6864
|
+
const cached = readPaintRepos(cwd)
|
|
6865
|
+
if (cached !== undefined) apply(cached, readPaintRoot(cwd))
|
|
6631
6866
|
void (async () => {
|
|
6632
6867
|
try {
|
|
6633
6868
|
const data = await request('repos', { cwd: cwd })
|
|
6634
6869
|
if (cancelled) return
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
const remembered = readRememberedRepo(sessionId)
|
|
6638
|
-
let pick = null
|
|
6639
|
-
if (remembered !== undefined && paths.indexOf(remembered) >= 0) pick = remembered
|
|
6640
|
-
else if (data.isRepo === true) pick = data.cwd
|
|
6641
|
-
else if (paths.length > 0) pick = paths[0]
|
|
6642
|
-
setRepoRoot(pick)
|
|
6870
|
+
writePaintRepos(cwd, data)
|
|
6871
|
+
apply(data, readPaintRoot(cwd))
|
|
6643
6872
|
} catch (caught) {
|
|
6644
6873
|
if (!cancelled) setError(caught instanceof Error ? caught.message : String(caught))
|
|
6645
6874
|
}
|
|
@@ -6654,7 +6883,9 @@ window.__ModuleLoader__.load({
|
|
|
6654
6883
|
}, [base, showIgnored])
|
|
6655
6884
|
|
|
6656
6885
|
const loadBranches = useCallback(async () => {
|
|
6657
|
-
|
|
6886
|
+
const data = await request('branches', base)
|
|
6887
|
+
setBranches(data)
|
|
6888
|
+
return data
|
|
6658
6889
|
}, [base])
|
|
6659
6890
|
|
|
6660
6891
|
const loadCommits = useCallback(async (skip, overridePath) => {
|
|
@@ -6664,19 +6895,39 @@ window.__ModuleLoader__.load({
|
|
|
6664
6895
|
const data = await request('log', payload)
|
|
6665
6896
|
setCommits((previous) => (skip === undefined || skip === 0 ? data.commits : previous.concat(data.commits)))
|
|
6666
6897
|
setHasMore(data.hasMore)
|
|
6898
|
+
return data
|
|
6667
6899
|
}, [base, pathFilter])
|
|
6668
6900
|
|
|
6669
6901
|
const refresh = useCallback(async () => {
|
|
6670
6902
|
if (repoRoot === null) return
|
|
6671
6903
|
await guard(async () => {
|
|
6672
|
-
await Promise.all([loadSummary(), loadBranches(), loadCommits(0)])
|
|
6904
|
+
const [summaryData, branchesData, page] = await Promise.all([loadSummary(), loadBranches(), loadCommits(0)])
|
|
6905
|
+
/* The write side of the first-paint cache: exactly the FIRST page
|
|
6906
|
+
(a paged fetch never reaches this path), replaced on every
|
|
6907
|
+
refresh — the auto-refresh included — so a session switch through
|
|
6908
|
+
the same workspace seeds fresh data, not the mount-time
|
|
6909
|
+
snapshot. */
|
|
6910
|
+
writePaintPage(repoRoot, { summary: summaryData, branches: branchesData, commits: page.commits, hasMore: page.hasMore })
|
|
6673
6911
|
})
|
|
6674
6912
|
}, [guard, loadSummary, loadBranches, loadCommits, repoRoot])
|
|
6675
6913
|
|
|
6676
6914
|
useEffect(() => {
|
|
6677
6915
|
if (repoRoot === null) return undefined
|
|
6916
|
+
/* First-paint cache: seed the first page synchronously so a session
|
|
6917
|
+
switch through the same workspace paints the full chrome (rail,
|
|
6918
|
+
branch bar, history) at once instead of re-growing it piece by
|
|
6919
|
+
piece; the refresh below re-proves every field right after. Only a
|
|
6920
|
+
cold mount seeds — a later effect run (tick / repo switch) must not
|
|
6921
|
+
roll live state back to a stale snapshot. */
|
|
6922
|
+
const cachedPage = readPaintPage(repoRoot)
|
|
6923
|
+
if (cachedPage !== undefined) {
|
|
6924
|
+
if (summary === null) setSummary(cachedPage.summary)
|
|
6925
|
+
if (branches === null) setBranches(cachedPage.branches)
|
|
6926
|
+
if (commits.length === 0) { setCommits(cachedPage.commits); setHasMore(cachedPage.hasMore) }
|
|
6927
|
+
}
|
|
6678
6928
|
void refresh()
|
|
6679
6929
|
return undefined
|
|
6930
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
6680
6931
|
}, [repoRoot, refresh, tick])
|
|
6681
6932
|
|
|
6682
6933
|
useEffect(() => {
|
|
@@ -7466,7 +7717,7 @@ window.__ModuleLoader__.load({
|
|
|
7466
7717
|
|
|
7467
7718
|
const CSS = [
|
|
7468
7719
|
'.dig-root{position:relative;display:flex;flex-direction:column;height:100%;min-height:0;background:transparent;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family,system-ui,sans-serif);font-size:13px;line-height:1.5;font-weight:600;overflow:hidden}',
|
|
7469
|
-
'.dig-topbar{display:flex;align-items:center;gap:6px;padding:4px 6px;flex:none;min-width:0;border-bottom:1px solid var(--dsw-alias-
|
|
7720
|
+
'.dig-topbar{display:flex;align-items:center;gap:6px;padding:4px 6px;flex:none;min-width:0;border-bottom:1px solid var(--dsw-alias-border-l2);overflow:hidden}',
|
|
7470
7721
|
'.dig-topbar-spacer{flex:1;min-width:4px}',
|
|
7471
7722
|
'.dig-busy{color:var(--dsw-alias-label-tertiary);flex:none;white-space:nowrap}',
|
|
7472
7723
|
'.dig-track{color:var(--dsw-alias-label-secondary);flex:none;font-variant-numeric:tabular-nums;white-space:nowrap}',
|
|
@@ -7489,10 +7740,10 @@ window.__ModuleLoader__.load({
|
|
|
7489
7740
|
'.dig-note{display:flex;align-items:center;gap:8px;padding:4px 8px;flex:none;color:var(--dsw-alias-label-secondary)}',
|
|
7490
7741
|
'.dig-banner-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}',
|
|
7491
7742
|
'.dig-shell{flex:1;min-height:0;display:flex;overflow:hidden}',
|
|
7492
|
-
'.dig-rail{width:32px;flex:none;display:flex;flex-direction:column;align-items:center;gap:2px;padding:4px 0;overflow:hidden;border-right:1px solid var(--dsw-alias-
|
|
7743
|
+
'.dig-rail{width:32px;flex:none;display:flex;flex-direction:column;align-items:center;gap:2px;padding:4px 0;overflow:hidden;border-right:1px solid var(--dsw-alias-border-l2)}',
|
|
7493
7744
|
'.dig-rail-gap{flex:1;min-height:2px}',
|
|
7494
7745
|
'.dig-rail-gap-x{flex:1;min-width:2px}',
|
|
7495
|
-
'.dig-rail-row{display:flex;align-items:center;gap:2px;padding:3px 6px;flex:none;overflow:hidden;border-bottom:1px solid var(--dsw-alias-
|
|
7746
|
+
'.dig-rail-row{display:flex;align-items:center;gap:2px;padding:3px 6px;flex:none;overflow:hidden;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
7496
7747
|
'.dig-rail-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;padding:0;flex:none}',
|
|
7497
7748
|
'.dig-rail-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
7498
7749
|
'.dig-rail-btn:disabled{opacity:.28;cursor:default}',
|
|
@@ -7575,13 +7826,13 @@ window.__ModuleLoader__.load({
|
|
|
7575
7826
|
'.dig-body-stack{flex-direction:column}',
|
|
7576
7827
|
'.dig-body-compact{flex-direction:column}',
|
|
7577
7828
|
'.dig-pane{display:flex;flex-direction:column;min-height:0;min-width:0}',
|
|
7578
|
-
'.dig-pane-tree{width:200px;flex:none;border-right:1px solid var(--dsw-alias-
|
|
7579
|
-
'.dig-pane-tree-stack{max-height:36%;flex:none;border-bottom:1px solid var(--dsw-alias-
|
|
7829
|
+
'.dig-pane-tree{width:200px;flex:none;border-right:1px solid var(--dsw-alias-border-l2)}',
|
|
7830
|
+
'.dig-pane-tree-stack{max-height:36%;flex:none;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
7580
7831
|
'.dig-pane-main{flex:1;min-width:0}',
|
|
7581
|
-
'.dig-pane-changes{width:290px;flex:none;border-left:1px solid var(--dsw-alias-
|
|
7582
|
-
'.dig-pane-changes-stack{max-height:46%;flex:none;border-bottom:1px solid var(--dsw-alias-
|
|
7832
|
+
'.dig-pane-changes{width:290px;flex:none;border-left:1px solid var(--dsw-alias-border-l2)}',
|
|
7833
|
+
'.dig-pane-changes-stack{max-height:46%;flex:none;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
7583
7834
|
'.dig-compact-bar{display:flex;align-items:center;gap:6px;padding:4px 6px;flex:none}',
|
|
7584
|
-
'.dig-compact-tree{max-height:42%;flex:none;border-bottom:1px solid var(--dsw-alias-
|
|
7835
|
+
'.dig-compact-tree{max-height:42%;flex:none;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
7585
7836
|
// A divider: an 8px hit area (a fingertip needs it) painted as a 1px hairline
|
|
7586
7837
|
// that lights up on hover, focus or while dragging. touch-action:none is what
|
|
7587
7838
|
// makes a touch drag resize the pane instead of scrolling the panel.
|
|
@@ -7665,7 +7916,7 @@ window.__ModuleLoader__.load({
|
|
|
7665
7916
|
'.dig-ref-remote{color:#b083f0}',
|
|
7666
7917
|
'.dig-ref-tag{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary))}',
|
|
7667
7918
|
'.dig-ref-more{color:var(--dsw-alias-label-tertiary)}',
|
|
7668
|
-
'.dig-filters{display:flex;align-items:center;gap:4px;padding:4px 6px;flex:none;min-width:0;overflow-x:auto;overflow-y:hidden;border-bottom:1px solid var(--dsw-alias-
|
|
7919
|
+
'.dig-filters{display:flex;align-items:center;gap:4px;padding:4px 6px;flex:none;min-width:0;overflow-x:auto;overflow-y:hidden;border-bottom:1px solid var(--dsw-alias-border-l2)}',
|
|
7669
7920
|
'.dig-filter-text{flex:1 1 90px;min-width:80px;width:auto}',
|
|
7670
7921
|
'.dig-filter-select{flex:none;max-width:118px;appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,8px);color:var(--dsw-alias-label-secondary);font:inherit;font-weight:500;height:22px;padding:0 4px;cursor:pointer;text-overflow:ellipsis}',
|
|
7671
7922
|
'.dig-filter-select:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
@@ -7695,7 +7946,7 @@ window.__ModuleLoader__.load({
|
|
|
7695
7946
|
'.dig-mini{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:none;border-radius:var(--dsw-radius-xs,4px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;opacity:0;flex:none;padding:0}',
|
|
7696
7947
|
'.dig-row:hover .dig-mini{opacity:1}',
|
|
7697
7948
|
'.dig-mini:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
7698
|
-
'.dig-commit-box{flex:none;padding:6px;display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--dsw-alias-
|
|
7949
|
+
'.dig-commit-box{flex:none;padding:6px;display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--dsw-alias-border-l2)}',
|
|
7699
7950
|
'.dig-commit-box-compact{flex-direction:row;align-items:center;gap:6px;padding:4px 6px;flex-wrap:wrap}',
|
|
7700
7951
|
'.dig-textarea{width:100%;box-sizing:border-box;min-height:50px;resize:vertical;padding:5px 6px;border-radius:var(--dsw-radius-sm,8px);border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;outline:none}',
|
|
7701
7952
|
'.dig-textarea::placeholder{color:var(--dsw-alias-label-tertiary)}',
|
|
@@ -7717,7 +7968,7 @@ window.__ModuleLoader__.load({
|
|
|
7717
7968
|
'.dig-detail-files-head{font-weight:600;margin-top:4px}',
|
|
7718
7969
|
'.dig-detail-files{display:flex;flex-direction:column}',
|
|
7719
7970
|
'.dig-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}',
|
|
7720
|
-
'.dig-diff-pane{flex:none;max-height:55%;display:flex;flex-direction:column;min-height:0;border-top:1px solid var(--dsw-alias-
|
|
7971
|
+
'.dig-diff-pane{flex:none;max-height:55%;display:flex;flex-direction:column;min-height:0;border-top:1px solid var(--dsw-alias-border-l2)}',
|
|
7721
7972
|
'.dig-diff-head{display:flex;align-items:center;gap:8px;padding:3px 8px;color:var(--dsw-alias-label-tertiary);flex:none;min-width:0}',
|
|
7722
7973
|
'.dig-diff-path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}',
|
|
7723
7974
|
'.dig-diff{flex:1;overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;font-weight:500;min-height:0}',
|
|
@@ -7757,6 +8008,10 @@ window.__ModuleLoader__.load({
|
|
|
7757
8008
|
const NATIVE_ID = 'dsh-ide-git'
|
|
7758
8009
|
const NATIVE_KIND = 'ide-git'
|
|
7759
8010
|
const NATIVE_WORKSPACE_ITEMS = (state) => state.items
|
|
8011
|
+
/* The conversation.view slot id (issue #8). Order 30 sits the tab after
|
|
8012
|
+
Chat (0) and Trajectory (10), and after dsh-context's tab (20) when both
|
|
8013
|
+
are installed. */
|
|
8014
|
+
const CONVERSATION_VIEW_ID = 'ide-git'
|
|
7760
8015
|
|
|
7761
8016
|
/* The native seat hands a tab the standard props; the panel wants
|
|
7762
8017
|
`{ scope, t }`. A session's working directory is the path of the
|
|
@@ -7789,6 +8044,67 @@ window.__ModuleLoader__.load({
|
|
|
7789
8044
|
return E(Panel, { scope: props.scope, t: props.t, visible: props.visible, dock: props.dock, openSettings: openSettings })
|
|
7790
8045
|
}
|
|
7791
8046
|
|
|
8047
|
+
/* The conversation-area seat (issue #8): the same panel as a view tab
|
|
8048
|
+
beside 对话/轨迹. The session's own cwd decides the repository — the
|
|
8049
|
+
harness records it when the session starts in a workspace, so this is
|
|
8050
|
+
the same source the sidebar seats resolve through. A session without a
|
|
8051
|
+
cwd (blank workspace, or a host without the sessions service) shows the
|
|
8052
|
+
panel's repo-pick state, never an error. */
|
|
8053
|
+
function sessionCwdOf(ctx, sessionId) {
|
|
8054
|
+
if (typeof sessionId !== 'string' || sessionId === '') return undefined
|
|
8055
|
+
try {
|
|
8056
|
+
const sessions = ctx.get('sessions')
|
|
8057
|
+
const list = sessions !== undefined && sessions !== null ? sessions.list : undefined
|
|
8058
|
+
if (list === undefined || typeof list.getSnapshot !== 'function') return undefined
|
|
8059
|
+
const snapshot = list.getSnapshot()
|
|
8060
|
+
const entry = snapshot !== null && typeof snapshot === 'object' && snapshot.byId !== undefined
|
|
8061
|
+
&& snapshot.byId[sessionId] !== undefined && snapshot.byId[sessionId] !== null
|
|
8062
|
+
? snapshot.byId[sessionId] : undefined
|
|
8063
|
+
if (entry !== undefined && typeof entry.cwd === 'string' && entry.cwd !== '') return entry.cwd
|
|
8064
|
+
} catch (error) { void error }
|
|
8065
|
+
return undefined
|
|
8066
|
+
}
|
|
8067
|
+
|
|
8068
|
+
/* The conversation shell re-renders on EVERY keystroke (the composer owns
|
|
8069
|
+
the draft state), and the view area re-renders with it. Without a bail-out
|
|
8070
|
+
at this boundary that cascade dragged the ENTIRE Git panel tree — branch
|
|
8071
|
+
rows, graph lanes, changes — through a synchronous render per keypress
|
|
8072
|
+
while the Git view was the active one (typing lag, reported against
|
|
8073
|
+
v0.13.0). The component consumes exactly three stable props (sessionId,
|
|
8074
|
+
t, ctx); everything else the slot passes (inspectCall, viewRequest, …)
|
|
8075
|
+
changes identity per shell render and is deliberately ignored, so the
|
|
8076
|
+
comparator is: same session, same bindings → skip the subtree. cwd moves
|
|
8077
|
+
through the sessions subscription inside, not through props. */
|
|
8078
|
+
const ConversationPanel = memo(function ConversationPanel(props) {
|
|
8079
|
+
const ctx = props.ctx
|
|
8080
|
+
const sessionId = typeof props.sessionId === 'string' ? props.sessionId : ''
|
|
8081
|
+
const [cwd, setCwd] = useState(() => sessionCwdOf(ctx, sessionId))
|
|
8082
|
+
useEffect(() => {
|
|
8083
|
+
let alive = true
|
|
8084
|
+
const sync = () => { if (alive === true) setCwd(sessionCwdOf(ctx, sessionId)) }
|
|
8085
|
+
sync()
|
|
8086
|
+
/* The session list is a live store; without a subscription a later
|
|
8087
|
+
start (first message in a blank session) would leave the tab on the
|
|
8088
|
+
no-repo chrome until the next remount. The notify walks are cheap:
|
|
8089
|
+
getSnapshot + a string compare, and setCwd with an identical string
|
|
8090
|
+
bails out without a render. */
|
|
8091
|
+
let unsubscribe
|
|
8092
|
+
try {
|
|
8093
|
+
const sessions = ctx.get('sessions')
|
|
8094
|
+
const list = sessions !== undefined && sessions !== null ? sessions.list : undefined
|
|
8095
|
+
if (list !== undefined && typeof list.subscribe === 'function') unsubscribe = list.subscribe(sync)
|
|
8096
|
+
} catch (error) { void error }
|
|
8097
|
+
return () => {
|
|
8098
|
+
alive = false
|
|
8099
|
+
if (typeof unsubscribe === 'function') {
|
|
8100
|
+
try { unsubscribe() } catch (error) { void error }
|
|
8101
|
+
}
|
|
8102
|
+
}
|
|
8103
|
+
}, [ctx, sessionId])
|
|
8104
|
+
const scope = useMemo(() => ({ cwd: cwd, sessionId: sessionId }), [cwd, sessionId])
|
|
8105
|
+
return E(LocaleLive, { ctx: ctx, scope: scope, t: props.t, visible: true })
|
|
8106
|
+
}, (prev, next) => prev.sessionId === next.sessionId && prev.t === next.t && prev.ctx === next.ctx)
|
|
8107
|
+
|
|
7792
8108
|
function NativePanel(props) {
|
|
7793
8109
|
const workspaces = typeof props.useWorkspaces === 'function'
|
|
7794
8110
|
? props.useWorkspaces(NATIVE_WORKSPACE_ITEMS)
|
|
@@ -7927,6 +8243,60 @@ window.__ModuleLoader__.load({
|
|
|
7927
8243
|
there, so only a host without better-sidebar reaches the native seats. */
|
|
7928
8244
|
if (!hostedByBetterSidebar) hostNatively()
|
|
7929
8245
|
|
|
8246
|
+
/* ---- door 3: the conversation-area view tab (issue #8) ---- */
|
|
8247
|
+
/* A third seat beside 对话/轨迹, registered into ui-conversation's
|
|
8248
|
+
`conversation.view` slot (the same contract dsh-context rides). It is
|
|
8249
|
+
ON by default and follows the plugin settings card's switch (row
|
|
8250
|
+
Config field `conversationTab`) live — no reload. Hosts without the
|
|
8251
|
+
row Config keep the tab: there is no editor surface there to turn it
|
|
8252
|
+
off with, and the card says as much for the rail already. The seat
|
|
8253
|
+
rides `slots` (already a declared inject), so a host without
|
|
8254
|
+
ui-conversation simply never mounts it — nothing throws. */
|
|
8255
|
+
const conversationTabEnabled = () => {
|
|
8256
|
+
const ready = railFormReady()
|
|
8257
|
+
if (ready === null) return true
|
|
8258
|
+
const value = ready.value !== null && typeof ready.value === 'object' ? ready.value.conversationTab : undefined
|
|
8259
|
+
return value !== false
|
|
8260
|
+
}
|
|
8261
|
+
ctx.inject(['slots'], (viewCtx) => {
|
|
8262
|
+
const slots = viewCtx.get('slots')
|
|
8263
|
+
if (slots === undefined || slots === null || typeof slots.inject !== 'function') return
|
|
8264
|
+
viewCtx.effect(() => {
|
|
8265
|
+
let offView = null
|
|
8266
|
+
const view = (viewProps) => E(ConversationPanel, Object.assign({}, viewProps, { t: t, ctx: ctx }))
|
|
8267
|
+
const sync = () => {
|
|
8268
|
+
const enabled = conversationTabEnabled()
|
|
8269
|
+
if (enabled === true && offView === null) {
|
|
8270
|
+
try {
|
|
8271
|
+
offView = slots.inject('conversation.view', () => slots.register(
|
|
8272
|
+
{ name: 'conversation.view', id: CONVERSATION_VIEW_ID, order: 30, locale: LOCALE_NS, label: () => t('title') },
|
|
8273
|
+
view,
|
|
8274
|
+
))
|
|
8275
|
+
} catch (error) {
|
|
8276
|
+
/* A late re-registration can lose a race with the previous
|
|
8277
|
+
entry's asynchronous disposal (same keyed cell, same
|
|
8278
|
+
priority): better one missed re-registration — the next
|
|
8279
|
+
config change or reload retries — than a sync that throws
|
|
8280
|
+
inside every rail-config notification. */
|
|
8281
|
+
console.warn('[dsh-ide-git] conversation seat registration failed: ' + String(error && error.message ? error.message : error))
|
|
8282
|
+
}
|
|
8283
|
+
} else if (enabled !== true && offView !== null) {
|
|
8284
|
+
try { offView() } catch (error) { void error }
|
|
8285
|
+
offView = null
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
8288
|
+
sync()
|
|
8289
|
+
const unsubscribe = subscribeRailConfig(sync)
|
|
8290
|
+
return () => {
|
|
8291
|
+
unsubscribe()
|
|
8292
|
+
if (offView !== null) {
|
|
8293
|
+
try { offView() } catch (error) { void error }
|
|
8294
|
+
offView = null
|
|
8295
|
+
}
|
|
8296
|
+
}
|
|
8297
|
+
}, 'dsh-ide-git: conversation view tab')
|
|
8298
|
+
})
|
|
8299
|
+
|
|
7930
8300
|
/* ---- settings: the row Config is the rail's source of truth ---- */
|
|
7931
8301
|
/* dsh >= 0.1.7 serves a plugin's row Config to the browser through
|
|
7932
8302
|
ctx.configForms; from then on the settings card and the rail read that one
|
package/src/index.js
CHANGED
|
@@ -65,6 +65,11 @@ function railConfigSchema(schema) {
|
|
|
65
65
|
shape.commitModel = live(schema.string().default(''))
|
|
66
66
|
shape.commitReasoning = live(schema.string().default(''))
|
|
67
67
|
shape.commitPrompt = live(schema.string().default(''))
|
|
68
|
+
/* Conversation-area view tab (issue #8): on by default, switchable off from
|
|
69
|
+
the plugin settings card. false means "do not register the
|
|
70
|
+
conversation.view seat at all" — the sidebar and dock seats are not
|
|
71
|
+
affected; a missing field means the default (shown). */
|
|
72
|
+
shape.conversationTab = live(schema.boolean().default(true))
|
|
68
73
|
return schema.object(shape)
|
|
69
74
|
}
|
|
70
75
|
|