dsh-ide-git 0.19.11 → 0.19.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-ide-git",
3
- "version": "0.19.11",
3
+ "version": "0.19.12",
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.19.11",
3
+ "version": "0.19.12",
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
@@ -295,6 +295,8 @@ window.__ModuleLoader__.load({
295
295
  'composer.branchHint': '切换分支',
296
296
  'composer.detached': 'HEAD(游离)',
297
297
  'composer.changesHint': '未提交变更数',
298
+ 'composer.fileHint': '新增 {a} · 删除 {d} 个文件',
299
+ 'composer.linesHint': '+{ins} −{del} 行',
298
300
  'composer.behindHint': '远程新提交(可拉取)',
299
301
  'composer.aheadHint': '本地待推送提交',
300
302
  'composer.inWorktree': '已被其他工作树签出',
@@ -623,6 +625,8 @@ window.__ModuleLoader__.load({
623
625
  'composer.branchHint': 'Switch branch',
624
626
  'composer.detached': 'HEAD (detached)',
625
627
  'composer.changesHint': 'Uncommitted changes',
628
+ 'composer.fileHint': '{a} added · {d} deleted files',
629
+ 'composer.linesHint': '+{ins} −{del} lines',
626
630
  'composer.behindHint': 'New upstream commits (pullable)',
627
631
  'composer.aheadHint': 'Local commits waiting to push',
628
632
  'composer.inWorktree': 'Checked out in another worktree',
@@ -959,6 +963,8 @@ window.__ModuleLoader__.load({
959
963
  'composer.branchHint': '切換分支',
960
964
  'composer.detached': 'HEAD(游離)',
961
965
  'composer.changesHint': '未提交變更數',
966
+ 'composer.fileHint': '新增 {a} · 刪除 {d} 個檔案',
967
+ 'composer.linesHint': '+{ins} −{del} 行',
962
968
  'composer.behindHint': '遠端新提交(可以拉取)',
963
969
  'composer.aheadHint': '本地等待推送提交',
964
970
  'composer.inWorktree': '已經喺其他工作樹簽出',
@@ -1287,6 +1293,8 @@ window.__ModuleLoader__.load({
1287
1293
  'composer.branchHint': '切換分支',
1288
1294
  'composer.detached': 'HEAD(分離)',
1289
1295
  'composer.changesHint': '未提交變更數',
1296
+ 'composer.fileHint': '新增 {a} · 刪除 {d} 個檔案',
1297
+ 'composer.linesHint': '+{ins} −{del} 行',
1290
1298
  'composer.behindHint': '遠端新提交(可拉取)',
1291
1299
  'composer.aheadHint': '本地待推送提交',
1292
1300
  'composer.inWorktree': '已被其他工作樹簽出',
@@ -1615,6 +1623,8 @@ window.__ModuleLoader__.load({
1615
1623
  'composer.branchHint': '切換分支',
1616
1624
  'composer.detached': 'HEAD(游離)',
1617
1625
  'composer.changesHint': '未提交變更數',
1626
+ 'composer.fileHint': '新增 {a} · 刪除 {d} 個檔案',
1627
+ 'composer.linesHint': '+{ins} −{del} 行',
1618
1628
  'composer.behindHint': '遠端新提交(可以拉取)',
1619
1629
  'composer.aheadHint': '本地等待推送提交',
1620
1630
  'composer.inWorktree': '已經喺其他工作樹簽出',
@@ -1943,6 +1953,8 @@ window.__ModuleLoader__.load({
1943
1953
  'composer.branchHint': 'ブランチを切り替え',
1944
1954
  'composer.detached': 'HEAD(切断)',
1945
1955
  'composer.changesHint': '未コミットの変更数',
1956
+ 'composer.fileHint': '追加 {a} · 削除 {d} ファイル',
1957
+ 'composer.linesHint': '+{ins} −{del} 行',
1946
1958
  'composer.behindHint': 'リモートの新規コミット(取得可能)',
1947
1959
  'composer.aheadHint': 'プッシュ待ちのローカルコミット',
1948
1960
  'composer.inWorktree': '別のワークツリーでチェックアウト中',
@@ -2271,6 +2283,8 @@ window.__ModuleLoader__.load({
2271
2283
  'composer.branchHint': '브랜치 전환',
2272
2284
  'composer.detached': 'HEAD(분리됨)',
2273
2285
  'composer.changesHint': '커밋되지 않은 변경 수',
2286
+ 'composer.fileHint': '추가 {a} · 삭제 {d}개 파일',
2287
+ 'composer.linesHint': '+{ins} −{del}줄',
2274
2288
  'composer.behindHint': '원격 새 커밋(가져오기 가능)',
2275
2289
  'composer.aheadHint': '푸시 대기 중인 로컬 커밋',
2276
2290
  'composer.inWorktree': '다른 작업 트리에서 체크아웃됨',
@@ -2599,6 +2613,8 @@ window.__ModuleLoader__.load({
2599
2613
  'composer.branchHint': 'Zweig wechseln',
2600
2614
  'composer.detached': 'HEAD (detached)',
2601
2615
  'composer.changesHint': 'Uncommittete Änderungen',
2616
+ 'composer.fileHint': '{a} hinzugefügt · {d} gelöscht',
2617
+ 'composer.linesHint': '+{ins} −{del} Zeilen',
2602
2618
  'composer.behindHint': 'Neue Upstream-Commits (abrufbar)',
2603
2619
  'composer.aheadHint': 'Lokale Commits zum Pushen',
2604
2620
  'composer.inWorktree': 'In anderem Worktree ausgecheckt',
@@ -2927,6 +2943,8 @@ window.__ModuleLoader__.load({
2927
2943
  'composer.branchHint': 'Changer de branche',
2928
2944
  'composer.detached': 'HEAD (détaché)',
2929
2945
  'composer.changesHint': 'Modifications non commitées',
2946
+ 'composer.fileHint': '{a} ajoutés · {d} supprimés',
2947
+ 'composer.linesHint': '+{ins} −{del} lignes',
2930
2948
  'composer.behindHint': 'Nouveaux commits amont (récupérables)',
2931
2949
  'composer.aheadHint': 'Commits locaux en attente d’envoi',
2932
2950
  'composer.inWorktree': 'Extrait dans un autre arbre de travail',
@@ -3255,6 +3273,8 @@ window.__ModuleLoader__.load({
3255
3273
  'composer.branchHint': 'Сменить ветку',
3256
3274
  'composer.detached': 'HEAD (отделён)',
3257
3275
  'composer.changesHint': 'Незакоммиченные изменения',
3276
+ 'composer.fileHint': 'добавлено: {a} · удалено: {d}',
3277
+ 'composer.linesHint': '+{ins} −{del} строк',
3258
3278
  'composer.behindHint': 'Новые коммиты upstream (можно забрать)',
3259
3279
  'composer.aheadHint': 'Локальные коммиты, ожидающие отправки',
3260
3280
  'composer.inWorktree': 'Выверен в другом worktree',
@@ -3583,6 +3603,8 @@ window.__ModuleLoader__.load({
3583
3603
  'composer.branchHint': 'Trocar branch',
3584
3604
  'composer.detached': 'HEAD (destacado)',
3585
3605
  'composer.changesHint': 'Alterações não commitadas',
3606
+ 'composer.fileHint': '{a} adicionados · {d} removidos',
3607
+ 'composer.linesHint': '+{ins} −{del} linhas',
3586
3608
  'composer.behindHint': 'Novos commits remotos (para pull)',
3587
3609
  'composer.aheadHint': 'Commits locais aguardando push',
3588
3610
  'composer.inWorktree': 'Em checkout em outra worktree',
@@ -3911,6 +3933,8 @@ window.__ModuleLoader__.load({
3911
3933
  'composer.branchHint': 'Cambia ramo',
3912
3934
  'composer.detached': 'HEAD (staccato)',
3913
3935
  'composer.changesHint': 'Modifiche non ancora committate',
3936
+ 'composer.fileHint': '{a} aggiunti · {d} eliminati',
3937
+ 'composer.linesHint': '+{ins} −{del} righe',
3914
3938
  'composer.behindHint': 'Nuovi commit upstream (scaricabili)',
3915
3939
  'composer.aheadHint': 'Commit locali in attesa di push',
3916
3940
  'composer.inWorktree': 'Estratto in un altro worktree',
@@ -4239,6 +4263,8 @@ window.__ModuleLoader__.load({
4239
4263
  'composer.branchHint': 'Branch wisselen',
4240
4264
  'composer.detached': 'HEAD (los)',
4241
4265
  'composer.changesHint': 'Ongecommitte wijzigingen',
4266
+ 'composer.fileHint': '{a} toegevoegd · {d} verwijderd',
4267
+ 'composer.linesHint': '+{ins} −{del} regels',
4242
4268
  'composer.behindHint': 'Nieuwe upstream-commits (op te halen)',
4243
4269
  'composer.aheadHint': 'Lokale commits die wachten op push',
4244
4270
  'composer.inWorktree': 'Uitgecheckt in een andere worktree',
@@ -4567,6 +4593,8 @@ window.__ModuleLoader__.load({
4567
4593
  'composer.branchHint': 'Przełącz gałąź',
4568
4594
  'composer.detached': 'HEAD (odłączony)',
4569
4595
  'composer.changesHint': 'Niezatwierdzone zmiany',
4596
+ 'composer.fileHint': 'dodane: {a} · usunięte: {d}',
4597
+ 'composer.linesHint': '+{ins} −{del} linii',
4570
4598
  'composer.behindHint': 'Nowe commity upstream (do pobrania)',
4571
4599
  'composer.aheadHint': 'Lokalne commity oczekujące na wysłanie',
4572
4600
  'composer.inWorktree': 'Wybrane w innym drzewie roboczym',
@@ -4895,6 +4923,8 @@ window.__ModuleLoader__.load({
4895
4923
  'composer.branchHint': 'Byt gren',
4896
4924
  'composer.detached': 'HEAD (frånkopplad)',
4897
4925
  'composer.changesHint': 'Ocommittade ändringar',
4926
+ 'composer.fileHint': '{a} tillagda · {d} borttagna',
4927
+ 'composer.linesHint': '+{ins} −{del} rader',
4898
4928
  'composer.behindHint': 'Nya upstream-commits (hämtbara)',
4899
4929
  'composer.aheadHint': 'Lokala commits som väntar på push',
4900
4930
  'composer.inWorktree': 'Utcheckad i ett annat arbetsträd',
@@ -5223,6 +5253,8 @@ window.__ModuleLoader__.load({
5223
5253
  'composer.branchHint': 'Dal değiştir',
5224
5254
  'composer.detached': 'HEAD (ayrık)',
5225
5255
  'composer.changesHint': 'Commitlenmemiş değişiklikler',
5256
+ 'composer.fileHint': '{a} eklendi · {d} silindi',
5257
+ 'composer.linesHint': '+{ins} −{del} satır',
5226
5258
  'composer.behindHint': 'Yeni upstream commitleri (çekilebilir)',
5227
5259
  'composer.aheadHint': 'Push bekleyen yerel commitler',
5228
5260
  'composer.inWorktree': 'Başka bir worktree’da checkout yapılmış',
@@ -5551,6 +5583,8 @@ window.__ModuleLoader__.load({
5551
5583
  'composer.branchHint': 'Ganti cabang',
5552
5584
  'composer.detached': 'HEAD (lepas)',
5553
5585
  'composer.changesHint': 'Perubahan yang belum di-commit',
5586
+ 'composer.fileHint': '{a} ditambahkan · {d} dihapus',
5587
+ 'composer.linesHint': '+{ins} −{del} baris',
5554
5588
  'composer.behindHint': 'Commit upstream baru (bisa ditarik)',
5555
5589
  'composer.aheadHint': 'Commit lokal menunggu push',
5556
5590
  'composer.inWorktree': 'Sudah di-checkout di worktree lain',
@@ -5879,6 +5913,8 @@ window.__ModuleLoader__.load({
5879
5913
  'composer.branchHint': 'Chuyển nhánh',
5880
5914
  'composer.detached': 'HEAD (tách rời)',
5881
5915
  'composer.changesHint': 'Thay đổi chưa commit',
5916
+ 'composer.fileHint': 'thêm {a} · xóa {d} tệp',
5917
+ 'composer.linesHint': '+{ins} −{del} dòng',
5882
5918
  'composer.behindHint': 'Commit mới từ xa (có thể kéo về)',
5883
5919
  'composer.aheadHint': 'Commit cục bộ chờ đẩy lên',
5884
5920
  'composer.inWorktree': 'Đã được checkout ở worktree khác',
@@ -6207,6 +6243,8 @@ window.__ModuleLoader__.load({
6207
6243
  'composer.branchHint': 'تبديل الفرع',
6208
6244
  'composer.detached': 'HEAD (منفصل)',
6209
6245
  'composer.changesHint': 'تغييرات غير ملتزَم بها',
6246
+ 'composer.fileHint': 'مضاف {a} · محذوف {d}',
6247
+ 'composer.linesHint': '+{ins} −{del} أسطر',
6210
6248
  'composer.behindHint': 'التزامات جديدة في المستودع البعيد (قابلة للسحب)',
6211
6249
  'composer.aheadHint': 'التزامات محلية بانتظار الدفع',
6212
6250
  'composer.inWorktree': 'مسحوب في شجرة عمل أخرى',
@@ -6535,6 +6573,8 @@ window.__ModuleLoader__.load({
6535
6573
  'composer.branchHint': 'शाखा बदलें',
6536
6574
  'composer.detached': 'HEAD (पृथक)',
6537
6575
  'composer.changesHint': 'बिना कमिट बदलाव',
6576
+ 'composer.fileHint': '{a} जोड़ी गईं · {d} हटाई गईं',
6577
+ 'composer.linesHint': '+{ins} −{del} पंक्तियाँ',
6538
6578
  'composer.behindHint': 'अपस्ट्रीम के नए कमिट (खींचने योग्य)',
6539
6579
  'composer.aheadHint': 'पुश के लिए प्रतीक्षारत स्थानीय कमिट',
6540
6580
  'composer.inWorktree': 'दूसरे वर्कट्री में चेकआउट',
@@ -6863,6 +6903,8 @@ window.__ModuleLoader__.load({
6863
6903
  'composer.branchHint': 'สลับแบรนช์',
6864
6904
  'composer.detached': 'HEAD (แยกขาด)',
6865
6905
  'composer.changesHint': 'การเปลี่ยนแปลงที่ยังไม่คอมมิต',
6906
+ 'composer.fileHint': 'เพิ่ม {a} · ลบ {d} ไฟล์',
6907
+ 'composer.linesHint': '+{ins} −{del} บรรทัด',
6866
6908
  'composer.behindHint': 'คอมมิตใหม่จากรีโมต (ดึงได้)',
6867
6909
  'composer.aheadHint': 'คอมมิตในเครื่องรอพุช',
6868
6910
  'composer.inWorktree': 'ถูกเช็คเอาต์ในเวิร์กทรีอื่น',
@@ -7339,6 +7381,58 @@ window.__ModuleLoader__.load({
7339
7381
  return null
7340
7382
  }
7341
7383
 
7384
+ /* How many changed FILES are additions vs deletions, from the status
7385
+ letters the summary already carries (index/worktree per entry) — no
7386
+ extra git call. Untracked entries ARE additions in git's own model
7387
+ (?? = to-be-added); a rename (R/C) or a conflict counts as neither. */
7388
+ function composerFileSplitOf(summary) {
7389
+ const changes = summary !== null && summary !== undefined && summary.changes !== null && typeof summary.changes === 'object' ? summary.changes : null
7390
+ if (changes === null) return { added: 0, deleted: 0 }
7391
+ let added = 0
7392
+ let deleted = 0
7393
+ const count = (list) => {
7394
+ for (const entry of list === undefined || list === null ? [] : list) {
7395
+ if (entry.index === 'A' || entry.worktree === 'A') added += 1
7396
+ else if (entry.index === 'D' || entry.worktree === 'D') deleted += 1
7397
+ }
7398
+ }
7399
+ count(changes.staged)
7400
+ count(changes.unstaged)
7401
+ added += changes.untracked === undefined || changes.untracked === null ? 0 : changes.untracked.length
7402
+ return { added: added, deleted: deleted }
7403
+ }
7404
+
7405
+ /* Total uncommitted line churn: the packed [path, additions, deletions,
7406
+ binary] rows the host's stats method returns, summed over both the
7407
+ worktree and the index passes. Untracked files have no line counts
7408
+ (git cannot diff what it does not track) — they only ever move the
7409
+ file counters above. */
7410
+ function composerStatsLinesOf(pack) {
7411
+ let additions = 0
7412
+ let deletions = 0
7413
+ if (pack !== null && pack !== undefined && typeof pack === 'object') {
7414
+ for (const row of pack.unstaged === undefined || pack.unstaged === null ? [] : pack.unstaged) { additions += row[1]; deletions += row[2] }
7415
+ for (const row of pack.staged === undefined || pack.staged === null ? [] : pack.staged) { additions += row[1]; deletions += row[2] }
7416
+ }
7417
+ return { additions: additions, deletions: deletions }
7418
+ }
7419
+
7420
+ /* Whether the line counts need a refetch: any path/status move or a new
7421
+ HEAD commit/checkout invalidates them; lines rewritten inside
7422
+ already-dirty files do NOT move this — the slow COMPOSER_STATS_MS
7423
+ floor covers those. */
7424
+ function composerFingerprintOf(summary) {
7425
+ if (summary === null || summary === undefined || summary.changes === null || typeof summary.changes !== 'object') return ''
7426
+ const rows = []
7427
+ const collect = (list) => { for (const entry of list === undefined || list === null ? [] : list) rows.push(entry.path + ':' + entry.index + entry.worktree) }
7428
+ collect(summary.changes.staged)
7429
+ collect(summary.changes.unstaged)
7430
+ collect(summary.changes.untracked)
7431
+ collect(summary.changes.conflicted)
7432
+ rows.sort()
7433
+ return rows.join('|') + '#' + String(summary.head === undefined || summary.head === null ? '' : summary.head)
7434
+ }
7435
+
7342
7436
  async function prewarmWorkspace(sessionId, cwd) {
7343
7437
  const generation = ++prewarmGeneration
7344
7438
  try {
@@ -14903,6 +14997,13 @@ window.__ModuleLoader__.load({
14903
14997
  '.dig-cs-changes{color:var(--dsw-alias-state-warn-primary)}',
14904
14998
  '.dig-cs-behind{color:var(--dsw-alias-state-business-primary)}',
14905
14999
  '.dig-cs-ahead{color:var(--dsw-alias-state-success-primary)}',
15000
+ /* v0.19.12: file-level add/del split rides INSIDE the ± chip at one
15001
+ step down (11px) so the two +/− groups read as two levels — small
15002
+ green/red = FILES, full-size green/red = LINES. */
15003
+ '.dig-cs-split{font-size:11px;line-height:20px;font-variant-numeric:tabular-nums;margin-left:2px}',
15004
+ '.dig-cs-add{color:var(--dsw-alias-state-success-primary)}',
15005
+ '.dig-cs-del{color:var(--dsw-alias-state-error-primary)}',
15006
+ '.dig-cs-lines{display:inline-flex;align-items:center;gap:2px;margin-left:5px}',
14906
15007
  '.dig-cs-error{display:inline-flex;align-items:center;color:var(--dsw-alias-state-error-primary);font-size:12px;padding:0 3px;cursor:help}',
14907
15008
  // .dig-cs-menu opens upward (the composer sits at the bottom of the UI). The surface
14908
15009
  // carries no backdrop-filter of its own: the data-cordis-panel attribute opts the popover
@@ -15064,6 +15165,13 @@ window.__ModuleLoader__.load({
15064
15165
 
15065
15166
  const COMPOSER_POLL_MS = 30000
15066
15167
  const COMPOSER_ERROR_TTL_MS = 6000
15168
+ /* Line counts re-run at most this often without a real change: the two
15169
+ numstat passes are the expensive step on a big dirty tree (issue #11),
15170
+ so the pill keeps its cheap stats:false poll and refreshes lines only
15171
+ when the change set or HEAD moves — plus this slow floor for edits
15172
+ that rewrite lines in already-dirty files (nothing else in the
15173
+ fingerprint moves then). */
15174
+ const COMPOSER_STATS_MS = 90000
15067
15175
 
15068
15176
  /* Windows paths ride backslashes, so both pill labels cut the leaf with
15069
15177
  this slash-aware split instead of a bare forward-slash split — a drive
@@ -15134,6 +15242,13 @@ window.__ModuleLoader__.load({
15134
15242
  const [checkingOut, setCheckingOut] = useState('')
15135
15243
  const [error, setError] = useState('')
15136
15244
  const [tick, setTick] = useState(0)
15245
+ /* Uncommitted line churn (+n −m), fetched lazily: only when the change
15246
+ set or HEAD moves — never on the ambient 30s tick by itself (the
15247
+ root tag keeps a previous repository's numbers from leaking into
15248
+ this one's pill). */
15249
+ const [lineStats, setLineStats] = useState(null)
15250
+ const statsStampRef = useRef('')
15251
+ const statsAtRef = useRef(0)
15137
15252
  const rootRef = useRef(null)
15138
15253
 
15139
15254
  /* The switch flips live through the same rail-config notify walk the
@@ -15192,7 +15307,7 @@ window.__ModuleLoader__.load({
15192
15307
  The panel keeps its own 12s poll for its bigger surface; 30s here is
15193
15308
  only the ambient state, never a correctness dependency. */
15194
15309
  useEffect(() => {
15195
- if (repoRoot === null || enabled !== true) { setSummary(null); return undefined }
15310
+ if (repoRoot === null || enabled !== true) { setSummary(null); setLineStats(null); return undefined }
15196
15311
  let cancelled = false
15197
15312
  const load = async () => {
15198
15313
  try {
@@ -15204,6 +15319,27 @@ window.__ModuleLoader__.load({
15204
15319
  mount with zero round trips. Merge only our field — a panel
15205
15320
  page keeps its commits. */
15206
15321
  mergePaintPage(repoRoot, { summary: data })
15322
+ /* Line counts ride the same tick only when something actually
15323
+ moved (any status/path change or a new HEAD), or when the slow
15324
+ floor elapsed for in-place edits of dirty files. A clean tree
15325
+ skips the numstat passes entirely and just zeroes the badges. */
15326
+ const fingerprint = composerFingerprintOf(data)
15327
+ const stamp = repoRoot + '::' + fingerprint
15328
+ const now = Date.now()
15329
+ const count = data.changes === null || typeof data.changes !== 'object' ? 0 : data.changes.staged.length + data.changes.unstaged.length + data.changes.untracked.length
15330
+ if (count === 0) {
15331
+ statsStampRef.current = stamp
15332
+ statsAtRef.current = now
15333
+ setLineStats({ root: repoRoot, additions: 0, deletions: 0 })
15334
+ } else if (stamp !== statsStampRef.current || now - statsAtRef.current >= COMPOSER_STATS_MS) {
15335
+ try {
15336
+ const pack = await request('stats', { cwd: cwd, repoRoot: repoRoot })
15337
+ if (cancelled === true) return
15338
+ statsStampRef.current = stamp
15339
+ statsAtRef.current = now
15340
+ setLineStats(Object.assign({ root: repoRoot }, composerStatsLinesOf(pack)))
15341
+ } catch (statsError) { void statsError }
15342
+ }
15207
15343
  } catch (error) { void error }
15208
15344
  }
15209
15345
  const seed = readPaintPage(repoRoot)
@@ -15274,6 +15410,18 @@ window.__ModuleLoader__.load({
15274
15410
  const changesCount = summary !== null && summary.changes !== null && typeof summary.changes === 'object'
15275
15411
  ? summary.changes.staged.length + summary.changes.unstaged.length + summary.changes.untracked.length
15276
15412
  : null
15413
+ /* File-level add/del split (status letters, free) + the lazily fetched
15414
+ line churn, assembled into one tooltip that reads as a sentence. */
15415
+ const fileSplit = composerFileSplitOf(summary)
15416
+ const linesLive = lineStats !== null && lineStats.root === repoRoot ? lineStats : null
15417
+ const changesTitle = (() => {
15418
+ let title = t('composer.changesHint')
15419
+ if (changesCount !== null && changesCount > 0) {
15420
+ title += ' · ' + fill(t('composer.fileHint'), { a: fileSplit.added, d: fileSplit.deleted })
15421
+ if (linesLive !== null) title += ' · ' + fill(t('composer.linesHint'), { ins: linesLive.additions, del: linesLive.deletions })
15422
+ }
15423
+ return title
15424
+ })()
15277
15425
 
15278
15426
  const openBranchMenu = async () => {
15279
15427
  setMenu('branch')
@@ -15340,7 +15488,14 @@ window.__ModuleLoader__.load({
15340
15488
  E('span', { className: 'dig-cs-name' }, detached === true ? t('composer.detached') : branchName),
15341
15489
  E(Icon, { name: 'chevron', size: 10 })) : null,
15342
15490
  changesCount !== null && changesCount > 0
15343
- ? E('span', { className: 'dig-cs-badge dig-cs-changes', title: t('composer.changesHint') }, '±' + String(changesCount))
15491
+ ? E('span', { className: 'dig-cs-badge dig-cs-changes', title: changesTitle }, '±' + String(changesCount),
15492
+ fileSplit.added > 0 ? E('span', { className: 'dig-cs-split dig-cs-add' }, '+' + String(fileSplit.added)) : null,
15493
+ fileSplit.deleted > 0 ? E('span', { className: 'dig-cs-split dig-cs-del' }, '−' + String(fileSplit.deleted)) : null)
15494
+ : null,
15495
+ linesLive !== null && (linesLive.additions > 0 || linesLive.deletions > 0)
15496
+ ? E('span', { className: 'dig-cs-lines' },
15497
+ linesLive.additions > 0 ? E('span', { className: 'dig-cs-badge dig-cs-add', title: changesTitle }, '+' + String(linesLive.additions)) : null,
15498
+ linesLive.deletions > 0 ? E('span', { className: 'dig-cs-badge dig-cs-del', title: changesTitle }, '−' + String(linesLive.deletions)) : null)
15344
15499
  : null,
15345
15500
  behind > 0 ? E('span', { className: 'dig-cs-badge dig-cs-behind', title: t('composer.behindHint') }, '↓' + String(behind)) : null,
15346
15501
  ahead > 0 ? E('span', { className: 'dig-cs-badge dig-cs-ahead', title: t('composer.aheadHint') }, '↑' + String(ahead)) : null,