dsh-ide-git 0.19.3 → 0.19.5

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.3",
3
+ "version": "0.19.5",
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.3",
3
+ "version": "0.19.5",
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
@@ -10375,6 +10375,24 @@ window.__ModuleLoader__.load({
10375
10375
  selKeys (`local:name` / `remote:name` / `tag:name` — the prefix keeps a
10376
10376
  branch and a tag that share a name from colliding). */
10377
10377
  const [picks, setPicks] = useState(() => new Set())
10378
+ /* v0.19.4: the Panel bumps clearSignal when ANOTHER list gains the
10379
+ selection (or a blank click clears everywhere) — drop ours. Monotonic
10380
+ epochs start at 0, so the mount pass never clears. */
10381
+ const treeClearSignal = props.clearSignal === undefined ? 0 : props.clearSignal
10382
+ const seenTreeClear = useRef(treeClearSignal)
10383
+ /* v0.19.4 echo guard: only USER gestures may flow up to the Panel — the
10384
+ mirror effect below reports picks exactly once per gesture. Reactive
10385
+ setPicks calls (the clear-signal effect above, the survival filter)
10386
+ must NOT re-enter the forwarder, or the epoch it bumps clears the
10387
+ selection the OTHER list just made (the T1b echo bug). */
10388
+ const reportGestureRef = useRef(false)
10389
+ useEffect(() => {
10390
+ if (seenTreeClear.current !== treeClearSignal) {
10391
+ seenTreeClear.current = treeClearSignal
10392
+ reportGestureRef.current = false
10393
+ setPicks(new Set())
10394
+ }
10395
+ }, [treeClearSignal])
10378
10396
  const pickAnchor = useRef(null)
10379
10397
  const visibleRef = useRef([])
10380
10398
  const [foldedFolders, setFoldedFolders] = useState(() => {
@@ -10409,6 +10427,7 @@ window.__ModuleLoader__.load({
10409
10427
  const headEntry = locals.find((entry) => entry.head === true) || null
10410
10428
  /* A refresh that makes a picked ref disappear must not keep it selected. */
10411
10429
  useEffect(() => {
10430
+ reportGestureRef.current = false
10412
10431
  setPicks((previous) => {
10413
10432
  if (previous.size === 0) return previous
10414
10433
  const alive = new Set()
@@ -10426,6 +10445,8 @@ window.__ModuleLoader__.load({
10426
10445
  render-time call would trip the hooks rules): the rail reads them so
10427
10446
  its buttons can act on the selection instead of only on HEAD. */
10428
10447
  useEffect(() => {
10448
+ if (reportGestureRef.current !== true) return
10449
+ reportGestureRef.current = false
10429
10450
  if (props.onPicksChange !== undefined) props.onPicksChange(picks)
10430
10451
  }, [picks])
10431
10452
  /**
@@ -10460,6 +10481,7 @@ window.__ModuleLoader__.load({
10460
10481
  return out
10461
10482
  }
10462
10483
  const handlePick = useCallback((event, entry, selKey) => {
10484
+ reportGestureRef.current = true
10463
10485
  const ordered = visibleRef.current.map((leaf) => branchSelKeyOf(leaf.entry))
10464
10486
  if (event.shiftKey === true) {
10465
10487
  /* No anchor yet (fresh tree, nothing clicked): VS Code semantics —
@@ -10581,7 +10603,7 @@ window.__ModuleLoader__.load({
10581
10603
  })
10582
10604
  return rects
10583
10605
  },
10584
- onPick: (keys) => setPicks(new Set(keys)),
10606
+ onPick: (keys) => { reportGestureRef.current = true; setPicks(new Set(keys)) },
10585
10607
  })
10586
10608
  /* List-scope keyboard: Ctrl/Cmd+C copies the picked names, Delete asks
10587
10609
  Panel for a grouped delete (head branches are skipped — git refuses
@@ -10630,7 +10652,16 @@ window.__ModuleLoader__.load({
10630
10652
  ref: treeScrollRef,
10631
10653
  onPointerDown: treeMarquee.onPointerDown,
10632
10654
  onClickCapture: (event) => {
10633
- if (treeMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation() }
10655
+ if (treeMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation(); return }
10656
+ /* v0.19.4: a blank click (not on a branch row or a control) clears
10657
+ the picks here AND everywhere else — "selection follows the last
10658
+ gesture". The Set goes up through the same forwarder a row click
10659
+ uses, so the other two lists reset as well. */
10660
+ const target = event.target
10661
+ if (target instanceof Element && target.closest('[data-mkey],button,input,textarea,select,a') === null) {
10662
+ reportGestureRef.current = true
10663
+ setPicks(new Set())
10664
+ }
10634
10665
  },
10635
10666
  },
10636
10667
  headEntry === null ? E('div', { className: 'dig-section-head dig-section-head-static' }, t('branches.head')) : E('div', {
@@ -11082,7 +11113,11 @@ window.__ModuleLoader__.load({
11082
11113
  },
11083
11114
  onPointerDown: changeMarquee.onPointerDown,
11084
11115
  onClickCapture: (event) => {
11085
- if (changeMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation() }
11116
+ if (changeMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation(); return }
11117
+ /* v0.19.4: blank click clears — the wrapped setter also bumps the
11118
+ other lists' epochs, so this is a clear-everywhere gesture. */
11119
+ const target = event.target
11120
+ if (target instanceof Element && target.closest('.dig-row,.dig-group-head,button,input,textarea,select,a') === null) setSelection(new Set())
11086
11121
  },
11087
11122
  },
11088
11123
  collapsed ? null : [
@@ -11723,7 +11758,12 @@ window.__ModuleLoader__.load({
11723
11758
  onKeyDown: onListKeyDown,
11724
11759
  onPointerDown: historyMarquee.onPointerDown,
11725
11760
  onClickCapture: (event) => {
11726
- if (historyMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation() }
11761
+ if (historyMarquee.swallowRef.current === true) { event.preventDefault(); event.stopPropagation(); return }
11762
+ /* v0.19.4: blank click clears — see the changes/tree twins. */
11763
+ const target = event.target
11764
+ if (target instanceof Element && target.closest('.dig-commit,button,input,textarea,select,a') === null) {
11765
+ if (props.onPicksChange !== undefined) props.onPicksChange(new Set())
11766
+ }
11727
11767
  },
11728
11768
  onPointerDownCapture: (event) => {
11729
11769
  if (isTypingTarget(event.target) === false) event.currentTarget.focus({ preventScroll: true })
@@ -11845,6 +11885,28 @@ window.__ModuleLoader__.load({
11845
11885
  /* The branch tree reports its picks up (onPicksChange) so the rail can act
11846
11886
  on the selection: "select main, press pull" must pull main, not HEAD. */
11847
11887
  const [branchPicks, setBranchPicks] = useState(() => new Set())
11888
+ /* v0.19.4: the three lists' selections are mutually exclusive — a pick in
11889
+ one list (row click or rubber band) clears the other two, and a blank
11890
+ click inside a list clears everything too. Each interaction bumps the
11891
+ OTHER lists' epoch counters; the effects below (and a clearSignal prop
11892
+ into BranchTree, whose picks live inside it) reset on change. Epochs
11893
+ are monotonic and start at 0, so the mount pass clears nothing. */
11894
+ const [pickEpochs, setPickEpochs] = useState(() => ({ changes: 0, history: 0, tree: 0 }))
11895
+ const clearPicksExcept = useCallback((owner) => {
11896
+ setPickEpochs((previous) => ({
11897
+ changes: owner === 'changes' ? previous.changes : previous.changes + 1,
11898
+ history: owner === 'history' ? previous.history : previous.history + 1,
11899
+ tree: owner === 'tree' ? previous.tree : previous.tree + 1,
11900
+ }))
11901
+ }, [])
11902
+ useEffect(() => { if (pickEpochs.changes > 0) setChangeSelection(new Set()) }, [pickEpochs.changes])
11903
+ useEffect(() => { if (pickEpochs.history > 0) setPickedCommits(new Set()) }, [pickEpochs.history])
11904
+ /* Forwarders: the raw setters wrapped so EVERY selection gesture reports
11905
+ an owner. The updates arrive either as a Set or as a functional
11906
+ updater (the memoised rows hold stale closures) — forwarded as-is. */
11907
+ const onBranchPicks = useCallback((update) => { setBranchPicks(update); clearPicksExcept('tree') }, [clearPicksExcept])
11908
+ const onChangeSelection = useCallback((update) => { setChangeSelection(update); clearPicksExcept('changes') }, [clearPicksExcept])
11909
+ const onCommitPicks = useCallback((update) => { setPickedCommits(update); clearPicksExcept('history') }, [clearPicksExcept])
11848
11910
  const [menu, setMenu] = useState(null)
11849
11911
  const [dialog, setDialog] = useState(null)
11850
11912
  const [tick, setTick] = useState(0)
@@ -13269,8 +13331,8 @@ window.__ModuleLoader__.load({
13269
13331
  onLoadMore: () => { void guard(() => loadCommits(commits.length)) },
13270
13332
  /* ---- multi-select (issue #13) ---- */
13271
13333
  pickedHashes: pickedCommits,
13272
- onPicksChange: setPickedCommits,
13273
- onSelectAll: (hashes) => setPickedCommits(new Set(hashes)),
13334
+ onPicksChange: onCommitPicks,
13335
+ onSelectAll: (hashes) => onCommitPicks(new Set(hashes)),
13274
13336
  onKeyDown: onHistoryKeyDown,
13275
13337
  mergeAnchor: mergeAnchor,
13276
13338
  base: base,
@@ -13543,7 +13605,7 @@ window.__ModuleLoader__.load({
13543
13605
  onChangeMenu: changeMenu,
13544
13606
  /* ---- multi-select (issue #13) ---- */
13545
13607
  selection: changeSelection,
13546
- onSelectionChange: setChangeSelection,
13608
+ onSelectionChange: onChangeSelection,
13547
13609
  anchorRef: changeAnchor,
13548
13610
  onBulkStage: bulkStageChanges,
13549
13611
  onBulkUnstage: bulkUnstageChanges,
@@ -13824,19 +13886,19 @@ window.__ModuleLoader__.load({
13824
13886
  title: t('toolbar.tree'),
13825
13887
  onClick: () => foldTree(!treeOpen),
13826
13888
  }, E(Icon, { name: treeOpen ? 'eye' : 'eyeOff' }))),
13827
- treeOpen ? E('div', { className: 'dig-compact-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13889
+ treeOpen ? E('div', { className: 'dig-compact-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: onBranchPicks, clearSignal: pickEpochs.tree })) : null,
13828
13890
  treeOpen ? gutter('tree', 'prev') : null,
13829
13891
  view === 'changes' ? changesPane : E('div', { className: 'dig-pane dig-pane-main' }, historyPane, diffGutter, diffPane))
13830
13892
  } else if (columns) {
13831
13893
  body = E('div', { className: 'dig-body dig-body-columns', ref: bodyRef },
13832
- treeOpen ? E('div', { className: 'dig-pane dig-pane-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13894
+ treeOpen ? E('div', { className: 'dig-pane dig-pane-tree', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: onBranchPicks, clearSignal: pickEpochs.tree })) : null,
13833
13895
  treeOpen ? gutter('tree', 'prev') : null,
13834
13896
  E('div', { className: 'dig-pane dig-pane-main' }, historyPane, diffGutter, diffPane),
13835
13897
  gutter('changes', 'next'),
13836
13898
  E('div', { className: 'dig-pane dig-pane-changes', 'data-pane': 'changes', style: paneStyle('changes') }, changesPane))
13837
13899
  } else {
13838
13900
  body = E('div', { className: 'dig-body dig-body-stack', ref: bodyRef },
13839
- treeOpen ? E('div', { className: 'dig-pane dig-pane-tree-stack', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: setBranchPicks })) : null,
13901
+ treeOpen ? E('div', { className: 'dig-pane dig-pane-tree-stack', 'data-pane': 'tree', style: paneStyle('tree') }, E(BranchTree, { t: t, branches: branches, favorites: favorites, onCheckout: checkout, onBrowse: browseBranch, onBranchMenu: branchMenu, onDeleteEntries: deleteBranchEntries, onCopyValues: copyValues, onPicksChange: onBranchPicks, clearSignal: pickEpochs.tree })) : null,
13840
13902
  treeOpen ? gutter('tree', 'prev') : null,
13841
13903
  E('div', { className: 'dig-pane dig-pane-changes-stack', 'data-pane': 'changes', style: paneStyle('changes') }, changesPane),
13842
13904
  gutter('changes', 'prev'),
@@ -14392,7 +14454,18 @@ window.__ModuleLoader__.load({
14392
14454
  '.dig-head-branch{cursor:pointer;transition:background .12s}',
14393
14455
  '.dig-head-branch:hover{background:var(--dsw-alias-interactive-bg-hover)}',
14394
14456
  '.dig-row-head{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary))}',
14395
- '.dig-row-selected{background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover));color:var(--dsw-alias-label-primary);box-shadow:inset 2px 0 0 var(--dsw-alias-brand-primary)}',
14457
+ /* v0.19.5: the v0.19.4 mix folded the row's own TEXT color in, and on
14458
+ the dark theme that text is near-white — 42% of it whitewashed the
14459
+ fill over bright wallpapers (user report). Keep the mix SHAPE (58%
14460
+ alias tint + 42% OPAQUE gray = the >=.42 alpha floor that keeps the
14461
+ pick readable through any-background's transparency/frost) but mix a
14462
+ THEME-AWARE NEUTRAL GRAY instead of the text color: light theme mixes
14463
+ the host's pale highlight gray (bluish-75 — the light hover-solid
14464
+ tone), dark theme mixes charcoal (bluish-800 — the dark hover-solid
14465
+ tone) via the host's body[data-ds-dark-theme] switch. Selection reads
14466
+ as gray on both themes, wallpaper/frost stays visible, never white. */
14467
+ '.dig-row-selected{background:color-mix(in srgb,var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover)) 58%,var(--dsw-static-neutral-bluish-75,var(--dsw-alias-bg-layer-3)) 42%);color:var(--dsw-alias-label-primary);box-shadow:inset 2px 0 0 var(--dsw-alias-brand-primary)}',
14468
+ 'body[data-ds-dark-theme] .dig-row-selected{background:color-mix(in srgb,var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover)) 58%,var(--dsw-static-neutral-bluish-800,var(--dsw-alias-bg-layer-3)) 42%)}',
14396
14469
  '.dig-row-remote .dig-row-label{color:var(--dsw-alias-label-secondary);font-weight:500}',
14397
14470
  '.dig-row-tag .dig-row-label{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary));font-weight:500}',
14398
14471
  '.dig-row-head .dig-row-label{font-weight:600}',
@@ -14416,7 +14489,9 @@ window.__ModuleLoader__.load({
14416
14489
  '.dig-history-scroll{flex:1;overflow:auto}',
14417
14490
  '.dig-commit{display:flex;align-items:center;gap:6px;padding:1px 8px 1px 0;cursor:pointer;height:26px;overflow:hidden}',
14418
14491
  '.dig-commit:hover{background:var(--dsw-alias-interactive-bg-hover)}',
14419
- '.dig-commit-selected{background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover));color:var(--dsw-alias-label-primary);box-shadow:inset 2px 0 0 var(--dsw-alias-brand-primary)}',
14492
+ /* Same theme-aware gray mix as .dig-row-selected (see it). */
14493
+ '.dig-commit-selected{background:color-mix(in srgb,var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover)) 58%,var(--dsw-static-neutral-bluish-75,var(--dsw-alias-bg-layer-3)) 42%);color:var(--dsw-alias-label-primary);box-shadow:inset 2px 0 0 var(--dsw-alias-brand-primary)}',
14494
+ 'body[data-ds-dark-theme] .dig-commit-selected{background:color-mix(in srgb,var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover)) 58%,var(--dsw-static-neutral-bluish-800,var(--dsw-alias-bg-layer-3)) 42%)}',
14420
14495
  // A favorited branch renders its star SOLID (BranchRow adds this class
14421
14496
  // and flips the tone to warn) — the hollow green HEAD star read as
14422
14497
  // "not favorited" (v0.18.5).