dsh-ide-git 0.13.1 → 0.13.2

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.13.1",
3
+ "version": "0.13.2",
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.13.1",
3
+ "version": "0.13.2",
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
@@ -28,6 +28,12 @@ window.__ModuleLoader__.load({
28
28
  const useCallback = React.useCallback
29
29
  const useLayoutEffect = React.useLayoutEffect
30
30
  const memo = React.memo
31
+ /* Measure-and-derive effects run here for the layout timing (the chrome and
32
+ the rail capacity derive from these numbers — a paint-then-correct cycle
33
+ read as buttons lighting up one by one on every seat switch). Environments
34
+ without a frame loop (the SSR self-check's shims) get the plain effect —
35
+ there is no paint to sync with there, and no server warning either. */
36
+ const useMeasureLayoutEffect = typeof requestAnimationFrame === 'function' ? useLayoutEffect : useEffect
31
37
 
32
38
  /* API base is MOUNT-RELATIVE (no leading slash). dsh 0.1.7 serves the
33
39
  shell with <base href="./">, so document.baseURI is the mount the page
@@ -4282,6 +4288,38 @@ window.__ModuleLoader__.load({
4282
4288
 
4283
4289
  /* ============================== storage ============================== */
4284
4290
 
4291
+ /* ---- tree UI state (v0.13.1): the session-scoped seats remount on every
4292
+ session switch, and component state died with them — branch sections
4293
+ snapped back to defaults and folders re-folded. One localStorage home,
4294
+ GLOBAL by design: which sections you keep folded is a habit, not a
4295
+ property of the repository. ---- */
4296
+ const TREE_KEY = 'dsh-ide-git.tree.v1'
4297
+
4298
+ function readTreeState() {
4299
+ try {
4300
+ const raw = window.localStorage.getItem(TREE_KEY)
4301
+ if (raw === null || raw === '') return null
4302
+ const parsed = JSON.parse(raw)
4303
+ return parsed !== null && typeof parsed === 'object' ? parsed : null
4304
+ } catch (error) { void error }
4305
+ return null
4306
+ }
4307
+
4308
+ function writeTreeState(state) {
4309
+ try { window.localStorage.setItem(TREE_KEY, JSON.stringify(state)) } catch (error) { void error }
4310
+ }
4311
+
4312
+ function treeSectionDefaults() {
4313
+ return { favorites: false, remote: false, tags: true }
4314
+ }
4315
+
4316
+ function treeSectionsOf(stored) {
4317
+ const sections = stored !== null && typeof stored === 'object' && stored.sections !== null && typeof stored.sections === 'object'
4318
+ ? stored.sections
4319
+ : {}
4320
+ return Object.assign(treeSectionDefaults(), sections)
4321
+ }
4322
+
4285
4323
  /* ---- ai commit pickers (issue #7): the model catalog and per-model
4286
4324
  reasoning efforts, served by the host's commit-models / commit-efforts
4287
4325
  routes. Cached for a short window so the settings card can mount twice
@@ -6022,9 +6060,23 @@ window.__ModuleLoader__.load({
6022
6060
  function BranchTree(props) {
6023
6061
  const t = props.t
6024
6062
  const [filter, setFilter] = useState('')
6025
- const [collapsed, setCollapsed] = useState({ favorites: false, remote: false, tags: true })
6063
+ const [collapsed, setCollapsed] = useState(() => treeSectionsOf(readTreeState()))
6026
6064
  const [picked, setPicked] = useState('')
6027
- const [foldedFolders, setFoldedFolders] = useState({})
6065
+ const [foldedFolders, setFoldedFolders] = useState(() => {
6066
+ const stored = readTreeState()
6067
+ return stored !== null && typeof stored === 'object' && stored.folders !== null && typeof stored.folders === 'object'
6068
+ ? stored.folders
6069
+ : {}
6070
+ })
6071
+ /* Survive the remount a session switch performs (v0.13.1). */
6072
+ useEffect(() => {
6073
+ const stored = readTreeState()
6074
+ writeTreeState(Object.assign({}, stored, { sections: collapsed }))
6075
+ }, [collapsed])
6076
+ useEffect(() => {
6077
+ const stored = readTreeState()
6078
+ writeTreeState(Object.assign({}, stored, { folders: foldedFolders }))
6079
+ }, [foldedFolders])
6028
6080
  const branches = props.branches
6029
6081
  const needle = filter.trim().toLowerCase()
6030
6082
  const match = (name) => needle === '' || String(name).toLowerCase().indexOf(needle) >= 0
@@ -6140,7 +6192,15 @@ window.__ModuleLoader__.load({
6140
6192
  const summary = props.summary
6141
6193
  const [message, setMessage] = useState('')
6142
6194
  const [amend, setAmend] = useState(false)
6143
- const [collapsed, setCollapsed] = useState(false)
6195
+ const [collapsed, setCollapsed] = useState(() => {
6196
+ const stored = readTreeState()
6197
+ return stored !== null && typeof stored === 'object' && stored.changesCollapsed === true
6198
+ })
6199
+ /* Same remount-survival as the branch sections (v0.13.1). */
6200
+ useEffect(() => {
6201
+ const stored = readTreeState()
6202
+ writeTreeState(Object.assign({}, stored, { changesCollapsed: collapsed === true }))
6203
+ }, [collapsed])
6144
6204
  /* AI-written message (issue #6). The draft is kept so the write can be
6145
6205
  undone, and an existing draft is never overwritten without a word. */
6146
6206
  const [aiBusy, setAiBusy] = useState(false)
@@ -6620,7 +6680,12 @@ window.__ModuleLoader__.load({
6620
6680
  const sessionId = typeof scope.sessionId === 'string' ? scope.sessionId : 'default'
6621
6681
  const base = useMemo(() => ({ cwd: cwd, repoRoot: repoRoot === null ? undefined : repoRoot }), [cwd, repoRoot])
6622
6682
 
6623
- useEffect(() => {
6683
+ /* Layout effect ON PURPOSE: the chrome (compact/columns/stack) and the
6684
+ rail capacity derive from these numbers, and a paint-then-correct
6685
+ cycle showed up on every seat switch as buttons lighting up one by
6686
+ one (v0.13.1 report). Measuring in the commit phase keeps the first
6687
+ paint on the final chrome. */
6688
+ useMeasureLayoutEffect(() => {
6624
6689
  const element = hostRef.current
6625
6690
  if (element === null) return undefined
6626
6691
  const measure = () => {
@@ -6664,7 +6729,7 @@ window.__ModuleLoader__.load({
6664
6729
  // Like the rail, the panes measure the box that actually holds them instead
6665
6730
  // of trusting the root probe: the column chrome keeps a vertical rail
6666
6731
  // beside the body, and the header sits above it.
6667
- useEffect(() => {
6732
+ useMeasureLayoutEffect(() => {
6668
6733
  const element = bodyRef.current
6669
6734
  if (element === null) { setBodyBox({ width: 0, height: 0 }); return undefined }
6670
6735
  const measure = () => {
@@ -6802,7 +6867,9 @@ window.__ModuleLoader__.load({
6802
6867
  // function of the strip that holds them (a 1200x300 workbench and a 300x900
6803
6868
  // sidebar disagree about that). railBox keeps the last measured span; until
6804
6869
  // the first measurement lands, every action is rendered.
6805
- useEffect(() => {
6870
+ // Layout effect (see the host measure above): the button count derives
6871
+ // from this box, and a late measure reads as the rail lighting up late.
6872
+ useMeasureLayoutEffect(() => {
6806
6873
  const element = railHostRef.current
6807
6874
  if (element === null) { setRailBox({ width: 0, height: 0 }); return undefined }
6808
6875
  const measure = () => {
@@ -8079,6 +8146,47 @@ window.__ModuleLoader__.load({
8079
8146
  const ctx = props.ctx
8080
8147
  const sessionId = typeof props.sessionId === 'string' ? props.sessionId : ''
8081
8148
  const [cwd, setCwd] = useState(() => sessionCwdOf(ctx, sessionId))
8149
+ /* The active conversation's view area is `flex: 1 0 auto; min-height:
8150
+ auto` — it GROWS with content and scrolls inside the shared
8151
+ conversation scrollport. An auto-height panel there measured
8152
+ content-tall (a 120-commit history, a long diff), so the width-vs-
8153
+ height chrome test flipped the wide seat into the stacked sidebar
8154
+ layout and everything shifted downward (v0.13.1 report). Pin the seat
8155
+ to the scrollport's VISIBLE height instead: the panel fills exactly
8156
+ the on-screen area and scrolls internally, the way the sidebar seats
8157
+ behave. */
8158
+ const seatRef = useRef(null)
8159
+ const [seatHeight, setSeatHeight] = useState(null)
8160
+ useEffect(() => {
8161
+ const el = seatRef.current
8162
+ if (el === null) return undefined
8163
+ const measure = () => {
8164
+ let scroll = null
8165
+ try { scroll = el.closest('[data-conversation-scroll]') } catch (error) { void error }
8166
+ if (scroll === null || scroll === undefined) return
8167
+ const offset = Math.max(0, el.getBoundingClientRect().top - scroll.getBoundingClientRect().top)
8168
+ const available = Math.round(scroll.clientHeight - offset - 8)
8169
+ if (available >= 240) setSeatHeight(available)
8170
+ }
8171
+ measure()
8172
+ const raf = requestAnimationFrame(() => { measure() })
8173
+ const late = setTimeout(() => { measure() }, 400)
8174
+ let observer
8175
+ try {
8176
+ const scroll = el.closest('[data-conversation-scroll]')
8177
+ if (scroll !== null && scroll !== undefined && typeof ResizeObserver === 'function') {
8178
+ observer = new ResizeObserver(() => { measure() })
8179
+ observer.observe(scroll)
8180
+ }
8181
+ } catch (error) { void error }
8182
+ window.addEventListener('resize', measure)
8183
+ return () => {
8184
+ cancelAnimationFrame(raf)
8185
+ clearTimeout(late)
8186
+ if (observer !== undefined) observer.disconnect()
8187
+ window.removeEventListener('resize', measure)
8188
+ }
8189
+ }, [])
8082
8190
  useEffect(() => {
8083
8191
  let alive = true
8084
8192
  const sync = () => { if (alive === true) setCwd(sessionCwdOf(ctx, sessionId)) }
@@ -8102,7 +8210,11 @@ window.__ModuleLoader__.load({
8102
8210
  }
8103
8211
  }, [ctx, sessionId])
8104
8212
  const scope = useMemo(() => ({ cwd: cwd, sessionId: sessionId }), [cwd, sessionId])
8105
- return E(LocaleLive, { ctx: ctx, scope: scope, t: props.t, visible: true })
8213
+ return E('div', {
8214
+ ref: seatRef,
8215
+ className: 'dig-conversation-seat',
8216
+ style: seatHeight === null ? undefined : { height: seatHeight + 'px' },
8217
+ }, E(LocaleLive, { ctx: ctx, scope: scope, t: props.t, visible: true }))
8106
8218
  }, (prev, next) => prev.sessionId === next.sessionId && prev.t === next.t && prev.ctx === next.ctx)
8107
8219
 
8108
8220
  function NativePanel(props) {