@chainingintention/pi-web-cn 1.202608.9 → 1.202608.10

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.
Files changed (187) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +123 -72
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/{CodeViewer-B9BLGnQ2.js → CodeViewer-DCl56K0Z.js} +2 -2
  5. package/dist/client/assets/{TerminalPanel-C1BWg5W2.js → TerminalPanel-DCvoEjUF.js} +6 -6
  6. package/dist/client/assets/{index-x7EWN9ro.js → index-CMvms5cF.js} +534 -512
  7. package/dist/client/index.html +1 -1
  8. package/dist/config.js +39 -18
  9. package/dist/config.js.map +1 -1
  10. package/dist/nativeServices/serviceAction.js +128 -0
  11. package/dist/nativeServices/serviceAction.js.map +1 -0
  12. package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
  13. package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
  14. package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
  15. package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
  16. package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
  17. package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
  18. package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
  19. package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
  20. package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
  21. package/dist/pi-web-plugins/git/git-backend.js +433 -0
  22. package/dist/pi-web-plugins/git/package.json +15 -0
  23. package/dist/pi-web-plugins/git/server-plugin.js +176 -0
  24. package/dist/pi-web-plugins/info/infoInternals.js +1 -1
  25. package/dist/pi-web-plugins/info/pi-web-plugin.js +2 -2
  26. package/dist/pi-web-plugins/relays/pi-web-plugin.js +1 -1
  27. package/dist/pi-web-plugins/relays/relayDiscovery.js +128 -23
  28. package/dist/pi-web-plugins/relays/relaysPanelElement.js +138 -16
  29. package/dist/pi-web-plugins/updates/pi-web-plugin.js +1 -1
  30. package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +1 -1
  31. package/dist/piWebVersionReport.js +63 -4
  32. package/dist/piWebVersionReport.js.map +1 -1
  33. package/dist/plugin-api.d.ts +40 -18
  34. package/dist/pluginRecoveryCli.js +172 -0
  35. package/dist/pluginRecoveryCli.js.map +1 -0
  36. package/dist/server/activity/workspaceActivityService.js +7 -1
  37. package/dist/server/activity/workspaceActivityService.js.map +1 -1
  38. package/dist/server/app.js +49 -23
  39. package/dist/server/app.js.map +1 -1
  40. package/dist/server/configRoutes.js +4 -5
  41. package/dist/server/configRoutes.js.map +1 -1
  42. package/dist/server/machines/machineClient.js +23 -4
  43. package/dist/server/machines/machineClient.js.map +1 -1
  44. package/dist/server/machines/machineProxyRoutes.js +162 -8
  45. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  46. package/dist/server/piWebPluginCatalog.js +441 -0
  47. package/dist/server/piWebPluginCatalog.js.map +1 -0
  48. package/dist/server/piWebPluginLifecycle.js +162 -0
  49. package/dist/server/piWebPluginLifecycle.js.map +1 -0
  50. package/dist/server/piWebPluginService.js +55 -7
  51. package/dist/server/piWebPluginService.js.map +1 -1
  52. package/dist/server/plugins/pluginBackendProxyRoutes.js +80 -0
  53. package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
  54. package/dist/server/plugins/serverPluginExec.js +184 -0
  55. package/dist/server/plugins/serverPluginExec.js.map +1 -0
  56. package/dist/server/plugins/serverPluginRuntime.js +480 -0
  57. package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
  58. package/dist/server/realtime/sessionEventHub.js +13 -0
  59. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  60. package/dist/server/realtime/sessionEventScope.js +8 -1
  61. package/dist/server/realtime/sessionEventScope.js.map +1 -1
  62. package/dist/server/requestCancellation.js +28 -0
  63. package/dist/server/requestCancellation.js.map +1 -0
  64. package/dist/server/sessiond/agentProcessEnvironment.js +61 -0
  65. package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -0
  66. package/dist/server/sessiond/managementProjectResolver.js +16 -0
  67. package/dist/server/sessiond/managementProjectResolver.js.map +1 -0
  68. package/dist/server/sessiond/pluginBackendRoutes.js +78 -0
  69. package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
  70. package/dist/server/sessiond/sessionProxyRoutes.js +1 -0
  71. package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
  72. package/dist/server/sessiond/sessionServiceDependencies.js +0 -1
  73. package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
  74. package/dist/server/sessiond/sessiondStateOwnership.js +139 -0
  75. package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
  76. package/dist/server/sessiond/workspaceCatalogRoutes.js +38 -0
  77. package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
  78. package/dist/server/sessiond/workspaceRemovalRoutes.js +56 -0
  79. package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
  80. package/dist/server/sessiond.js +98 -11
  81. package/dist/server/sessiond.js.map +1 -1
  82. package/dist/server/sessions/authService.js +15 -51
  83. package/dist/server/sessions/authService.js.map +1 -1
  84. package/dist/server/sessions/dockerEnvironmentFacts.js +181 -0
  85. package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
  86. package/dist/server/sessions/piSessionManagerGateway.js +129 -2
  87. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
  88. package/dist/server/sessions/piSessionService.js +131 -97
  89. package/dist/server/sessions/piSessionService.js.map +1 -1
  90. package/dist/server/sessions/sessionCommandService.js +15 -1
  91. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  92. package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
  93. package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
  94. package/dist/server/sessions/sessionFileFormat.js +27 -0
  95. package/dist/server/sessions/sessionFileFormat.js.map +1 -0
  96. package/dist/server/sessions/sessionFileHeader.js +82 -23
  97. package/dist/server/sessions/sessionFileHeader.js.map +1 -1
  98. package/dist/server/sessions/sessionRoutes.js +16 -2
  99. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  100. package/dist/server/sessions/sessionSummaryScanner.js +491 -0
  101. package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
  102. package/dist/server/sessions/spawnSubsessionTool.js +4 -8
  103. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  104. package/dist/server/sessions/spawnTargetResolver.js +2 -2
  105. package/dist/server/sessions/spawnTargetResolver.js.map +1 -1
  106. package/dist/server/status/machineStatusRoutes.js +10 -0
  107. package/dist/server/status/machineStatusRoutes.js.map +1 -0
  108. package/dist/server/status/machineStatusService.js +115 -0
  109. package/dist/server/status/machineStatusService.js.map +1 -0
  110. package/dist/server/status/workspaceAttribution.js +71 -0
  111. package/dist/server/status/workspaceAttribution.js.map +1 -0
  112. package/dist/server/terminalProxyRoutes.js.map +1 -1
  113. package/dist/server/workspaceExplorerRoutes.js +12 -25
  114. package/dist/server/workspaceExplorerRoutes.js.map +1 -1
  115. package/dist/server/workspaces/effectivePathAccess.js +3 -2
  116. package/dist/server/workspaces/effectivePathAccess.js.map +1 -1
  117. package/dist/server/workspaces/fileContentService.js +17 -9
  118. package/dist/server/workspaces/fileContentService.js.map +1 -1
  119. package/dist/server/workspaces/fileOperationService.js +4 -17
  120. package/dist/server/workspaces/fileOperationService.js.map +1 -1
  121. package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
  122. package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
  123. package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
  124. package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
  125. package/dist/server/workspaces/filePreviewService.js +81 -0
  126. package/dist/server/workspaces/filePreviewService.js.map +1 -0
  127. package/dist/server/workspaces/projectWorkspaceCwds.js +2 -2
  128. package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
  129. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +416 -0
  130. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
  131. package/dist/server/workspaces/workspaceCatalog.js +87 -0
  132. package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
  133. package/dist/server/workspaces/workspaceContext.js +5 -5
  134. package/dist/server/workspaces/workspaceContext.js.map +1 -1
  135. package/dist/server/workspaces/workspaceDeletionRoutes.js +114 -99
  136. package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
  137. package/dist/server/workspaces/workspaceProviderRegistry.js +650 -0
  138. package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
  139. package/dist/server/workspaces/workspaceRemovalService.js +258 -0
  140. package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
  141. package/dist/server/workspaces/workspaceRouteContext.js +2 -1
  142. package/dist/server/workspaces/workspaceRouteContext.js.map +1 -1
  143. package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
  144. package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
  145. package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
  146. package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
  147. package/dist/server-plugin-api.d.ts +115 -0
  148. package/dist/server-plugin-api.js +2 -0
  149. package/dist/server-plugin-api.js.map +1 -0
  150. package/dist/serverPluginRecovery.js +138 -0
  151. package/dist/serverPluginRecovery.js.map +1 -0
  152. package/dist/sessiond/config.js +6 -0
  153. package/dist/sessiond/config.js.map +1 -1
  154. package/dist/sessiond/sessionDaemonClient.js +7 -6
  155. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  156. package/dist/shared/apiTypes.js +2 -0
  157. package/dist/shared/apiTypes.js.map +1 -1
  158. package/dist/shared/capabilities.js +2 -0
  159. package/dist/shared/capabilities.js.map +1 -1
  160. package/dist/shared/federatedRoutes.js +28 -2
  161. package/dist/shared/federatedRoutes.js.map +1 -1
  162. package/dist/shared/machineStatus.js +60 -0
  163. package/dist/shared/machineStatus.js.map +1 -0
  164. package/dist/shared/pluginApiTypes.d.ts +155 -0
  165. package/dist/shared/pluginBackendProtocol.js +110 -0
  166. package/dist/shared/pluginBackendProtocol.js.map +1 -0
  167. package/dist/shared/pluginIds.js +3 -0
  168. package/dist/shared/pluginIds.js.map +1 -1
  169. package/dist/shared/pluginRecoveryCommands.js +14 -0
  170. package/dist/shared/pluginRecoveryCommands.js.map +1 -0
  171. package/dist/shared/workspaceFiles.js +41 -2
  172. package/dist/shared/workspaceFiles.js.map +1 -1
  173. package/dist/shared/workspaceRemovalProtocol.js +24 -0
  174. package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
  175. package/docs/config.md +12 -5
  176. package/docs/plugins.md +15 -21
  177. package/package.json +26 -8
  178. package/server-plugin-api.d.ts +1 -0
  179. package/dist/client/assets/UnifiedDiffViewer-ClTQ6Ok0.js +0 -39
  180. package/dist/plugin-api/unstable.d.ts +0 -22
  181. package/dist/server/sessions/parentSessionLocator.js +0 -75
  182. package/dist/server/sessions/parentSessionLocator.js.map +0 -1
  183. package/dist/server/workspaces/imagePreviewService.js +0 -40
  184. package/dist/server/workspaces/imagePreviewService.js.map +0 -1
  185. package/dist/shared/apiTypes.d.ts +0 -1323
  186. package/dist/shared/thinkingLevels.d.ts +0 -29
  187. package/plugin-api/unstable.d.ts +0 -1
@@ -0,0 +1,609 @@
1
+ // Generated from pi-web-plugins/git/browser/git-panel.ts. Do not edit directly.
2
+ import { GIT_DIFF_OPERATION, GIT_STATUS_OPERATION, parseGitDiffResponse, parseGitStatusResponse, } from "./git-contract.js";
3
+ import { buildGitFileList } from "./gitFileList.js";
4
+ import { buildGitFileTree, collectGitFileTreeDirectoryPaths } from "./gitFileTree.js";
5
+ import { readGitFileView, writeGitFileView } from "./gitFileViewPreference.js";
6
+ import { createGitDiffRoute } from "./gitRoute.js";
7
+ import { parseUnifiedDiff } from "./unifiedDiff.js";
8
+ const GIT_PANEL_LOCAL_ID = "workspace.git";
9
+ const GIT_POLL_INTERVAL_MS = 8_000;
10
+ // Keep navigation state for a few recent workspaces; heavy diff views are
11
+ // released as soon as another machine/workspace becomes active.
12
+ const GIT_WORKSPACE_STATE_LIMIT = 8;
13
+ const activityElementTag = "pi-web-git-panel-activity";
14
+ const EMPTY_LIST_MODEL = { submodules: [], files: [] };
15
+ const EMPTY_VIEW_STATE = { nodes: [], listModel: EMPTY_LIST_MODEL, expandablePaths: [] };
16
+ export function createGitBrowserContributions(sourcePluginId, runtimePluginId, html, svg) {
17
+ const panelId = `${runtimePluginId}:${GIT_PANEL_LOCAL_ID}`;
18
+ const controller = new GitUiController(sourcePluginId, createGitDiffRoute(panelId));
19
+ defineGitPanelActivityElement();
20
+ return {
21
+ actions: createGitActions(panelId, controller),
22
+ workspacePanels: [createGitPanel(html, svg, controller)],
23
+ };
24
+ }
25
+ class GitUiController {
26
+ sourcePluginId;
27
+ route;
28
+ states = new Map();
29
+ activeWorkspaceKey;
30
+ connectedWorkspaceKey;
31
+ routeNavigationPending = true;
32
+ view = readGitFileView();
33
+ constructor(sourcePluginId, route) {
34
+ this.sourcePluginId = sourcePluginId;
35
+ this.route = route;
36
+ }
37
+ isOwnedWorkspace(workspace) {
38
+ return workspace?.provider?.pluginId === this.sourcePluginId;
39
+ }
40
+ state(context) {
41
+ return this.stateFor(context);
42
+ }
43
+ connect(context) {
44
+ const key = workspaceContextKey(context);
45
+ const changedWorkspace = this.activeWorkspaceKey !== key;
46
+ if (changedWorkspace)
47
+ this.releaseInactiveDiff();
48
+ const state = this.stateFor(context);
49
+ this.activeWorkspaceKey = key;
50
+ this.connectedWorkspaceKey = key;
51
+ this.synchronizeRoute(state, changedWorkspace);
52
+ if (state.status === undefined && state.statusRequest === undefined)
53
+ void this.refresh(context);
54
+ else if (state.selectedDiffPath !== undefined && (state.selectedDiff === undefined || state.selectedStagedDiff === undefined) && !state.diffLoading) {
55
+ if (state.status?.files.some((file) => file.path === state.selectedDiffPath) === true)
56
+ void this.refreshDiff(state, state.selectedDiffPath, context);
57
+ else
58
+ this.clearSelection(state, true);
59
+ }
60
+ }
61
+ disconnect(context) {
62
+ if (this.connectedWorkspaceKey === workspaceContextKey(context))
63
+ this.connectedWorkspaceKey = undefined;
64
+ }
65
+ handlePopState(context) {
66
+ this.routeNavigationPending = true;
67
+ if (!this.route.matches(context))
68
+ return;
69
+ this.connect(context);
70
+ this.requestRender(this.stateFor(context));
71
+ }
72
+ poll(context) {
73
+ void this.refresh(context);
74
+ }
75
+ invalidate(context) {
76
+ if (!this.isOwnedWorkspace(context.workspace))
77
+ return Promise.resolve();
78
+ const state = this.stateFor(context);
79
+ state.stale = state.status !== undefined;
80
+ this.requestRender(state);
81
+ return this.refresh(context);
82
+ }
83
+ refresh(context) {
84
+ const state = this.stateFor(context);
85
+ if (state.statusRequest !== undefined)
86
+ return state.statusRequest;
87
+ state.statusLoading = true;
88
+ this.requestRender(state);
89
+ const request = requestGitBackend(context, GIT_STATUS_OPERATION, null)
90
+ .then(parseGitStatusResponse)
91
+ .then(async (status) => {
92
+ if (!state.retained)
93
+ return;
94
+ state.status = state.status?.hash === status.hash ? state.status : status;
95
+ state.stale = false;
96
+ state.error = undefined;
97
+ const path = state.selectedDiffPath;
98
+ if (path === undefined)
99
+ return;
100
+ if (!status.files.some((file) => file.path === path))
101
+ this.clearSelection(state, true);
102
+ else if (this.connectedWorkspaceKey === workspaceContextKey(context))
103
+ await this.refreshDiff(state, path, context);
104
+ })
105
+ .catch((error) => {
106
+ if (state.retained)
107
+ state.error = errorMessage(error);
108
+ })
109
+ .finally(() => {
110
+ if (state.statusRequest !== request)
111
+ return;
112
+ state.statusRequest = undefined;
113
+ state.statusLoading = false;
114
+ this.requestRender(state);
115
+ });
116
+ state.statusRequest = request;
117
+ return request;
118
+ }
119
+ selectDiff(context, path) {
120
+ const state = this.stateFor(context);
121
+ state.selectedDiffPath = path;
122
+ state.selectedDiff = undefined;
123
+ state.selectedStagedDiff = undefined;
124
+ state.diffLoading = true;
125
+ state.error = undefined;
126
+ if (this.route.matches(context))
127
+ this.route.write(path);
128
+ this.requestRender(state);
129
+ void this.refreshDiff(state, path, context);
130
+ }
131
+ setView(context, view) {
132
+ if (this.view === view)
133
+ return;
134
+ this.view = view;
135
+ writeGitFileView(view);
136
+ for (const state of this.states.values()) {
137
+ state.expandedDirectories = new Set();
138
+ state.viewStateCache = undefined;
139
+ }
140
+ this.requestRender(this.stateFor(context));
141
+ }
142
+ currentView() {
143
+ return this.view;
144
+ }
145
+ viewState(state) {
146
+ const cached = state.viewStateCache;
147
+ if (cached !== undefined && cached.status === state.status && cached.view === this.view)
148
+ return cached.viewState;
149
+ const viewState = buildViewState(state.status, this.view);
150
+ state.viewStateCache = { status: state.status, view: this.view, viewState };
151
+ return viewState;
152
+ }
153
+ toggleDirectory(context, path) {
154
+ const state = this.stateFor(context);
155
+ const next = new Set(state.expandedDirectories);
156
+ if (next.has(path))
157
+ next.delete(path);
158
+ else
159
+ next.add(path);
160
+ state.expandedDirectories = next;
161
+ this.requestRender(state);
162
+ }
163
+ toggleExpandAll(context, paths, collapse) {
164
+ const state = this.stateFor(context);
165
+ state.expandedDirectories = collapse ? new Set() : new Set(paths);
166
+ this.requestRender(state);
167
+ }
168
+ stateFor(context) {
169
+ const key = workspaceContextKey(context);
170
+ const existing = this.states.get(key);
171
+ if (existing !== undefined) {
172
+ existing.context = context;
173
+ this.states.delete(key);
174
+ this.states.set(key, existing);
175
+ return existing;
176
+ }
177
+ this.evictOldestState();
178
+ const created = {
179
+ context,
180
+ retained: true,
181
+ routeInitialized: false,
182
+ status: undefined,
183
+ statusLoading: false,
184
+ stale: false,
185
+ selectedDiffPath: undefined,
186
+ selectedDiff: undefined,
187
+ selectedStagedDiff: undefined,
188
+ diffLoading: false,
189
+ error: undefined,
190
+ expandedDirectories: new Set(),
191
+ statusRequest: undefined,
192
+ diffRequestSequence: 0,
193
+ viewStateCache: undefined,
194
+ };
195
+ this.states.set(key, created);
196
+ return created;
197
+ }
198
+ releaseInactiveDiff() {
199
+ if (this.activeWorkspaceKey === undefined)
200
+ return;
201
+ const state = this.states.get(this.activeWorkspaceKey);
202
+ if (state === undefined)
203
+ return;
204
+ state.selectedDiff = undefined;
205
+ state.selectedStagedDiff = undefined;
206
+ state.diffLoading = false;
207
+ state.diffRequestSequence += 1;
208
+ }
209
+ evictOldestState() {
210
+ if (this.states.size < GIT_WORKSPACE_STATE_LIMIT)
211
+ return;
212
+ const key = [...this.states.keys()].find((candidate) => candidate !== this.connectedWorkspaceKey) ?? this.states.keys().next().value;
213
+ if (key === undefined)
214
+ return;
215
+ const state = this.states.get(key);
216
+ if (state !== undefined) {
217
+ state.retained = false;
218
+ state.diffRequestSequence += 1;
219
+ }
220
+ this.states.delete(key);
221
+ }
222
+ synchronizeRoute(state, changedWorkspace) {
223
+ if (!this.route.matches(state.context))
224
+ return;
225
+ const routePath = this.route.read();
226
+ if (this.routeNavigationPending || !state.routeInitialized || (!changedWorkspace && routePath !== state.selectedDiffPath)) {
227
+ this.routeNavigationPending = false;
228
+ state.routeInitialized = true;
229
+ this.applyRouteSelection(state, routePath);
230
+ this.route.write(routePath, { replace: true });
231
+ return;
232
+ }
233
+ if (changedWorkspace)
234
+ this.route.write(state.selectedDiffPath, { replace: true });
235
+ }
236
+ applyRouteSelection(state, path) {
237
+ if (state.selectedDiffPath === path)
238
+ return;
239
+ state.selectedDiffPath = path;
240
+ state.selectedDiff = undefined;
241
+ state.selectedStagedDiff = undefined;
242
+ state.diffLoading = false;
243
+ state.diffRequestSequence += 1;
244
+ }
245
+ clearSelection(state, replaceUrl) {
246
+ this.applyRouteSelection(state, undefined);
247
+ if (replaceUrl && this.connectedWorkspaceKey === workspaceContextKey(state.context) && this.route.matches(state.context)) {
248
+ this.route.write(undefined, { replace: true });
249
+ }
250
+ }
251
+ async refreshDiff(state, path, context) {
252
+ const sequence = state.diffRequestSequence + 1;
253
+ state.diffRequestSequence = sequence;
254
+ state.diffLoading = true;
255
+ this.requestRender(state);
256
+ try {
257
+ const [selectedDiff, selectedStagedDiff] = await Promise.all([
258
+ requestGitBackend(context, GIT_DIFF_OPERATION, { path }).then(parseGitDiffResponse),
259
+ requestGitBackend(context, GIT_DIFF_OPERATION, { path, staged: true }).then(parseGitDiffResponse),
260
+ ]);
261
+ if (!state.retained || state.diffRequestSequence !== sequence || state.selectedDiffPath !== path)
262
+ return;
263
+ state.selectedDiff = createDiffView(selectedDiff, state.selectedDiff);
264
+ state.selectedStagedDiff = createDiffView(selectedStagedDiff, state.selectedStagedDiff);
265
+ state.error = undefined;
266
+ }
267
+ catch (error) {
268
+ if (!state.retained || state.diffRequestSequence !== sequence || state.selectedDiffPath !== path)
269
+ return;
270
+ state.error = errorMessage(error);
271
+ }
272
+ finally {
273
+ if (state.retained && state.diffRequestSequence === sequence && state.selectedDiffPath === path) {
274
+ state.diffLoading = false;
275
+ this.requestRender(state);
276
+ }
277
+ }
278
+ }
279
+ requestRender(state) {
280
+ if (state.retained)
281
+ state.context.host.requestRender();
282
+ }
283
+ }
284
+ function createGitActions(panelId, controller) {
285
+ const hasGitWorkspace = (context) => controller.isOwnedWorkspace(context.state.selectedWorkspace);
286
+ return [
287
+ {
288
+ id: "view.git",
289
+ title: "转到 Git",
290
+ shortcut: "mod+3",
291
+ shortcutAliases: ["core:view.git"],
292
+ group: "导航",
293
+ enabled: hasGitWorkspace,
294
+ run: (context) => { context.selectMainView(panelId); },
295
+ },
296
+ {
297
+ id: "workspace.refresh-git",
298
+ title: "刷新 Git",
299
+ shortcut: "mod+shift+g",
300
+ shortcutAliases: ["core:workspace.refresh-git"],
301
+ group: "工作区",
302
+ enabled: hasGitWorkspace,
303
+ run: (context) => context.refreshWorkspacePanels?.(panelId),
304
+ },
305
+ ];
306
+ }
307
+ function createGitPanel(html, svg, controller) {
308
+ return {
309
+ id: GIT_PANEL_LOCAL_ID,
310
+ title: "Git",
311
+ icon: svg `
312
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
313
+ <circle cx="6" cy="6" r="2"></circle>
314
+ <circle cx="18" cy="6" r="2"></circle>
315
+ <circle cx="12" cy="18" r="2"></circle>
316
+ <path d="M8 6h6"></path>
317
+ <path d="M6 8v2a6 6 0 0 0 6 6"></path>
318
+ <path d="M18 8v2a6 6 0 0 1-6 6"></path>
319
+ </svg>
320
+ `,
321
+ order: 20,
322
+ routeAliases: ["git", "core:workspace.git"],
323
+ visible: (context) => controller.isOwnedWorkspace(context.workspace),
324
+ onInvalidate: (context) => controller.invalidate(context),
325
+ render: (context) => renderGitPanel(html, controller, context),
326
+ };
327
+ }
328
+ function requestGitBackend(context, operation, input) {
329
+ if (context.backend === undefined || context.workspace.provider?.capabilities.request === false) {
330
+ return Promise.reject(new Error("Git 工作区后端不可用。请更新并重启此机器上的 PI WEB,然后重新加载浏览器。"));
331
+ }
332
+ return context.backend.request(operation, input);
333
+ }
334
+ function renderGitPanel(html, controller, context) {
335
+ const state = controller.state(context);
336
+ const viewState = controller.viewState(state);
337
+ return html `
338
+ <section class="git-panel">
339
+ <style .textContent=${gitPanelStyles}></style>
340
+ <pi-web-git-panel-activity .controller=${controller} .context=${context}></pi-web-git-panel-activity>
341
+ <section class="git-toolbar">
342
+ <strong>Git</strong>
343
+ ${state.stale ? html `<span class="git-stale">数据已过期</span>` : null}
344
+ <div class="git-toolbar-actions">
345
+ ${viewState.expandablePaths.length === 0 ? null : renderExpandCollapseAll(html, controller, context, state, viewState.expandablePaths)}
346
+ ${renderViewToggle(html, controller, context)}
347
+ <button type="button" ?disabled=${state.statusLoading} @click=${() => { void controller.refresh(context); }}>刷新</button>
348
+ </div>
349
+ </section>
350
+ ${state.error === undefined ? null : html `<div class="git-error" role="alert">${state.error}</div>`}
351
+ <section class="git-split">
352
+ <div class="git-file-list">${renderFileList(html, controller, context, state, viewState)}</div>
353
+ <div class="git-viewer">${renderDiffViewer(html, state)}</div>
354
+ </section>
355
+ </section>
356
+ `;
357
+ }
358
+ function renderViewToggle(html, controller, context) {
359
+ return html `
360
+ <div class="git-view-toggle" role="group" aria-label="已更改文件视图">
361
+ ${renderViewToggleButton(html, controller, context, "list", "列表")}
362
+ ${renderViewToggleButton(html, controller, context, "tree", "树形")}
363
+ </div>
364
+ `;
365
+ }
366
+ function renderViewToggleButton(html, controller, context, view, label) {
367
+ const active = controller.currentView() === view;
368
+ return html `<button type="button" class=${active ? "is-selected" : ""} aria-pressed=${String(active)} @click=${() => { controller.setView(context, view); }}>${label}</button>`;
369
+ }
370
+ function renderExpandCollapseAll(html, controller, context, state, expandablePaths) {
371
+ const allExpanded = expandablePaths.every((path) => state.expandedDirectories.has(path));
372
+ return html `<button type="button" @click=${() => { controller.toggleExpandAll(context, expandablePaths, allExpanded); }}>${allExpanded ? "全部折叠" : "全部展开"}</button>`;
373
+ }
374
+ function renderFileList(html, controller, context, state, viewState) {
375
+ const status = state.status;
376
+ if (status === undefined)
377
+ return html `<p class="git-muted">${state.error === undefined ? "正在加载状态…" : "状态不可用。"}</p>`;
378
+ if (!status.isGitRepo)
379
+ return html `<p class="git-muted">当前工作区不是 Git 仓库。</p>`;
380
+ const summary = html `<p class="git-summary">${gitSummary(status)}</p>`;
381
+ if (status.files.length === 0)
382
+ return html `${summary}<p class="git-muted">没有更改。</p>`;
383
+ const body = controller.currentView() === "tree"
384
+ ? viewState.nodes.map((node) => renderTreeNode(html, controller, context, state, node, 0))
385
+ : renderListBody(html, controller, context, state, viewState.listModel);
386
+ return html `${summary}${body}`;
387
+ }
388
+ function renderListBody(html, controller, context, state, model) {
389
+ return html `
390
+ ${model.submodules.map((group) => renderSubmoduleGroup(html, controller, context, state, group))}
391
+ ${model.files.map((file) => renderFileRow(html, controller, context, state, file))}
392
+ `;
393
+ }
394
+ function renderSubmoduleGroup(html, controller, context, state, group) {
395
+ const expanded = state.expandedDirectories.has(group.path);
396
+ return html `
397
+ <button type="button" class="git-row" style="--depth:0" aria-expanded=${String(expanded)} @click=${() => { controller.toggleDirectory(context, group.path); }}>
398
+ <span class="git-twisty">${expanded ? "▾" : "▸"}</span>
399
+ <span>${group.name}${submoduleBadge(html)}</span>
400
+ </button>
401
+ ${expanded ? html `
402
+ ${group.pointer === undefined ? null : renderSelectableRow(html, controller, context, state, group.path, group.pointer.name, group.pointer.file, 1)}
403
+ ${group.files.map((entry) => renderSubmoduleFileRow(html, controller, context, state, entry))}
404
+ ` : null}
405
+ `;
406
+ }
407
+ function renderSubmoduleFileRow(html, controller, context, state, entry) {
408
+ return renderSelectableRow(html, controller, context, state, entry.path, entry.relativePath, entry.file, 1);
409
+ }
410
+ function renderTreeNode(html, controller, context, state, node, depth) {
411
+ if (node.kind === "directory") {
412
+ const expanded = state.expandedDirectories.has(node.path);
413
+ return html `
414
+ <button type="button" class="git-row" style=${`--depth:${String(depth)}`} aria-expanded=${String(expanded)} @click=${() => { controller.toggleDirectory(context, node.path); }}>
415
+ <span class="git-twisty">${expanded ? "▾" : "▸"}</span>
416
+ <span>${node.name}${node.isSubmodule === true ? submoduleBadge(html) : null}</span>
417
+ </button>
418
+ ${expanded ? node.children.map((child) => renderTreeNode(html, controller, context, state, child, depth + 1)) : null}
419
+ `;
420
+ }
421
+ return renderSelectableRow(html, controller, context, state, node.path, node.name, node.file, depth);
422
+ }
423
+ function renderFileRow(html, controller, context, state, file) {
424
+ return renderSelectableRow(html, controller, context, state, file.path, file.path, file, 0);
425
+ }
426
+ function renderSelectableRow(html, controller, context, state, path, label, file, depth) {
427
+ const selected = state.selectedDiffPath === path;
428
+ return html `
429
+ <button type="button" class=${selected ? "git-row is-selected" : "git-row"} style=${`--depth:${String(depth)}`} @click=${() => { controller.selectDiff(context, path); }}>
430
+ <span>${stateLabel(file.index, file.workingTree)}</span>
431
+ <span>${label}</span>
432
+ </button>
433
+ `;
434
+ }
435
+ function renderDiffViewer(html, state) {
436
+ if (state.selectedDiffPath === undefined)
437
+ return html `<p class="git-muted">请选择一个已更改文件。</p>`;
438
+ const unstaged = state.selectedDiff;
439
+ const staged = state.selectedStagedDiff;
440
+ if (unstaged === undefined || staged === undefined)
441
+ return html `<p class="git-muted">正在加载差异…</p>`;
442
+ const diffs = [staged, unstaged].filter((diff) => diff.response.diff !== "");
443
+ if (diffs.length === 0)
444
+ return html `<p class="git-muted">没有已暂存或未暂存的差异。</p>`;
445
+ return html `<div class=${diffs.length === 1 ? "git-diffs is-single" : "git-diffs"}>${diffs.map((diff) => renderDiffSection(html, diff))}</div>`;
446
+ }
447
+ function renderDiffSection(html, view) {
448
+ const diff = view.response;
449
+ const lines = view.lines ??= parseUnifiedDiff(diff.diff);
450
+ return html `
451
+ <section class="git-diff-section">
452
+ <div class="git-viewer-header"><strong>${diff.path ?? "差异"}</strong><small>${diff.staged ? "已暂存" : "未暂存"}${diff.truncated ? " · 已截断" : ""}</small></div>
453
+ ${lines.length === 0 ? html `<p class="git-muted">没有差异。</p>` : html `
454
+ <div class="git-diff-scroller">
455
+ <div class="git-diff-grid" role="table" aria-label="统一差异">
456
+ ${lines.map((line) => renderDiffLine(html, line))}
457
+ </div>
458
+ </div>
459
+ `}
460
+ </section>
461
+ `;
462
+ }
463
+ function renderDiffLine(html, line) {
464
+ return html `
465
+ <div class="git-diff-line" role="row">
466
+ <span class=${`git-diff-cell git-line-number ${line.kind}`} role="cell">${formatLineNumber(line.oldLineNumber)}</span>
467
+ <span class=${`git-diff-cell git-line-number ${line.kind}`} role="cell">${formatLineNumber(line.newLineNumber)}</span>
468
+ <span class=${`git-diff-cell git-prefix ${line.kind}`} role="cell">${line.prefix}</span>
469
+ <span class=${`git-diff-cell git-content ${line.kind}`} role="cell">${renderDiffSpans(html, line.spans)}</span>
470
+ </div>
471
+ `;
472
+ }
473
+ function renderDiffSpans(html, spans) {
474
+ return spans.map((span) => html `<span class=${span.changed ? "inline-change" : ""}>${span.text}</span>`);
475
+ }
476
+ function buildViewState(status, view) {
477
+ if (status === undefined || !status.isGitRepo || status.files.length === 0)
478
+ return EMPTY_VIEW_STATE;
479
+ if (view === "tree") {
480
+ const nodes = buildGitFileTree(status.files, status.submodules);
481
+ return { nodes, listModel: EMPTY_LIST_MODEL, expandablePaths: collectGitFileTreeDirectoryPaths(nodes) };
482
+ }
483
+ const listModel = buildGitFileList(status.files, status.submodules);
484
+ return { nodes: [], listModel, expandablePaths: listModel.submodules.map((group) => group.path) };
485
+ }
486
+ function defineGitPanelActivityElement() {
487
+ if (typeof customElements === "undefined" || typeof HTMLElement === "undefined" || customElements.get(activityElementTag) !== undefined)
488
+ return;
489
+ class GitPanelActivityElement extends HTMLElement {
490
+ controllerValue;
491
+ contextValue;
492
+ pollTimer;
493
+ set controller(value) {
494
+ if (this.controllerValue === value)
495
+ return;
496
+ this.controllerValue = value;
497
+ this.restart();
498
+ }
499
+ set context(value) {
500
+ const previousKey = this.contextValue === undefined ? undefined : workspaceContextKey(this.contextValue);
501
+ this.contextValue = value;
502
+ if (previousKey !== (value === undefined ? undefined : workspaceContextKey(value)))
503
+ this.restart();
504
+ }
505
+ connectedCallback() {
506
+ window.addEventListener("popstate", this.onPopState);
507
+ this.restart();
508
+ }
509
+ disconnectedCallback() {
510
+ window.removeEventListener("popstate", this.onPopState);
511
+ if (this.controllerValue !== undefined && this.contextValue !== undefined)
512
+ this.controllerValue.disconnect(this.contextValue);
513
+ this.stopTimer();
514
+ }
515
+ restart() {
516
+ this.stopTimer();
517
+ if (!this.isConnected || this.controllerValue === undefined || this.contextValue === undefined)
518
+ return;
519
+ this.controllerValue.connect(this.contextValue);
520
+ this.pollTimer = window.setInterval(() => {
521
+ if (this.controllerValue !== undefined && this.contextValue !== undefined)
522
+ this.controllerValue.poll(this.contextValue);
523
+ }, GIT_POLL_INTERVAL_MS);
524
+ }
525
+ stopTimer() {
526
+ if (this.pollTimer !== undefined)
527
+ window.clearInterval(this.pollTimer);
528
+ this.pollTimer = undefined;
529
+ }
530
+ onPopState = () => {
531
+ if (this.controllerValue !== undefined && this.contextValue !== undefined)
532
+ this.controllerValue.handlePopState(this.contextValue);
533
+ };
534
+ }
535
+ customElements.define(activityElementTag, GitPanelActivityElement);
536
+ }
537
+ function submoduleBadge(html) {
538
+ return html `<span class="submodule-badge">子模块</span>`;
539
+ }
540
+ function gitSummary(status) {
541
+ const branch = status.branch ?? "游离 HEAD";
542
+ const ahead = status.ahead ?? 0;
543
+ const behind = status.behind ?? 0;
544
+ return ahead === 0 && behind === 0 ? branch : `${branch} · ↑${String(ahead)} ↓${String(behind)}`;
545
+ }
546
+ function stateLabel(index, workingTree) {
547
+ const label = workingTree !== "unmodified" ? workingTree : index;
548
+ return label.slice(0, 1).toUpperCase();
549
+ }
550
+ function formatLineNumber(lineNumber) {
551
+ return lineNumber === undefined ? "" : String(lineNumber);
552
+ }
553
+ function createDiffView(response, previous) {
554
+ return {
555
+ response,
556
+ lines: previous?.response.hash === response.hash ? previous.lines : undefined,
557
+ };
558
+ }
559
+ function workspaceContextKey(context) {
560
+ return JSON.stringify([context.machine.id, context.workspace.projectId, context.workspace.id]);
561
+ }
562
+ function errorMessage(error) {
563
+ return error instanceof Error ? error.message : String(error);
564
+ }
565
+ const gitPanelStyles = `
566
+ .git-panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; color: var(--pi-text); background: var(--pi-bg); font: 13px system-ui, sans-serif; }
567
+ .git-panel ${activityElementTag} { display: none; }
568
+ .git-panel button { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--pi-border); border-radius: 7px; background: var(--pi-surface); color: var(--pi-text); padding: 5px 7px; cursor: pointer; }
569
+ .git-panel button:disabled { cursor: wait; opacity: .65; }
570
+ .git-panel small, .git-panel .git-muted { color: var(--pi-muted); }
571
+ .git-panel p { margin: 10px; }
572
+ .git-panel .git-toolbar { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 8px; border-bottom: 1px solid var(--pi-border-muted); }
573
+ .git-panel .git-toolbar-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
574
+ .git-panel .git-view-toggle { display: inline-flex; }
575
+ .git-panel .git-view-toggle button { border-radius: 0; }
576
+ .git-panel .git-view-toggle button:first-child { border-top-left-radius: 7px; border-bottom-left-radius: 7px; }
577
+ .git-panel .git-view-toggle button:last-child { margin-left: -1px; border-top-right-radius: 7px; border-bottom-right-radius: 7px; }
578
+ .git-panel .git-view-toggle button.is-selected { position: relative; z-index: 1; border-color: var(--pi-accent); background: var(--pi-selection-bg); }
579
+ .git-panel .git-stale { border: 1px solid var(--pi-warning-border); border-radius: 999px; color: var(--pi-warning); padding: 1px 6px; font-size: 12px; }
580
+ .git-panel .git-error { flex: 0 0 auto; margin: 8px; border: 1px solid var(--pi-danger); border-radius: 7px; color: var(--pi-danger); padding: 8px; }
581
+ .git-panel .git-split { flex: 1 1 auto; min-height: 0; display: grid; grid-template-rows: minmax(160px, 34%) minmax(0, 1fr); }
582
+ .git-panel .git-file-list { min-height: 0; overflow: auto; border-bottom: 1px solid var(--pi-border); padding: 6px; }
583
+ .git-panel .git-row { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 4px; width: 100%; border: 0; border-radius: 5px; background: transparent; text-align: left; padding: 4px 6px 4px calc(6px + var(--depth, 0) * 14px); }
584
+ .git-panel .git-row:hover, .git-panel .git-row.is-selected { background: var(--pi-selection-bg); }
585
+ .git-panel .git-row span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
586
+ .git-panel .git-twisty { color: var(--pi-dim, var(--pi-muted)); }
587
+ .git-panel .git-summary { margin: 4px 6px 8px; color: var(--pi-muted); }
588
+ .git-panel .submodule-badge { display: inline-block; margin-left: 6px; border: 1px solid var(--pi-border); border-radius: 999px; color: var(--pi-muted); padding: 0 5px; font-size: 11px; font-weight: 400; vertical-align: baseline; }
589
+ .git-panel .git-viewer { min-height: 0; overflow: auto; display: flex; flex-direction: column; }
590
+ .git-panel .git-diffs { flex: 1 1 auto; min-height: 0; overflow: auto; display: grid; grid-template-rows: minmax(120px, 1fr) minmax(120px, 1fr); }
591
+ .git-panel .git-diffs.is-single { grid-template-rows: minmax(0, 1fr); }
592
+ .git-panel .git-diff-section { min-height: 0; display: flex; flex-direction: column; border-bottom: 1px solid var(--pi-border); }
593
+ .git-panel .git-diff-section:last-child { border-bottom: 0; }
594
+ .git-panel .git-viewer-header { position: sticky; top: 0; display: flex; justify-content: space-between; gap: 8px; padding: 8px; border-bottom: 1px solid var(--pi-border-muted); background: var(--pi-bg); }
595
+ .git-panel .git-viewer-header strong { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
596
+ .git-panel .git-diff-scroller { flex: 1 1 auto; min-height: 0; overflow: auto; background: var(--pi-bg); }
597
+ .git-panel .git-diff-grid { display: grid; grid-template-columns: max-content max-content 2ch max-content; width: max-content; min-width: 100%; padding: 6px 0; font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; line-height: 1.45; }
598
+ .git-panel .git-diff-line { display: contents; }
599
+ .git-panel .git-diff-cell { min-height: 1.45em; white-space: pre; }
600
+ .git-panel .git-line-number { min-width: 4ch; padding: 0 8px; border-right: 1px solid var(--pi-border-muted); color: var(--pi-dim); text-align: right; user-select: none; }
601
+ .git-panel .git-prefix { padding: 0 4px; color: var(--pi-dim); text-align: center; user-select: none; }
602
+ .git-panel .git-content { padding: 0 12px 0 4px; }
603
+ .git-panel .git-diff-cell.meta, .git-panel .git-diff-cell.marker { color: var(--pi-dim); }
604
+ .git-panel .git-diff-cell.hunk { background: color-mix(in srgb, var(--pi-accent) 9%, transparent); color: var(--pi-accent); }
605
+ .git-panel .git-diff-cell.add { background: color-mix(in srgb, var(--pi-success) 12%, transparent); }
606
+ .git-panel .git-diff-cell.remove { background: color-mix(in srgb, var(--pi-danger) 12%, transparent); }
607
+ .git-panel .git-content.add .inline-change { border-radius: 2px; background: color-mix(in srgb, var(--pi-success) 36%, transparent); color: var(--pi-text); }
608
+ .git-panel .git-content.remove .inline-change { border-radius: 2px; background: color-mix(in srgb, var(--pi-danger) 36%, transparent); color: var(--pi-text); }
609
+ `;
@@ -0,0 +1,43 @@
1
+ // Generated from pi-web-plugins/git/browser/gitFileList.ts. Do not edit directly.
2
+ import { pointerName, segmentName } from "./gitFileShared.js";
3
+ /**
4
+ * Group the flat changed-file list for list view. Unlike the tree, submodule
5
+ * contents are flattened: each submodule becomes one expandable group holding
6
+ * its pointer row and its changed files, and everything else stays a flat list.
7
+ */
8
+ export function buildGitFileList(files, submodules = []) {
9
+ const submoduleSet = new Set(submodules);
10
+ const pointers = new Map();
11
+ const grouped = new Map();
12
+ for (const submodule of submodules)
13
+ grouped.set(submodule, []);
14
+ const flat = [];
15
+ for (const file of files) {
16
+ if (submoduleSet.has(file.path)) {
17
+ pointers.set(file.path, { name: pointerName(file), file });
18
+ continue;
19
+ }
20
+ const owner = ownerSubmodule(file.path, submodules);
21
+ if (owner !== undefined) {
22
+ grouped.get(owner)?.push({ path: file.path, relativePath: file.path.slice(owner.length + 1), file });
23
+ continue;
24
+ }
25
+ flat.push(file);
26
+ }
27
+ const groups = submodules
28
+ .map((submodule) => {
29
+ const pointer = pointers.get(submodule);
30
+ const inner = grouped.get(submodule) ?? [];
31
+ return { path: submodule, name: segmentName(submodule), ...(pointer === undefined ? {} : { pointer }), files: [...inner].sort((left, right) => left.relativePath.localeCompare(right.relativePath)) };
32
+ })
33
+ .sort((left, right) => left.name.localeCompare(right.name));
34
+ return { submodules: groups, files: flat };
35
+ }
36
+ function ownerSubmodule(path, submodules) {
37
+ let best;
38
+ for (const submodule of submodules) {
39
+ if (submodule !== "" && path.startsWith(`${submodule}/`) && (best === undefined || submodule.length > best.length))
40
+ best = submodule;
41
+ }
42
+ return best;
43
+ }
@@ -0,0 +1,16 @@
1
+ // Generated from pi-web-plugins/git/browser/gitFileShared.ts. Do not edit directly.
2
+ /**
3
+ * Display label for a submodule commit-pointer row: the `<old> → <new>`
4
+ * short-SHA summary, or "commit" when the server did not resolve both ends.
5
+ */
6
+ export function pointerName(file) {
7
+ const from = file.submoduleFromCommit;
8
+ const to = file.submoduleToCommit;
9
+ return from !== undefined && to !== undefined ? `${from} → ${to}` : "commit";
10
+ }
11
+ /** Final segment of a path, used as the display name for tree/list rows. */
12
+ export function segmentName(path) {
13
+ const segments = path.split("/");
14
+ const last = segments[segments.length - 1];
15
+ return last ?? path;
16
+ }