dsh-git-changes 0.1.2 → 0.1.3

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.en.md CHANGED
@@ -30,6 +30,11 @@ DSH's builtin review shows one hunk at a time; this panel gives you the **whole
30
30
 
31
31
  ## 🆕 What's New
32
32
 
33
+ ### v0.1.3 · Entry & baseline fixes
34
+
35
+ - **Composer pill fixed** — opening the panel from the "this turn" pill now selects the turn baseline and the turn's first changed file instead of an empty Git panel after the work was committed.
36
+ - **Empty-worktree fallback** — when the default Git uncommitted panel has nothing to show but the turn changed files, the panel switches to the turn baseline and says so.
37
+
33
38
  ### v0.1.1 · Polish
34
39
 
35
40
  - **New Project tab** — a second segment beside Changes that browses the whole workspace tree; opening a file there is plain code viewing.
package/README.md CHANGED
@@ -30,6 +30,11 @@ DSH 自带的审查是一个个 hunk 片段;这个插件给的是**整个文
30
30
 
31
31
  ## 🆕 版本更新
32
32
 
33
+ ### v0.1.3 · 入口与基线修复
34
+
35
+ - **「本轮修改」入口修复**:从聊天输入框上方的胶囊打开时,直接进入「本轮改动」基线并选中该轮的首个改动文件,不再因工作区已提交而显示空面板。
36
+ - **空工作区回落**:默认的 Git 未提交面板若没有改动、而本轮对话有改动,会自动切换到本轮基线并提示。
37
+
33
38
  ### v0.1.1 · 细节打磨
34
39
 
35
40
  - **新增「项目」标签**:与「更改」并列的第二个分段,浏览整个工作区文件树;从项目树打开文件为纯代码查看。
package/lib/client.js CHANGED
@@ -393,6 +393,7 @@ const ZH = {
393
393
 
394
394
  'mode.turn': 'Agent 本轮',
395
395
  'mode.turn.desc': '本轮对话里 Agent 改了什么(与开始时对比)',
396
+ 'mode.git.emptyToTurn': '工作区没有未提交改动,已切换到「本轮改动」',
396
397
  'mode.turn.emptyToGit': '本轮没有可审查的改动,已切换到「Git 未提交」',
397
398
  'mode.git.desc': '本地还没提交的改动(暂存看 HEAD,未暂存看索引)',
398
399
  'mode.commit.desc': '最近一次提交改了什么',
@@ -666,6 +667,7 @@ const EN = {
666
667
 
667
668
  'mode.turn': 'Agent turn',
668
669
  'mode.turn.desc': 'What the agent changed this turn (vs the turn start)',
670
+ 'mode.git.emptyToTurn': 'The working tree is clean; switched to this turn\'s changes',
669
671
  'mode.turn.emptyToGit': 'No conversation changes to review; switched to uncommitted changes',
670
672
  'mode.git.desc': 'Uncommitted local changes (staged vs HEAD, unstaged vs index)',
671
673
  'mode.commit.desc': 'What the latest commit changed',
@@ -5838,6 +5840,21 @@ function changesReviewAddressOf(sessionId, seq, turn) {
5838
5840
  return CHANGES_REVIEW_PREFIX + encodeURIComponent(sessionId) + '/' + String(seq) + '/' + String(turn)
5839
5841
  }
5840
5842
 
5843
+ /**
5844
+ * Parse a builtin hunk-review address back into its turn coordinates.
5845
+ * @param {string|null|undefined} address resource address
5846
+ * @returns {{seq:number,turn:number}|null} parsed coordinates or null
5847
+ */
5848
+ function turnAddressOf(address) {
5849
+ if (typeof address !== 'string' || address.indexOf(CHANGES_REVIEW_PREFIX) !== 0) return null
5850
+ const parts = address.slice(CHANGES_REVIEW_PREFIX.length).split('/')
5851
+ if (parts.length < 3) return null
5852
+ const seq = Number(parts[1])
5853
+ const turn = Number(parts[2])
5854
+ if (!Number.isFinite(seq) || !Number.isFinite(turn)) return null
5855
+ return { seq, turn }
5856
+ }
5857
+
5841
5858
  /**
5842
5859
  * Call `openResource` defensively; returns false instead of throwing.
5843
5860
  * @param {any} right sidebarRight controller
@@ -6322,6 +6339,17 @@ function ReviewPage(props) {
6322
6339
  autoOpenedRef.current = true
6323
6340
  return
6324
6341
  }
6342
+ // Opened from the chat composer's "this turn" pill: the builtin review
6343
+ // address carries the turn coordinates, so this visit is about that turn
6344
+ // even when a previous visit left another baseline behind.
6345
+ const addressTurn = turnAddressOf(address)
6346
+ if (addressTurn && latest.available) {
6347
+ setMode('turn')
6348
+ setModeTouched(true)
6349
+ autoOpenedRef.current = false
6350
+ restoreLoadRef.current = false
6351
+ return
6352
+ }
6325
6353
  const saved = rememberedPageState(sessionId)
6326
6354
  if (!saved) {
6327
6355
  // First mount in this app run: honour the baseline the user left behind in
@@ -6372,7 +6400,7 @@ function ReviewPage(props) {
6372
6400
  setRevision(nextRevision)
6373
6401
  setMode(nextMode)
6374
6402
  setModeTouched(saved.modeTouched === true)
6375
- }, [latest, gitStatus, sessionId])
6403
+ }, [latest, gitStatus, sessionId, address])
6376
6404
 
6377
6405
  // Persist the page's workspace state before this tab body unmounts. DSH
6378
6406
  // unmounts the previous conversation's sidebar tab bodies on a session switch,
@@ -6473,6 +6501,20 @@ function ReviewPage(props) {
6473
6501
  notify(t('mode.turn.emptyToGit'), 'info')
6474
6502
  }, [mode, modeTouched, comparison, regions.length, notify, t])
6475
6503
 
6504
+ // The mirror case: a clean working tree plus a turn that did change files
6505
+ // should not open on an empty Git panel.
6506
+ React.useEffect(() => {
6507
+ if (mode !== 'git' || modeTouched) return
6508
+ if (!latest || latest.available !== true) return
6509
+ const files = latest.summary && Array.isArray(latest.summary.files) ? latest.summary.files : []
6510
+ if (files.length === 0) return
6511
+ const status = gitStatus
6512
+ if (!status || status.available !== true || !Array.isArray(status.files) || status.files.length > 0) return
6513
+ setMode('turn')
6514
+ setModeTouched(true)
6515
+ notify(t('mode.git.emptyToTurn'), 'info')
6516
+ }, [mode, modeTouched, latest, gitStatus, notify, t])
6517
+
6476
6518
  /** Load one file's current content, using the per-path cache for instant switch. */
6477
6519
  const loadFile = React.useCallback(async (path, options) => {
6478
6520
  if (!sessionId || !path) return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-git-changes",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Cursor-style Git changes panel for the DSH right sidebar: staged/unstaged trees, stage/unstage, commit & push, inline or side-by-side diffs, AI commit messages, blame and full-source review.",
@@ -14,6 +14,7 @@ const ZH = {
14
14
 
15
15
  'mode.turn': 'Agent 本轮',
16
16
  'mode.turn.desc': '本轮对话里 Agent 改了什么(与开始时对比)',
17
+ 'mode.git.emptyToTurn': '工作区没有未提交改动,已切换到「本轮改动」',
17
18
  'mode.turn.emptyToGit': '本轮没有可审查的改动,已切换到「Git 未提交」',
18
19
  'mode.git.desc': '本地还没提交的改动(暂存看 HEAD,未暂存看索引)',
19
20
  'mode.commit.desc': '最近一次提交改了什么',
@@ -287,6 +288,7 @@ const EN = {
287
288
 
288
289
  'mode.turn': 'Agent turn',
289
290
  'mode.turn.desc': 'What the agent changed this turn (vs the turn start)',
291
+ 'mode.git.emptyToTurn': 'The working tree is clean; switched to this turn\'s changes',
290
292
  'mode.turn.emptyToGit': 'No conversation changes to review; switched to uncommitted changes',
291
293
  'mode.git.desc': 'Uncommitted local changes (staged vs HEAD, unstaged vs index)',
292
294
  'mode.commit.desc': 'What the latest commit changed',
@@ -207,6 +207,21 @@ function changesReviewAddressOf(sessionId, seq, turn) {
207
207
  return CHANGES_REVIEW_PREFIX + encodeURIComponent(sessionId) + '/' + String(seq) + '/' + String(turn)
208
208
  }
209
209
 
210
+ /**
211
+ * Parse a builtin hunk-review address back into its turn coordinates.
212
+ * @param {string|null|undefined} address resource address
213
+ * @returns {{seq:number,turn:number}|null} parsed coordinates or null
214
+ */
215
+ function turnAddressOf(address) {
216
+ if (typeof address !== 'string' || address.indexOf(CHANGES_REVIEW_PREFIX) !== 0) return null
217
+ const parts = address.slice(CHANGES_REVIEW_PREFIX.length).split('/')
218
+ if (parts.length < 3) return null
219
+ const seq = Number(parts[1])
220
+ const turn = Number(parts[2])
221
+ if (!Number.isFinite(seq) || !Number.isFinite(turn)) return null
222
+ return { seq, turn }
223
+ }
224
+
210
225
  /**
211
226
  * Call `openResource` defensively; returns false instead of throwing.
212
227
  * @param {any} right sidebarRight controller
@@ -691,6 +706,17 @@ function ReviewPage(props) {
691
706
  autoOpenedRef.current = true
692
707
  return
693
708
  }
709
+ // Opened from the chat composer's "this turn" pill: the builtin review
710
+ // address carries the turn coordinates, so this visit is about that turn
711
+ // even when a previous visit left another baseline behind.
712
+ const addressTurn = turnAddressOf(address)
713
+ if (addressTurn && latest.available) {
714
+ setMode('turn')
715
+ setModeTouched(true)
716
+ autoOpenedRef.current = false
717
+ restoreLoadRef.current = false
718
+ return
719
+ }
694
720
  const saved = rememberedPageState(sessionId)
695
721
  if (!saved) {
696
722
  // First mount in this app run: honour the baseline the user left behind in
@@ -741,7 +767,7 @@ function ReviewPage(props) {
741
767
  setRevision(nextRevision)
742
768
  setMode(nextMode)
743
769
  setModeTouched(saved.modeTouched === true)
744
- }, [latest, gitStatus, sessionId])
770
+ }, [latest, gitStatus, sessionId, address])
745
771
 
746
772
  // Persist the page's workspace state before this tab body unmounts. DSH
747
773
  // unmounts the previous conversation's sidebar tab bodies on a session switch,
@@ -842,6 +868,20 @@ function ReviewPage(props) {
842
868
  notify(t('mode.turn.emptyToGit'), 'info')
843
869
  }, [mode, modeTouched, comparison, regions.length, notify, t])
844
870
 
871
+ // The mirror case: a clean working tree plus a turn that did change files
872
+ // should not open on an empty Git panel.
873
+ React.useEffect(() => {
874
+ if (mode !== 'git' || modeTouched) return
875
+ if (!latest || latest.available !== true) return
876
+ const files = latest.summary && Array.isArray(latest.summary.files) ? latest.summary.files : []
877
+ if (files.length === 0) return
878
+ const status = gitStatus
879
+ if (!status || status.available !== true || !Array.isArray(status.files) || status.files.length > 0) return
880
+ setMode('turn')
881
+ setModeTouched(true)
882
+ notify(t('mode.git.emptyToTurn'), 'info')
883
+ }, [mode, modeTouched, latest, gitStatus, notify, t])
884
+
845
885
  /** Load one file's current content, using the per-path cache for instant switch. */
846
886
  const loadFile = React.useCallback(async (path, options) => {
847
887
  if (!sessionId || !path) return