dsh-ide-git 0.18.7 → 0.19.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 -0
- package/README_EN.md +1 -0
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +764 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ DSH 自带的 Git 面板覆盖「暂存 / 提交 / 还原 / 看历史」;`dsh-id
|
|
|
16
16
|
- **想指给 AI 一段代码**:在 diff 窗里选中几行、右键,把「哪个文件哪几行」(附提交或工作区上下文)直接发进当前会话的输入框。
|
|
17
17
|
- **想比较分支**:分支右键即可与当前分支比较,结果按文件与提交数汇报。
|
|
18
18
|
- **操作要够全**:签出、从任意分支/提交新建分支、重命名、删除、合并到当前、变基当前到此、新建标签、cherry-pick、还原提交、重置(保留/丢弃)、fetch / pull --ff-only / push(确认后)。
|
|
19
|
+
- **输入框里就能看 Git**:输入框工具条上有仓库与分支胶囊(着色的未提交 / 可拉取 / 待推送计数),点击即可切换,多仓库工作区两边都能切;切换与面板实时联动,设置里可关(默认开)。切到新会话也不用等——插件会在你点开会话时就预热首屏数据,面板秒开。
|
|
19
20
|
|
|
20
21
|
## 效果
|
|
21
22
|
|
|
@@ -95,6 +96,7 @@ DSH 自带的 Git 面板覆盖「暂存 / 提交 / 还原 / 看历史」;`dsh-id
|
|
|
95
96
|
|---|---|
|
|
96
97
|
| 动作条 | IDEA 式左侧竖排(底部工作台)/ 顶栏下一行横排(右侧栏):刷新、新建分支、签出、删除、比较、显示差异、贮藏、新建标签、收藏、抓取、拉取、推送。**按可用空间自动决定放几个**,放不下的收进 `⋯ 更多`;末尾 `⚙ 设置` 可拖动排序、逐项显隐(存 `dsh-ide-git.rail.v1`)。每个按钮按当前状态点亮/置灰(没有其他分支就不能删除/签出/比较,没有远程就不能抓取/推送,没有上游就不能拉取,没有变更就没有差异可看) |
|
|
97
98
|
| 状态行 | 当前分支、上游、`↑ahead ↓behind`、stash 数量、忙碌指示 |
|
|
99
|
+
| 输入框状态条 | 宿主输入框工具条里的仓库/分支胶囊(着色 ±未提交 / ↓可拉取 / ↑待推送),点击切换仓库与分支,与面板双向联动;单仓库只切分支,多仓库两边都切;设置可关(默认开) |
|
|
98
100
|
| 分支树 | `HEAD / 本地 / 远程 / 标签` 分组(可折叠)、过滤框、星标当前分支、ahead/behind 角标、worktree 占用标记;本地蓝 / 远程紫 / 标签黄 / HEAD 绿 |
|
|
99
101
|
| 分支右键 | 签出、将当前分支变基到此、合并到当前分支、与当前分支比较、从该分支新建分支、重命名、删除(当前分支禁用)、更新(Fetch)、推送、发送到对话(分支名 + 仓库) |
|
|
100
102
|
| 历史筛选 | 文本或哈希、分支或标签、提交人、日期(今天 / 近 7 天 / 近 30 天 / 今年),外加排序方向(新→旧 / 旧→新)与一键清除;路径筛选回车后由 git 服务端过滤(`git log -- <path>`) |
|
package/README_EN.md
CHANGED
|
@@ -84,6 +84,7 @@ The panel never guesses: it measures itself with a `ResizeObserver` (width ≥ 6
|
|
|
84
84
|
|
|
85
85
|
- Action rail: the JetBrains-style vertical strip in the bottom workbench (and one horizontal row under the header in the right sidebar) with refresh, new branch, checkout, delete, compare, show diff, stash, new tag, favorite, fetch, pull and push. **It shows as many buttons as fit**, folds the rest into a `⋯ more` menu, and pins a `⚙ settings` button that lets you reorder actions and toggle each of them (stored in `dsh-ide-git.rail.v1`). Every action lights up or greys out with the current state (no other branch → delete / checkout / compare off, no remote → fetch / push off, no upstream → pull off, no changes → diff off).
|
|
86
86
|
- Status line: branch, upstream, ahead/behind, stash count, busy indicator.
|
|
87
|
+
- Composer status pill: repository/branch capsule in the host input-bar tool row (colored uncommitted / pullable / to-push counters), click to switch repo and branch — kept in sync with the panel both ways; single-repo workspaces switch branches only, multi-repo workspaces switch both. Off-switch in the settings (on by default). Sessions are pre-warmed on switch so the panel opens instantly.
|
|
87
88
|
- Branch tree: HEAD / Local / Remote / Tags groups, filter box, ahead/behind badges, worktree marker; local blue / remote violet / tag amber / HEAD green.
|
|
88
89
|
- Branch context menu: checkout, rebase current onto this, merge into current, compare with current, new branch from here, rename, delete, fetch, push, send to chat (branch + repo).
|
|
89
90
|
- History filters: text or hash, branch or tag, author, date (today / 7 days / 30 days / this year), an order toggle (newest / oldest first) and one-click clear; the path filter runs server-side (`git log -- <path>`) on Enter.
|
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.19.0",
|
|
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.19.0",
|
|
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": {
|
package/src/client.js
CHANGED
|
@@ -288,6 +288,16 @@ window.__ModuleLoader__.load({
|
|
|
288
288
|
'settings.card.desc': '面板放置、动作条按钮与 AI 提交信息',
|
|
289
289
|
'settings.placement.title': '面板放置',
|
|
290
290
|
'settings.placement.tab': '在主对话区显示 Git 标签页',
|
|
291
|
+
'settings.composer.title': '输入框 Git 状态',
|
|
292
|
+
'settings.composer.hint': '在输入框工具条显示仓库与分支(可点击切换,与 Git 面板联动);关闭后输入框不再显示。',
|
|
293
|
+
'composer.repoHint': '切换仓库',
|
|
294
|
+
'composer.branchHint': '切换分支',
|
|
295
|
+
'composer.detached': 'HEAD(游离)',
|
|
296
|
+
'composer.changesHint': '未提交变更数',
|
|
297
|
+
'composer.behindHint': '远程新提交(可拉取)',
|
|
298
|
+
'composer.aheadHint': '本地待推送提交',
|
|
299
|
+
'composer.inWorktree': '已被其他工作树签出',
|
|
300
|
+
'composer.busy': 'Git 操作进行中…',
|
|
291
301
|
'settings.placement.tabHint': '关闭后仍可从右侧栏或底部面板打开。',
|
|
292
302
|
'settings.placement.immersive': '沉浸模式(占满全高,隐藏输入框)',
|
|
293
303
|
'settings.placement.immersiveHint': '开启后 Git 标签页占满整个会话区高度,输入框自动收起;审批与提问仍会正常显示。',
|
|
@@ -603,6 +613,16 @@ window.__ModuleLoader__.load({
|
|
|
603
613
|
'settings.card.desc': 'Placement, action rail buttons and AI commit message',
|
|
604
614
|
'settings.placement.title': 'Placement',
|
|
605
615
|
'settings.placement.tab': 'Show the Git tab in the conversation area',
|
|
616
|
+
'settings.composer.title': 'Git status in the input bar',
|
|
617
|
+
'settings.composer.hint': 'Show repository and branch beside the composer tool row — click to switch, synced with the Git panel. Off, the input bar stays clean.',
|
|
618
|
+
'composer.repoHint': 'Switch repository',
|
|
619
|
+
'composer.branchHint': 'Switch branch',
|
|
620
|
+
'composer.detached': 'HEAD (detached)',
|
|
621
|
+
'composer.changesHint': 'Uncommitted changes',
|
|
622
|
+
'composer.behindHint': 'New upstream commits (pullable)',
|
|
623
|
+
'composer.aheadHint': 'Local commits waiting to push',
|
|
624
|
+
'composer.inWorktree': 'Checked out in another worktree',
|
|
625
|
+
'composer.busy': 'Git operation in progress…',
|
|
606
626
|
'settings.placement.tabHint': 'Off, the panel stays reachable from the right sidebar and the bottom panel.',
|
|
607
627
|
'settings.placement.immersive': 'Immersive mode (full height, hidden composer)',
|
|
608
628
|
'settings.placement.immersiveHint': 'On, the Git tab fills the whole conversation area and the composer steps aside; approvals and questions still show.',
|
|
@@ -926,6 +946,16 @@ window.__ModuleLoader__.load({
|
|
|
926
946
|
'settings.card.desc': '面板放置、動作列按鈕與 AI 提交訊息',
|
|
927
947
|
'settings.placement.title': '面板放置',
|
|
928
948
|
'settings.placement.tab': '喺主對話區顯示 Git 分頁',
|
|
949
|
+
'settings.composer.title': '輸入框 Git 狀態',
|
|
950
|
+
'settings.composer.hint': '喺輸入框工具列顯示儲存庫同分支(可以點擊切換,同 Git 面板聯動);關閉之後輸入框就唔會再顯示。',
|
|
951
|
+
'composer.repoHint': '切換儲存庫',
|
|
952
|
+
'composer.branchHint': '切換分支',
|
|
953
|
+
'composer.detached': 'HEAD(游離)',
|
|
954
|
+
'composer.changesHint': '未提交變更數',
|
|
955
|
+
'composer.behindHint': '遠端新提交(可以拉取)',
|
|
956
|
+
'composer.aheadHint': '本地等待推送提交',
|
|
957
|
+
'composer.inWorktree': '已經喺其他工作樹簽出',
|
|
958
|
+
'composer.busy': 'Git 操作進行中…',
|
|
929
959
|
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
930
960
|
'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
|
|
931
961
|
'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
|
|
@@ -1241,6 +1271,16 @@ window.__ModuleLoader__.load({
|
|
|
1241
1271
|
'settings.card.desc': '面板放置、動作列按鈕與 AI 提交訊息',
|
|
1242
1272
|
'settings.placement.title': '面板放置',
|
|
1243
1273
|
'settings.placement.tab': '在主對話區顯示 Git 分頁',
|
|
1274
|
+
'settings.composer.title': '輸入框 Git 狀態',
|
|
1275
|
+
'settings.composer.hint': '在輸入框工具列顯示儲存庫與分支(可點擊切換,並與 Git 面板連動);關閉後輸入框不再顯示。',
|
|
1276
|
+
'composer.repoHint': '切換儲存庫',
|
|
1277
|
+
'composer.branchHint': '切換分支',
|
|
1278
|
+
'composer.detached': 'HEAD(分離)',
|
|
1279
|
+
'composer.changesHint': '未提交變更數',
|
|
1280
|
+
'composer.behindHint': '遠端新提交(可拉取)',
|
|
1281
|
+
'composer.aheadHint': '本地待推送提交',
|
|
1282
|
+
'composer.inWorktree': '已被其他工作樹簽出',
|
|
1283
|
+
'composer.busy': 'Git 操作進行中…',
|
|
1244
1284
|
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
1245
1285
|
'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
|
|
1246
1286
|
'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個對話區高度,輸入框自動收起;審核與提問仍會正常顯示。',
|
|
@@ -1556,6 +1596,16 @@ window.__ModuleLoader__.load({
|
|
|
1556
1596
|
'settings.card.desc': '面板放置、動作列按鈕與 AI 提交訊息',
|
|
1557
1597
|
'settings.placement.title': '面板放置',
|
|
1558
1598
|
'settings.placement.tab': '喺主對話區顯示 Git 分頁',
|
|
1599
|
+
'settings.composer.title': '輸入框 Git 狀態',
|
|
1600
|
+
'settings.composer.hint': '喺輸入框工具列顯示儲存庫同分支(可以點擊切換,同 Git 面板聯動);關閉之後輸入框就唔會再顯示。',
|
|
1601
|
+
'composer.repoHint': '切換儲存庫',
|
|
1602
|
+
'composer.branchHint': '切換分支',
|
|
1603
|
+
'composer.detached': 'HEAD(游離)',
|
|
1604
|
+
'composer.changesHint': '未提交變更數',
|
|
1605
|
+
'composer.behindHint': '遠端新提交(可以拉取)',
|
|
1606
|
+
'composer.aheadHint': '本地等待推送提交',
|
|
1607
|
+
'composer.inWorktree': '已經喺其他工作樹簽出',
|
|
1608
|
+
'composer.busy': 'Git 操作進行中…',
|
|
1559
1609
|
'settings.placement.tabHint': '關閉之後仍可從右側欄或底部面板開啟。',
|
|
1560
1610
|
'settings.placement.immersive': '沉浸模式(佔滿全高,隱藏輸入框)',
|
|
1561
1611
|
'settings.placement.immersiveHint': '開啟之後 Git 分頁會佔滿整個會話區高度,輸入框自動收起;審批與提問仍會正常顯示。',
|
|
@@ -1871,6 +1921,16 @@ window.__ModuleLoader__.load({
|
|
|
1871
1921
|
'settings.card.desc': 'パネル配置、アクションレールのボタンと AI コミットメッセージ',
|
|
1872
1922
|
'settings.placement.title': 'パネル配置',
|
|
1873
1923
|
'settings.placement.tab': '会話エリアに Git タブを表示',
|
|
1924
|
+
'settings.composer.title': '入力欄の Git ステータス',
|
|
1925
|
+
'settings.composer.hint': '入力欄ツールバーにリポジトリとブランチを表示します(クリックで切り替え、Git パネルと連動)。オフにすると入力欄には表示されません。',
|
|
1926
|
+
'composer.repoHint': 'リポジトリを切り替え',
|
|
1927
|
+
'composer.branchHint': 'ブランチを切り替え',
|
|
1928
|
+
'composer.detached': 'HEAD(切断)',
|
|
1929
|
+
'composer.changesHint': '未コミットの変更数',
|
|
1930
|
+
'composer.behindHint': 'リモートの新規コミット(取得可能)',
|
|
1931
|
+
'composer.aheadHint': 'プッシュ待ちのローカルコミット',
|
|
1932
|
+
'composer.inWorktree': '別のワークツリーでチェックアウト中',
|
|
1933
|
+
'composer.busy': 'Git 操作を実行中…',
|
|
1874
1934
|
'settings.placement.tabHint': 'オフにしても右サイドバーと下部パネルから開けます。',
|
|
1875
1935
|
'settings.placement.immersive': '没入モード(全高表示・入力欄を非表示)',
|
|
1876
1936
|
'settings.placement.immersiveHint': 'オンにすると Git タブが会話エリアの高さいっぱいに広がり、入力欄は引っ込みます。承認や質問は通常どおり表示されます。',
|
|
@@ -2186,6 +2246,16 @@ window.__ModuleLoader__.load({
|
|
|
2186
2246
|
'settings.card.desc': '패널 배치, 액션 레일 버튼 및 AI 커밋 메시지',
|
|
2187
2247
|
'settings.placement.title': '패널 배치',
|
|
2188
2248
|
'settings.placement.tab': '대화 영역에 Git 탭 표시',
|
|
2249
|
+
'settings.composer.title': '입력창 Git 상태',
|
|
2250
|
+
'settings.composer.hint': '입력창 도구 모음에 저장소와 브랜치를 표시합니다(클릭하여 전환, Git 패널과 연동). 끄면 입력창에 표시되지 않습니다.',
|
|
2251
|
+
'composer.repoHint': '저장소 전환',
|
|
2252
|
+
'composer.branchHint': '브랜치 전환',
|
|
2253
|
+
'composer.detached': 'HEAD(분리됨)',
|
|
2254
|
+
'composer.changesHint': '커밋되지 않은 변경 수',
|
|
2255
|
+
'composer.behindHint': '원격 새 커밋(가져오기 가능)',
|
|
2256
|
+
'composer.aheadHint': '푸시 대기 중인 로컬 커밋',
|
|
2257
|
+
'composer.inWorktree': '다른 작업 트리에서 체크아웃됨',
|
|
2258
|
+
'composer.busy': 'Git 작업 진행 중…',
|
|
2189
2259
|
'settings.placement.tabHint': '꺼도 오른쪽 사이드바와 하단 패널에서 열 수 있습니다.',
|
|
2190
2260
|
'settings.placement.immersive': '몰입 모드(전체 높이, 입력창 숨김)',
|
|
2191
2261
|
'settings.placement.immersiveHint': '켜면 Git 탭이 대화 영역 전체 높이를 차지하고 입력창은 숨겨집니다. 승인과 질문은 정상적으로 표시됩니다.',
|
|
@@ -2501,6 +2571,16 @@ window.__ModuleLoader__.load({
|
|
|
2501
2571
|
'settings.card.desc': 'Panel-Platzierung, Aktionsleisten-Schaltflächen und KI-Commit-Nachricht',
|
|
2502
2572
|
'settings.placement.title': 'Panel-Platzierung',
|
|
2503
2573
|
'settings.placement.tab': 'Git-Tab im Gesprächsbereich anzeigen',
|
|
2574
|
+
'settings.composer.title': 'Git-Status in der Eingabeleiste',
|
|
2575
|
+
'settings.composer.hint': 'Repository und Zweig neben der Eingabeleiste zeigen — anklickbar zum Wechseln, mit dem Git-Panel synchron. Aus schaltet die Anzeige ab.',
|
|
2576
|
+
'composer.repoHint': 'Repository wechseln',
|
|
2577
|
+
'composer.branchHint': 'Zweig wechseln',
|
|
2578
|
+
'composer.detached': 'HEAD (detached)',
|
|
2579
|
+
'composer.changesHint': 'Uncommittete Änderungen',
|
|
2580
|
+
'composer.behindHint': 'Neue Upstream-Commits (abrufbar)',
|
|
2581
|
+
'composer.aheadHint': 'Lokale Commits zum Pushen',
|
|
2582
|
+
'composer.inWorktree': 'In anderem Worktree ausgecheckt',
|
|
2583
|
+
'composer.busy': 'Git-Aktion läuft…',
|
|
2504
2584
|
'settings.placement.tabHint': 'Ausgeschaltet bleibt das Panel über rechte Seitenleiste und unteres Panel erreichbar.',
|
|
2505
2585
|
'settings.placement.immersive': 'Immersiver Modus (volle Höhe, ausgeblendetes Eingabefeld)',
|
|
2506
2586
|
'settings.placement.immersiveHint': 'Eingeschaltet füllt der Git-Tab die gesamte Gesprächshöhe und das Eingabefeld weicht zurück; Freigaben und Rückfragen bleiben sichtbar.',
|
|
@@ -2816,6 +2896,16 @@ window.__ModuleLoader__.load({
|
|
|
2816
2896
|
'settings.card.desc': 'Placement du panneau, boutons de la barre d’actions et message de commit par IA',
|
|
2817
2897
|
'settings.placement.title': 'Placement du panneau',
|
|
2818
2898
|
'settings.placement.tab': 'Afficher l’onglet Git dans la zone de conversation',
|
|
2899
|
+
'settings.composer.title': 'Statut Git dans la barre de saisie',
|
|
2900
|
+
'settings.composer.hint': 'Affiche le dépôt et la branche à côté de la barre de saisie — cliquables pour basculer, synchronisés avec le panneau Git. Désactivé, la barre reste épurée.',
|
|
2901
|
+
'composer.repoHint': 'Changer de dépôt',
|
|
2902
|
+
'composer.branchHint': 'Changer de branche',
|
|
2903
|
+
'composer.detached': 'HEAD (détaché)',
|
|
2904
|
+
'composer.changesHint': 'Modifications non commitées',
|
|
2905
|
+
'composer.behindHint': 'Nouveaux commits amont (récupérables)',
|
|
2906
|
+
'composer.aheadHint': 'Commits locaux en attente d’envoi',
|
|
2907
|
+
'composer.inWorktree': 'Extrait dans un autre arbre de travail',
|
|
2908
|
+
'composer.busy': 'Opération Git en cours…',
|
|
2819
2909
|
'settings.placement.tabHint': 'Désactivé, le panneau reste accessible depuis la barre latérale droite et le panneau inférieur.',
|
|
2820
2910
|
'settings.placement.immersive': 'Mode immersif (pleine hauteur, zone de saisie masquée)',
|
|
2821
2911
|
'settings.placement.immersiveHint': 'Activé, l’onglet Git occupe toute la hauteur de la conversation et la zone de saisie s’efface ; approbations et questions restent visibles.',
|
|
@@ -3131,6 +3221,16 @@ window.__ModuleLoader__.load({
|
|
|
3131
3221
|
'settings.card.desc': 'Размещение панели, кнопки панели действий и сообщение коммита от ИИ',
|
|
3132
3222
|
'settings.placement.title': 'Размещение панели',
|
|
3133
3223
|
'settings.placement.tab': 'Показывать вкладку Git в области диалога',
|
|
3224
|
+
'settings.composer.title': 'Статус Git в строке ввода',
|
|
3225
|
+
'settings.composer.hint': 'Показывает репозиторий и ветку рядом с панелью ввода — клик переключает, синхронно с панелью Git. Выключено — строка ввода остаётся чистой.',
|
|
3226
|
+
'composer.repoHint': 'Сменить репозиторий',
|
|
3227
|
+
'composer.branchHint': 'Сменить ветку',
|
|
3228
|
+
'composer.detached': 'HEAD (отделён)',
|
|
3229
|
+
'composer.changesHint': 'Незакоммиченные изменения',
|
|
3230
|
+
'composer.behindHint': 'Новые коммиты upstream (можно забрать)',
|
|
3231
|
+
'composer.aheadHint': 'Локальные коммиты, ожидающие отправки',
|
|
3232
|
+
'composer.inWorktree': 'Выверен в другом worktree',
|
|
3233
|
+
'composer.busy': 'Операция Git выполняется…',
|
|
3134
3234
|
'settings.placement.tabHint': 'В выключенном виде панель доступна из правой боковой панели и нижней панели.',
|
|
3135
3235
|
'settings.placement.immersive': 'Полноэкранный режим (во всю высоту, без поля ввода)',
|
|
3136
3236
|
'settings.placement.immersiveHint': 'Во включённом виде вкладка Git занимает всю высоту области диалога, поле ввода скрывается; подтверждения и вопросы остаются на виду.',
|
|
@@ -3446,6 +3546,16 @@ window.__ModuleLoader__.load({
|
|
|
3446
3546
|
'settings.card.desc': 'Posição do painel, botões da barra de ações e mensagem de commit por IA',
|
|
3447
3547
|
'settings.placement.title': 'Posição do painel',
|
|
3448
3548
|
'settings.placement.tab': 'Mostrar a aba Git na área de conversa',
|
|
3549
|
+
'settings.composer.title': 'Status do Git na barra de entrada',
|
|
3550
|
+
'settings.composer.hint': 'Mostra repositório e branch ao lado da barra de entrada — clique para alternar, em sincronia com o painel Git. Desligado, a barra fica limpa.',
|
|
3551
|
+
'composer.repoHint': 'Trocar repositório',
|
|
3552
|
+
'composer.branchHint': 'Trocar branch',
|
|
3553
|
+
'composer.detached': 'HEAD (destacado)',
|
|
3554
|
+
'composer.changesHint': 'Alterações não commitadas',
|
|
3555
|
+
'composer.behindHint': 'Novos commits remotos (para pull)',
|
|
3556
|
+
'composer.aheadHint': 'Commits locais aguardando push',
|
|
3557
|
+
'composer.inWorktree': 'Em checkout em outra worktree',
|
|
3558
|
+
'composer.busy': 'Operação Git em andamento…',
|
|
3449
3559
|
'settings.placement.tabHint': 'Desligado, o painel continua disponível na barra lateral direita e no painel inferior.',
|
|
3450
3560
|
'settings.placement.immersive': 'Modo imersivo (altura total, campo de entrada oculto)',
|
|
3451
3561
|
'settings.placement.immersiveHint': 'Ativado, a aba Git ocupa toda a altura da conversa e o campo de entrada fica de lado; aprovações e perguntas continuam visíveis.',
|
|
@@ -3761,6 +3871,16 @@ window.__ModuleLoader__.load({
|
|
|
3761
3871
|
'settings.card.desc': 'Posizione del pannello, pulsanti della barra azioni e messaggio di commit con IA',
|
|
3762
3872
|
'settings.placement.title': 'Posizione del pannello',
|
|
3763
3873
|
'settings.placement.tab': 'Mostra la scheda Git nell’area conversazione',
|
|
3874
|
+
'settings.composer.title': 'Stato Git nella barra di inserimento',
|
|
3875
|
+
'settings.composer.hint': 'Mostra repository e ramo accanto alla barra di inserimento — cliccabili per cambiare, sincronizzati con il pannello Git. Disattivato, la barra resta pulita.',
|
|
3876
|
+
'composer.repoHint': 'Cambia repository',
|
|
3877
|
+
'composer.branchHint': 'Cambia ramo',
|
|
3878
|
+
'composer.detached': 'HEAD (staccato)',
|
|
3879
|
+
'composer.changesHint': 'Modifiche non ancora committate',
|
|
3880
|
+
'composer.behindHint': 'Nuovi commit upstream (scaricabili)',
|
|
3881
|
+
'composer.aheadHint': 'Commit locali in attesa di push',
|
|
3882
|
+
'composer.inWorktree': 'Estratto in un altro worktree',
|
|
3883
|
+
'composer.busy': 'Operazione Git in corso…',
|
|
3764
3884
|
'settings.placement.tabHint': 'Da spento, il pannello resta raggiungibile dalla barra laterale destra e dal pannello inferiore.',
|
|
3765
3885
|
'settings.placement.immersive': 'Modalità immersiva (altezza piena, input nascosto)',
|
|
3766
3886
|
'settings.placement.immersiveHint': 'Attiva, la scheda Git occupa tutta l’altezza della conversazione e la barra di input resta nascosta; approvazioni e domande restano visibili.',
|
|
@@ -4076,6 +4196,16 @@ window.__ModuleLoader__.load({
|
|
|
4076
4196
|
'settings.card.desc': 'Paneelplaatsing, knoppen van de actiebalk en AI-commitbericht',
|
|
4077
4197
|
'settings.placement.title': 'Paneelplaatsing',
|
|
4078
4198
|
'settings.placement.tab': 'Git-tabblad in het gespreksgebied tonen',
|
|
4199
|
+
'settings.composer.title': 'Git-status in de invoerbalk',
|
|
4200
|
+
'settings.composer.hint': 'Toont repository en branch naast de invoerbalk — klikbaar om te wisselen, gesynchroniseerd met het Git-paneel. Uit houdt de balk schoon.',
|
|
4201
|
+
'composer.repoHint': 'Repository wisselen',
|
|
4202
|
+
'composer.branchHint': 'Branch wisselen',
|
|
4203
|
+
'composer.detached': 'HEAD (los)',
|
|
4204
|
+
'composer.changesHint': 'Ongecommitte wijzigingen',
|
|
4205
|
+
'composer.behindHint': 'Nieuwe upstream-commits (op te halen)',
|
|
4206
|
+
'composer.aheadHint': 'Lokale commits die wachten op push',
|
|
4207
|
+
'composer.inWorktree': 'Uitgecheckt in een andere worktree',
|
|
4208
|
+
'composer.busy': 'Git-actie gaande…',
|
|
4079
4209
|
'settings.placement.tabHint': 'Uitgeschakeld blijft het paneel bereikbaar via de rechterzijbalk en het onderpaneel.',
|
|
4080
4210
|
'settings.placement.immersive': 'Meeslepende modus (volledige hoogte, verborgen invoerveld)',
|
|
4081
4211
|
'settings.placement.immersiveHint': 'Aan: het Git-tabblad vult de hele gespreksruimte en het invoerveld wijkt; goedkeuringen en vragen blijven zichtbaar.',
|
|
@@ -4391,6 +4521,16 @@ window.__ModuleLoader__.load({
|
|
|
4391
4521
|
'settings.card.desc': 'Rozmieszczenie panelu, przyciski paska akcji i wiadomość commita od AI',
|
|
4392
4522
|
'settings.placement.title': 'Rozmieszczenie panelu',
|
|
4393
4523
|
'settings.placement.tab': 'Pokazuj kartę Git w obszarze rozmowy',
|
|
4524
|
+
'settings.composer.title': 'Status Git w pasku wprowadzania',
|
|
4525
|
+
'settings.composer.hint': 'Pokazuje repozytorium i gałąź obok paska wprowadzania — kliknij, aby przełączyć; synchronizacja z panelem Git. Wyłączone — pasek pozostaje czysty.',
|
|
4526
|
+
'composer.repoHint': 'Przełącz repozytorium',
|
|
4527
|
+
'composer.branchHint': 'Przełącz gałąź',
|
|
4528
|
+
'composer.detached': 'HEAD (odłączony)',
|
|
4529
|
+
'composer.changesHint': 'Niezatwierdzone zmiany',
|
|
4530
|
+
'composer.behindHint': 'Nowe commity upstream (do pobrania)',
|
|
4531
|
+
'composer.aheadHint': 'Lokalne commity oczekujące na wysłanie',
|
|
4532
|
+
'composer.inWorktree': 'Wybrane w innym drzewie roboczym',
|
|
4533
|
+
'composer.busy': 'Operacja Git w toku…',
|
|
4394
4534
|
'settings.placement.tabHint': 'Po wyłączeniu panel pozostaje dostępny z prawego paska bocznego i dolnego panelu.',
|
|
4395
4535
|
'settings.placement.immersive': 'Tryb immersyjny (pełna wysokość, ukryte pole wpisywania)',
|
|
4396
4536
|
'settings.placement.immersiveHint': 'Po włączeniu karta Git wypełnia całą wysokość rozmowy, a pole wpisywania się chowa; zatwierdzenia i pytania pozostają widoczne.',
|
|
@@ -4706,6 +4846,16 @@ window.__ModuleLoader__.load({
|
|
|
4706
4846
|
'settings.card.desc': 'Panelplacering, knappar i åtgärdsfältet och AI-commitmeddelande',
|
|
4707
4847
|
'settings.placement.title': 'Panelplacering',
|
|
4708
4848
|
'settings.placement.tab': 'Visa Git-fliken i konversationsytan',
|
|
4849
|
+
'settings.composer.title': 'Git-status i inmatningsfältet',
|
|
4850
|
+
'settings.composer.hint': 'Visar repo och gren bredvid inmatningsfältet — klicka för att växla, synkat med Git-panelen. Av stänger av visningen.',
|
|
4851
|
+
'composer.repoHint': 'Byt repo',
|
|
4852
|
+
'composer.branchHint': 'Byt gren',
|
|
4853
|
+
'composer.detached': 'HEAD (frånkopplad)',
|
|
4854
|
+
'composer.changesHint': 'Ocommittade ändringar',
|
|
4855
|
+
'composer.behindHint': 'Nya upstream-commits (hämtbara)',
|
|
4856
|
+
'composer.aheadHint': 'Lokala commits som väntar på push',
|
|
4857
|
+
'composer.inWorktree': 'Utcheckad i ett annat arbetsträd',
|
|
4858
|
+
'composer.busy': 'Git-åtgärd pågår…',
|
|
4709
4859
|
'settings.placement.tabHint': 'Avstängd finns panelen kvar via höger sidopanel och nedre panelen.',
|
|
4710
4860
|
'settings.placement.immersive': 'Uppslukande läge (full höjd, dolt inmatningsfält)',
|
|
4711
4861
|
'settings.placement.immersiveHint': 'På: Git-fliken fyller hela konversationsytan och inmatningsfältet träder åt sidan; godkännanden och frågor visas fortfarande.',
|
|
@@ -5021,6 +5171,16 @@ window.__ModuleLoader__.load({
|
|
|
5021
5171
|
'settings.card.desc': 'Panel yerleşimi, eylem çubuğu düğmeleri ve AI commit mesajı',
|
|
5022
5172
|
'settings.placement.title': 'Panel yerleşimi',
|
|
5023
5173
|
'settings.placement.tab': 'Konuşma alanında Git sekmesini göster',
|
|
5174
|
+
'settings.composer.title': 'Giriş çubuğunda Git durumu',
|
|
5175
|
+
'settings.composer.hint': 'Giriş çubuğu yanında depo ve dal gösterir — geçiş için tıklayın, Git paneliyle eşitlenir. Kapalıyken giriş çubuğu temiz kalır.',
|
|
5176
|
+
'composer.repoHint': 'Depo değiştir',
|
|
5177
|
+
'composer.branchHint': 'Dal değiştir',
|
|
5178
|
+
'composer.detached': 'HEAD (ayrık)',
|
|
5179
|
+
'composer.changesHint': 'Commitlenmemiş değişiklikler',
|
|
5180
|
+
'composer.behindHint': 'Yeni upstream commitleri (çekilebilir)',
|
|
5181
|
+
'composer.aheadHint': 'Push bekleyen yerel commitler',
|
|
5182
|
+
'composer.inWorktree': 'Başka bir worktree’da checkout yapılmış',
|
|
5183
|
+
'composer.busy': 'Git işlemi sürüyor…',
|
|
5024
5184
|
'settings.placement.tabHint': 'Kapatıldığında panel sağ kenar çubuğundan ve alt panelden açılabilir.',
|
|
5025
5185
|
'settings.placement.immersive': 'Odaklanma modu (tam yükseklik, gizli giriş alanı)',
|
|
5026
5186
|
'settings.placement.immersiveHint': 'Açıkken Git sekmesi tüm konuşma alanını kaplar ve giriş alanı gizlenir; onaylar ve sorular yine de görünür.',
|
|
@@ -5336,6 +5496,16 @@ window.__ModuleLoader__.load({
|
|
|
5336
5496
|
'settings.card.desc': 'Penempatan panel, tombol bilah aksi dan pesan commit AI',
|
|
5337
5497
|
'settings.placement.title': 'Penempatan panel',
|
|
5338
5498
|
'settings.placement.tab': 'Tampilkan tab Git di area percakapan',
|
|
5499
|
+
'settings.composer.title': 'Status Git di bilah masukan',
|
|
5500
|
+
'settings.composer.hint': 'Menampilkan repositori dan cabang di samping bilah masukan — klik untuk beralih, tersinkron dengan panel Git. Matikan untuk menyembunyikannya.',
|
|
5501
|
+
'composer.repoHint': 'Ganti repositori',
|
|
5502
|
+
'composer.branchHint': 'Ganti cabang',
|
|
5503
|
+
'composer.detached': 'HEAD (lepas)',
|
|
5504
|
+
'composer.changesHint': 'Perubahan yang belum di-commit',
|
|
5505
|
+
'composer.behindHint': 'Commit upstream baru (bisa ditarik)',
|
|
5506
|
+
'composer.aheadHint': 'Commit lokal menunggu push',
|
|
5507
|
+
'composer.inWorktree': 'Sudah di-checkout di worktree lain',
|
|
5508
|
+
'composer.busy': 'Operasi Git sedang berjalan…',
|
|
5339
5509
|
'settings.placement.tabHint': 'Dimatikan, panel tetap bisa dibuka dari bilah sisi kanan dan panel bawah.',
|
|
5340
5510
|
'settings.placement.immersive': 'Mode imersif (tinggi penuh, kolom masukan tersembunyi)',
|
|
5341
5511
|
'settings.placement.immersiveHint': 'Aktif: tab Git memenuhi seluruh tinggi area percakapan dan kolom masukan mengalah; persetujuan dan pertanyaan tetap tampil.',
|
|
@@ -5651,6 +5821,16 @@ window.__ModuleLoader__.load({
|
|
|
5651
5821
|
'settings.card.desc': 'Vị trí bảng, nút thanh tác vụ và thông điệp commit do AI viết',
|
|
5652
5822
|
'settings.placement.title': 'Vị trí bảng',
|
|
5653
5823
|
'settings.placement.tab': 'Hiện thẻ Git trong vùng hội thoại',
|
|
5824
|
+
'settings.composer.title': 'Trạng thái Git trên thanh nhập',
|
|
5825
|
+
'settings.composer.hint': 'Hiện kho và nhánh bên cạnh thanh nhập — bấm để chuyển đổi, đồng bộ với bảng Git. Tắt để ẩn đi.',
|
|
5826
|
+
'composer.repoHint': 'Chuyển kho',
|
|
5827
|
+
'composer.branchHint': 'Chuyển nhánh',
|
|
5828
|
+
'composer.detached': 'HEAD (tách rời)',
|
|
5829
|
+
'composer.changesHint': 'Thay đổi chưa commit',
|
|
5830
|
+
'composer.behindHint': 'Commit mới từ xa (có thể kéo về)',
|
|
5831
|
+
'composer.aheadHint': 'Commit cục bộ chờ đẩy lên',
|
|
5832
|
+
'composer.inWorktree': 'Đã được checkout ở worktree khác',
|
|
5833
|
+
'composer.busy': 'Đang chạy tác vụ Git…',
|
|
5654
5834
|
'settings.placement.tabHint': 'Tắt rồi, bảng vẫn mở được từ thanh bên phải và bảng dưới.',
|
|
5655
5835
|
'settings.placement.immersive': 'Chế độ đắm chìm (chiều cao đầy đủ, ẩn ô nhập)',
|
|
5656
5836
|
'settings.placement.immersiveHint': 'Bật: thẻ Git chiếm toàn bộ chiều cao vùng hội thoại và ô nhập thu vào; phê duyệt và câu hỏi vẫn hiển thị bình thường.',
|
|
@@ -5966,6 +6146,16 @@ window.__ModuleLoader__.load({
|
|
|
5966
6146
|
'settings.card.desc': 'موضع اللوحة، أزرار شريط الإجراءات ورسالة إيداع بالذكاء الاصطناعي',
|
|
5967
6147
|
'settings.placement.title': 'موضع اللوحة',
|
|
5968
6148
|
'settings.placement.tab': 'إظهار تبويب Git في منطقة المحادثة',
|
|
6149
|
+
'settings.composer.title': 'حالة Git في شريط الإدخال',
|
|
6150
|
+
'settings.composer.hint': 'يعرض المستودع والفرز بجانب شريط الإدخال — انقر للتبديل، ومتزامن مع لوحة Git. عند الإيقاف يبقى الشريط نظيفًا.',
|
|
6151
|
+
'composer.repoHint': 'تبديل المستودع',
|
|
6152
|
+
'composer.branchHint': 'تبديل الفرع',
|
|
6153
|
+
'composer.detached': 'HEAD (منفصل)',
|
|
6154
|
+
'composer.changesHint': 'تغييرات غير ملتزَم بها',
|
|
6155
|
+
'composer.behindHint': 'التزامات جديدة في المستودع البعيد (قابلة للسحب)',
|
|
6156
|
+
'composer.aheadHint': 'التزامات محلية بانتظار الدفع',
|
|
6157
|
+
'composer.inWorktree': 'مسحوب في شجرة عمل أخرى',
|
|
6158
|
+
'composer.busy': 'عملية Git جارية…',
|
|
5969
6159
|
'settings.placement.tabHint': 'عند الإيقاف تبقى اللوحة متاحة من الشريط الجانبي الأيمن واللوحة السفلية.',
|
|
5970
6160
|
'settings.placement.immersive': 'الوضع الغامر (ارتفاع كامل، إخفاء صندوق الكتابة)',
|
|
5971
6161
|
'settings.placement.immersiveHint': 'عند التشغيل يملأ تبويب Git منطقة المحادثة بكامل ارتفاعها ويتراجع صندوق الكتابة؛ تظل الموافقات والأسئلة ظاهرة كالمعتاد.',
|
|
@@ -6281,6 +6471,16 @@ window.__ModuleLoader__.load({
|
|
|
6281
6471
|
'settings.card.desc': 'पैनल प्लेसमेंट, एक्शन रेल बटन और AI कमिट संदेश',
|
|
6282
6472
|
'settings.placement.title': 'पैनल प्लेसमेंट',
|
|
6283
6473
|
'settings.placement.tab': 'संवाद क्षेत्र में Git टैब दिखाएँ',
|
|
6474
|
+
'settings.composer.title': 'इनपुट बार में Git स्थिति',
|
|
6475
|
+
'settings.composer.hint': 'इनपुट बार के बगल में रिपॉज़िटरी और शाखा दिखाता है — बदलने के लिए क्लिक करें, Git पैनल के साथ सिंक। बंद करने पर इनपुट बार साफ़ रहती है।',
|
|
6476
|
+
'composer.repoHint': 'रिपॉज़िटरी बदलें',
|
|
6477
|
+
'composer.branchHint': 'शाखा बदलें',
|
|
6478
|
+
'composer.detached': 'HEAD (पृथक)',
|
|
6479
|
+
'composer.changesHint': 'बिना कमिट बदलाव',
|
|
6480
|
+
'composer.behindHint': 'अपस्ट्रीम के नए कमिट (खींचने योग्य)',
|
|
6481
|
+
'composer.aheadHint': 'पुश के लिए प्रतीक्षारत स्थानीय कमिट',
|
|
6482
|
+
'composer.inWorktree': 'दूसरे वर्कट्री में चेकआउट',
|
|
6483
|
+
'composer.busy': 'Git कार्य चल रहा है…',
|
|
6284
6484
|
'settings.placement.tabHint': 'बंद करने पर भी पैनल दाएँ साइडबार और निचले पैनल से खुलता है।',
|
|
6285
6485
|
'settings.placement.immersive': 'इमर्सिव मोड (पूरी ऊँचाई, छिपा इनपुट बॉक्स)',
|
|
6286
6486
|
'settings.placement.immersiveHint': 'चालू होने पर Git टैब पूरे संवाद क्षेत्र की ऊँचाई घेर लेता है और इनपुट बॉक्स हट जाता है; अनुमोदन और प्रश्न फिर भी दिखते हैं।',
|
|
@@ -6596,6 +6796,16 @@ window.__ModuleLoader__.load({
|
|
|
6596
6796
|
'settings.card.desc': 'ตำแหน่งแผง, ปุ่มแถบการทำงานและข้อความคอมมิตโดย AI',
|
|
6597
6797
|
'settings.placement.title': 'ตำแหน่งแผง',
|
|
6598
6798
|
'settings.placement.tab': 'แสดงแท็บ Git ในพื้นที่บทสนทนา',
|
|
6799
|
+
'settings.composer.title': 'สถานะ Git ในแถบป้อนข้อความ',
|
|
6800
|
+
'settings.composer.hint': 'แสดงที่เก็บและแบรนช์ข้างแถบป้อนข้อความ — คลิกเพื่อสลับ และซิงก์กับแผง Git ปิดแล้วแถบจะสะอาด',
|
|
6801
|
+
'composer.repoHint': 'สลับที่เก็บ',
|
|
6802
|
+
'composer.branchHint': 'สลับแบรนช์',
|
|
6803
|
+
'composer.detached': 'HEAD (แยกขาด)',
|
|
6804
|
+
'composer.changesHint': 'การเปลี่ยนแปลงที่ยังไม่คอมมิต',
|
|
6805
|
+
'composer.behindHint': 'คอมมิตใหม่จากรีโมต (ดึงได้)',
|
|
6806
|
+
'composer.aheadHint': 'คอมมิตในเครื่องรอพุช',
|
|
6807
|
+
'composer.inWorktree': 'ถูกเช็คเอาต์ในเวิร์กทรีอื่น',
|
|
6808
|
+
'composer.busy': 'กำลังดำเนินการ Git…',
|
|
6599
6809
|
'settings.placement.tabHint': 'ปิดแล้วยังเปิดแผงจากแถบข้างขวาหรือแผงล่างได้',
|
|
6600
6810
|
'settings.placement.immersive': 'โหมดดื่มด่ำ (เต็มความสูง, ซ่อนกล่องพิมพ์)',
|
|
6601
6811
|
'settings.placement.immersiveHint': 'เมื่อเปิด แท็บ Git จะกินพื้นที่สูงทั้งหมดของบทสนทนาและกล่องพิมพ์จะถอยออก; การอนุมัติและคำถามยังแสดงตามปกติ',
|
|
@@ -7017,6 +7227,120 @@ window.__ModuleLoader__.load({
|
|
|
7017
7227
|
return paintRoot.get(cwd)
|
|
7018
7228
|
}
|
|
7019
7229
|
|
|
7230
|
+
/* ============================== session-switch prewarm ============================== */
|
|
7231
|
+
|
|
7232
|
+
/* Everything above only fills when a PANEL MOUNTS. A session whose
|
|
7233
|
+
workspace the panel has never seen still paid the full cold start
|
|
7234
|
+
(repos discovery + summary + branches + first log page) the moment the
|
|
7235
|
+
user opened the Git tab from the rail or the right sidebar. The
|
|
7236
|
+
prewarmer closes that gap: it watches the session list — the same store
|
|
7237
|
+
and the same main-view criterion (retainedBy.mainView) the host itself
|
|
7238
|
+
uses to decide the active session — and whenever the active session's
|
|
7239
|
+
workspace changes, it runs exactly the READS the panel's first paint
|
|
7240
|
+
would run and writes them into the same cache. Opening the panel
|
|
7241
|
+
afterwards seeds from the warm cache and paints instantly; the mount
|
|
7242
|
+
refresh re-proves every field, exactly as before. Read routes only,
|
|
7243
|
+
always silent: any failure just means that panel opens cold, never an
|
|
7244
|
+
error surface. Hosts without the sessions service never arm it. */
|
|
7245
|
+
|
|
7246
|
+
const PREWARM_DEBOUNCE_MS = 300
|
|
7247
|
+
let prewarmTimer = null
|
|
7248
|
+
let prewarmGeneration = 0
|
|
7249
|
+
|
|
7250
|
+
/* The panel's own repo pick order (see the repos effect in Panel): the
|
|
7251
|
+
session's remembered repo, the workspace's last-viewed root, the
|
|
7252
|
+
workspace itself when it is a repository, then the first row. Picking
|
|
7253
|
+
identically is what makes the warmed page THE page the panel shows. */
|
|
7254
|
+
function prewarmPickOf(sessionId, cwd, data) {
|
|
7255
|
+
const paths = data.repos.map((repo) => repo.path)
|
|
7256
|
+
const remembered = readRememberedRepo(sessionId)
|
|
7257
|
+
if (remembered !== undefined && paths.indexOf(remembered) >= 0) return remembered
|
|
7258
|
+
const painted = readPaintRoot(cwd)
|
|
7259
|
+
if (painted !== undefined && paths.indexOf(painted) >= 0) return painted
|
|
7260
|
+
if (data.isRepo === true) return data.cwd
|
|
7261
|
+
if (paths.length > 0) return paths[0]
|
|
7262
|
+
return null
|
|
7263
|
+
}
|
|
7264
|
+
|
|
7265
|
+
async function prewarmWorkspace(sessionId, cwd) {
|
|
7266
|
+
const generation = ++prewarmGeneration
|
|
7267
|
+
try {
|
|
7268
|
+
let data = readPaintRepos(cwd)
|
|
7269
|
+
if (data === undefined) {
|
|
7270
|
+
data = await request('repos', { cwd: cwd })
|
|
7271
|
+
/* A newer switch arrived while this scan ran: drop the stale
|
|
7272
|
+
result on the floor instead of writing and fetching on. */
|
|
7273
|
+
if (generation !== prewarmGeneration) return
|
|
7274
|
+
writePaintRepos(cwd, data)
|
|
7275
|
+
}
|
|
7276
|
+
const pick = prewarmPickOf(sessionId, cwd, data)
|
|
7277
|
+
if (pick === null || readPaintPage(pick) !== undefined) return
|
|
7278
|
+
const base = { cwd: cwd, repoRoot: pick }
|
|
7279
|
+
const pages = await Promise.all([
|
|
7280
|
+
request('summary', Object.assign({}, base, { ignored: false, stats: false })),
|
|
7281
|
+
request('branches', base),
|
|
7282
|
+
request('log', Object.assign({}, base, { skip: 0, limit: 120 })),
|
|
7283
|
+
])
|
|
7284
|
+
if (generation !== prewarmGeneration) return
|
|
7285
|
+
writePaintPage(pick, { summary: pages[0], branches: pages[1], commits: pages[2].commits, hasMore: pages[2].hasMore })
|
|
7286
|
+
} catch (error) { void error }
|
|
7287
|
+
}
|
|
7288
|
+
|
|
7289
|
+
/* The active session, by the host's own rule: whoever the main view
|
|
7290
|
+
retains (retainedBy.mainView > 0). A switch can briefly retain BOTH
|
|
7291
|
+
sides; the freshest updatedAt wins. Null when no session is active. */
|
|
7292
|
+
/* The LIVE counts come from retainInfo(id).getSnapshot(), not from the
|
|
7293
|
+
list row: the row's retainedBy field only refreshes with the next host
|
|
7294
|
+
list sync, so right after a click it still names the PREVIOUS session
|
|
7295
|
+
(the same reason the host's own ui-session reads retainInfo in its
|
|
7296
|
+
publishMain). cwd and updatedAt still come from the row. */
|
|
7297
|
+
function activeSessionOf(ctx) {
|
|
7298
|
+
try {
|
|
7299
|
+
const sessions = ctx.get('sessions')
|
|
7300
|
+
const list = sessions !== undefined && sessions !== null ? sessions.list : undefined
|
|
7301
|
+
if (list === undefined || typeof list.getSnapshot !== 'function') return null
|
|
7302
|
+
if (typeof sessions.retainInfo !== 'function') return null
|
|
7303
|
+
const snapshot = list.getSnapshot()
|
|
7304
|
+
if (snapshot === null || typeof snapshot !== 'object' || snapshot.byId === undefined || snapshot.byId === null) return null
|
|
7305
|
+
let active = null
|
|
7306
|
+
for (const key of Object.keys(snapshot.byId)) {
|
|
7307
|
+
const entry = snapshot.byId[key]
|
|
7308
|
+
if (entry === null || typeof entry !== 'object') continue
|
|
7309
|
+
let main = 0
|
|
7310
|
+
try {
|
|
7311
|
+
const info = sessions.retainInfo(key)
|
|
7312
|
+
const counted = info !== undefined && info !== null && typeof info.getSnapshot === 'function' ? info.getSnapshot() : null
|
|
7313
|
+
const retained = counted !== null && typeof counted === 'object' ? counted.retainedBy : null
|
|
7314
|
+
main = retained !== undefined && retained !== null && typeof retained === 'object'
|
|
7315
|
+
? (typeof retained.mainView === 'number' ? retained.mainView : 0)
|
|
7316
|
+
: 0
|
|
7317
|
+
} catch (error) { void error }
|
|
7318
|
+
if (main <= 0) continue
|
|
7319
|
+
if (active === null || entry.updatedAt > active.updatedAt) active = entry
|
|
7320
|
+
}
|
|
7321
|
+
return active
|
|
7322
|
+
} catch (error) { void error; return null }
|
|
7323
|
+
}
|
|
7324
|
+
|
|
7325
|
+
function schedulePrewarm(ctx) {
|
|
7326
|
+
if (prewarmTimer !== null) clearTimeout(prewarmTimer)
|
|
7327
|
+
prewarmTimer = setTimeout(() => {
|
|
7328
|
+
prewarmTimer = null
|
|
7329
|
+
const active = activeSessionOf(ctx)
|
|
7330
|
+
if (active === null) return
|
|
7331
|
+
if (typeof active.cwd !== 'string' || active.cwd === '') return
|
|
7332
|
+
const sessionId = typeof active.id === 'string' ? active.id : 'default'
|
|
7333
|
+
/* Skip everything when BOTH cache layers already cover what the panel
|
|
7334
|
+
would paint: repos fresh AND the pick's page kept warm. */
|
|
7335
|
+
const cachedRepos = readPaintRepos(active.cwd)
|
|
7336
|
+
if (cachedRepos !== undefined) {
|
|
7337
|
+
const pick = prewarmPickOf(sessionId, active.cwd, cachedRepos)
|
|
7338
|
+
if (pick !== null && readPaintPage(pick) !== undefined) return
|
|
7339
|
+
}
|
|
7340
|
+
void prewarmWorkspace(sessionId, active.cwd)
|
|
7341
|
+
}, PREWARM_DEBOUNCE_MS)
|
|
7342
|
+
}
|
|
7343
|
+
|
|
7020
7344
|
/* ============================== storage ============================== */
|
|
7021
7345
|
|
|
7022
7346
|
/* ---- tree UI state (v0.13.1): the session-scoped seats remount on every
|
|
@@ -9523,6 +9847,49 @@ window.__ModuleLoader__.load({
|
|
|
9523
9847
|
E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: 'commit', size: 13 })),
|
|
9524
9848
|
E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.placement.immersive'))),
|
|
9525
9849
|
E('div', { className: 'dig-settings-subnote' }, t('settings.placement.immersiveHint')))
|
|
9850
|
+
})(),
|
|
9851
|
+
(() => {
|
|
9852
|
+
/* Composer status pill (issue: status in the input bar). Same
|
|
9853
|
+
optimistic-write contract as the two rows above; the pill reads
|
|
9854
|
+
composerStatus through composerStatusEnabled() and appears or
|
|
9855
|
+
disappears live. Default ON. */
|
|
9856
|
+
const field = 'composerStatus'
|
|
9857
|
+
const inputId = 'dig-placement-' + field
|
|
9858
|
+
const shown = Object.prototype.hasOwnProperty.call(intent.current, field)
|
|
9859
|
+
? intent.current[field] === true
|
|
9860
|
+
: values[field] !== false
|
|
9861
|
+
const toggle = (event) => {
|
|
9862
|
+
if (readOnly === true) { setNote(t('settings.rail.readonly')); return }
|
|
9863
|
+
const fromControl = event !== undefined && event !== null && event.target !== null
|
|
9864
|
+
&& event.target !== undefined && typeof event.target.checked === 'boolean'
|
|
9865
|
+
? event.target.checked
|
|
9866
|
+
: null
|
|
9867
|
+
const wanted = fromControl !== null
|
|
9868
|
+
? fromControl
|
|
9869
|
+
: (Object.prototype.hasOwnProperty.call(intent.current, field)
|
|
9870
|
+
? intent.current[field] !== true
|
|
9871
|
+
: values[field] !== false) !== true
|
|
9872
|
+
intent.current[field] = wanted
|
|
9873
|
+
dirty.current.set(field, wanted)
|
|
9874
|
+
setPending((now) => Object.assign({}, now, { [field]: wanted }))
|
|
9875
|
+
setNote('')
|
|
9876
|
+
bump()
|
|
9877
|
+
scheduleFlush()
|
|
9878
|
+
}
|
|
9879
|
+
return E('div', { key: field },
|
|
9880
|
+
E('div', { className: 'dig-settings-row', 'data-placement-row': field },
|
|
9881
|
+
E('input', {
|
|
9882
|
+
id: inputId,
|
|
9883
|
+
type: 'checkbox',
|
|
9884
|
+
className: 'dig-settings-check',
|
|
9885
|
+
'aria-labelledby': inputId + '-label',
|
|
9886
|
+
checked: shown,
|
|
9887
|
+
disabled: readOnly === true,
|
|
9888
|
+
onChange: toggle,
|
|
9889
|
+
}),
|
|
9890
|
+
E('span', { className: 'dig-settings-glyph' }, E(Icon, { name: 'branch', size: 13 })),
|
|
9891
|
+
E('span', { id: inputId + '-label', className: 'dig-settings-label', onClick: toggle }, t('settings.composer.title'))),
|
|
9892
|
+
E('div', { className: 'dig-settings-subnote' }, t('settings.composer.hint')))
|
|
9526
9893
|
})()),
|
|
9527
9894
|
/* Section 3 — the AI commit message (issue #6): model, reasoning and the
|
|
9528
9895
|
prompt template. */
|
|
@@ -11708,7 +12075,9 @@ window.__ModuleLoader__.load({
|
|
|
11708
12075
|
|
|
11709
12076
|
const pickRepo = useCallback((path) => {
|
|
11710
12077
|
setRepoRoot(path)
|
|
11711
|
-
|
|
12078
|
+
/* Shared write: the composer status pill watches this store and
|
|
12079
|
+
follows, so both seats always name the same repository. */
|
|
12080
|
+
rememberRepoShared(sessionId, path)
|
|
11712
12081
|
setSummary(null)
|
|
11713
12082
|
setBranches(null)
|
|
11714
12083
|
setCommits([])
|
|
@@ -11724,6 +12093,29 @@ window.__ModuleLoader__.load({
|
|
|
11724
12093
|
setPathFilter('')
|
|
11725
12094
|
}, [sessionId])
|
|
11726
12095
|
|
|
12096
|
+
/* The composer status pill and this panel are separate trees; when the
|
|
12097
|
+
pill (or any other seat) switches the repository or the branch, this
|
|
12098
|
+
subscription lands the same selection here — pickRepo for a repo move
|
|
12099
|
+
(full state reset, same as an in-panel pick), refresh() for a branch
|
|
12100
|
+
move (the panel's own post-action path). Declared AFTER pickRepo and
|
|
12101
|
+
refresh: hooks dependency arrays evaluate during render (invariant 12). */
|
|
12102
|
+
useEffect(() => {
|
|
12103
|
+
const onRepo = (sid, root) => {
|
|
12104
|
+
if (sid !== sessionId || typeof root !== 'string' || root === '') return
|
|
12105
|
+
setRepoRoot((current) => (current === root ? current : pickRepo(root)))
|
|
12106
|
+
}
|
|
12107
|
+
const onBranch = () => {
|
|
12108
|
+
if (repoRoot === null) return
|
|
12109
|
+
void refresh()
|
|
12110
|
+
}
|
|
12111
|
+
composerRepoWatchers.add(onRepo)
|
|
12112
|
+
composerBranchWatchers.add(onBranch)
|
|
12113
|
+
return () => {
|
|
12114
|
+
composerRepoWatchers.delete(onRepo)
|
|
12115
|
+
composerBranchWatchers.delete(onBranch)
|
|
12116
|
+
}
|
|
12117
|
+
}, [sessionId, repoRoot, pickRepo, refresh])
|
|
12118
|
+
|
|
11727
12119
|
const openDiff = useCallback(async (seed) => {
|
|
11728
12120
|
setPatchLoading(true)
|
|
11729
12121
|
setPatch('')
|
|
@@ -13926,6 +14318,32 @@ window.__ModuleLoader__.load({
|
|
|
13926
14318
|
'.dig-lf-tool svg{width:15px;height:15px}',
|
|
13927
14319
|
'.dig-lf-tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}',
|
|
13928
14320
|
'.dig-lf-tool[aria-pressed=true] .dig-lf-compareIcon{transform:rotate(90deg)}',
|
|
14321
|
+
/* ---- composer git status pill (input-bar, issue: status beside the
|
|
14322
|
+
tool row). Colors follow the host's own state language: warn for
|
|
14323
|
+
uncommitted changes, business(blue) for pulls, success(green) for
|
|
14324
|
+
pushes — the same three tones the panel and DSH toasts use. ---- */
|
|
14325
|
+
'.dig-cs-root{position:relative;display:inline-flex;align-items:center;gap:2px;min-width:0;flex:none}',
|
|
14326
|
+
'.dig-cs-pill{display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 8px;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:var(--dsh-content-font-size-secondary,13px);line-height:20px;white-space:nowrap;cursor:pointer}',
|
|
14327
|
+
'.dig-cs-pill:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
14328
|
+
'.dig-cs-pill:disabled{cursor:default}',
|
|
14329
|
+
'.dig-cs-name{overflow:hidden;text-overflow:ellipsis;max-width:150px}',
|
|
14330
|
+
'.dig-cs-pill svg{flex:none}',
|
|
14331
|
+
'.dig-cs-badge{display:inline-flex;align-items:center;font-size:12px;line-height:20px;font-variant-numeric:tabular-nums;white-space:nowrap;padding:0 3px;font-weight:500}',
|
|
14332
|
+
'.dig-cs-changes{color:var(--dsw-alias-state-warn-primary)}',
|
|
14333
|
+
'.dig-cs-behind{color:var(--dsw-alias-state-business-primary)}',
|
|
14334
|
+
'.dig-cs-ahead{color:var(--dsw-alias-state-success-primary)}',
|
|
14335
|
+
'.dig-cs-error{display:inline-flex;align-items:center;color:var(--dsw-alias-state-error-primary);font-size:12px;padding:0 3px;cursor:help}',
|
|
14336
|
+
'.dig-cs-menu{position:absolute;top:calc(100% + 6px);left:0;z-index:60;min-width:220px;max-width:320px;max-height:320px;overflow:auto;padding:6px;border-radius:var(--dsw-radius-lg,12px);background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1)));backdrop-filter:var(--dsw-menu-backdrop-filter,blur(20px));box-shadow:var(--dsw-elevation-prominent,var(--dsw-elevation-dimmed,0 8px 24px rgba(0,0,0,.2)));color:var(--dsw-alias-label-secondary);font-size:12px;line-height:20px}',
|
|
14337
|
+
'.dig-cs-item{display:flex;align-items:center;gap:8px;width:100%;padding:5px 10px;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13px;line-height:20px;cursor:pointer;text-align:left}',
|
|
14338
|
+
'.dig-cs-item:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
|
|
14339
|
+
'.dig-cs-item:disabled{opacity:.5;cursor:default}',
|
|
14340
|
+
'.dig-cs-item[data-current=true]{color:var(--dsw-alias-brand-primary)}',
|
|
14341
|
+
'.dig-cs-item svg{flex:none}',
|
|
14342
|
+
'.dig-cs-item-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
14343
|
+
'.dig-cs-item-path{margin-left:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-dimmed,var(--dsw-alias-label-tertiary));font-size:11px;max-width:140px}',
|
|
14344
|
+
'.dig-cs-item-note{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none}',
|
|
14345
|
+
'.dig-cs-item-counts{display:inline-flex;gap:5px;margin-left:auto;flex:none;font-variant-numeric:tabular-nums;font-size:11px}',
|
|
14346
|
+
'.dig-cs-static{color:var(--dsw-alias-label-tertiary);cursor:default}',
|
|
13929
14347
|
].concat(LANE_TRACE_RULES).join('\n')
|
|
13930
14348
|
|
|
13931
14349
|
/* ============================== plugin ============================== */
|
|
@@ -14022,6 +14440,280 @@ window.__ModuleLoader__.load({
|
|
|
14022
14440
|
return undefined
|
|
14023
14441
|
}
|
|
14024
14442
|
|
|
14443
|
+
/* ============================== composer git status ============================== */
|
|
14444
|
+
|
|
14445
|
+
/* The input-bar Git pill (status beside the composer's tool row):
|
|
14446
|
+
repository + branch with change/pull/push counters, living in the host's
|
|
14447
|
+
'conversation.input.left' slot — the gap right of the permission chip.
|
|
14448
|
+
The pill and the panel are SEPARATE React trees, so cross-seat selection
|
|
14449
|
+
travels through this tiny module store; rememberRepo() keeps the choice
|
|
14450
|
+
across reloads and is the first thing the panel reads on mount, which is
|
|
14451
|
+
what makes "switch in the composer, open the panel later" land on the
|
|
14452
|
+
right repository without any extra wiring. */
|
|
14453
|
+
|
|
14454
|
+
const composerRepoWatchers = new Set() /* (sessionId, repoRoot) */
|
|
14455
|
+
const composerBranchWatchers = new Set() /* (sessionId, repoRoot) */
|
|
14456
|
+
|
|
14457
|
+
function rememberRepoShared(sessionId, repoRoot) {
|
|
14458
|
+
rememberRepo(sessionId, repoRoot)
|
|
14459
|
+
for (const watcher of composerRepoWatchers) {
|
|
14460
|
+
try { watcher(sessionId, repoRoot) } catch (error) { void error }
|
|
14461
|
+
}
|
|
14462
|
+
}
|
|
14463
|
+
|
|
14464
|
+
function notifyBranchSwitched(sessionId, repoRoot) {
|
|
14465
|
+
for (const watcher of composerBranchWatchers) {
|
|
14466
|
+
try { watcher(sessionId, repoRoot) } catch (error) { void error }
|
|
14467
|
+
}
|
|
14468
|
+
}
|
|
14469
|
+
|
|
14470
|
+
/* The pill's own on/off switch: the same read the conversation tab's
|
|
14471
|
+
placement switch uses — row Config when the host serves it, always-on
|
|
14472
|
+
before it answers (default ON per the request). */
|
|
14473
|
+
function composerStatusEnabled() {
|
|
14474
|
+
const ready = railFormReady()
|
|
14475
|
+
if (ready === null) return true
|
|
14476
|
+
const value = ready.value !== null && typeof ready.value === 'object' ? ready.value.composerStatus : undefined
|
|
14477
|
+
return value !== false
|
|
14478
|
+
}
|
|
14479
|
+
|
|
14480
|
+
const COMPOSER_POLL_MS = 30000
|
|
14481
|
+
const COMPOSER_ERROR_TTL_MS = 6000
|
|
14482
|
+
|
|
14483
|
+
function ComposerGitStatus(props) {
|
|
14484
|
+
const ctx = props.ctx
|
|
14485
|
+
const t = props.t
|
|
14486
|
+
const sessionId = typeof props.sessionId === 'string' && props.sessionId !== '' ? props.sessionId : 'default'
|
|
14487
|
+
const [enabled, setEnabled] = useState(() => composerStatusEnabled())
|
|
14488
|
+
const [cwd, setCwd] = useState(() => sessionCwdOf(ctx, sessionId))
|
|
14489
|
+
const [repoState, setRepoState] = useState(null)
|
|
14490
|
+
const [repoRoot, setRepoRoot] = useState(null)
|
|
14491
|
+
const [summary, setSummary] = useState(null)
|
|
14492
|
+
const [menu, setMenu] = useState(null)
|
|
14493
|
+
const [branchList, setBranchList] = useState(null)
|
|
14494
|
+
const [branchLoading, setBranchLoading] = useState(false)
|
|
14495
|
+
const [checkingOut, setCheckingOut] = useState('')
|
|
14496
|
+
const [error, setError] = useState('')
|
|
14497
|
+
const [tick, setTick] = useState(0)
|
|
14498
|
+
const rootRef = useRef(null)
|
|
14499
|
+
|
|
14500
|
+
/* The switch flips live through the same rail-config notify walk the
|
|
14501
|
+
settings card and the conversation tab ride. */
|
|
14502
|
+
useEffect(() => {
|
|
14503
|
+
const sync = () => setEnabled(composerStatusEnabled())
|
|
14504
|
+
sync()
|
|
14505
|
+
return subscribeRailConfig(sync)
|
|
14506
|
+
}, [])
|
|
14507
|
+
|
|
14508
|
+
/* cwd follows the session list, exactly like ConversationPanel. */
|
|
14509
|
+
useEffect(() => {
|
|
14510
|
+
let alive = true
|
|
14511
|
+
const sync = () => { if (alive === true) setCwd(sessionCwdOf(ctx, sessionId)) }
|
|
14512
|
+
sync()
|
|
14513
|
+
let unsubscribe
|
|
14514
|
+
try {
|
|
14515
|
+
const sessions = ctx.get('sessions')
|
|
14516
|
+
const list = sessions !== undefined && sessions !== null ? sessions.list : undefined
|
|
14517
|
+
if (list !== undefined && list !== null && typeof list.subscribe === 'function') unsubscribe = list.subscribe(sync)
|
|
14518
|
+
} catch (error) { void error }
|
|
14519
|
+
return () => {
|
|
14520
|
+
alive = false
|
|
14521
|
+
if (typeof unsubscribe === 'function') { try { unsubscribe() } catch (error) { void error } }
|
|
14522
|
+
}
|
|
14523
|
+
}, [ctx, sessionId])
|
|
14524
|
+
|
|
14525
|
+
/* Discovery per workspace; the pick order matches the panel's (remembered
|
|
14526
|
+
repo → workspace root when it is a repository → first row). */
|
|
14527
|
+
useEffect(() => {
|
|
14528
|
+
if (cwd === undefined || enabled !== true) { setRepoState(null); setRepoRoot(null); setSummary(null); return undefined }
|
|
14529
|
+
let cancelled = false
|
|
14530
|
+
void (async () => {
|
|
14531
|
+
try {
|
|
14532
|
+
const data = await request('repos', { cwd: cwd })
|
|
14533
|
+
if (cancelled === true) return
|
|
14534
|
+
setRepoState(data)
|
|
14535
|
+
const remembered = readRememberedRepo(sessionId)
|
|
14536
|
+
const paths = data.repos.map((repo) => repo.path)
|
|
14537
|
+
let pick = null
|
|
14538
|
+
if (remembered !== undefined && paths.indexOf(remembered) >= 0) pick = remembered
|
|
14539
|
+
else if (data.isRepo === true) pick = data.cwd
|
|
14540
|
+
else if (paths.length > 0) pick = paths[0]
|
|
14541
|
+
setRepoRoot(pick)
|
|
14542
|
+
} catch (error) { void error; if (cancelled !== true) setRepoState({ repos: [], isRepo: false }) }
|
|
14543
|
+
})()
|
|
14544
|
+
return () => { cancelled = true }
|
|
14545
|
+
}, [cwd, sessionId, enabled])
|
|
14546
|
+
|
|
14547
|
+
/* Summary on a slow poll: branch, ahead/behind and the change counters.
|
|
14548
|
+
The panel keeps its own 12s poll for its bigger surface; 30s here is
|
|
14549
|
+
only the ambient state, never a correctness dependency. */
|
|
14550
|
+
useEffect(() => {
|
|
14551
|
+
if (repoRoot === null || enabled !== true) { setSummary(null); return undefined }
|
|
14552
|
+
let cancelled = false
|
|
14553
|
+
const load = async () => {
|
|
14554
|
+
try {
|
|
14555
|
+
const data = await request('summary', { cwd: cwd, repoRoot: repoRoot, stats: false })
|
|
14556
|
+
if (cancelled === true) return
|
|
14557
|
+
setSummary(data)
|
|
14558
|
+
} catch (error) { void error }
|
|
14559
|
+
}
|
|
14560
|
+
void load()
|
|
14561
|
+
const timer = setInterval(() => { void load() }, COMPOSER_POLL_MS)
|
|
14562
|
+
return () => { cancelled = true; clearInterval(timer) }
|
|
14563
|
+
}, [cwd, repoRoot, enabled, tick])
|
|
14564
|
+
|
|
14565
|
+
/* The panel picking a repo (or this pill picking one in another seat)
|
|
14566
|
+
lands here: follow it so both seats always show the same repository. */
|
|
14567
|
+
useEffect(() => {
|
|
14568
|
+
const watcher = (sid, root) => {
|
|
14569
|
+
if (sid !== sessionId || typeof root !== 'string' || root === '') return
|
|
14570
|
+
setRepoRoot((current) => (current === root ? current : root))
|
|
14571
|
+
setSummary(null)
|
|
14572
|
+
setBranchList(null)
|
|
14573
|
+
setTick((value) => value + 1)
|
|
14574
|
+
}
|
|
14575
|
+
composerRepoWatchers.add(watcher)
|
|
14576
|
+
return () => { composerRepoWatchers.delete(watcher) }
|
|
14577
|
+
}, [sessionId])
|
|
14578
|
+
|
|
14579
|
+
/* A branch switched in the panel (or here) refreshes the summary now
|
|
14580
|
+
instead of at the next poll tick. */
|
|
14581
|
+
useEffect(() => {
|
|
14582
|
+
const watcher = () => { setTick((value) => value + 1) }
|
|
14583
|
+
composerBranchWatchers.add(watcher)
|
|
14584
|
+
return () => { composerBranchWatchers.delete(watcher) }
|
|
14585
|
+
}, [sessionId])
|
|
14586
|
+
|
|
14587
|
+
/* Click-away closes the dropdown; nothing here is anchored to a panel
|
|
14588
|
+
(.dig-root) — the pill lives in the host's composer DOM, so the menu
|
|
14589
|
+
is positioned inside this relative root. */
|
|
14590
|
+
useEffect(() => {
|
|
14591
|
+
if (menu === null) return undefined
|
|
14592
|
+
const close = (event) => {
|
|
14593
|
+
const el = rootRef.current
|
|
14594
|
+
if (el !== null && el !== undefined && event.target !== null && event.target !== undefined
|
|
14595
|
+
&& typeof Node === 'function' && event.target instanceof Node === true && el.contains(event.target) === true) return
|
|
14596
|
+
setMenu(null)
|
|
14597
|
+
}
|
|
14598
|
+
document.addEventListener('mousedown', close)
|
|
14599
|
+
return () => document.removeEventListener('mousedown', close)
|
|
14600
|
+
}, [menu])
|
|
14601
|
+
|
|
14602
|
+
useEffect(() => {
|
|
14603
|
+
if (error === '') return undefined
|
|
14604
|
+
const timer = setTimeout(() => { setError('') }, COMPOSER_ERROR_TTL_MS)
|
|
14605
|
+
return () => clearTimeout(timer)
|
|
14606
|
+
}, [error])
|
|
14607
|
+
|
|
14608
|
+
if (enabled !== true) return null
|
|
14609
|
+
const data = repoState
|
|
14610
|
+
if (data === null || (data.isRepo !== true && (data.repos === undefined || data.repos.length === 0))) return null
|
|
14611
|
+
const repoPaths = data.repos.map((repo) => repo.path)
|
|
14612
|
+
const singleRepo = repoPaths.length <= 1
|
|
14613
|
+
const repoName = repoRoot === null ? '' : (repoRoot.split('/').filter((segment) => segment !== '').pop() || repoRoot)
|
|
14614
|
+
const branchName = summary !== null && typeof summary.branch === 'string' && summary.branch !== '' ? summary.branch : null
|
|
14615
|
+
const detached = summary !== null && summary.detached === true
|
|
14616
|
+
const ahead = summary !== null && typeof summary.ahead === 'number' ? summary.ahead : 0
|
|
14617
|
+
const behind = summary !== null && typeof summary.behind === 'number' ? summary.behind : 0
|
|
14618
|
+
const changesCount = summary !== null && summary.changes !== null && typeof summary.changes === 'object'
|
|
14619
|
+
? summary.changes.staged.length + summary.changes.unstaged.length + summary.changes.untracked.length
|
|
14620
|
+
: null
|
|
14621
|
+
|
|
14622
|
+
const openBranchMenu = async () => {
|
|
14623
|
+
setMenu('branch')
|
|
14624
|
+
setBranchList(null)
|
|
14625
|
+
setBranchLoading(true)
|
|
14626
|
+
try {
|
|
14627
|
+
const branchesData = await request('branches', { cwd: cwd, repoRoot: repoRoot })
|
|
14628
|
+
setBranchList(branchesData)
|
|
14629
|
+
} catch (caught) { void caught; setBranchList(null) } finally { setBranchLoading(false) }
|
|
14630
|
+
}
|
|
14631
|
+
const checkoutBranch = async (name) => {
|
|
14632
|
+
if (repoRoot === null || checkingOut !== '') return
|
|
14633
|
+
setCheckingOut(name)
|
|
14634
|
+
try {
|
|
14635
|
+
await request('checkout', { cwd: cwd, repoRoot: repoRoot, branch: name, confirm: true })
|
|
14636
|
+
setMenu(null)
|
|
14637
|
+
setTick((value) => value + 1)
|
|
14638
|
+
notifyBranchSwitched(sessionId, repoRoot)
|
|
14639
|
+
} catch (caught) {
|
|
14640
|
+
setMenu(null)
|
|
14641
|
+
setError(caught instanceof Error ? caught.message : String(caught))
|
|
14642
|
+
} finally { setCheckingOut('') }
|
|
14643
|
+
}
|
|
14644
|
+
const pickRepoRoot = (path) => {
|
|
14645
|
+
setMenu(null)
|
|
14646
|
+
setRepoRoot(path)
|
|
14647
|
+
setSummary(null)
|
|
14648
|
+
setBranchList(null)
|
|
14649
|
+
setTick((value) => value + 1)
|
|
14650
|
+
rememberRepoShared(sessionId, path)
|
|
14651
|
+
}
|
|
14652
|
+
|
|
14653
|
+
return E('div', { ref: rootRef, className: 'dig-cs-root', 'data-dsh-ide-git-status': '' },
|
|
14654
|
+
E('button', {
|
|
14655
|
+
type: 'button',
|
|
14656
|
+
className: 'dig-cs-pill',
|
|
14657
|
+
title: singleRepo === true ? undefined : t('composer.repoHint'),
|
|
14658
|
+
disabled: singleRepo === true,
|
|
14659
|
+
onClick: singleRepo === true ? undefined : () => { setMenu(menu === 'repo' ? null : 'repo') },
|
|
14660
|
+
},
|
|
14661
|
+
E(Icon, { name: 'commit', size: 13 }),
|
|
14662
|
+
E('span', { className: 'dig-cs-name' }, repoName),
|
|
14663
|
+
singleRepo === true ? null : E(Icon, { name: 'chevron', size: 10 })),
|
|
14664
|
+
branchName !== null ? E('button', {
|
|
14665
|
+
type: 'button',
|
|
14666
|
+
className: 'dig-cs-pill',
|
|
14667
|
+
title: t('composer.branchHint'),
|
|
14668
|
+
onClick: () => { if (menu !== 'branch') void openBranchMenu(); else setMenu(null) },
|
|
14669
|
+
},
|
|
14670
|
+
E(Icon, { name: 'branch', size: 13 }),
|
|
14671
|
+
E('span', { className: 'dig-cs-name' }, detached === true ? t('composer.detached') : branchName),
|
|
14672
|
+
E(Icon, { name: 'chevron', size: 10 })) : null,
|
|
14673
|
+
changesCount !== null && changesCount > 0
|
|
14674
|
+
? E('span', { className: 'dig-cs-badge dig-cs-changes', title: t('composer.changesHint') }, '±' + String(changesCount))
|
|
14675
|
+
: null,
|
|
14676
|
+
behind > 0 ? E('span', { className: 'dig-cs-badge dig-cs-behind', title: t('composer.behindHint') }, '↓' + String(behind)) : null,
|
|
14677
|
+
ahead > 0 ? E('span', { className: 'dig-cs-badge dig-cs-ahead', title: t('composer.aheadHint') }, '↑' + String(ahead)) : null,
|
|
14678
|
+
error !== '' ? E('span', { className: 'dig-cs-error', title: error }, '×') : null,
|
|
14679
|
+
menu === 'repo' ? E('div', { className: 'dig-cs-menu' },
|
|
14680
|
+
repoPaths.map((path) => E('button', {
|
|
14681
|
+
key: path,
|
|
14682
|
+
type: 'button',
|
|
14683
|
+
className: 'dig-cs-item',
|
|
14684
|
+
'data-current': path === repoRoot ? 'true' : 'false',
|
|
14685
|
+
title: path,
|
|
14686
|
+
onClick: () => pickRepoRoot(path),
|
|
14687
|
+
},
|
|
14688
|
+
E(Icon, { name: 'commit', size: 11 }),
|
|
14689
|
+
E('span', { className: 'dig-cs-item-name' }, path.split('/').filter((segment) => segment !== '').pop() || path),
|
|
14690
|
+
E('span', { className: 'dig-cs-item-path' }, path))))
|
|
14691
|
+
: null,
|
|
14692
|
+
menu === 'branch' ? (branchLoading === true || branchList === null
|
|
14693
|
+
? E('div', { className: 'dig-cs-menu' }, E('div', { className: 'dig-cs-item dig-cs-static' }, '…'))
|
|
14694
|
+
: E('div', { className: 'dig-cs-menu' },
|
|
14695
|
+
branchList.local.map((entry) => E('button', {
|
|
14696
|
+
key: entry.name,
|
|
14697
|
+
type: 'button',
|
|
14698
|
+
className: 'dig-cs-item',
|
|
14699
|
+
'data-current': entry.head === true ? 'true' : 'false',
|
|
14700
|
+
disabled: entry.worktree !== null || checkingOut !== '',
|
|
14701
|
+
title: entry.worktree !== null ? t('composer.inWorktree') : (checkingOut !== '' ? t('composer.busy') : entry.name),
|
|
14702
|
+
onClick: () => { void checkoutBranch(entry.name) },
|
|
14703
|
+
},
|
|
14704
|
+
E(Icon, { name: 'branch', size: 11 }),
|
|
14705
|
+
E('span', { className: 'dig-cs-item-name' }, entry.name),
|
|
14706
|
+
entry.worktree !== null ? E('span', { className: 'dig-cs-item-note' }, t('composer.inWorktree')) : null,
|
|
14707
|
+
checkingOut === entry.name ? E('span', { className: 'dig-cs-item-note' }, '…') : null,
|
|
14708
|
+
entry.ahead > 0 || entry.behind > 0
|
|
14709
|
+
? E('span', { className: 'dig-cs-item-counts' },
|
|
14710
|
+
entry.ahead > 0 ? E('span', { className: 'dig-cs-ahead' }, '↑' + String(entry.ahead)) : null,
|
|
14711
|
+
entry.behind > 0 ? E('span', { className: 'dig-cs-behind' }, '↓' + String(entry.behind)) : null)
|
|
14712
|
+
: null))))
|
|
14713
|
+
: null,
|
|
14714
|
+
)
|
|
14715
|
+
}
|
|
14716
|
+
|
|
14025
14717
|
/* The conversation shell re-renders on EVERY keystroke (the composer owns
|
|
14026
14718
|
the draft state), and the view area re-renders with it. Without a bail-out
|
|
14027
14719
|
at this boundary that cascade dragged the ENTIRE Git panel tree — branch
|
|
@@ -14518,6 +15210,77 @@ window.__ModuleLoader__.load({
|
|
|
14518
15210
|
}, 'dsh-ide-git: conversation view tab')
|
|
14519
15211
|
})
|
|
14520
15212
|
|
|
15213
|
+
/* ---- door 4: the composer status pill (issue: status in the input bar) ---- */
|
|
15214
|
+
/* A tiny repo/branch/change readout inside the host composer's tool row,
|
|
15215
|
+
in the 'conversation.input.left' gap right of the permission chip. It
|
|
15216
|
+
shares the first-paint cache's read paths (read-only routes only) and
|
|
15217
|
+
syncs repository/branch selection with the panel through the module
|
|
15218
|
+
store above. The slot is a plain list; a host without ui-conversation
|
|
15219
|
+
never mounts it and nothing throws. */
|
|
15220
|
+
ctx.inject(['slots'], (pillCtx) => {
|
|
15221
|
+
const pillSlots = pillCtx.get('slots')
|
|
15222
|
+
if (pillSlots === undefined || pillSlots === null || typeof pillSlots.inject !== 'function') return
|
|
15223
|
+
pillCtx.effect(() => {
|
|
15224
|
+
const offPill = pillSlots.inject('conversation.input.left', () => pillSlots.register(
|
|
15225
|
+
{ name: 'conversation.input.left', id: 'dsh-ide-git:status', order: 20, locale: LOCALE_NS },
|
|
15226
|
+
(pillProps) => E(ComposerGitStatus, Object.assign({}, pillProps, { t: t, ctx: ctx })),
|
|
15227
|
+
))
|
|
15228
|
+
return () => {
|
|
15229
|
+
try { offPill() } catch (error) { void error }
|
|
15230
|
+
}
|
|
15231
|
+
}, 'dsh-ide-git: composer status pill')
|
|
15232
|
+
})
|
|
15233
|
+
|
|
15234
|
+
/* ---- session-switch prewarm ---- */
|
|
15235
|
+
/* Warm the first-paint cache the moment a workspace session becomes
|
|
15236
|
+
active — before any Git tab exists. ctx.inject keeps this optional:
|
|
15237
|
+
a host without the sessions service simply never arms it. */
|
|
15238
|
+
ctx.inject(['sessions'], (sessionCtx) => {
|
|
15239
|
+
const sessions = sessionCtx.get('sessions')
|
|
15240
|
+
if (sessions === undefined || sessions === null || sessions.list === undefined
|
|
15241
|
+
|| sessions.list === null || typeof sessions.list.subscribe !== 'function'
|
|
15242
|
+
|| typeof sessions.retainInfo !== 'function') return
|
|
15243
|
+
sessionCtx.effect(() => {
|
|
15244
|
+
/* Becoming the main view is a LOCAL retain event: the host's own
|
|
15245
|
+
ui-session subscribes retainInfo(id) per session for exactly this
|
|
15246
|
+
(a plain list subscription never fires on it). Mirror that shape —
|
|
15247
|
+
the list feeds membership, retainInfo per listed session feeds the
|
|
15248
|
+
main-view transitions. */
|
|
15249
|
+
const armed = new Map()
|
|
15250
|
+
const armAll = () => {
|
|
15251
|
+
let snapshot = null
|
|
15252
|
+
try { snapshot = sessions.list.getSnapshot() } catch (error) { void error; return }
|
|
15253
|
+
const ids = snapshot !== null && typeof snapshot === 'object' && Array.isArray(snapshot.ids) ? snapshot.ids : []
|
|
15254
|
+
const wanted = new Set(ids.map((id) => String(id)))
|
|
15255
|
+
for (const [id, dispose] of armed) {
|
|
15256
|
+
if (wanted.has(id) === true) continue
|
|
15257
|
+
armed.delete(id)
|
|
15258
|
+
try { dispose() } catch (error) { void error }
|
|
15259
|
+
}
|
|
15260
|
+
for (const raw of ids) {
|
|
15261
|
+
const id = String(raw)
|
|
15262
|
+
if (armed.has(id) === true) continue
|
|
15263
|
+
let info = null
|
|
15264
|
+
try { info = sessions.retainInfo(raw) } catch (error) { void error; continue }
|
|
15265
|
+
if (info === undefined || info === null || typeof info.subscribe !== 'function') continue
|
|
15266
|
+
armed.set(id, info.subscribe(() => { schedulePrewarm(ctx) }))
|
|
15267
|
+
}
|
|
15268
|
+
}
|
|
15269
|
+
const unsubscribe = sessions.list.subscribe(armAll)
|
|
15270
|
+
armAll()
|
|
15271
|
+
/* The page may already hold an active session (first load, or this
|
|
15272
|
+
plugin applied late): warm it without waiting for a switch. */
|
|
15273
|
+
schedulePrewarm(ctx)
|
|
15274
|
+
return () => {
|
|
15275
|
+
if (prewarmTimer !== null) { clearTimeout(prewarmTimer); prewarmTimer = null }
|
|
15276
|
+
prewarmGeneration += 1
|
|
15277
|
+
try { unsubscribe() } catch (error) { void error }
|
|
15278
|
+
for (const dispose of armed.values()) { try { dispose() } catch (error) { void error } }
|
|
15279
|
+
armed.clear()
|
|
15280
|
+
}
|
|
15281
|
+
}, 'dsh-ide-git: session-switch prewarm')
|
|
15282
|
+
})
|
|
15283
|
+
|
|
14521
15284
|
/* ---- settings: the row Config is the rail's source of truth ---- */
|
|
14522
15285
|
/* dsh >= 0.1.7 serves a plugin's row Config to the browser through
|
|
14523
15286
|
ctx.configForms; from then on the settings card and the rail read that one
|