dsh-gitbar-lite 0.2.0 → 0.2.1

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
@@ -23,6 +23,7 @@ The column is narrow (300px minimum, and a narrow viewport makes it full-screen)
23
23
  | Commit | Commits exactly what is ticked — because ticking wrote the index |
24
24
  | Commit and Push | Commits, then pushes. **A failed push never claims the commit failed**: the commit happened, and the panel says "committed `abc1234`, but the push failed" |
25
25
  | Context button | **Push** when local commits are unpushed, **Publish** when the branch has no upstream, **Fetch** otherwise |
26
+ | Automatic fetch | Every five minutes while the panel is visible and the repository has a remote (`autoFetchMs`, `0` to disable). Failures are silent — no error banner |
26
27
  | Discard | One file or all of them; the diff is archived *before* anything is restored, and the confirmation offers "delete for good" |
27
28
  | Trash | Its own tab. Every discard keeps a diff, recorded **against the commit it was taken from** (`abc1234 initial commit`) |
28
29
  | Diff preview | Double-click a file, or a trash entry, to open a diff tab in the column — **one tab, ever**, re-navigated in place |
@@ -137,12 +138,14 @@ The order is fixed and cannot be swapped:
137
138
  | `maxFileBytes` | `2097152` | Larger untracked files are listed but not read, archived, or diffed |
138
139
  | `trashLimit` | `200` | Trash entries kept; the oldest are dropped along with their diff files |
139
140
  | `scanDepth` | `3` | Directory depth the picker scans below the workspace root |
141
+ | `autoFetchMs` | `300000` | How often the panel fetches on its own; `0` disables it, leaving the manual button |
140
142
 
141
143
  Every field is volatile, so a change on the settings page takes effect without a restart.
142
144
 
143
145
  **Where state lives**: `<DSH_HOME>/dsh-gitbar-lite/` (falling back to `~/.dsh`) — `bindings.json` (workspace →
144
- repository), `prefs.json` (the two remembered choices), and `trash/index.json` plus `trash/<id>.diff`. Every write
145
- goes through a temp file and a rename, so a crash mid-write cannot truncate a file.
146
+ repository), `prefs.json` (the two remembered choices), `trash/index.json` plus `trash/<id>.diff`, and
147
+ `trash/<id>.content.json` (the raw contents a restore needs). Every write goes through a temp file and a rename, so a
148
+ crash mid-write cannot truncate a file.
146
149
 
147
150
  ## How it works
148
151
 
package/README.md CHANGED
@@ -23,6 +23,7 @@ DSH 的右栏本来就是一个常驻的标签区。Git 面板放这里,意味
23
23
  | Commit | 只提交勾上的(因为勾选写进了索引) |
24
24
  | Commit and Push | 提交后立刻推送;**推送失败不会说「提交失败」**——提交已经发生了,面板会明说「已提交 xxx,但推送失败」 |
25
25
  | 上下文按钮 | 有本地未推送提交时是 **Push**,没有上游时是 **Publish**,否则是 **Fetch** |
26
+ | 自动 Fetch | 面板可见且仓库有远端时,每 5 分钟自己 fetch 一次(`autoFetchMs` 可调,`0` 关掉)。失败是静默的,不弹错误 |
26
27
  | 舍弃更改 | 单个文件或「全部舍弃」;动手前先把这次改动的 diff 归档,确认框里可以勾「彻底删除」 |
27
28
  | 回收站 | 独立 tab。每次舍弃存一份 diff,并记下**是对哪个提交(`abc1234 初始提交`)取的 diff** |
28
29
  | diff 预览 | 双击文件,或点回收站里的一条,在右栏开一个 diff 标签页——**永远只占一个**,再点就复用 |
@@ -132,11 +133,13 @@ dsh plugin --profile <你的 profile> remove dsh-gitbar-lite
132
133
  | `maxFileBytes` | `2097152` | 比这大的未跟踪文件只列出来,不读内容、不归档、不出 diff |
133
134
  | `trashLimit` | `200` | 回收站保留多少条;超出丢弃最旧的连同它的 diff 文件 |
134
135
  | `scanDepth` | `3` | 「不是仓库时」扫描工作区找仓库的目录深度 |
136
+ | `autoFetchMs` | `300000` | 面板自己多久 fetch 一次;`0` = 关掉,只用手动按钮 |
135
137
 
136
138
  每个字段都是 volatile 的,所以在设置页改了立刻生效、不用重启。
137
139
 
138
140
  **状态存在哪**:`<DSH_HOME>/dsh-gitbar-lite/`(没有 `DSH_HOME` 就回落 `~/.dsh`)——
139
- `bindings.json`(工作区 → 仓库)、`prefs.json`(那两个勾选偏好)、`trash/index.json` + `trash/<id>.diff`。
141
+ `bindings.json`(工作区 → 仓库)、`prefs.json`(那两个勾选偏好)、`trash/index.json` + `trash/<id>.diff` +
142
+ `trash/<id>.content.json`(恢复用的原始内容)。
140
143
  都是先写临时文件再 rename,所以写到一半崩了也不会把文件截断。
141
144
 
142
145
  ## 工作原理
package/lib/client.js CHANGED
@@ -861,8 +861,11 @@ window.__ModuleLoader__.load({
861
861
  '.gitbar-status-conflicted{color:var(--dsw-alias-state-error-primary,#b91c1c)}',
862
862
  '.gitbar-path{flex:1 1 auto;min-width:0;display:flex;align-items:baseline;gap:0;overflow:hidden}',
863
863
  '.gitbar-dir{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left;color:var(--dsw-alias-label-tertiary,#6b7280)}',
864
- '.gitbar-sep{flex:none;color:var(--dsw-alias-label-tertiary,#6b7280)}',
865
- '.gitbar-name{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
864
+ // `direction` is inherited, so the rtl above would otherwise reach the
865
+ // separator (moving the slash to the wrong end) and the file name (right-
866
+ // aligning it). Both are spelled back to ltr explicitly.
867
+ '.gitbar-sep{flex:none;direction:ltr;color:var(--dsw-alias-label-tertiary,#6b7280)}',
868
+ '.gitbar-name{flex:0 1 auto;min-width:0;direction:ltr;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
866
869
  '.gitbar-stats{flex:none;display:flex;gap:3px;font-size:10.5px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}',
867
870
  '.gitbar-plus{color:var(--dsw-alias-state-success-primary,#15803d)}',
868
871
  '.gitbar-minus{color:var(--dsw-alias-state-error-primary,#b91c1c)}',
@@ -1334,6 +1337,50 @@ window.__ModuleLoader__.load({
1334
1337
  }
1335
1338
  }, [tab.visible, sessionId, load])
1336
1339
 
1340
+ // Fetch on a rhythm, unattended, while the panel is actually being looked
1341
+ // at. Three conditions gate it, and each one exists for a reason:
1342
+ //
1343
+ // * the tab must be visible — a hidden panel has no business touching the
1344
+ // network every few minutes, and the host may have several sessions;
1345
+ // * the repository must have a remote — otherwise `fetch` is a guaranteed
1346
+ // E_NO_REMOTE, and a timer that only ever fails is noise;
1347
+ // * nothing else may be in flight — a user mid-commit must not have an
1348
+ // extra git child appear under them.
1349
+ //
1350
+ // A failure is silent. The button stays there, the banner stays quiet, and
1351
+ // the next tick tries again: a fetch that failed once (offline, credential
1352
+ // prompt suppressed) usually succeeds later, and a panel that pops an error
1353
+ // every five minutes for a laptop that was closed is worse than one that
1354
+ // says nothing. Success refreshes the status, which is what moves
1355
+ // ahead/behind and the "last fetch" line.
1356
+ //
1357
+ // The conditions are read through a ref rather than listed as dependencies
1358
+ // on purpose: a dependency on `state` would restart the clock on every
1359
+ // refresh, and a panel the user keeps interacting with would then never
1360
+ // fetch at all.
1361
+ const latest = useRef({ state: null, busy: null })
1362
+ latest.current = { state, busy }
1363
+
1364
+ const autoFetchMs = typeof state?.autoFetchMs === 'number' && state.autoFetchMs > 0 ? state.autoFetchMs : 0
1365
+ useEffect(() => {
1366
+ if (autoFetchMs === 0) return undefined
1367
+ if (tab.visible === false) return undefined
1368
+
1369
+ const timer = setInterval(() => {
1370
+ const current = latest.current
1371
+ if (current.busy !== null) return
1372
+ if (!Array.isArray(current.state?.remotes) || current.state.remotes.length === 0) return
1373
+ void rpc(ACTIONS.FETCH, { sessionId }, tab.signal)
1374
+ .then(() => {
1375
+ if (alive.current) void load({ quiet: true })
1376
+ })
1377
+ .catch(() => {
1378
+ // Deliberately swallowed: see the note above.
1379
+ })
1380
+ }, autoFetchMs)
1381
+ return () => clearInterval(timer)
1382
+ }, [autoFetchMs, tab.visible, tab.signal, sessionId, load])
1383
+
1337
1384
  // The column's refresh shortcut, when it has one.
1338
1385
  useEffect(
1339
1386
  () => (typeof tab.actions?.bindCommands === 'function' ? tab.actions.bindCommands({ refresh: () => void load({ quiet: true }) }) : undefined),
package/lib/host/repo.js CHANGED
@@ -206,6 +206,13 @@ async function fetchTimeOf(gitDirResult) {
206
206
  * @param {object} config resolved plugin config
207
207
  */
208
208
  export function createRepo(git, config) {
209
+ /**
210
+ * How often the panel should fetch on its own, in milliseconds, or 0 for
211
+ * "never". Resolved once: the value rides along with every status payload,
212
+ * because a client bundle cannot read the host's config.
213
+ */
214
+ const autoFetchMs = Number.isFinite(config.autoFetchMs) && config.autoFetchMs > 0 ? Math.floor(config.autoFetchMs) : 0
215
+
209
216
  /**
210
217
  * Identity, branch and working-tree status of one repository.
211
218
  *
@@ -305,6 +312,10 @@ export function createRepo(git, config) {
305
312
  counts,
306
313
  primary,
307
314
  lastFetchAt,
315
+ // The panel cannot read the host's config (a client bundle has no access to
316
+ // it), so the rhythm it should fetch on travels with the status it is
317
+ // already reading. Zero means "never, on your own".
318
+ autoFetchMs,
308
319
  }
309
320
  }
310
321
 
package/lib/index.js CHANGED
@@ -67,6 +67,15 @@ export const Config = vol(
67
67
  trashLimit: z.number().default(200),
68
68
  /** Directory depth the repository picker scans below the workspace root. */
69
69
  scanDepth: z.number().default(3),
70
+ /**
71
+ * How often the panel fetches on its own, in milliseconds.
72
+ *
73
+ * A fetch only moves remote refs, never the working tree, so it is safe to
74
+ * do unattended — but it does hit the network and it does write
75
+ * `.git/FETCH_HEAD`, which is why it is opt-out rather than opt-in. Set to 0
76
+ * to disable it; the button still works.
77
+ */
78
+ autoFetchMs: z.number().default(300000),
70
79
  }),
71
80
  )
72
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-gitbar-lite",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A GitHub Desktop-style Git panel for DSH: changed files, tick-to-stage, Summary + Description, Commit and Commit and Push, discard-to-trash, and diff preview, in the built-in right sidebar.",
5
5
  "type": "module",
6
6
  "license": "MIT",