@caozeal/dsh-next-git 0.0.0-stage → 0.1.0

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/lib/client.js ADDED
@@ -0,0 +1,1790 @@
1
+ // Browser bundle for @caozeal/dsh-next-git.
2
+ //
3
+ // Registers two contributions into the shipped Harness Web UI:
4
+ // - `sidebar.panellist` : the Git icon in the global panel list
5
+ // - `main` (keyed) : the Git panel selected by that icon
6
+ // All data comes from the Host half through `POST /api/git-manager`; this file
7
+ // has no build step and must stay a single classic script. Copy the shell
8
+ // pattern from the other plugins in this repository.
9
+ window.__ModuleLoader__.load({
10
+ id: '@caozeal/dsh-next-git',
11
+ factory: (require) => {
12
+ var module = { exports: {} }
13
+ var exports = module.exports
14
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
15
+
16
+ var React = require('react')
17
+ var h = React.createElement
18
+ var useState = React.useState
19
+ var useEffect = React.useEffect
20
+ var useRef = React.useRef
21
+
22
+ // Host settings namespace / RPC route / slot identity.
23
+ var NS = 'git-manager'
24
+ var RPC_PATH = '/api/git-manager'
25
+ var TAB_ID = 'git-manager'
26
+ var TAB_KIND = 'git-manager'
27
+ var POLL_MS = 5000
28
+
29
+ // ---------------------------------------------------------------- locale
30
+
31
+ var ZH = {
32
+ 'panel.label': 'Git 管理',
33
+ 'panel.title': 'Git 管理',
34
+ 'panel.open': '打开 Git 管理',
35
+ 'panel.guide': '在当前会话仓库里查看/暂存变更、提交、切换分支、看提交图与 diff',
36
+ 'header.refresh': '刷新',
37
+ 'header.fetch': 'Fetch',
38
+ 'header.pull': 'Pull',
39
+ 'header.push': 'Push',
40
+ 'header.newBranch': '新建分支',
41
+ 'header.newBranchPlaceholder': '新分支名(可再填起点分支)',
42
+ 'header.fromPlaceholder': '起点分支(可选)',
43
+ 'header.create': '创建',
44
+ 'header.cancel': '取消',
45
+ 'header.repo': '仓库',
46
+ 'header.ahead': '领先 {n}',
47
+ 'header.behind': '落后 {n}',
48
+ 'header.detached': '游离 HEAD',
49
+ 'branch.local': '本地分支',
50
+ 'branch.remote': '远程分支',
51
+ 'branch.current': '当前',
52
+ 'branch.gone': 'upstream 已删除',
53
+ 'branch.switch': '切换',
54
+ 'branch.delete': '删除',
55
+ 'branch.empty': '没有分支',
56
+ 'changes.title': '变更',
57
+ 'changes.staged': '已暂存',
58
+ 'changes.unstaged': '未暂存',
59
+ 'changes.untracked': '未跟踪',
60
+ 'changes.conflicted': '冲突',
61
+ 'changes.empty': '工作区干净,没有未提交的变更',
62
+ 'changes.emptyHint': '切到「提交图」看看历史,或者继续写代码',
63
+ 'changes.stageAll': '全部暂存',
64
+ 'changes.unstageAll': '全部取消暂存',
65
+ 'changes.stage': '暂存',
66
+ 'changes.unstage': '取消暂存',
67
+ 'commit.title': '提交信息',
68
+ 'commit.placeholder': '填写提交信息,或先点「AI 生成信息」',
69
+ 'commit.hintPlaceholder': '给 AI 的补充说明(可选)',
70
+ 'commit.cmdEnter': '⌘↵ 提交',
71
+ 'commit.ai': 'AI 生成信息',
72
+ 'commit.aiShort': 'AI 生成',
73
+ 'commit.aiBusy': '生成中…',
74
+ 'commit.commit': '提交',
75
+ 'commit.push': '提交并推送',
76
+ 'commit.messageRequired': '请先填写提交信息',
77
+ 'commit.empty': '没有可提交的暂存变更',
78
+ 'tabs.changes': '变更',
79
+ 'tabs.graph': '提交图',
80
+ 'tabs.branches': '分支',
81
+ 'tabs.diff': '差异',
82
+ 'graph.empty': '暂无提交',
83
+ 'graph.truncated': '提交图已截断',
84
+ 'diff.empty': '点击左侧变更文件或提交图中的提交,查看差异',
85
+ 'diff.binary': '二进制文件,不显示文本差异',
86
+ 'diff.truncated': '差异过大,已截断',
87
+ 'diff.additions': '+{n}',
88
+ 'diff.deletions': '-{n}',
89
+ 'diff.staged': '暂存区',
90
+ 'diff.worktree': '工作区',
91
+ 'diff.commit': '提交 {hash}',
92
+ 'diff.untracked': '新文件',
93
+ 'status.loading': '加载中…',
94
+ 'status.noSession': '请先在左侧选择一个会话,Git 面板跟随当前会话的工作目录',
95
+ 'status.noCwd': '当前会话没有工作目录',
96
+ 'status.notRepo': '当前目录不是 Git 仓库:{path}',
97
+ 'status.notRepoTitle': '不是 Git 仓库',
98
+ 'status.error': '出错了:{message}',
99
+ 'status.busy': '执行中:{label}',
100
+ 'status.fetching': 'Fetch 中…',
101
+ 'status.pulling': 'Pull 中…',
102
+ 'status.pushing': 'Push 中…',
103
+ 'status.switching': '正在切换到 {branch}…',
104
+ 'status.staging': '正在暂存…',
105
+ 'status.unstaging': '正在取消暂存…',
106
+ 'status.committing': '正在提交…',
107
+ 'status.commitOk': '已提交 {hash}',
108
+ 'status.pushOk': '推送完成',
109
+ 'status.pullOk': '拉取完成',
110
+ 'status.fetchOk': 'Fetch 完成',
111
+ 'status.branchSwitched': '已切换到 {branch}',
112
+ 'status.branchCreated': '已创建分支 {branch}',
113
+ 'status.branchDeleted': '已删除分支 {branch}',
114
+ 'status.deleteConfirm': '确定删除分支 {branch}?',
115
+ 'status.aiModel': '由 {model} 生成',
116
+ 'status.aiFallback': '模型输出不可用,已生成降级文案',
117
+ 'toast.error': '失败',
118
+ 'details.output': '最近一次操作输出',
119
+ 'details.dismiss': '关闭',
120
+ 'session.untitled': '未命名会话',
121
+ 'error.transport': '无法连接 Git 服务',
122
+ 'error.badResponse': 'Git 服务返回了非 JSON 响应',
123
+ 'error.gitFailed': 'Git 操作失败',
124
+ 'error.boundary': 'Git 面板渲染出错',
125
+ 'status.cwdUnavailable': '当前会话的工作目录不存在或无法访问:{path}',
126
+ 'status.stageOk': '已暂存',
127
+ 'status.unstageOk': '已取消暂存',
128
+ 'status.stageAllOk': '已暂存全部变更',
129
+ 'status.unstageAllOk': '已取消暂存全部变更',
130
+ 'status.aiOk': '已生成提交信息({model})',
131
+ 'status.aiModelEdit': '由 {model} 生成,可继续编辑',
132
+ 'status.ready': '就绪',
133
+ 'changes.stagedCount': '已暂存 {n}',
134
+ 'changes.conflictBanner': '存在 {n} 个冲突文件,解决后再提交',
135
+ 'branch.selectTitle': '切换分支',
136
+ 'header.refreshing': '刷新中…',
137
+ 'commit.committing': '提交中…',
138
+ 'diff.truncatedShort': '已截断',
139
+ 'diff.noText': '没有文本差异',
140
+ }
141
+
142
+ var EN = {
143
+ 'panel.label': 'Git',
144
+ 'panel.title': 'Git Manager',
145
+ 'panel.open': 'Open Git Manager',
146
+ 'panel.guide': 'Review and stage changes, commit, switch branches, browse the graph and diffs for this session repository',
147
+ 'header.refresh': 'Refresh',
148
+ 'header.fetch': 'Fetch',
149
+ 'header.pull': 'Pull',
150
+ 'header.push': 'Push',
151
+ 'header.newBranch': 'New branch',
152
+ 'header.newBranchPlaceholder': 'new branch name',
153
+ 'header.fromPlaceholder': 'start point (optional)',
154
+ 'header.create': 'Create',
155
+ 'header.cancel': 'Cancel',
156
+ 'header.repo': 'Repo',
157
+ 'header.ahead': '{n} ahead',
158
+ 'header.behind': '{n} behind',
159
+ 'header.detached': 'detached HEAD',
160
+ 'branch.local': 'Local branches',
161
+ 'branch.remote': 'Remote branches',
162
+ 'branch.current': 'current',
163
+ 'branch.gone': 'upstream gone',
164
+ 'branch.switch': 'Switch',
165
+ 'branch.delete': 'Delete',
166
+ 'branch.empty': 'No branches',
167
+ 'changes.title': 'Changes',
168
+ 'changes.staged': 'Staged',
169
+ 'changes.unstaged': 'Unstaged',
170
+ 'changes.untracked': 'Untracked',
171
+ 'changes.conflicted': 'Conflicts',
172
+ 'changes.empty': 'Working tree clean',
173
+ 'changes.emptyHint': 'Check the commit graph, or keep coding',
174
+ 'changes.stageAll': 'Stage all',
175
+ 'changes.unstageAll': 'Unstage all',
176
+ 'changes.stage': 'Stage',
177
+ 'changes.unstage': 'Unstage',
178
+ 'commit.title': 'Commit message',
179
+ 'commit.placeholder': 'Write a commit message, or generate one with AI',
180
+ 'commit.hintPlaceholder': 'Optional hint for the AI',
181
+ 'commit.cmdEnter': '⌘↵ commit',
182
+ 'commit.ai': 'AI message',
183
+ 'commit.aiShort': 'AI',
184
+ 'commit.aiBusy': 'Generating…',
185
+ 'commit.commit': 'Commit',
186
+ 'commit.push': 'Commit & push',
187
+ 'commit.messageRequired': 'Commit message is required',
188
+ 'commit.empty': 'Nothing staged to commit',
189
+ 'tabs.changes': 'Changes',
190
+ 'tabs.graph': 'Graph',
191
+ 'tabs.branches': 'Branches',
192
+ 'tabs.diff': 'Diff',
193
+ 'graph.empty': 'No commits yet',
194
+ 'graph.truncated': 'Graph truncated',
195
+ 'diff.empty': 'Pick a changed file or a commit to see its diff',
196
+ 'diff.binary': 'Binary file; no textual diff',
197
+ 'diff.truncated': 'Diff truncated',
198
+ 'diff.additions': '+{n}',
199
+ 'diff.deletions': '-{n}',
200
+ 'diff.staged': 'Staged',
201
+ 'diff.worktree': 'Working tree',
202
+ 'diff.commit': 'Commit {hash}',
203
+ 'diff.untracked': 'New file',
204
+ 'status.loading': 'Loading…',
205
+ 'status.noSession': 'Select a session on the left; the Git panel follows its working directory',
206
+ 'status.noCwd': 'The current session has no working directory',
207
+ 'status.notRepo': 'Not a Git repository: {path}',
208
+ 'status.notRepoTitle': 'Not a Git repository',
209
+ 'status.error': 'Error: {message}',
210
+ 'status.busy': 'Running: {label}',
211
+ 'status.fetching': 'Fetching…',
212
+ 'status.pulling': 'Pulling…',
213
+ 'status.pushing': 'Pushing…',
214
+ 'status.switching': 'Switching to {branch}…',
215
+ 'status.staging': 'Staging…',
216
+ 'status.unstaging': 'Unstaging…',
217
+ 'status.committing': 'Committing…',
218
+ 'status.commitOk': 'Committed {hash}',
219
+ 'status.pushOk': 'Push complete',
220
+ 'status.pullOk': 'Pull complete',
221
+ 'status.fetchOk': 'Fetch complete',
222
+ 'status.branchSwitched': 'Switched to {branch}',
223
+ 'status.branchCreated': 'Created branch {branch}',
224
+ 'status.branchDeleted': 'Deleted branch {branch}',
225
+ 'status.deleteConfirm': 'Delete branch {branch}?',
226
+ 'status.aiModel': 'Generated by {model}',
227
+ 'status.aiFallback': 'Model output unusable; used a fallback message',
228
+ 'toast.error': 'Failed',
229
+ 'details.output': 'Last command output',
230
+ 'details.dismiss': 'Dismiss',
231
+ 'session.untitled': 'Untitled session',
232
+ 'error.transport': 'Cannot reach the Git service',
233
+ 'error.badResponse': 'The Git service returned a non-JSON response',
234
+ 'error.gitFailed': 'Git operation failed',
235
+ 'error.boundary': 'The Git panel crashed',
236
+ 'status.cwdUnavailable': 'The session working directory is missing or inaccessible: {path}',
237
+ 'status.stageOk': 'Staged',
238
+ 'status.unstageOk': 'Unstaged',
239
+ 'status.stageAllOk': 'Staged all changes',
240
+ 'status.unstageAllOk': 'Unstaged all changes',
241
+ 'status.aiOk': 'Commit message generated ({model})',
242
+ 'status.aiModelEdit': 'Generated by {model}; edit as needed',
243
+ 'status.ready': 'Ready',
244
+ 'changes.stagedCount': '{n} staged',
245
+ 'changes.conflictBanner': '{n} conflicted file(s); resolve them before committing',
246
+ 'branch.selectTitle': 'Switch branch',
247
+ 'header.refreshing': 'Refreshing…',
248
+ 'commit.committing': 'Committing…',
249
+ 'diff.truncatedShort': 'truncated',
250
+ 'diff.noText': 'No textual diff',
251
+ }
252
+
253
+ // ---------------------------------------------------------------- styles
254
+
255
+ var mono = 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace'
256
+
257
+ // One component-scoped stylesheet, rendered as a <style> element inside the
258
+ // panel root (removed with the panel). Class names are prefixed `gm-` and all
259
+ // colors come from --dsw-alias-* tokens, so light/dark follow the app theme.
260
+ var PANEL_CSS = `
261
+ /* ---- DeepSeek-blue whale palette ---------------------------------- */
262
+ .gm-root {
263
+ --gm-blue: #4d6bfe;
264
+ --gm-blue-2: #7b93ff;
265
+ --gm-cyan: #43c9ff;
266
+ --gm-deep: #2f49d6;
267
+ --gm-ink: var(--dsw-alias-label-primary);
268
+ --gm-dim: var(--dsw-alias-label-secondary);
269
+ --gm-line: var(--dsw-alias-border-l1);
270
+ --gm-card: var(--dsw-alias-bg-layer-1);
271
+ --gm-card2: var(--dsw-alias-bg-layer-2);
272
+ --gm-base: var(--dsw-alias-bg-base);
273
+ --gm-blue-04: color-mix(in srgb, var(--gm-blue) 4%, transparent);
274
+ --gm-blue-06: color-mix(in srgb, var(--gm-blue) 6%, transparent);
275
+ --gm-blue-08: color-mix(in srgb, var(--gm-blue) 8%, transparent);
276
+ --gm-blue-12: color-mix(in srgb, var(--gm-blue) 12%, transparent);
277
+ --gm-blue-18: color-mix(in srgb, var(--gm-blue) 18%, transparent);
278
+ --gm-blue-28: color-mix(in srgb, var(--gm-blue) 28%, transparent);
279
+ --gm-blue-40: color-mix(in srgb, var(--gm-blue) 40%, transparent);
280
+ --gm-ok: var(--dsw-alias-state-success-primary);
281
+ --gm-err: var(--dsw-alias-state-error-primary);
282
+ --gm-warn: var(--dsw-alias-state-warn-primary);
283
+ display:flex; flex-direction:column; height:100%; min-height:0;
284
+ container-type:inline-size; container-name:gm;
285
+ background:linear-gradient(180deg, var(--gm-blue-04), transparent 180px), var(--gm-base);
286
+ color:var(--gm-ink); font-size:12px; line-height:1.5;
287
+ animation:gm-fade .18s ease;
288
+ }
289
+ .gm-root *, .gm-root *::before, .gm-root *::after { box-sizing:border-box; }
290
+ .gm-scroll { flex:1 1 0; min-height:0; overflow:auto; scrollbar-width:thin;
291
+ scrollbar-color:var(--gm-blue-28) transparent; }
292
+ .gm-scroll::-webkit-scrollbar { width:10px; height:10px; }
293
+ .gm-scroll::-webkit-scrollbar-thumb { background:var(--gm-blue-28); border-radius:99px; border:3px solid transparent; background-clip:content-box; }
294
+ .gm-scroll::-webkit-scrollbar-track { background:transparent; }
295
+ .gm-scroll::-webkit-scrollbar-thumb:hover { background:var(--gm-blue-40); background-clip:content-box; }
296
+ .gm-content-inner { width:100%; }
297
+ .gm-row { display:flex; align-items:center; gap:6px; min-width:0; }
298
+ .gm-spacer { flex:1 1 auto; min-width:0; }
299
+ .gm-mark { display:inline-flex; align-items:center; justify-content:center; flex:none;
300
+ background:linear-gradient(150deg, var(--gm-blue-12), var(--gm-blue-06));
301
+ box-shadow:inset 0 0 0 1px var(--gm-blue-18), 0 2px 6px var(--gm-blue-12); }
302
+ .gm-mark img { width:80%; height:80%; object-fit:contain; display:block;
303
+ filter:drop-shadow(0 1px 3px var(--gm-blue-28)); }
304
+
305
+ /* ---- playful top bar ---------------------------------------------- */
306
+ .gm-top { position:relative; flex:none; display:flex; flex-direction:column; gap:7px; padding:10px 12px 9px;
307
+ border-bottom:1px solid var(--gm-line); overflow:hidden;
308
+ background:linear-gradient(135deg, color-mix(in srgb, var(--gm-blue) 12%, var(--gm-card)), var(--gm-card) 72%); }
309
+ .gm-top::before { content:''; position:absolute; right:-34px; top:-46px; width:132px; height:132px; border-radius:50%;
310
+ background:radial-gradient(circle at 32% 32%, color-mix(in srgb, var(--gm-cyan) 42%, transparent), transparent 68%);
311
+ opacity:.55; pointer-events:none; }
312
+ .gm-top::after { content:''; position:absolute; left:-20px; bottom:-30px; width:90px; height:90px; border-radius:50%;
313
+ background:radial-gradient(circle, var(--gm-blue-18), transparent 70%); pointer-events:none; }
314
+ .gm-top > * { position:relative; z-index:1; }
315
+ .gm-repo { font-size:13.5px; font-weight:700; letter-spacing:-0.01em; white-space:nowrap; overflow:hidden;
316
+ text-overflow:ellipsis; min-width:0; }
317
+ .gm-repo-dot { width:7px; height:7px; border-radius:50%; flex:none;
318
+ background:linear-gradient(135deg, var(--gm-cyan), var(--gm-blue)); box-shadow:0 0 0 3px var(--gm-blue-12); }
319
+ .gm-subtle { font-size:10.5px; color:var(--gm-dim); white-space:nowrap; }
320
+
321
+ /* ---- controls ------------------------------------------------------ */
322
+ .gm-btn { display:inline-flex; align-items:center; justify-content:center; gap:4px; height:25px; padding:0 10px;
323
+ border-radius:9px; border:1px solid var(--gm-blue-12); background:var(--gm-blue-06); color:var(--gm-ink);
324
+ font:inherit; font-size:11.5px; line-height:1; cursor:pointer; white-space:nowrap;
325
+ transition:transform .12s, box-shadow .12s, background .12s, border-color .12s, color .12s, opacity .12s; }
326
+ .gm-btn svg { width:13px; height:13px; flex:none; }
327
+ .gm-btn:hover:not(:disabled) { background:var(--gm-blue-12); border-color:var(--gm-blue-28); color:var(--gm-blue); transform:translateY(-1px); }
328
+ .gm-btn:active:not(:disabled) { transform:translateY(0); }
329
+ .gm-btn:focus-visible { outline:2px solid var(--gm-blue-40); outline-offset:1px; }
330
+ .gm-btn:disabled { opacity:.4; cursor:default; }
331
+ .gm-btn.primary { border-color:transparent; color:#fff; font-weight:600;
332
+ background:linear-gradient(135deg, var(--gm-blue), var(--gm-deep));
333
+ box-shadow:0 4px 14px var(--gm-blue-28); }
334
+ .gm-btn.primary:hover:not(:disabled) { color:#fff; filter:brightness(1.06); box-shadow:0 6px 18px var(--gm-blue-40); }
335
+ .gm-btn.ghost { border-color:transparent; background:transparent; color:var(--gm-dim); }
336
+ .gm-btn.ghost:hover:not(:disabled) { background:var(--gm-blue-08); color:var(--gm-blue); }
337
+ .gm-btn.icon { width:25px; padding:0; }
338
+ .gm-btn.sm { height:21px; font-size:11px; border-radius:7px; }
339
+ .gm-btn.icon.sm { width:21px; }
340
+ .gm-ai-busy { position:relative; overflow:hidden; }
341
+ .gm-ai-busy::after { content:''; position:absolute; inset:0; background:linear-gradient(110deg, transparent 20%, rgba(255,255,255,.35) 50%, transparent 80%);
342
+ transform:translateX(-100%); animation:gm-shimmer 1.2s linear infinite; }
343
+
344
+ .gm-select { height:26px; max-width:174px; padding:0 26px 0 10px; border-radius:999px;
345
+ border:1px solid var(--gm-blue-28); background-color:var(--gm-blue-08); color:var(--gm-ink);
346
+ font-size:11.5px; font-weight:600; cursor:pointer; appearance:none;
347
+ background-image:linear-gradient(45deg,transparent 50%,var(--gm-blue) 50%),linear-gradient(135deg,var(--gm-blue) 50%,transparent 50%);
348
+ background-position:calc(100% - 14px) 11px, calc(100% - 10px) 11px; background-size:4px 4px,4px 4px; background-repeat:no-repeat;
349
+ transition:box-shadow .12s, background-color .12s; }
350
+ .gm-select:hover { background-color:var(--gm-blue-12); box-shadow:0 2px 10px var(--gm-blue-18); }
351
+ .gm-select:focus-visible { outline:2px solid var(--gm-blue-40); outline-offset:-1px; }
352
+
353
+ /* ---- badges -------------------------------------------------------- */
354
+ .gm-badge { display:inline-flex; align-items:center; gap:3px; height:18px; padding:0 7px; border-radius:999px;
355
+ border:1px solid var(--gm-blue-18); background:var(--gm-blue-06); color:var(--gm-dim);
356
+ font-size:10.5px; font-variant-numeric:tabular-nums; white-space:nowrap; flex:none; }
357
+ .gm-badge.brand { color:#fff; border-color:transparent;
358
+ background:linear-gradient(135deg, var(--gm-blue), var(--gm-cyan)); box-shadow:0 2px 8px var(--gm-blue-28); }
359
+ .gm-badge.ok { color:var(--gm-ok); border-color:color-mix(in srgb, var(--gm-ok) 35%, transparent); background:color-mix(in srgb, var(--gm-ok) 8%, transparent); }
360
+ .gm-badge.err { color:var(--gm-err); border-color:color-mix(in srgb, var(--gm-err) 35%, transparent); background:color-mix(in srgb, var(--gm-err) 8%, transparent); }
361
+ .gm-badge.warn { color:var(--gm-warn); border-color:color-mix(in srgb, var(--gm-warn) 38%, transparent); background:color-mix(in srgb, var(--gm-warn) 10%, transparent); }
362
+
363
+ /* ---- pill tabs ----------------------------------------------------- */
364
+ .gm-tabs { flex:none; display:flex; align-items:center; gap:4px; padding:7px 10px; border-bottom:1px solid var(--gm-line);
365
+ background:var(--gm-card); overflow-x:auto; scrollbar-width:none; }
366
+ .gm-tabs::-webkit-scrollbar { display:none; }
367
+ .gm-tab { display:inline-flex; align-items:center; gap:5px; height:27px; padding:0 11px; border-radius:999px;
368
+ border:1px solid transparent; background:transparent; color:var(--gm-dim); font:inherit; font-size:12px;
369
+ cursor:pointer; white-space:nowrap; transition:background .14s, color .14s, box-shadow .14s, transform .14s; }
370
+ .gm-tab:hover { background:var(--gm-blue-08); color:var(--gm-blue); }
371
+ .gm-tab.active { color:#fff; font-weight:600;
372
+ background:linear-gradient(135deg, var(--gm-blue), var(--gm-deep));
373
+ box-shadow:0 3px 12px var(--gm-blue-28); }
374
+ .gm-tab:focus-visible { outline:2px solid var(--gm-blue-40); outline-offset:1px; }
375
+ .gm-count { min-width:16px; height:16px; padding:0 4px; border-radius:999px; background:var(--gm-blue-12);
376
+ color:var(--gm-blue); font-size:10px; font-weight:600; display:inline-flex; align-items:center;
377
+ justify-content:center; font-variant-numeric:tabular-nums; }
378
+ .gm-tab.active .gm-count { background:rgba(255,255,255,.24); color:#fff; }
379
+
380
+ /* ---- changes ------------------------------------------------------- */
381
+ .gm-quick { display:flex; gap:6px; padding:9px 10px 1px; flex-wrap:wrap; }
382
+ .gm-section { display:flex; align-items:center; gap:6px; padding:11px 12px 3px; font-size:10.5px;
383
+ font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--gm-dim); }
384
+ .gm-section::before { content:''; width:6px; height:6px; border-radius:2px; flex:none;
385
+ background:linear-gradient(135deg, var(--gm-cyan), var(--gm-blue)); box-shadow:0 0 0 2px var(--gm-blue-12); }
386
+ .gm-section-count { font-weight:500; letter-spacing:0; font-variant-numeric:tabular-nums; color:var(--gm-blue); }
387
+ .gm-file { display:flex; align-items:center; gap:7px; min-height:26px; padding:3px 8px; margin:0 6px;
388
+ border-radius:9px; cursor:pointer; transition:background .12s; }
389
+ .gm-file:hover { background:var(--gm-blue-06); }
390
+ .gm-file.active { background:var(--gm-blue-12); box-shadow:inset 3px 0 0 var(--gm-blue); }
391
+ .gm-stat { width:16px; height:16px; border-radius:5px; flex:none; display:inline-flex; align-items:center;
392
+ justify-content:center; font-family:${mono}; font-size:10px; font-weight:700; background:var(--gm-blue-08); }
393
+ .gm-stat.m { color:var(--gm-warn); }
394
+ .gm-stat.a { color:var(--gm-ok); }
395
+ .gm-stat.d, .gm-stat.u { color:var(--gm-err); }
396
+ .gm-stat.q { color:var(--gm-dim); }
397
+ .gm-fpath { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
398
+ font-family:${mono}; font-size:11.5px; }
399
+ .gm-dir { color:var(--gm-dim); }
400
+ .gm-act { opacity:0; transition:opacity .12s, transform .12s; flex:none; }
401
+ .gm-file:hover .gm-act, .gm-file:focus-within .gm-act, .gm-branch:hover .gm-act { opacity:1; }
402
+ .gm-act:hover { transform:scale(1.12); }
403
+ @media (hover:none) { .gm-act { opacity:1; } }
404
+ .gm-conflict-bar { margin:9px 10px 2px; padding:6px 10px; border-radius:10px; font-size:11px; font-weight:600;
405
+ color:var(--gm-err); background:color-mix(in srgb, var(--gm-err) 8%, transparent);
406
+ border:1px solid color-mix(in srgb, var(--gm-err) 28%, transparent); }
407
+
408
+ /* ---- graph --------------------------------------------------------- */
409
+ .gm-graph { padding:6px 0 14px; font-family:${mono}; font-size:11.5px; line-height:20px; }
410
+ .gm-grow { display:flex; align-items:center; gap:6px; min-height:20px; padding:0 10px; margin:0 6px;
411
+ border-radius:9px; cursor:pointer; transition:background .12s; }
412
+ .gm-grow:hover { background:var(--gm-blue-06); }
413
+ .gm-grow.active { background:var(--gm-blue-12); box-shadow:inset 3px 0 0 var(--gm-blue); }
414
+ .gm-glane { flex:none; overflow:hidden;
415
+ -webkit-mask-image:linear-gradient(90deg, #000 88%, transparent 100%);
416
+ mask-image:linear-gradient(90deg, #000 88%, transparent 100%); }
417
+ .gm-glane line { stroke:var(--gm-blue); stroke-width:1.6; stroke-linecap:round; opacity:.65; }
418
+ .gm-glane circle { fill:var(--gm-blue); stroke:var(--gm-bg); stroke-width:1.4; }
419
+ .gm-grow.active .gm-glane line, .gm-grow:hover .gm-glane line { opacity:.95; }
420
+ .gm-grow.active .gm-glane circle, .gm-grow:hover .gm-glane circle { fill:var(--gm-deep); }
421
+ .gm-hash { flex:none; font-size:10.5px; color:var(--gm-blue); opacity:.75; }
422
+ .gm-subject { flex:1 1 auto; min-width:56px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:inherit; }
423
+ .gm-meta { flex:none; max-width:145px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
424
+ font-size:10.5px; color:var(--gm-dim); font-family:inherit; }
425
+ .gm-ref { height:17px; max-width:120px; overflow:hidden; text-overflow:ellipsis; display:inline-block; }
426
+
427
+ /* ---- branches ------------------------------------------------------ */
428
+ .gm-branch { display:flex; align-items:center; gap:6px; min-height:30px; padding:3px 10px; margin:0 6px;
429
+ border-radius:9px; transition:background .12s; }
430
+ .gm-branch:hover { background:var(--gm-blue-06); }
431
+ .gm-branch > svg { color:var(--gm-blue); opacity:.85; flex:none; }
432
+ .gm-bname { flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
433
+ font-family:${mono}; font-size:11.5px; }
434
+ .gm-bmeta { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
435
+ font-size:10.5px; color:var(--gm-dim); text-align:right; }
436
+
437
+ /* ---- diff ---------------------------------------------------------- */
438
+ .gm-diff { padding-bottom:18px; }
439
+ .gm-diff-head { position:sticky; top:0; z-index:2; display:flex; align-items:center; gap:6px; padding:8px 12px;
440
+ border-bottom:1px solid var(--gm-line);
441
+ background:linear-gradient(135deg, color-mix(in srgb, var(--gm-blue) 9%, var(--gm-card)), var(--gm-card) 80%); }
442
+ .gm-diff-title { font-size:10.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
443
+ color:var(--gm-blue); flex:none; }
444
+ .gm-hunk { margin:9px 12px 3px; padding:3px 9px; border-radius:999px; background:var(--gm-blue-08);
445
+ border:1px solid var(--gm-blue-18); display:inline-block; max-width:calc(100% - 24px);
446
+ font-family:${mono}; font-size:10.5px; color:var(--gm-blue); white-space:pre-wrap; }
447
+ .gm-dlines { font-family:${mono}; font-size:11.5px; line-height:1.62; margin:0 6px 6px; }
448
+ .gm-dline { display:flex; align-items:baseline; white-space:pre-wrap; word-break:break-all; border-radius:4px; }
449
+ .gm-dline.add { background:color-mix(in srgb, var(--gm-ok) 10%, transparent); }
450
+ .gm-dline.del { background:color-mix(in srgb, var(--gm-err) 10%, transparent); }
451
+ .gm-ln { width:38px; flex:none; text-align:right; padding-right:8px; color:var(--gm-dim);
452
+ opacity:.6; user-select:none; font-variant-numeric:tabular-nums; }
453
+ .gm-sign { width:14px; flex:none; text-align:center; font-weight:700; }
454
+ .gm-dline.add .gm-sign { color:var(--gm-ok); }
455
+ .gm-dline.del .gm-sign { color:var(--gm-err); }
456
+ .gm-dtext { flex:1 1 auto; min-width:0; padding-right:10px; }
457
+ .gm-meta-line { padding:2px 12px; color:var(--gm-dim); font-style:italic; }
458
+
459
+ /* ---- commit card --------------------------------------------------- */
460
+ .gm-commit { position:relative; flex:none; overflow:hidden; display:flex; flex-direction:column; gap:7px; margin:8px 10px 10px; padding:11px 10px 10px;
461
+ border-radius:14px; border:1px solid var(--gm-blue-18);
462
+ background:linear-gradient(180deg, color-mix(in srgb, var(--gm-blue) 6%, var(--gm-card)), var(--gm-card));
463
+ box-shadow:0 8px 22px color-mix(in srgb, var(--gm-blue) 10%, transparent); }
464
+ .gm-commit::before { content:''; position:absolute; top:0; left:0; right:0; height:3px;
465
+ background:linear-gradient(90deg, var(--gm-cyan), var(--gm-blue) 45%, transparent 90%); }
466
+ .gm-commit-head { display:flex; align-items:center; gap:6px; }
467
+ .gm-commit-title { font-size:10.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
468
+ color:var(--gm-blue); }
469
+ .gm-textarea { width:100%; min-height:46px; max-height:140px; padding:7px 9px; border-radius:10px;
470
+ border:1px solid var(--gm-line); background:var(--gm-base); color:var(--gm-ink);
471
+ font:inherit; font-size:12px; line-height:1.5; resize:vertical; transition:border-color .12s, box-shadow .12s; }
472
+ .gm-textarea::placeholder, .gm-input::placeholder { color:var(--gm-dim); opacity:.7; }
473
+ .gm-textarea:focus-visible { outline:none; border-color:var(--gm-blue-40);
474
+ box-shadow:0 0 0 3px var(--gm-blue-12); }
475
+ .gm-input { flex:1 1 100px; min-width:0; height:25px; padding:0 9px; border-radius:9px;
476
+ border:1px solid var(--gm-line); background:var(--gm-base); color:var(--gm-ink);
477
+ font:inherit; font-size:11.5px; transition:border-color .12s, box-shadow .12s; }
478
+ .gm-input:focus-visible { outline:none; border-color:var(--gm-blue-40); box-shadow:0 0 0 3px var(--gm-blue-12); }
479
+ .gm-actions { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
480
+ .gm-hint { font-size:10.5px; color:var(--gm-dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
481
+ .gm-ai-line { font-size:10.5px; color:var(--gm-dim); }
482
+
483
+ /* ---- status pill ---------------------------------------------------- */
484
+ .gm-status { flex:none; display:flex; align-items:center; gap:6px; min-height:24px; padding:3px 11px;
485
+ margin:7px 10px 0; border-radius:999px; border:1px solid var(--gm-blue-12); background:var(--gm-blue-06);
486
+ font-size:11px; color:var(--gm-dim); animation:gm-pop .18s ease; }
487
+ .gm-dot { width:6px; height:6px; border-radius:50%; background:var(--gm-blue); flex:none;
488
+ box-shadow:0 0 0 3px var(--gm-blue-12); }
489
+ .gm-dot.ok { background:var(--gm-ok); box-shadow:0 0 0 3px color-mix(in srgb, var(--gm-ok) 18%, transparent); }
490
+ .gm-dot.err { background:var(--gm-err); box-shadow:0 0 0 3px color-mix(in srgb, var(--gm-err) 18%, transparent); }
491
+ .gm-dot.busy { background:var(--gm-cyan); box-shadow:0 0 0 3px color-mix(in srgb, var(--gm-cyan) 22%, transparent);
492
+ animation:gm-pulse 1.1s ease-in-out infinite; }
493
+ .gm-status .gm-msg { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
494
+ .gm-status .gm-msg.err { color:var(--gm-err); white-space:pre-wrap; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; }
495
+ .gm-status .gm-msg.ok { color:var(--gm-ok); }
496
+ .gm-details { flex:none; margin:7px 10px 0; }
497
+ .gm-details > summary { list-style:none; }
498
+ .gm-details > summary::-webkit-details-marker { display:none; }
499
+ .gm-details[open] > summary { border-bottom-left-radius:10px; border-bottom-right-radius:10px; }
500
+ .gm-pre { margin:0; padding:7px 12px; max-height:180px; overflow:auto; font-family:${mono}; font-size:11px;
501
+ color:var(--gm-dim); white-space:pre-wrap; word-break:break-word;
502
+ background:var(--gm-blue-04); border:1px solid var(--gm-blue-12); border-top:none;
503
+ border-bottom-left-radius:12px; border-bottom-right-radius:12px; }
504
+
505
+ /* ---- empty states + spinner ---------------------------------------- */
506
+ .gm-empty { flex:1 1 auto; min-height:0; display:flex; flex-direction:column; align-items:center;
507
+ justify-content:center; gap:10px; padding:30px 22px; text-align:center; color:var(--gm-dim); }
508
+ .gm-empty-bubble { position:relative; overflow:hidden; width:108px; height:108px; border-radius:36px; display:flex; align-items:center;
509
+ justify-content:center; background:radial-gradient(circle at 35% 30%, var(--gm-blue-18), var(--gm-blue-04) 70%);
510
+ border:1px solid var(--gm-blue-12); }
511
+ .gm-empty-bubble::before, .gm-empty-bubble::after { content:''; position:absolute; border-radius:50%;
512
+ background:var(--gm-blue-18); animation:gm-rise 3.2s ease-in infinite; }
513
+ .gm-empty-bubble::before { width:6px; height:6px; left:22px; bottom:16px; }
514
+ .gm-empty-bubble::after { width:4px; height:4px; left:78px; bottom:22px; animation-delay:1.4s; }
515
+ .gm-empty-mascot { width:88px; height:88px; object-fit:contain; pointer-events:none;
516
+ animation:gm-bob 3.2s ease-in-out infinite; filter:drop-shadow(0 8px 16px var(--gm-blue-28)); }
517
+ .gm-empty-title { color:var(--gm-ink); font-size:13px; font-weight:650; }
518
+ .gm-empty-hint { font-size:11.5px; line-height:1.6; max-width:280px; }
519
+ .gm-spinner { width:18px; height:18px; border-radius:50%; border:2px solid var(--gm-blue-18);
520
+ border-top-color:var(--gm-blue); animation:gm-spin .8s linear infinite; }
521
+
522
+ /* ---- animations ----------------------------------------------------- */
523
+ @keyframes gm-fade { from { opacity:0; } to { opacity:1; } }
524
+ @keyframes gm-pop { from { opacity:0; transform:translateY(4px); } to { opacity:1; transform:none; } }
525
+ @keyframes gm-bob { 0%,100% { transform:translateY(0) rotate(-1.5deg); } 50% { transform:translateY(-6px) rotate(1.5deg); } }
526
+ @keyframes gm-pulse { 50% { opacity:.35; } }
527
+ @keyframes gm-shimmer { to { transform:translateX(100%); } }
528
+ @keyframes gm-spin { to { transform:rotate(360deg); } }
529
+ @keyframes gm-rise { 0% { transform:translateY(6px) scale(.7); opacity:0; } 30% { opacity:.8; }
530
+ 100% { transform:translateY(-42px) scale(1.15); opacity:0; } }
531
+
532
+ /* ---- narrow column -------------------------------------------------- */
533
+ @container gm (max-width: 430px) {
534
+ .gm-top .gm-btn-label { display:none; }
535
+ .gm-top .gm-btn { width:27px; padding:0; }
536
+ .gm-repo { font-size:12.5px; }
537
+ .gm-bmeta { display:none; }
538
+ .gm-meta { display:none; }
539
+ .gm-empty-bubble { width:86px; height:86px; border-radius:28px; }
540
+ }
541
+ `
542
+
543
+ // The session header button lives outside the panel root, so it carries its own
544
+ // tiny stylesheet (same tokens, same look as neighbouring header utilities).
545
+ var HEADER_CSS = `
546
+ .gm-header-btn { display:inline-flex; align-items:center; justify-content:center; width:26px; height:26px; padding:0;
547
+ border:1px solid transparent; border-radius:9px; background:transparent; cursor:pointer;
548
+ transition:background .14s, transform .14s, box-shadow .14s; }
549
+ .gm-header-btn:hover { background:color-mix(in srgb, #4d6bfe 14%, transparent); transform:translateY(-1px); }
550
+ .gm-header-btn:hover svg { stroke:#4d6bfe !important; }
551
+ .gm-header-btn:focus-visible { outline:2px solid color-mix(in srgb, #4d6bfe 45%, transparent); outline-offset:1px; }
552
+ .gm-header-btn svg { display:block; }
553
+ `
554
+
555
+ function panelStyle() {
556
+ return h('style', { key: 'gm-css' }, PANEL_CSS)
557
+ }
558
+
559
+ // Locale access: `tr` closes over the binding installed by apply(), so every
560
+ // visible string goes through the Client locale service (zh/en dictionaries
561
+ // above). Missing binding degrades to the key itself (never throws).
562
+ var localeT = null
563
+ function tr(key, params) {
564
+ if (!localeT) return key
565
+ try {
566
+ return localeT(key, params)
567
+ } catch (error) {
568
+ return key
569
+ }
570
+ }
571
+
572
+ // --------------------------------------------------------------- helpers
573
+
574
+ // Icon paths are 24x24 line glyphs drawn with currentColor.
575
+ // Generated mascot: chibi Whale-chan (DeepSeek 鲸鱼娘), 224px webp, transparent.
576
+ var WHALE_IMG = 'data:image/webp;base64,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'
577
+ var MASCOT_IMG = 'data:image/webp;base64,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'
578
+
579
+ var ICONS = {
580
+ refresh: ['M20 11a8 8 0 1 0-2.3 5.7', 'M20 4v7h-7'],
581
+ fetch: ['M12 3v10', 'M8 9l4 4 4-4', 'M4 17h16', 'M4 21h16'],
582
+ pull: ['M12 3v12', 'M7 10l5 5 5-5', 'M4 21h16'],
583
+ push: ['M12 21V9', 'M7 14l5-5 5 5', 'M4 3h16'],
584
+ plus: ['M12 5v14', 'M5 12h14'],
585
+ minus: ['M5 12h14'],
586
+ close: ['M6 6l12 12', 'M18 6L6 18'],
587
+ ai: ['M12 3l1.7 4.6L18.5 9l-4.8 1.4L12 15l-1.7-4.6L5.5 9l4.8-1.4z', 'M18.5 15l.8 2.2L21.5 18l-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8z'],
588
+ check: ['M5 12l4.5 4.5L19 7'],
589
+ commit: ['M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16z', 'M8.5 12l2.5 2.5L16 9.5'],
590
+ file: ['M6 3h8l4 4v14H6z', 'M14 3v5h5'],
591
+ branch: ['M6 4v9', 'M6 17m-2 0a2 2 0 1 0 4 0a2 2 0 1 0-4 0', 'M18 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0-4 0', 'M18 8v1a6 6 0 0 1-6 6H8'],
592
+ }
593
+
594
+ function Icon(props) {
595
+ var paths = ICONS[props.name]
596
+ if (!paths) return null
597
+ var size = props.size || 13
598
+ return h('svg', {
599
+ width: size, height: size, viewBox: '0 0 24 24', fill: 'none',
600
+ stroke: 'currentColor', strokeWidth: props.strokeWidth || 1.8,
601
+ strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': 'true', focusable: 'false',
602
+ }, paths.map(function (d, index) { return h('path', { key: index, d: d }) }))
603
+ }
604
+
605
+ /** Small brand glyph next to the repo name / commit card: the generated chubby whale. */
606
+ function WhaleGlyph(props) {
607
+ var size = props.size || 26
608
+ return h('span', {
609
+ className: 'gm-mark',
610
+ style: { width: size + 'px', height: size + 'px', borderRadius: Math.round(size * 0.34) + 'px' },
611
+ }, h('img', { src: WHALE_IMG, alt: '', draggable: false }))
612
+ }
613
+
614
+ function Spinner() {
615
+ return h('span', { className: 'gm-spinner', role: 'status', 'aria-label': 'loading' })
616
+ }
617
+
618
+ /** Guide-page glyph: the generated Whale-chan mascot, sized by the registry's IconProps. */
619
+ function WhaleChanIcon(props) {
620
+ var size = props && props.size ? props.size : 16
621
+ return h('img', {
622
+ src: MASCOT_IMG, width: size, height: size, alt: '',
623
+ style: { borderRadius: '4px', objectFit: 'contain', display: 'block' },
624
+ })
625
+ }
626
+
627
+ /** One panel button: variant is 'primary' | 'ghost' | undefined; icon-only when no label. */
628
+ function Btn(props) {
629
+ var className = 'gm-btn'
630
+ if (props.variant === 'primary') className += ' primary'
631
+ else if (props.variant === 'ghost' || props.ghost) className += ' ghost'
632
+ if (props.icon && !props.label) className += ' icon'
633
+ if (props.small) className += ' sm'
634
+ if (props.className) className += ' ' + props.className
635
+ return h('button', {
636
+ type: 'button',
637
+ className: className,
638
+ title: props.title || props.label || undefined,
639
+ 'aria-label': props.title || props.label || undefined,
640
+ disabled: !!props.disabled,
641
+ onClick: props.disabled ? undefined : props.onClick,
642
+ onKeyDown: props.onKeyDown,
643
+ },
644
+ props.icon ? h(Icon, { name: props.icon, size: props.small ? 12 : 13 }) : null,
645
+ props.label ? h('span', { className: 'gm-btn-label' }, props.label) : null)
646
+ }
647
+
648
+ function statusLetter(group, entry) {
649
+ // 未跟踪按 git 习惯显示 `?`(`U` 留给 unmerged 状态,避免混淆)。
650
+ if (group === 'untracked') return '?'
651
+ return String(entry.status || '?').slice(0, 1)
652
+ }
653
+
654
+ function statusClass(group, entry) {
655
+ if (group === 'conflicted') return 'gm-stat u'
656
+ if (group === 'untracked') return 'gm-stat q'
657
+ var code = String(entry.status || '?').slice(0, 1)
658
+ if (code === 'A' || code === 'R' || code === 'C') return 'gm-stat a'
659
+ if (code === 'D' || code === 'U') return 'gm-stat d'
660
+ return 'gm-stat m'
661
+ }
662
+
663
+ function pathParts(path) {
664
+ var value = String(path || '')
665
+ var slash = value.lastIndexOf('/')
666
+ if (slash < 0) return { dir: '', base: value }
667
+ return { dir: value.slice(0, slash + 1), base: value.slice(slash + 1) }
668
+ }
669
+
670
+ // One RPC call to the Host half. Throws Error with a `.code` on failure.
671
+ async function rpc(op, params) {
672
+ var response
673
+ try {
674
+ response = await fetch(RPC_PATH, {
675
+ method: 'POST',
676
+ credentials: 'same-origin',
677
+ headers: { 'content-type': 'application/json' },
678
+ body: JSON.stringify(Object.assign({ op: op }, params || {})),
679
+ })
680
+ } catch (error) {
681
+ var transport = new Error(tr('error.transport') + ':' + (error && error.message ? error.message : error))
682
+ transport.code = 'transport'
683
+ throw transport
684
+ }
685
+ var body
686
+ try {
687
+ body = await response.json()
688
+ } catch (error) {
689
+ throw new Error(tr('error.badResponse') + ' (HTTP ' + response.status + ')')
690
+ }
691
+ if (!body || body.ok !== true) {
692
+ var failure = (body && body.error) || {}
693
+ var shaped = new Error(failure.message || tr('error.gitFailed'))
694
+ shaped.code = failure.code || 'internal'
695
+ throw shaped
696
+ }
697
+ return body.data
698
+ }
699
+
700
+ // ---------------------------------------------------------------- store
701
+
702
+ function createStore(initial) {
703
+ var state = initial
704
+ var listeners = []
705
+ return {
706
+ get: function () { return state },
707
+ set: function (patch) {
708
+ var next = typeof patch === 'function' ? patch(state) : patch
709
+ var merged = Object.assign({}, state, next)
710
+ if (merged === state) return
711
+ state = merged
712
+ for (var i = 0; i < listeners.length; i += 1) listeners[i]()
713
+ },
714
+ subscribe: function (listener) {
715
+ listeners.push(listener)
716
+ return function () {
717
+ listeners = listeners.filter(function (item) { return item !== listener })
718
+ }
719
+ },
720
+ }
721
+ }
722
+
723
+ function useStore(store) {
724
+ var pair = useState(store.get())
725
+ useEffect(function () {
726
+ var unsubscribe = store.subscribe(function () { pair[1](store.get()) })
727
+ pair[1](store.get())
728
+ return unsubscribe
729
+ }, [store])
730
+ return pair[0]
731
+ }
732
+
733
+ // Right-Sidebar tabs are session-scoped: the body receives `sessionId`, and the
734
+ // summary (with its cwd) is read from the injected sessions list.
735
+ function useSessionById(sessions, sessionId) {
736
+ var pair = useState(null)
737
+ useEffect(function () {
738
+ if (!sessions || !sessionId) {
739
+ pair[1](null)
740
+ return undefined
741
+ }
742
+ var read = function () { pair[1](sessions.list.getSnapshot().byId[sessionId] || null) }
743
+ read()
744
+ return sessions.list.subscribe(read)
745
+ }, [sessions, sessionId])
746
+ return pair[0]
747
+ }
748
+
749
+ // ------------------------------------------------------------- controller
750
+
751
+ function createController() {
752
+ var store = createStore({
753
+ cwd: null,
754
+ snapshot: null,
755
+ graph: null,
756
+ loading: false,
757
+ graphLoading: false,
758
+ error: null,
759
+ errorCode: null,
760
+ busy: null,
761
+ toast: null,
762
+ toastKind: 'ok',
763
+ lastOutput: null,
764
+ activeTab: 'changes',
765
+ selected: null,
766
+ diff: null,
767
+ diffLoading: false,
768
+ message: '',
769
+ aiHint: '',
770
+ aiModel: null,
771
+ aiFallback: false,
772
+ newBranchOpen: false,
773
+ newBranchName: '',
774
+ newBranchFrom: '',
775
+ })
776
+ var toastTimer = null
777
+ var loadToken = 0
778
+
779
+ function set(patch) { store.set(patch) }
780
+
781
+ function toast(kind, text, output) {
782
+ if (toastTimer) clearTimeout(toastTimer)
783
+ set({ toast: text, toastKind: kind, lastOutput: output || store.get().lastOutput })
784
+ toastTimer = setTimeout(function () {
785
+ toastTimer = null
786
+ set({ toast: null })
787
+ }, kind === 'error' ? 12000 : 6000)
788
+ }
789
+
790
+ function clearToast() {
791
+ if (toastTimer) clearTimeout(toastTimer)
792
+ toastTimer = null
793
+ set({ toast: null, error: null, errorCode: null })
794
+ }
795
+
796
+ async function loadSnapshot(cwd) {
797
+ var token = ++loadToken
798
+ // 首次加载才显示 loading;轮询刷新不闪「刷新中…」。
799
+ set({ loading: store.get().snapshot === null, error: null, errorCode: null })
800
+ try {
801
+ var data = await rpc('snapshot', { cwd: cwd })
802
+ if (token !== loadToken || cwd !== store.get().cwd) return
803
+ set({ snapshot: data, loading: false })
804
+ } catch (error) {
805
+ if (token !== loadToken || cwd !== store.get().cwd) return
806
+ set({ loading: false, error: error.message, errorCode: error.code || null })
807
+ }
808
+ }
809
+
810
+ async function loadGraph(cwd) {
811
+ if (!cwd) return
812
+ set({ graphLoading: true })
813
+ try {
814
+ var data = await rpc('graph', { cwd: cwd, limit: 100 })
815
+ if (cwd !== store.get().cwd) return
816
+ set({ graph: data, graphLoading: false })
817
+ } catch (error) {
818
+ if (cwd !== store.get().cwd) return
819
+ set({ graphLoading: false, graph: { lines: [] }, error: error.message })
820
+ }
821
+ }
822
+
823
+ async function loadDiff(target) {
824
+ var cwd = store.get().cwd
825
+ if (!cwd || !target) return
826
+ set({ diffLoading: true })
827
+ try {
828
+ var params = { cwd: cwd }
829
+ if (target.kind === 'commit') {
830
+ params.mode = 'commit'
831
+ params.hash = target.hash
832
+ } else {
833
+ params.mode = target.mode
834
+ params.path = target.path
835
+ }
836
+ var data = await rpc('diff', params)
837
+ var current = store.get().selected
838
+ if (!current || current.kind !== target.kind || current.path !== target.path || current.hash !== target.hash) return
839
+ set({ diff: data, diffLoading: false })
840
+ } catch (error) {
841
+ set({ diffLoading: false, diff: null, error: error.message, errorCode: error.code || null })
842
+ }
843
+ }
844
+
845
+ async function refresh() {
846
+ var cwd = store.get().cwd
847
+ if (!cwd) return
848
+ await Promise.all([loadSnapshot(cwd), loadGraph(cwd)])
849
+ }
850
+
851
+ async function refreshSoft() {
852
+ var cwd = store.get().cwd
853
+ if (!cwd || store.get().busy) return
854
+ await loadSnapshot(cwd)
855
+ }
856
+
857
+ function attach(cwd) {
858
+ if (cwd === store.get().cwd) {
859
+ if (cwd && !store.get().snapshot && !store.get().loading) void refresh()
860
+ return
861
+ }
862
+ set({
863
+ cwd: cwd,
864
+ snapshot: null,
865
+ graph: null,
866
+ loading: !!cwd,
867
+ error: null,
868
+ errorCode: null,
869
+ selected: null,
870
+ diff: null,
871
+ busy: null,
872
+ toast: null,
873
+ lastOutput: null,
874
+ message: '',
875
+ aiHint: '',
876
+ aiModel: null,
877
+ aiFallback: false,
878
+ newBranchOpen: false,
879
+ newBranchName: '',
880
+ newBranchFrom: '',
881
+ })
882
+ if (cwd) void refresh()
883
+ }
884
+
885
+ function setMessage(value) { set({ message: value }) }
886
+ function setAiHint(value) { set({ aiHint: value }) }
887
+ function setTab(tab) { set({ activeTab: tab }) }
888
+
889
+ function setNewBranch(open) {
890
+ set({ newBranchOpen: open, newBranchName: '', newBranchFrom: '' })
891
+ }
892
+
893
+ function setNewBranchName(value) { set({ newBranchName: value }) }
894
+ function setNewBranchFrom(value) { set({ newBranchFrom: value }) }
895
+
896
+ async function runBusy(label, operation) {
897
+ if (store.get().busy) return null
898
+ set({ busy: label, error: null, errorCode: null })
899
+ try {
900
+ var result = await operation()
901
+ set({ busy: null })
902
+ return result
903
+ } catch (error) {
904
+ set({ busy: null, error: error.message, errorCode: error.code || null })
905
+ toast('error', error.message)
906
+ return null
907
+ }
908
+ }
909
+
910
+ async function selectFile(group, entry) {
911
+ var cwd = store.get().cwd
912
+ if (!cwd) return
913
+ var mode = group === 'staged' ? 'staged' : group === 'untracked' ? 'untracked' : 'worktree'
914
+ var target = { kind: 'file', group: group, path: entry.path, mode: mode }
915
+ set({ activeTab: 'diff', selected: target, diff: null })
916
+ await loadDiff(target)
917
+ }
918
+
919
+ async function selectCommit(line) {
920
+ if (!line.full) return
921
+ var target = { kind: 'commit', hash: line.full, path: '', mode: 'commit' }
922
+ set({ activeTab: 'diff', selected: target, diff: null })
923
+ await loadDiff(target)
924
+ }
925
+
926
+ async function stage(group, entry) {
927
+ var cwd = store.get().cwd
928
+ if (!cwd) return
929
+ var result = await runBusy('stage', function () { return rpc('stage', { cwd: cwd, paths: [entry.path] }) })
930
+ if (result !== null) {
931
+ toast('ok', tr('status.stageOk'), result.output)
932
+ await refresh()
933
+ }
934
+ }
935
+
936
+ async function unstage(group, entry) {
937
+ var cwd = store.get().cwd
938
+ if (!cwd) return
939
+ var result = await runBusy('unstage', function () { return rpc('unstage', { cwd: cwd, paths: [entry.path] }) })
940
+ if (result !== null) {
941
+ toast('ok', tr('status.unstageOk'), result.output)
942
+ await refresh()
943
+ }
944
+ }
945
+
946
+ async function stageAll() {
947
+ var cwd = store.get().cwd
948
+ if (!cwd) return
949
+ var result = await runBusy('stage', function () { return rpc('stage', { cwd: cwd, all: true }) })
950
+ if (result !== null) {
951
+ toast('ok', tr('status.stageAllOk'), result.output)
952
+ await refresh()
953
+ }
954
+ }
955
+
956
+ async function unstageAll() {
957
+ var cwd = store.get().cwd
958
+ if (!cwd) return
959
+ var result = await runBusy('unstage', function () { return rpc('unstage', { cwd: cwd, all: true }) })
960
+ if (result !== null) {
961
+ toast('ok', tr('status.unstageAllOk'), result.output)
962
+ await refresh()
963
+ }
964
+ }
965
+
966
+ async function switchBranch(name) {
967
+ var cwd = store.get().cwd
968
+ if (!cwd || !name) return
969
+ var result = await runBusy('switch', function () { return rpc('switchBranch', { cwd: cwd, name: name }) })
970
+ if (result !== null) {
971
+ toast('ok', tr('status.branchSwitched', { branch: result.branch || name }), result.output)
972
+ set({ selected: null, diff: null, activeTab: 'changes' })
973
+ await refresh()
974
+ }
975
+ }
976
+
977
+ async function createBranch() {
978
+ var state = store.get()
979
+ var cwd = state.cwd
980
+ var name = String(state.newBranchName || '').trim()
981
+ if (!cwd || name === '') return
982
+ var params = { cwd: cwd, name: name, create: true }
983
+ var from = String(state.newBranchFrom || '').trim()
984
+ if (from !== '') params.from = from
985
+ var result = await runBusy('switch', function () { return rpc('switchBranch', params) })
986
+ if (result !== null) {
987
+ set({ newBranchOpen: false, newBranchName: '', newBranchFrom: '' })
988
+ toast('ok', tr('status.branchCreated', { branch: result.branch || name }), result.output)
989
+ await refresh()
990
+ }
991
+ }
992
+
993
+ async function deleteBranch(name) {
994
+ var cwd = store.get().cwd
995
+ if (!cwd || !name) return
996
+ if (typeof window !== 'undefined' && typeof window.confirm === 'function') {
997
+ if (!window.confirm(tr('status.deleteConfirm', { branch: name }))) return
998
+ }
999
+ var result = await runBusy('delete', function () { return rpc('deleteBranch', { cwd: cwd, name: name }) })
1000
+ if (result !== null) {
1001
+ toast('ok', tr('status.branchDeleted', { branch: name }), result.output)
1002
+ await refresh()
1003
+ }
1004
+ }
1005
+
1006
+ async function generateMessage() {
1007
+ var state = store.get()
1008
+ var cwd = state.cwd
1009
+ if (!cwd) return
1010
+ var result = await runBusy('ai', function () {
1011
+ return rpc('aiCommit', { cwd: cwd, hint: String(state.aiHint || '') })
1012
+ })
1013
+ if (result === null) return
1014
+ set({ message: result.message, aiModel: result.model || null, aiFallback: !!result.fallback })
1015
+ toast('ok', result.fallback ? tr('status.aiFallback') : tr('status.aiOk', { model: result.model || '' }))
1016
+ }
1017
+
1018
+ async function commit(andPush) {
1019
+ var state = store.get()
1020
+ var cwd = state.cwd
1021
+ var message = String(state.message || '').trim()
1022
+ if (!cwd) return
1023
+ if (message === '') {
1024
+ toast('error', tr('commit.messageRequired'))
1025
+ return
1026
+ }
1027
+ var result = await runBusy('commit', function () { return rpc('commit', { cwd: cwd, message: message }) })
1028
+ if (result === null) return
1029
+ set({ message: '', aiModel: null, aiFallback: false })
1030
+ toast('ok', tr('status.commitOk', { hash: result.hash || '' }), result.output)
1031
+ await refresh()
1032
+ if (andPush) await push()
1033
+ }
1034
+
1035
+ async function fetchRemote() {
1036
+ var cwd = store.get().cwd
1037
+ if (!cwd) return
1038
+ var result = await runBusy('fetch', function () { return rpc('fetch', { cwd: cwd }) })
1039
+ if (result !== null) {
1040
+ toast('ok', tr('status.fetchOk'), result.output)
1041
+ await refresh()
1042
+ }
1043
+ }
1044
+
1045
+ async function pull() {
1046
+ var cwd = store.get().cwd
1047
+ if (!cwd) return
1048
+ var result = await runBusy('pull', function () { return rpc('pull', { cwd: cwd }) })
1049
+ if (result !== null) {
1050
+ toast('ok', tr('status.pullOk'), result.output)
1051
+ await refresh()
1052
+ }
1053
+ }
1054
+
1055
+ async function push() {
1056
+ var cwd = store.get().cwd
1057
+ if (!cwd) return
1058
+ var result = await runBusy('push', function () { return rpc('push', { cwd: cwd }) })
1059
+ if (result !== null) {
1060
+ toast('ok', tr('status.pushOk'), result.output)
1061
+ await refresh()
1062
+ }
1063
+ }
1064
+
1065
+ return {
1066
+ store: store,
1067
+ attach: attach,
1068
+ refresh: refresh,
1069
+ refreshSoft: refreshSoft,
1070
+ selectFile: selectFile,
1071
+ selectCommit: selectCommit,
1072
+ stage: stage,
1073
+ unstage: unstage,
1074
+ stageAll: stageAll,
1075
+ unstageAll: unstageAll,
1076
+ switchBranch: switchBranch,
1077
+ createBranch: createBranch,
1078
+ deleteBranch: deleteBranch,
1079
+ generateMessage: generateMessage,
1080
+ commit: commit,
1081
+ fetch: fetchRemote,
1082
+ pull: pull,
1083
+ push: push,
1084
+ setMessage: setMessage,
1085
+ setAiHint: setAiHint,
1086
+ setTab: setTab,
1087
+ setNewBranch: setNewBranch,
1088
+ setNewBranchName: setNewBranchName,
1089
+ setNewBranchFrom: setNewBranchFrom,
1090
+ clearToast: clearToast,
1091
+ }
1092
+ }
1093
+
1094
+ // -------------------------------------------------------------- components
1095
+
1096
+ var BUSY_KEYS = {
1097
+ stage: 'changes.stage',
1098
+ unstage: 'changes.unstage',
1099
+ commit: 'commit.commit',
1100
+ fetch: 'header.fetch',
1101
+ pull: 'header.pull',
1102
+ push: 'header.push',
1103
+ switch: 'branch.switch',
1104
+ delete: 'branch.delete',
1105
+ ai: 'commit.ai',
1106
+ }
1107
+
1108
+ function busyLabel(kind) {
1109
+ var key = BUSY_KEYS[kind]
1110
+ return key ? tr(key) : String(kind || '')
1111
+ }
1112
+
1113
+ function EmptyState(props) {
1114
+ return h('div', { className: 'gm-empty' },
1115
+ props.spinner
1116
+ ? h(Spinner)
1117
+ : props.icon
1118
+ ? h(Icon, { name: props.icon, size: props.iconSize || 22 })
1119
+ : h('div', { className: 'gm-empty-bubble' },
1120
+ h('img', { className: 'gm-empty-mascot', src: MASCOT_IMG, alt: '', draggable: false })),
1121
+ props.title ? h('div', { className: 'gm-empty-title' }, props.title) : null,
1122
+ props.hint ? h('div', { className: 'gm-empty-hint' }, props.hint) : null)
1123
+ }
1124
+
1125
+ function ChangesSection(props) {
1126
+ var entries = props.entries || []
1127
+ if (entries.length === 0) return null
1128
+ return h('div', null,
1129
+ h('div', { className: 'gm-section' },
1130
+ h('span', null, props.title),
1131
+ h('span', { className: 'gm-section-count' }, String(entries.length))),
1132
+ entries.map(function (entry) {
1133
+ var selected = props.selected && props.selected.kind === 'file'
1134
+ && props.selected.group === props.group && props.selected.path === entry.path
1135
+ var parts = pathParts(entry.path)
1136
+ var staged = props.group === 'staged'
1137
+ return h('div', {
1138
+ key: props.group + ':' + entry.path,
1139
+ className: 'gm-file' + (selected ? ' active' : ''),
1140
+ title: entry.oldPath ? entry.oldPath + ' → ' + entry.path : entry.path,
1141
+ onClick: function () { props.onSelect(entry) },
1142
+ },
1143
+ h('span', { className: statusClass(props.group, entry) }, statusLetter(props.group, entry)),
1144
+ h('span', { className: 'gm-fpath' },
1145
+ parts.dir ? h('span', { className: 'gm-dir' }, parts.dir) : null,
1146
+ parts.base),
1147
+ h('button', {
1148
+ type: 'button',
1149
+ className: 'gm-btn icon sm ghost gm-act',
1150
+ title: staged ? tr('changes.unstage') : tr('changes.stage'),
1151
+ onClick: function (event) {
1152
+ event.stopPropagation()
1153
+ if (staged) props.onUnstage(entry)
1154
+ else props.onStage(entry)
1155
+ },
1156
+ }, h(Icon, { name: staged ? 'minus' : 'plus', size: 12 })))
1157
+ }))
1158
+ }
1159
+
1160
+ function ChangesPane(props) {
1161
+ var state = props.state
1162
+ var status = state.snapshot && state.snapshot.status ? state.snapshot.status : null
1163
+ var staged = status ? status.staged : []
1164
+ var unstaged = status ? status.unstaged : []
1165
+ var untracked = status ? status.untracked : []
1166
+ var conflicted = status ? status.conflicted : []
1167
+ var busy = state.busy
1168
+ function section(title, group, entries, tone) {
1169
+ return h(ChangesSection, {
1170
+ title: title,
1171
+ group: group,
1172
+ entries: entries,
1173
+ tone: tone,
1174
+ selected: state.selected,
1175
+ busy: busy,
1176
+ onSelect: function (entry) { props.actions.selectFile(group, entry) },
1177
+ onStage: function (entry) { props.actions.stage(group, entry) },
1178
+ onUnstage: function (entry) { props.actions.unstage(group, entry) },
1179
+ })
1180
+ }
1181
+ var empty = !conflicted.length && !staged.length && !unstaged.length && !untracked.length
1182
+ return h('div', { className: 'gm-scroll' },
1183
+ h('div', { className: 'gm-content-inner' },
1184
+ h('div', { className: 'gm-quick' },
1185
+ h(Btn, { label: tr('changes.stageAll'), icon: 'plus', disabled: !!busy || (unstaged.length + untracked.length === 0), onClick: props.actions.stageAll }),
1186
+ h(Btn, { label: tr('changes.unstageAll'), icon: 'minus', disabled: !!busy || staged.length === 0, onClick: props.actions.unstageAll })),
1187
+ conflicted.length > 0
1188
+ ? h('div', { className: 'gm-conflict-bar' }, tr('changes.conflictBanner', { n: conflicted.length }))
1189
+ : null,
1190
+ section(tr('changes.conflicted'), 'conflicted', conflicted, 'error'),
1191
+ section(tr('changes.staged'), 'staged', staged),
1192
+ section(tr('changes.unstaged'), 'unstaged', unstaged),
1193
+ section(tr('changes.untracked'), 'untracked', untracked),
1194
+ empty
1195
+ ? h(EmptyState, { title: tr('changes.empty'), hint: tr('changes.emptyHint') })
1196
+ : null))
1197
+ }
1198
+
1199
+ function CommitFooter(props) {
1200
+ var state = props.state
1201
+ var stagedCount = state.snapshot && state.snapshot.status ? state.snapshot.status.staged.length : 0
1202
+ var busy = state.busy
1203
+ var canCommit = !busy && String(state.message || '').trim() !== '' && stagedCount > 0
1204
+ function onKeyDown(event) {
1205
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
1206
+ event.preventDefault()
1207
+ if (canCommit) props.onCommit(false)
1208
+ }
1209
+ }
1210
+ return h('div', { className: 'gm-commit' },
1211
+ h('div', { className: 'gm-commit-head' },
1212
+ h(WhaleGlyph, { size: 22 }),
1213
+ h('span', { className: 'gm-commit-title' }, tr('commit.title')),
1214
+ h('span', { className: 'gm-spacer' }),
1215
+ stagedCount > 0 ? h('span', { className: 'gm-badge brand' }, tr('changes.stagedCount', { n: stagedCount })) : null),
1216
+ h('textarea', {
1217
+ className: 'gm-textarea',
1218
+ rows: 2,
1219
+ value: state.message,
1220
+ placeholder: tr('commit.placeholder'),
1221
+ onChange: function (event) { props.onMessage(event.target.value) },
1222
+ onKeyDown: onKeyDown,
1223
+ }),
1224
+ h('div', { className: 'gm-actions' },
1225
+ h('input', {
1226
+ className: 'gm-input',
1227
+ value: state.aiHint,
1228
+ placeholder: tr('commit.hintPlaceholder'),
1229
+ onChange: function (event) { props.onHint(event.target.value) },
1230
+ }),
1231
+ h(Btn, {
1232
+ label: busy === 'ai' ? tr('commit.aiBusy') : tr('commit.aiShort'),
1233
+ icon: 'ai',
1234
+ title: tr('commit.ai'),
1235
+ variant: 'primary',
1236
+ className: busy === 'ai' ? 'gm-ai-busy' : undefined,
1237
+ disabled: !!busy || !state.cwd,
1238
+ onClick: props.onGenerate,
1239
+ })),
1240
+ h('div', { className: 'gm-actions' },
1241
+ state.aiModel
1242
+ ? h('span', { className: 'gm-ai-line gm-hint', title: tr('status.aiModel', { model: state.aiModel }) },
1243
+ state.aiFallback ? tr('status.aiFallback') : tr('status.aiModelEdit', { model: state.aiModel }))
1244
+ : null,
1245
+ h('span', { className: 'gm-spacer' }),
1246
+ h('span', { className: 'gm-hint' }, tr('commit.cmdEnter')),
1247
+ h(Btn, {
1248
+ label: busy === 'commit' ? tr('commit.committing') : tr('commit.commit'),
1249
+ variant: 'primary',
1250
+ disabled: !canCommit,
1251
+ onClick: function () { props.onCommit(false) },
1252
+ }),
1253
+ h(Btn, {
1254
+ label: tr('commit.push'),
1255
+ disabled: !canCommit,
1256
+ onClick: function () { props.onCommit(true) },
1257
+ })))
1258
+ }
1259
+
1260
+ function BranchSelect(props) {
1261
+ var branches = props.branches || []
1262
+ var repo = props.repo || {}
1263
+ var value = repo.branch || ''
1264
+ var locals = branches.filter(function (branch) { return branch.kind === 'local' })
1265
+ var remotes = branches.filter(function (branch) { return branch.kind === 'remote' })
1266
+ return h('select', {
1267
+ className: 'gm-select',
1268
+ value: value,
1269
+ title: tr('branch.selectTitle'),
1270
+ disabled: props.disabled,
1271
+ onChange: function (event) { if (event.target.value && event.target.value !== value) props.onSwitch(event.target.value) },
1272
+ },
1273
+ repo.detached ? h('option', { value: '' }, tr('header.detached')) : null,
1274
+ h('optgroup', { label: tr('branch.local') }, locals.map(function (branch) {
1275
+ return h('option', { key: 'l:' + branch.name, value: branch.name },
1276
+ branch.name + (branch.ahead ? ' ↑' + branch.ahead : '') + (branch.behind ? ' ↓' + branch.behind : ''))
1277
+ })),
1278
+ remotes.length > 0
1279
+ ? h('optgroup', { label: tr('branch.remote') }, remotes.map(function (branch) {
1280
+ return h('option', { key: 'r:' + branch.name, value: branch.name }, branch.name)
1281
+ }))
1282
+ : null)
1283
+ }
1284
+
1285
+ function CompactTopBar(props) {
1286
+ var state = props.state
1287
+ var repo = state.snapshot && state.snapshot.repo ? state.snapshot.repo : null
1288
+ var busy = state.busy
1289
+ if (!repo || !repo.isRepo) {
1290
+ return h('div', { className: 'gm-top' },
1291
+ h('div', { className: 'gm-row' },
1292
+ h(WhaleGlyph, { size: 28 }),
1293
+ h('span', { className: 'gm-repo' }, tr('panel.title'))))
1294
+ }
1295
+ return h('div', { className: 'gm-top' },
1296
+ h('div', { className: 'gm-row' },
1297
+ h(WhaleGlyph, { size: 28 }),
1298
+ h('span', { className: 'gm-repo', title: repo.root }, repo.name || tr('panel.title')),
1299
+ h('span', { className: 'gm-repo-dot' }),
1300
+ repo.ahead ? h('span', { className: 'gm-badge' }, '↑' + repo.ahead) : null,
1301
+ repo.behind ? h('span', { className: 'gm-badge' }, '↓' + repo.behind) : null,
1302
+ h('span', { className: 'gm-spacer' }),
1303
+ h(BranchSelect, { branches: state.snapshot.branches, repo: repo, disabled: !!busy, onSwitch: props.onSwitch })),
1304
+ h('div', { className: 'gm-row' },
1305
+ h(Btn, { icon: 'fetch', label: 'Fetch', title: tr('header.fetch'), disabled: !!busy, onClick: props.onFetch }),
1306
+ h(Btn, { icon: 'pull', label: 'Pull', title: tr('header.pull'), disabled: !!busy, onClick: props.onPull }),
1307
+ h(Btn, { icon: 'push', label: 'Push', title: tr('header.push'), variant: 'primary', disabled: !!busy, onClick: props.onPush }),
1308
+ h('span', { className: 'gm-spacer' }),
1309
+ h(Btn, { icon: 'refresh', title: state.loading ? tr('header.refreshing') : tr('header.refresh'), disabled: !!busy, onClick: props.onRefresh }),
1310
+ h(Btn, { icon: 'plus', title: tr('header.newBranch'), disabled: !!busy, onClick: function () { props.onNewBranch(true) } })),
1311
+ state.newBranchOpen
1312
+ ? h('div', { className: 'gm-row' },
1313
+ h('input', {
1314
+ className: 'gm-input',
1315
+ value: state.newBranchName,
1316
+ placeholder: tr('header.newBranchPlaceholder'),
1317
+ onChange: function (event) { props.onNewBranchName(event.target.value) },
1318
+ onKeyDown: function (event) {
1319
+ if (event.key === 'Enter') props.onCreateBranch()
1320
+ if (event.key === 'Escape') props.onNewBranch(false)
1321
+ },
1322
+ }),
1323
+ h('input', {
1324
+ className: 'gm-input',
1325
+ style: { flex: '0 1 96px' },
1326
+ value: state.newBranchFrom,
1327
+ placeholder: tr('header.fromPlaceholder'),
1328
+ onChange: function (event) { props.onNewBranchFrom(event.target.value) },
1329
+ onKeyDown: function (event) {
1330
+ if (event.key === 'Enter') props.onCreateBranch()
1331
+ if (event.key === 'Escape') props.onNewBranch(false)
1332
+ },
1333
+ }),
1334
+ h(Btn, { label: tr('header.create'), variant: 'primary', disabled: !!busy || !state.newBranchName.trim(), onClick: props.onCreateBranch }),
1335
+ h(Btn, { label: tr('header.cancel'), variant: 'ghost', onClick: function () { props.onNewBranch(false) } }))
1336
+ : null)
1337
+ }
1338
+
1339
+ function GraphView(props) {
1340
+ var state = props.state
1341
+ var lines = state.graph && state.graph.lines ? state.graph.lines : []
1342
+ if (state.graphLoading && lines.length === 0) return h(EmptyState, { spinner: true, title: tr('status.loading') })
1343
+ if (lines.length === 0) return h(EmptyState, { title: tr('graph.empty') })
1344
+ // A stable graph column keeps hash/refs/subject aligned across rows. Histories with
1345
+ // dozens of live lanes would otherwise push the text miles to the right, so the
1346
+ // column is capped and the overflowing lanes fade out instead.
1347
+ var GRAPH_MAX_COLS = 10
1348
+ var maxPrefix = 2
1349
+ for (var gi = 0; gi < lines.length; gi += 1) {
1350
+ if ((lines[gi].graph || '').length > maxPrefix) maxPrefix = (lines[gi].graph || '').length
1351
+ }
1352
+ var graphCols = Math.min(maxPrefix, GRAPH_MAX_COLS)
1353
+ return h('div', { className: 'gm-scroll' },
1354
+ h('div', { className: 'gm-content-inner gm-graph' },
1355
+ lines.map(function (line, index) {
1356
+ if (!line.full) {
1357
+ return h('div', { key: 'g:' + index, className: 'gm-grow' },
1358
+ h(GraphLane, {
1359
+ graph: line.graph || '',
1360
+ prev: index > 0 ? (lines[index - 1].graph || '') : '',
1361
+ next: index + 1 < lines.length ? (lines[index + 1].graph || '') : '',
1362
+ cols: graphCols,
1363
+ }))
1364
+ }
1365
+ var selected = state.selected && state.selected.kind === 'commit' && state.selected.hash === line.full
1366
+ return h('div', {
1367
+ key: 'c:' + line.full + ':' + index,
1368
+ className: 'gm-grow' + (selected ? ' active' : ''),
1369
+ title: line.full,
1370
+ onClick: function () { props.onSelectCommit(line) },
1371
+ },
1372
+ h(GraphLane, {
1373
+ graph: line.graph || '',
1374
+ prev: index > 0 ? (lines[index - 1].graph || '') : '',
1375
+ next: index + 1 < lines.length ? (lines[index + 1].graph || '') : '',
1376
+ cols: graphCols,
1377
+ }),
1378
+ h('span', { className: 'gm-hash' }, line.hash),
1379
+ line.refs && line.refs.length > 0
1380
+ ? h('span', { className: 'gm-row', style: { gap: '4px', flex: 'none' } }, line.refs.map(function (ref, refIndex) {
1381
+ return h('span', {
1382
+ key: 'r:' + refIndex,
1383
+ className: 'gm-badge gm-ref' + (ref.indexOf('HEAD') === 0 ? ' brand' : ''),
1384
+ }, ref)
1385
+ }))
1386
+ : null,
1387
+ h('span', { className: 'gm-subject' }, line.subject),
1388
+ h('span', { className: 'gm-meta' }, line.author + ' · ' + line.date))
1389
+ })))
1390
+ }
1391
+
1392
+ var GRAPH_CELL = 8
1393
+ var GRAPH_ROW = 20
1394
+
1395
+ function laneChar(text, lane) {
1396
+ return lane >= 0 && lane < text.length ? text[lane] : ' '
1397
+ }
1398
+
1399
+ /** Does lane `lane` continue into this row from the row above? */
1400
+ function laneFromAbove(prev, lane) {
1401
+ var here = laneChar(prev, lane)
1402
+ if (here === '|' || here === '*') return true
1403
+ if (laneChar(prev, lane + 1) === '/') return true
1404
+ if (here === '\\') return true
1405
+ return false
1406
+ }
1407
+
1408
+ /** Does lane `lane` continue into the next row? */
1409
+ function laneToBelow(next, lane) {
1410
+ var here = laneChar(next, lane)
1411
+ if (here === '|' || here === '*') return true
1412
+ if (here === '/') return true
1413
+ if (laneChar(next, lane + 1) === '\\') return true
1414
+ return false
1415
+ }
1416
+
1417
+ /**
1418
+ * Draws one row of the commit graph as vector lanes, so vertical lines stay
1419
+ * continuous across rows (ASCII glyphs leave gaps at 20px row heights).
1420
+ */
1421
+ function GraphLane(props) {
1422
+ var graph = props.graph || ''
1423
+ var prev = props.prev || ''
1424
+ var next = props.next || ''
1425
+ var cols = props.cols
1426
+ var cell = GRAPH_CELL
1427
+ var row = GRAPH_ROW
1428
+ var parts = []
1429
+ for (var i = 0; i < cols; i += 1) {
1430
+ var ch = laneChar(graph, i)
1431
+ var cx = (i + 0.5) * cell
1432
+ if (ch === '|') {
1433
+ parts.push(h('line', { key: 'v' + i, x1: cx, y1: 0, x2: cx, y2: row }))
1434
+ } else if (ch === '/') {
1435
+ parts.push(h('line', { key: 's' + i, x1: cx, y1: 0, x2: cx - cell, y2: row }))
1436
+ } else if (ch === '\\') {
1437
+ parts.push(h('line', { key: 'b' + i, x1: cx - cell, y1: 0, x2: cx, y2: row }))
1438
+ } else if (ch === '_') {
1439
+ parts.push(h('line', { key: 'h' + i, x1: cx, y1: row - 1, x2: cx + cell, y2: row - 1 }))
1440
+ } else if (ch === '*') {
1441
+ if (laneFromAbove(prev, i)) parts.push(h('line', { key: 'u' + i, x1: cx, y1: 0, x2: cx, y2: row / 2 }))
1442
+ if (laneToBelow(next, i)) parts.push(h('line', { key: 'd' + i, x1: cx, y1: row / 2, x2: cx, y2: row }))
1443
+ parts.push(h('circle', { key: 'c' + i, cx: cx, cy: row / 2, r: 3.2 }))
1444
+ }
1445
+ }
1446
+ return h('svg', {
1447
+ className: 'gm-glane', width: cols * cell, height: row,
1448
+ viewBox: '0 0 ' + (cols * cell) + ' ' + row, 'aria-hidden': 'true',
1449
+ }, parts)
1450
+ }
1451
+
1452
+ function DiffView(props) {
1453
+ var state = props.state
1454
+ var diff = state.diff
1455
+ if (state.diffLoading) return h(EmptyState, { spinner: true, title: tr('status.loading') })
1456
+ if (!diff) return h(EmptyState, { title: tr('diff.empty') })
1457
+ var title = diff.mode === 'commit'
1458
+ ? tr('diff.commit', { hash: state.selected && state.selected.hash ? state.selected.hash.slice(0, 8) : '' })
1459
+ : diff.mode === 'staged' ? tr('diff.staged') : diff.mode === 'untracked' ? tr('diff.untracked') : tr('diff.worktree')
1460
+ return h('div', { className: 'gm-scroll' },
1461
+ h('div', { className: 'gm-content-inner gm-diff' },
1462
+ h('div', { className: 'gm-diff-head' },
1463
+ h('span', { className: 'gm-diff-title' }, title),
1464
+ h('span', { className: 'gm-fpath', title: diff.path || '' }, diff.path || ''),
1465
+ h('span', { className: 'gm-spacer' }),
1466
+ h('span', { className: 'gm-badge ok' }, '+' + diff.additions),
1467
+ h('span', { className: 'gm-badge err' }, '-' + diff.deletions),
1468
+ diff.truncated ? h('span', { className: 'gm-badge warn' }, tr('diff.truncatedShort')) : null),
1469
+ diff.binary
1470
+ ? h(EmptyState, { title: tr('diff.binary') })
1471
+ : diff.hunks.length === 0
1472
+ ? h(EmptyState, { title: tr('diff.noText') })
1473
+ : diff.hunks.map(function (hunk, hunkIndex) {
1474
+ return h('div', { key: 'h:' + hunkIndex },
1475
+ h('div', { className: 'gm-hunk' }, hunk.header),
1476
+ h('div', { className: 'gm-dlines' }, hunk.lines.map(function (line, lineIndex) {
1477
+ var kind = line.kind === 'add' ? 'add' : line.kind === 'del' ? 'del' : ''
1478
+ var sign = line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : line.kind === 'meta' ? '\\' : ' '
1479
+ return h('div', { key: 'l:' + hunkIndex + ':' + lineIndex, className: 'gm-dline ' + kind },
1480
+ h('span', { className: 'gm-ln' }, line.old === undefined ? '' : String(line.old)),
1481
+ h('span', { className: 'gm-ln' }, line.new === undefined ? '' : String(line.new)),
1482
+ h('span', { className: 'gm-sign' }, sign),
1483
+ h('span', { className: 'gm-dtext' }, line.text))
1484
+ })))
1485
+ })))
1486
+ }
1487
+
1488
+ function BranchesView(props) {
1489
+ var state = props.state
1490
+ var branches = state.snapshot && state.snapshot.branches ? state.snapshot.branches : []
1491
+ var locals = branches.filter(function (branch) { return branch.kind === 'local' })
1492
+ var remotes = branches.filter(function (branch) { return branch.kind === 'remote' })
1493
+ function group(title, items, allowDelete) {
1494
+ if (items.length === 0) return null
1495
+ return h('div', null,
1496
+ h('div', { className: 'gm-section' },
1497
+ h('span', null, title),
1498
+ h('span', { className: 'gm-section-count' }, String(items.length))),
1499
+ items.map(function (branch) {
1500
+ return h('div', { key: branch.kind + ':' + branch.name, className: 'gm-branch' },
1501
+ h(Icon, { name: 'branch', size: 12 }),
1502
+ h('span', { className: 'gm-bname', title: branch.name }, branch.name),
1503
+ branch.current ? h('span', { className: 'gm-badge brand' }, tr('branch.current')) : null,
1504
+ branch.upstream ? h('span', { className: 'gm-badge' }, branch.upstream) : null,
1505
+ branch.ahead ? h('span', { className: 'gm-badge' }, '↑' + branch.ahead) : null,
1506
+ branch.behind ? h('span', { className: 'gm-badge' }, '↓' + branch.behind) : null,
1507
+ branch.gone ? h('span', { className: 'gm-badge warn' }, tr('branch.gone')) : null,
1508
+ h('span', { className: 'gm-bmeta' }, branch.subject || ''),
1509
+ h('span', { className: 'gm-row gm-act', style: { gap: '4px', flex: 'none' } },
1510
+ !branch.current
1511
+ ? h(Btn, { label: tr('branch.switch'), small: true, disabled: !!state.busy, onClick: function () { props.onSwitch(branch.name) } })
1512
+ : null,
1513
+ !branch.current && allowDelete
1514
+ ? h(Btn, { label: tr('branch.delete'), small: true, ghost: true, disabled: !!state.busy, onClick: function () { props.onDelete(branch.name) } })
1515
+ : null))
1516
+ }))
1517
+ }
1518
+ return h('div', { className: 'gm-scroll' },
1519
+ h('div', { className: 'gm-content-inner' },
1520
+ group(tr('branch.local'), locals, true),
1521
+ group(tr('branch.remote'), remotes, false),
1522
+ locals.length === 0 && remotes.length === 0
1523
+ ? h(EmptyState, { title: tr('branch.empty') })
1524
+ : null))
1525
+ }
1526
+
1527
+ function StatusBar(props) {
1528
+ var state = props.state
1529
+ var dotClass = 'gm-dot'
1530
+ var message = tr('status.ready')
1531
+ var messageClass = 'gm-msg'
1532
+ var dismiss = null
1533
+ if (state.toast) {
1534
+ var isError = state.toastKind === 'error'
1535
+ dotClass += isError ? ' err' : ' ok'
1536
+ messageClass += isError ? ' err' : ' ok'
1537
+ message = state.toast
1538
+ dismiss = props.onDismiss
1539
+ } else if (state.error) {
1540
+ dotClass += ' err'
1541
+ messageClass += ' err'
1542
+ message = tr('status.error', { message: state.error })
1543
+ dismiss = props.onDismiss
1544
+ } else if (state.busy) {
1545
+ dotClass += ' busy'
1546
+ message = tr('status.busy', { label: busyLabel(state.busy) })
1547
+ } else if (state.lastOutput) {
1548
+ return h('details', { className: 'gm-details' },
1549
+ h('summary', { className: 'gm-status' },
1550
+ h('span', { className: 'gm-dot ok' }),
1551
+ h('span', { className: 'gm-msg' }, tr('details.output'))),
1552
+ h('pre', { className: 'gm-pre' }, state.lastOutput))
1553
+ }
1554
+ return h('div', { className: 'gm-status' },
1555
+ h('span', { className: dotClass }),
1556
+ h('span', { className: messageClass, title: message }, message),
1557
+ dismiss
1558
+ ? h('button', {
1559
+ type: 'button',
1560
+ className: 'gm-btn icon sm ghost',
1561
+ title: tr('details.dismiss'),
1562
+ onClick: dismiss,
1563
+ }, h(Icon, { name: 'close', size: 12 }))
1564
+ : null)
1565
+ }
1566
+
1567
+ function TabbedPanel(props) {
1568
+ var state = props.state
1569
+ var repo = state.snapshot && state.snapshot.repo ? state.snapshot.repo : null
1570
+ var status = state.snapshot && state.snapshot.status ? state.snapshot.status : null
1571
+ var changeCount = status
1572
+ ? status.staged.length + status.unstaged.length + status.untracked.length + status.conflicted.length
1573
+ : 0
1574
+ var tabs = [
1575
+ { id: 'changes', label: tr('tabs.changes'), count: changeCount },
1576
+ { id: 'graph', label: tr('tabs.graph'), count: 0 },
1577
+ { id: 'branches', label: tr('tabs.branches'), count: 0 },
1578
+ { id: 'diff', label: tr('tabs.diff'), count: 0 },
1579
+ ]
1580
+ var content
1581
+ if (state.activeTab === 'changes') content = h(ChangesPane, { state: state, actions: props.actions })
1582
+ else if (state.activeTab === 'graph') content = h(GraphView, { state: state, onSelectCommit: props.actions.selectCommit })
1583
+ else if (state.activeTab === 'branches') content = h(BranchesView, { state: state, onSwitch: props.actions.switchBranch, onDelete: props.actions.deleteBranch })
1584
+ else content = h(DiffView, { state: state })
1585
+ return h('div', { className: 'gm-root' },
1586
+ panelStyle(),
1587
+ h(CompactTopBar, {
1588
+ state: state,
1589
+ onSwitch: props.actions.switchBranch,
1590
+ onRefresh: props.actions.refresh,
1591
+ onFetch: props.actions.fetch,
1592
+ onPull: props.actions.pull,
1593
+ onPush: props.actions.push,
1594
+ onNewBranch: props.actions.setNewBranch,
1595
+ onNewBranchName: props.actions.setNewBranchName,
1596
+ onNewBranchFrom: props.actions.setNewBranchFrom,
1597
+ onCreateBranch: props.actions.createBranch,
1598
+ }),
1599
+ h('div', { className: 'gm-tabs' },
1600
+ tabs.map(function (tab) {
1601
+ return h('button', {
1602
+ key: tab.id,
1603
+ type: 'button',
1604
+ className: 'gm-tab' + (state.activeTab === tab.id ? ' active' : ''),
1605
+ onClick: function () { props.actions.setTab(tab.id) },
1606
+ },
1607
+ h('span', null, tab.label),
1608
+ tab.count > 0 ? h('span', { className: 'gm-count' }, String(tab.count)) : null)
1609
+ })),
1610
+ content,
1611
+ h(StatusBar, { state: state, onDismiss: props.actions.clearToast }),
1612
+ repo && repo.isRepo
1613
+ ? h(CommitFooter, {
1614
+ state: state,
1615
+ onMessage: props.actions.setMessage,
1616
+ onHint: props.actions.setAiHint,
1617
+ onGenerate: props.actions.generateMessage,
1618
+ onCommit: function (andPush) { props.actions.commit(andPush) },
1619
+ })
1620
+ : null)
1621
+ }
1622
+
1623
+ function EmptyPanel(props) {
1624
+ return h('div', { className: 'gm-root' },
1625
+ panelStyle(),
1626
+ h('div', { className: 'gm-top' },
1627
+ h('div', { className: 'gm-row' },
1628
+ h(WhaleGlyph, { size: 28 }),
1629
+ h('span', { className: 'gm-repo' }, tr('panel.title')))),
1630
+ props.spinner
1631
+ ? h(EmptyState, { spinner: true, title: props.title, hint: props.hint })
1632
+ : h(EmptyState, { title: props.title, hint: props.hint, whaleSize: 86 }))
1633
+ }
1634
+
1635
+ function GitPanel(props) {
1636
+ var controller = props.controller
1637
+ var summary = props.sessionSummary
1638
+ var cwd = summary && summary.cwd ? summary.cwd : null
1639
+ var state = useStore(controller.store)
1640
+
1641
+ useEffect(function () {
1642
+ controller.attach(cwd)
1643
+ }, [cwd])
1644
+
1645
+ useEffect(function () {
1646
+ if (!cwd) return undefined
1647
+ var timer = setInterval(function () {
1648
+ if (typeof document !== 'undefined' && document.hidden) return
1649
+ void controller.refreshSoft()
1650
+ }, POLL_MS)
1651
+ return function () { clearInterval(timer) }
1652
+ }, [cwd])
1653
+
1654
+ useEffect(function () {
1655
+ if (typeof window === 'undefined') return undefined
1656
+ var onFocus = function () { void controller.refreshSoft() }
1657
+ window.addEventListener('focus', onFocus)
1658
+ return function () { window.removeEventListener('focus', onFocus) }
1659
+ }, [])
1660
+
1661
+ if (!summary) return h(EmptyPanel, { title: tr('status.noSession'), hint: tr('panel.guide') })
1662
+ if (!cwd) return h(EmptyPanel, { title: tr('status.noCwd') })
1663
+ var repo = state.snapshot && state.snapshot.repo ? state.snapshot.repo : null
1664
+ if (state.error && state.errorCode === 'cwd-unavailable') {
1665
+ return h(EmptyPanel, { title: tr('status.cwdUnavailable', { path: cwd }) })
1666
+ }
1667
+ if (state.error && (!state.snapshot || (repo && repo.isRepo === false))) {
1668
+ return h(EmptyPanel, { title: tr('status.error', { message: state.error }) })
1669
+ }
1670
+ if (state.snapshot && repo && repo.isRepo === false) {
1671
+ return h(EmptyPanel, { title: tr('status.notRepoTitle'), hint: cwd })
1672
+ }
1673
+ if (!state.snapshot && state.loading) return h(EmptyPanel, { spinner: true, title: tr('status.loading') })
1674
+ return h(TabbedPanel, { state: state, actions: controller })
1675
+ }
1676
+
1677
+ function GitIcon(props) {
1678
+ var size = props.size || 16
1679
+ var color = props.active ? '#2f49d6' : '#4d6bfe'
1680
+ return h('svg', {
1681
+ width: size, height: size, viewBox: '0 0 24 24', fill: 'none',
1682
+ stroke: color, strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
1683
+ 'aria-hidden': 'true', focusable: 'false',
1684
+ },
1685
+ h('line', { x1: 6, y1: 3, x2: 6, y2: 15 }),
1686
+ h('circle', { cx: 18, cy: 6, r: 3 }),
1687
+ h('circle', { cx: 6, cy: 18, r: 3 }),
1688
+ h('path', { d: 'M18 9a9 9 0 0 1-9 9' }))
1689
+ }
1690
+
1691
+ function makeBoundary(fallbackText) {
1692
+ function Boundary(props) {
1693
+ React.Component.call(this, props)
1694
+ this.state = { error: null }
1695
+ }
1696
+ Boundary.prototype = Object.create(React.Component.prototype)
1697
+ Boundary.prototype.constructor = Boundary
1698
+ Boundary.getDerivedStateFromError = function (error) { return { error: error } }
1699
+ Boundary.prototype.componentDidCatch = function (error) {
1700
+ if (typeof console !== 'undefined') console.error('[git-manager] panel crashed:', error)
1701
+ }
1702
+ Boundary.prototype.render = function () {
1703
+ if (this.state.error) {
1704
+ return h('div', { className: 'gm-root' }, panelStyle(),
1705
+ h(EmptyState, {
1706
+ title: fallbackText,
1707
+ hint: this.state.error && this.state.error.message ? this.state.error.message : String(this.state.error),
1708
+ }))
1709
+ }
1710
+ return this.props.children
1711
+ }
1712
+ return Boundary
1713
+ }
1714
+
1715
+ // ------------------------------------------------------------------ apply
1716
+
1717
+ exports.inject = ['slots', 'locale', 'sessions', 'sidebarRight', 'sidebarRightTabs']
1718
+
1719
+ exports.apply = function (ctx) {
1720
+ var controller = createController()
1721
+ var sessions = ctx.sessions
1722
+
1723
+ ctx.effect(function () {
1724
+ return ctx.locale.register(NS, { zh: ZH, en: EN })
1725
+ }, 'git-manager: dictionaries')
1726
+
1727
+ var t = ctx.locale.bind(NS)
1728
+ localeT = t
1729
+ var Boundary = makeBoundary(t('error.boundary'))
1730
+
1731
+ // Stage one: the tab type (chip title + the guide's entry box).
1732
+ ctx.effect(function () {
1733
+ return ctx.sidebarRightTabs.register({
1734
+ id: TAB_ID,
1735
+ kind: TAB_KIND,
1736
+ priority: 'extension',
1737
+ title: function () { return t('panel.title') },
1738
+ guide: [{
1739
+ order: 30,
1740
+ title: function () { return t('panel.title') },
1741
+ description: function () { return t('panel.guide') },
1742
+ icon: WhaleChanIcon,
1743
+ }],
1744
+ })
1745
+ }, 'git-manager: right sidebar tab type')
1746
+
1747
+ // Stage two: the tab body, keyed by the definition id.
1748
+ function GitTabBody(props) {
1749
+ var summary = useSessionById(sessions, props.sessionId)
1750
+ return h(Boundary, null, h(GitPanel, { controller: controller, sessionSummary: summary }))
1751
+ }
1752
+
1753
+ ctx.effect(function () {
1754
+ return ctx.slots.inject('sidebar.right.pane.tab', function () {
1755
+ return ctx.slots.register({
1756
+ name: 'sidebar.right.pane.tab',
1757
+ key: TAB_ID,
1758
+ locale: NS,
1759
+ }, GitTabBody)
1760
+ })
1761
+ }, 'git-manager: right sidebar tab body')
1762
+
1763
+ // The way in: one icon in the session header's utilities row.
1764
+ function HeaderGitButton() {
1765
+ return h(React.Fragment, null,
1766
+ h('style', { key: 'gm-header-css' }, HEADER_CSS),
1767
+ h('button', {
1768
+ type: 'button',
1769
+ className: 'gm-header-btn',
1770
+ title: t('panel.open'),
1771
+ 'aria-label': t('panel.open'),
1772
+ onClick: function () { ctx.sidebarRight.openTab(TAB_KIND) },
1773
+ }, h(GitIcon, { size: 16, active: false })))
1774
+ }
1775
+
1776
+ ctx.effect(function () {
1777
+ return ctx.slots.inject('conversation.session.header.utilities', function () {
1778
+ return ctx.slots.register({
1779
+ name: 'conversation.session.header.utilities',
1780
+ id: TAB_ID,
1781
+ order: 10,
1782
+ label: function () { return t('panel.open') },
1783
+ }, HeaderGitButton)
1784
+ })
1785
+ }, 'git-manager: session header button')
1786
+ }
1787
+
1788
+ return module.exports
1789
+ },
1790
+ })