@jetcrab/pi-desk 1.1.22-dev → 1.1.24-dev

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 (77) hide show
  1. package/docs/commands.md +4 -2
  2. package/docs/plugins.md +2 -0
  3. package/package.json +1 -1
  4. package/src/client/l2_biz/project-preview/l2-project-preview-dialog.tsx +1 -1
  5. package/src/client/l2_biz/project-preview/l2-project-preview-page.tsx +1 -1
  6. package/src/client/l2_biz/project-preview/l2-project-preview.tsx +28 -34
  7. package/src/client/l3_modules/project-files/l3-project-document-view.tsx +162 -151
  8. package/src/client/l3_modules/project-files/l3-project-file-browser.module.css +30 -0
  9. package/src/client/l3_modules/project-files/l3-project-file-browser.tsx +137 -111
  10. package/src/client/l3_modules/project-files/l3-project-file-tabs.tsx +26 -6
  11. package/src/client/l3_modules/project-files/views/l3-project-search-controls.tsx +118 -123
  12. package/src/client/l3_modules/project-files/views/l3-project-search-results.tsx +29 -25
  13. package/src/server/l1_entry/api/l1-plugin-management-route.ts +0 -4
  14. package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +4 -37
  15. package/src/server/l2_biz/plugin-management/l2-plugin-updates.ts +1 -4
  16. package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +4 -2
  17. package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +16 -1
  18. package/src/server/l4_foundation/pi/l4-pi-package-root-consistency.ts +3 -6
  19. package/src/server/l4_foundation/pi/l4-pi-package-root-gate.ts +1 -1
  20. package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +4 -0
  21. package/src/server/l4_foundation/pi/l4-pi-plugin-sources.ts +5 -1
  22. package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +30 -34
  23. package/src/server/l4_foundation/pi/l4-pi-session-resources.ts +15 -14
  24. package/src/server/l4_foundation/pi/l4-pi-work-session-runtime.ts +1 -1
  25. package/src/server/l4_foundation/skills/runtime/l4-skill-catalog.ts +159 -166
  26. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  27. package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
  28. package/temp/build/pi-desk/release/.next/required-server-files.js +2 -2
  29. package/temp/build/pi-desk/release/.next/required-server-files.json +2 -2
  30. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  31. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  32. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +1 -1
  33. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  34. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  35. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  37. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  38. package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
  39. package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
  40. package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
  41. package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
  42. package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
  43. package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
  44. package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
  45. package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
  46. package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
  47. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/temp/build/pi-desk/release/.next/server/app/page.js +2 -2
  49. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -1
  51. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -1
  52. package/temp/build/pi-desk/release/.next/server/chunks/16438.js +1 -1
  53. package/temp/build/pi-desk/release/.next/server/chunks/18748.js +1 -0
  54. package/temp/build/pi-desk/release/.next/server/chunks/20460.js +2 -2
  55. package/temp/build/pi-desk/release/.next/server/chunks/24557.js +1 -1
  56. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +13 -14
  57. package/temp/build/pi-desk/release/.next/server/chunks/36361.js +2 -2
  58. package/temp/build/pi-desk/release/.next/server/chunks/50520.js +2 -2
  59. package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
  60. package/temp/build/pi-desk/release/.next/server/chunks/80290.js +1 -1
  61. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +15 -16
  62. package/temp/build/pi-desk/release/.next/server/instrumentation.js +1 -1
  63. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  64. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  65. package/temp/build/pi-desk/release/.next/static/chunks/9038-05fd2688407b39c1.js +15 -0
  66. package/temp/build/pi-desk/release/.next/static/chunks/app/{page-e8a5274c115f528d.js → page-ff44dcf27c2ba1c2.js} +2 -2
  67. package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/{page-d70ee4e9409b970a.js → page-bc18765121950e65.js} +1 -1
  68. package/temp/build/pi-desk/release/.next/static/chunks/{webpack-b60891a214e0925d.js → webpack-48eecbb66781c006.js} +1 -1
  69. package/temp/build/pi-desk/release/.next/static/css/10e063cfb3cda7bd.css +1 -0
  70. package/temp/build/pi-desk/release/.next/static/css/2c3b0c0ff1f49c68.css +1 -0
  71. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  72. package/temp/build/pi-desk/release/.next/server/chunks/93049.js +0 -1
  73. package/temp/build/pi-desk/release/.next/static/chunks/9038-03144ed96d613f75.js +0 -15
  74. package/temp/build/pi-desk/release/.next/static/css/6dbae2922f88ca31.css +0 -1
  75. package/temp/build/pi-desk/release/.next/static/css/f646ca6c07245c4c.css +0 -1
  76. /package/temp/build/pi-desk/release/.next/static/{MV6_lBt7Mzmi1rBNI-3dx → w3CeIboVupqJdq890TlcG}/_buildManifest.js +0 -0
  77. /package/temp/build/pi-desk/release/.next/static/{MV6_lBt7Mzmi1rBNI-3dx → w3CeIboVupqJdq890TlcG}/_ssgManifest.js +0 -0
package/docs/commands.md CHANGED
@@ -71,6 +71,8 @@
71
71
 
72
72
  接纳后结束当前轮;宿主等本会话完全空闲后重载,再通知并唤醒本会话。重载可能重建插件内存状态、使缓存失效,配置和工具声明变化也可能影响提示词缓存。
73
73
 
74
- 插件管理只等待目标来源必要的在途调用,不冻结常规聊天。完成通知表示包和宿主能力已应用;新建会话使用最新工具配置,已有原生 Pi 会话不自动重载。Node/Browser 替换会重新挂载目标插件界面,旧调用可能失效;结果未知的副作用请求不自动重发。
74
+ 插件管理最多等待目标来源的在途调用 15 秒;超过时仅该项维护失败,可稍后手动重试,不冻结常规聊天或阻止后续其他来源维护。完成通知表示包和宿主能力已应用;新建会话使用最新工具配置,已有原生 Pi 会话不自动重载。Node/Browser 替换会重新挂载目标插件界面,旧调用可能失效;结果未知的副作用请求不自动重发。
75
75
 
76
- 需要重启时按插件管理界面的维护流程处理,以最终结果为准。
76
+ 维护期间或失败后允许目标包暂时缺失。会话跳过缺失或版本不符的包,明确的单插件加载/启动错误在插件管理中保留诊断,不因此让其他正常插件一起降级。聊天订阅、刷新和重连不等待包维护或插件初始化;未就绪的模型与插件状态稍后同步。修复包后按需重载目标会话,不自动重载已有会话。
77
+
78
+ 需要重启时按插件管理界面的维护流程处理,只延后受影响来源,以最终结果为准。
package/docs/plugins.md CHANGED
@@ -339,4 +339,6 @@ try {
339
339
 
340
340
  ## 更新生效
341
341
 
342
+ 包维护不阻塞聊天订阅、刷新或其他会话。缺失或版本不符的包会被跳过,导入/工厂失败的入口不加载;Pi 生命周期回调和插件 Provider 注册的明确错误保留在插件管理诊断中,不升级为整会话失败。正常扩展继续加载。此隔离不终止同步死循环,也不回滚插件已发生的外部副作用;整体初始化超时仍可能使当前会话回退基础聊天,不阻塞其他会话的历史同步。
343
+
342
344
  `plugins install/reload` 应用 Node/Browser 能力;目标插件活动视图重挂,旧 Browser Owner 失效。现有 Native 会话不自动重载,新 UI 可能连接旧会话方法,需兼容既有消息和方法数据;需要现有会话加载当前原生代码时使用 `session reload`。热加载无法处理的资源按宿主已有重启流程处理,不把重新挂载界面当作后端任务已停止。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetcrab/pi-desk",
3
- "version": "1.1.22-dev",
3
+ "version": "1.1.24-dev",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -54,7 +54,7 @@ export function L2ProjectPreviewDialog({
54
54
  refreshOnFocus
55
55
  onEscape={onClose}
56
56
  title={
57
- <L4AppDialogTitle className="truncate" title={cwd}>
57
+ <L4AppDialogTitle className="truncate text-sm" title={cwd}>
58
58
  {projectName}
59
59
  </L4AppDialogTitle>
60
60
  }
@@ -45,7 +45,7 @@ export function L2ProjectPreviewPage({
45
45
  refreshOnFocus={false}
46
46
  onUnauthorized={onUnauthorized}
47
47
  actions={
48
- <Button size="sm" variant="outline" onClick={() => onSelect(null)}>
48
+ <Button size="sm" variant="ghost" onClick={() => onSelect(null)}>
49
49
  <FolderOpenIcon className="size-4" />
50
50
  <span>{t('switchProject')}</span>
51
51
  </Button>
@@ -78,6 +78,7 @@ export function L2ProjectPreview({
78
78
  <L3ProjectFileBrowser
79
79
  cwd={cwd}
80
80
  projectName={projectName}
81
+ showRootTitle={false}
81
82
  runtime={files}
82
83
  gitRuntime={git}
83
84
  onOpenGitBranches={openGit}
@@ -85,25 +86,26 @@ export function L2ProjectPreview({
85
86
  />
86
87
  )
87
88
  const code = (
88
- <div className="flex size-full min-h-0 min-w-0 flex-col">
89
- <L3ProjectFileTabs
90
- cwd={cwd}
91
- gitRuntime={git}
92
- paths={state.tabs}
93
- activePath={state.activePath}
94
- diffPath={diffPath}
95
- mobile={narrow}
96
- onActivate={(path) => files.openFile(path)}
97
- onClose={(path, scope) => files.closeTabs(path, scope)}
98
- />
99
- <L3ProjectDocumentView
100
- runtime={files}
101
- gitRuntime={git}
102
- compact={width < 1100}
103
- refreshOnFocus={refreshOnFocus}
104
- onEscape={onEscape}
105
- />
106
- </div>
89
+ <L3ProjectDocumentView
90
+ runtime={files}
91
+ gitRuntime={git}
92
+ compact={width < 1100}
93
+ refreshOnFocus={refreshOnFocus}
94
+ onEscape={onEscape}
95
+ renderHeader={(actions) => (
96
+ <L3ProjectFileTabs
97
+ cwd={cwd}
98
+ gitRuntime={git}
99
+ paths={state.tabs}
100
+ activePath={state.activePath}
101
+ diffPath={diffPath}
102
+ mobile={narrow}
103
+ actions={actions}
104
+ onActivate={(path) => files.openFile(path)}
105
+ onClose={(path, scope) => files.closeTabs(path, scope)}
106
+ />
107
+ )}
108
+ />
107
109
  )
108
110
  const gitView = (
109
111
  <div className="flex size-full min-h-0 min-w-0 flex-col">
@@ -226,7 +228,7 @@ export function L2ProjectPreview({
226
228
  className="flex size-full min-h-0 min-w-0 flex-col overflow-hidden"
227
229
  data-testid="project-preview"
228
230
  >
229
- <header className="flex min-h-14 shrink-0 flex-wrap items-center gap-2 border-b bg-muted px-3 py-2 pt-[max(8px,env(safe-area-inset-top))]">
231
+ <header className="flex min-h-9 shrink-0 items-center gap-1 bg-muted px-2 py-1 pt-[max(4px,env(safe-area-inset-top))]">
230
232
  {!stacked ? (
231
233
  <Button
232
234
  size="icon-sm"
@@ -241,15 +243,11 @@ export function L2ProjectPreview({
241
243
  )}
242
244
  </Button>
243
245
  ) : null}
244
- <div className="min-w-0 flex-1">
245
- {title ?? (
246
- <h1 className="truncate text-lg font-semibold" title={cwd}>
247
- {projectName}
248
- </h1>
249
- )}
250
- <p className="truncate text-xs text-muted-foreground" title={cwd}>
251
- {cwd}
252
- </p>
246
+ <div className="flex min-w-0 flex-1 items-baseline gap-3" title={cwd}>
247
+ <div className="min-w-0 shrink-0 max-w-[50%] max-sm:max-w-full max-sm:shrink">
248
+ {title ?? <h1 className="truncate text-sm font-semibold">{projectName}</h1>}
249
+ </div>
250
+ <p className="min-w-0 truncate text-xs text-muted-foreground max-sm:hidden">{cwd}</p>
253
251
  </div>
254
252
  <Button
255
253
  size="sm"
@@ -272,11 +270,7 @@ export function L2ProjectPreview({
272
270
  ) : null}
273
271
  {stacked ? (
274
272
  <>
275
- <div
276
- className="flex shrink-0 gap-1 border-b px-2 py-1"
277
- role="group"
278
- aria-label={t('previewArea')}
279
- >
273
+ <div className="flex shrink-0 gap-1 px-2 py-1" role="group" aria-label={t('previewArea')}>
280
274
  <Button
281
275
  size="sm"
282
276
  variant={page === 'files' ? 'secondary' : 'ghost'}
@@ -19,6 +19,7 @@ import {
19
19
  useRef,
20
20
  useState,
21
21
  useSyncExternalStore,
22
+ type ReactNode,
22
23
  type Ref
23
24
  } from 'react'
24
25
  import { useTranslation } from 'react-i18next'
@@ -55,7 +56,8 @@ export function L3ProjectDocumentView({
55
56
  visible = true,
56
57
  refreshOnFocus = true,
57
58
  onEscape,
58
- onSwitchPane
59
+ onSwitchPane,
60
+ renderHeader
59
61
  }: {
60
62
  ref?: Ref<L3ProjectDocumentHandle>
61
63
  runtime: L3ProjectFileRuntime
@@ -65,6 +67,7 @@ export function L3ProjectDocumentView({
65
67
  refreshOnFocus?: boolean
66
68
  onEscape: () => void
67
69
  onSwitchPane?: () => void
70
+ renderHeader?: (actions: ReactNode) => ReactNode
68
71
  }): React.JSX.Element {
69
72
  const { t } = useTranslation('projectFiles')
70
73
  const { wrapLines, diffViewMode, collapseUnchanged } = useL4CodePreferences()
@@ -149,6 +152,160 @@ export function L3ProjectDocumentView({
149
152
  else toast.error(t('copyContentFailed'))
150
153
  })
151
154
  }
155
+ const actions =
156
+ activeDiff || activePath ? (
157
+ <>
158
+ {activeDiff ? (
159
+ <>
160
+ <span className="mr-auto shrink-0 px-1 text-xs text-muted-foreground">
161
+ {activeDiff.label ?? t('headToWorking')}
162
+ {activeDiff.status === 'text' &&
163
+ activeDiff.original.content === activeDiff.modified.content
164
+ ? t('noDiffChanges')
165
+ : ''}
166
+ </span>
167
+ <L3CodeSurfaceToolbarButton
168
+ label={t('previousChange')}
169
+ onClick={() => diffRef.current?.previousChange()}
170
+ >
171
+ <ArrowUpIcon className="size-4" />
172
+ </L3CodeSurfaceToolbarButton>
173
+ <L3CodeSurfaceToolbarButton
174
+ label={t('nextChange')}
175
+ onClick={() => diffRef.current?.nextChange()}
176
+ >
177
+ <ArrowDownIcon className="size-4" />
178
+ </L3CodeSurfaceToolbarButton>
179
+ {!compact ? (
180
+ <L3CodeSurfaceToolbarButton
181
+ label={diffViewMode === 'side-by-side' ? t('inlineDiff') : t('sideBySideDiff')}
182
+ active={diffViewMode === 'inline'}
183
+ onClick={() =>
184
+ runtime.setDiffViewMode(
185
+ diffViewMode === 'side-by-side' ? 'inline' : 'side-by-side'
186
+ )
187
+ }
188
+ >
189
+ <Columns2Icon className="size-4" />
190
+ </L3CodeSurfaceToolbarButton>
191
+ ) : null}
192
+ <L3CodeSurfaceToolbarButton
193
+ label={t(wrapLines ? 'wrapOff' : 'wrapOn')}
194
+ active={wrapLines}
195
+ onClick={() => runtime.setDiffWrapLines(!wrapLines)}
196
+ >
197
+ <WrapTextIcon className="size-4" />
198
+ </L3CodeSurfaceToolbarButton>
199
+ <L3CodeSurfaceToolbarButton
200
+ label={t(collapseUnchanged ? 'expandUnchanged' : 'collapseUnchanged')}
201
+ active={collapseUnchanged}
202
+ onClick={() => runtime.setDiffCollapseUnchanged(!collapseUnchanged)}
203
+ >
204
+ <FoldVerticalIcon className="size-4" />
205
+ </L3CodeSurfaceToolbarButton>
206
+ <L3CodeSurfaceToolbarButton
207
+ label={t('reloadDiff')}
208
+ onClick={() => runtime.reloadDiff()}
209
+ >
210
+ <RefreshCwIcon className="size-4" />
211
+ </L3CodeSurfaceToolbarButton>
212
+ </>
213
+ ) : (
214
+ <>
215
+ {previewKind && document?.status === 'text' && activePath ? (
216
+ <div
217
+ className="mr-auto flex shrink-0"
218
+ role="group"
219
+ aria-label={t(previewKind === 'markdown' ? 'markdownMode' : 'htmlMode')}
220
+ >
221
+ {(['preview', 'source'] as const).map((mode) => (
222
+ <Button
223
+ key={mode}
224
+ variant={document.viewMode === mode ? 'secondary' : 'ghost'}
225
+ size="sm"
226
+ aria-pressed={document.viewMode === mode}
227
+ onClick={() => {
228
+ savePosition()
229
+ runtime.setTextViewMode(activePath, mode)
230
+ }}
231
+ >
232
+ {t(mode === 'preview' ? 'previewMode' : 'sourceMode')}
233
+ </Button>
234
+ ))}
235
+ </div>
236
+ ) : null}
237
+ {showSource && gitStatus ? (
238
+ <span
239
+ data-testid="file-git-status"
240
+ role="status"
241
+ className="mr-auto min-w-0 truncate text-xs text-muted-foreground"
242
+ title={comparison?.status === 'error' ? comparison.error : gitStatus}
243
+ >
244
+ {compact && comparison?.status === 'ready' && typeof count === 'number'
245
+ ? t('gitChangesCompact', { count })
246
+ : gitStatus}
247
+ </span>
248
+ ) : null}
249
+ {showSource && comparison?.status === 'ready' ? (
250
+ <>
251
+ {typeof count === 'number' && count > 0 ? (
252
+ <>
253
+ <L3CodeSurfaceToolbarButton
254
+ label={t('previousChange')}
255
+ onClick={() => previewRef.current?.previousChange()}
256
+ >
257
+ <ArrowUpIcon className="size-4" />
258
+ </L3CodeSurfaceToolbarButton>
259
+ <L3CodeSurfaceToolbarButton
260
+ label={t('nextChange')}
261
+ onClick={() => previewRef.current?.nextChange()}
262
+ >
263
+ <ArrowDownIcon className="size-4" />
264
+ </L3CodeSurfaceToolbarButton>
265
+ </>
266
+ ) : null}
267
+ <L3CodeSurfaceToolbarButton label={t('viewDiff')} onClick={() => openDiff()}>
268
+ <GitCompareArrowsIcon className="size-4" />
269
+ </L3CodeSurfaceToolbarButton>
270
+ </>
271
+ ) : null}
272
+ {showSource && activePath ? (
273
+ <>
274
+ <L3CodeSurfaceToolbarButton
275
+ label={t('findInFile')}
276
+ onClick={() => previewRef.current?.openSearch()}
277
+ >
278
+ <SearchIcon className="size-4" />
279
+ </L3CodeSurfaceToolbarButton>
280
+ {!compact ? (
281
+ <L3CodeSurfaceToolbarButton
282
+ label={t('goToLine')}
283
+ onClick={() => previewRef.current?.gotoLine()}
284
+ >
285
+ <ListOrderedIcon className="size-4" />
286
+ </L3CodeSurfaceToolbarButton>
287
+ ) : null}
288
+ <L3CodeSurfaceToolbarButton
289
+ label={t(wrapLines ? 'wrapOff' : 'wrapOn')}
290
+ active={wrapLines}
291
+ onClick={() => runtime.setWrapLines(activePath, !wrapLines)}
292
+ >
293
+ <WrapTextIcon className="size-4" />
294
+ </L3CodeSurfaceToolbarButton>
295
+ <L3CodeSurfaceToolbarButton label={t('copySelectionOrAll')} onClick={copyText}>
296
+ <CopyIcon className="size-4" />
297
+ </L3CodeSurfaceToolbarButton>
298
+ </>
299
+ ) : null}
300
+ {activePath ? (
301
+ <L3CodeSurfaceToolbarButton label={t('reloadCurrentFile')} onClick={refresh}>
302
+ <RefreshCwIcon className="size-4" />
303
+ </L3CodeSurfaceToolbarButton>
304
+ ) : null}
305
+ </>
306
+ )}
307
+ </>
308
+ ) : null
152
309
  return (
153
310
  <div
154
311
  ref={hostRef}
@@ -162,160 +319,14 @@ export function L3ProjectDocumentView({
162
319
  hostRef.current?.querySelector<HTMLElement>('[data-testid="markdown-preview"]')?.focus()
163
320
  }}
164
321
  >
165
- {activeDiff || activePath ? (
322
+ {renderHeader ? (
323
+ renderHeader(actions)
324
+ ) : actions ? (
166
325
  <div
167
326
  className="flex min-h-9 shrink-0 flex-wrap items-center justify-end gap-2 border-b px-3 py-1"
168
327
  aria-label={t('fileActions')}
169
328
  >
170
- {activeDiff ? (
171
- <>
172
- <span className="mr-auto shrink-0 px-1 text-xs text-muted-foreground">
173
- {activeDiff.label ?? t('headToWorking')}
174
- {activeDiff.status === 'text' &&
175
- activeDiff.original.content === activeDiff.modified.content
176
- ? t('noDiffChanges')
177
- : ''}
178
- </span>
179
- <L3CodeSurfaceToolbarButton
180
- label={t('previousChange')}
181
- onClick={() => diffRef.current?.previousChange()}
182
- >
183
- <ArrowUpIcon className="size-4" />
184
- </L3CodeSurfaceToolbarButton>
185
- <L3CodeSurfaceToolbarButton
186
- label={t('nextChange')}
187
- onClick={() => diffRef.current?.nextChange()}
188
- >
189
- <ArrowDownIcon className="size-4" />
190
- </L3CodeSurfaceToolbarButton>
191
- {!compact ? (
192
- <L3CodeSurfaceToolbarButton
193
- label={diffViewMode === 'side-by-side' ? t('inlineDiff') : t('sideBySideDiff')}
194
- active={diffViewMode === 'inline'}
195
- onClick={() =>
196
- runtime.setDiffViewMode(
197
- diffViewMode === 'side-by-side' ? 'inline' : 'side-by-side'
198
- )
199
- }
200
- >
201
- <Columns2Icon className="size-4" />
202
- </L3CodeSurfaceToolbarButton>
203
- ) : null}
204
- <L3CodeSurfaceToolbarButton
205
- label={t(wrapLines ? 'wrapOff' : 'wrapOn')}
206
- active={wrapLines}
207
- onClick={() => runtime.setDiffWrapLines(!wrapLines)}
208
- >
209
- <WrapTextIcon className="size-4" />
210
- </L3CodeSurfaceToolbarButton>
211
- <L3CodeSurfaceToolbarButton
212
- label={t(collapseUnchanged ? 'expandUnchanged' : 'collapseUnchanged')}
213
- active={collapseUnchanged}
214
- onClick={() => runtime.setDiffCollapseUnchanged(!collapseUnchanged)}
215
- >
216
- <FoldVerticalIcon className="size-4" />
217
- </L3CodeSurfaceToolbarButton>
218
- <L3CodeSurfaceToolbarButton
219
- label={t('reloadDiff')}
220
- onClick={() => runtime.reloadDiff()}
221
- >
222
- <RefreshCwIcon className="size-4" />
223
- </L3CodeSurfaceToolbarButton>
224
- </>
225
- ) : (
226
- <>
227
- {previewKind && document?.status === 'text' && activePath ? (
228
- <div
229
- className="mr-auto flex shrink-0"
230
- role="group"
231
- aria-label={t(previewKind === 'markdown' ? 'markdownMode' : 'htmlMode')}
232
- >
233
- {(['preview', 'source'] as const).map((mode) => (
234
- <Button
235
- key={mode}
236
- variant={document.viewMode === mode ? 'secondary' : 'ghost'}
237
- size="sm"
238
- aria-pressed={document.viewMode === mode}
239
- onClick={() => {
240
- savePosition()
241
- runtime.setTextViewMode(activePath, mode)
242
- }}
243
- >
244
- {t(mode === 'preview' ? 'previewMode' : 'sourceMode')}
245
- </Button>
246
- ))}
247
- </div>
248
- ) : null}
249
- {showSource && gitStatus ? (
250
- <span
251
- data-testid="file-git-status"
252
- role="status"
253
- className="mr-auto min-w-0 truncate text-xs text-muted-foreground"
254
- title={comparison?.status === 'error' ? comparison.error : gitStatus}
255
- >
256
- {compact && comparison?.status === 'ready' && typeof count === 'number'
257
- ? t('gitChangesCompact', { count })
258
- : gitStatus}
259
- </span>
260
- ) : null}
261
- {showSource && comparison?.status === 'ready' ? (
262
- <>
263
- {typeof count === 'number' && count > 0 ? (
264
- <>
265
- <L3CodeSurfaceToolbarButton
266
- label={t('previousChange')}
267
- onClick={() => previewRef.current?.previousChange()}
268
- >
269
- <ArrowUpIcon className="size-4" />
270
- </L3CodeSurfaceToolbarButton>
271
- <L3CodeSurfaceToolbarButton
272
- label={t('nextChange')}
273
- onClick={() => previewRef.current?.nextChange()}
274
- >
275
- <ArrowDownIcon className="size-4" />
276
- </L3CodeSurfaceToolbarButton>
277
- </>
278
- ) : null}
279
- <L3CodeSurfaceToolbarButton label={t('viewDiff')} onClick={() => openDiff()}>
280
- <GitCompareArrowsIcon className="size-4" />
281
- </L3CodeSurfaceToolbarButton>
282
- </>
283
- ) : null}
284
- {showSource && activePath ? (
285
- <>
286
- <L3CodeSurfaceToolbarButton
287
- label={t('findInFile')}
288
- onClick={() => previewRef.current?.openSearch()}
289
- >
290
- <SearchIcon className="size-4" />
291
- </L3CodeSurfaceToolbarButton>
292
- {!compact ? (
293
- <L3CodeSurfaceToolbarButton
294
- label={t('goToLine')}
295
- onClick={() => previewRef.current?.gotoLine()}
296
- >
297
- <ListOrderedIcon className="size-4" />
298
- </L3CodeSurfaceToolbarButton>
299
- ) : null}
300
- <L3CodeSurfaceToolbarButton
301
- label={t(wrapLines ? 'wrapOff' : 'wrapOn')}
302
- active={wrapLines}
303
- onClick={() => runtime.setWrapLines(activePath, !wrapLines)}
304
- >
305
- <WrapTextIcon className="size-4" />
306
- </L3CodeSurfaceToolbarButton>
307
- <L3CodeSurfaceToolbarButton label={t('copySelectionOrAll')} onClick={copyText}>
308
- <CopyIcon className="size-4" />
309
- </L3CodeSurfaceToolbarButton>
310
- </>
311
- ) : null}
312
- {activePath ? (
313
- <L3CodeSurfaceToolbarButton label={t('reloadCurrentFile')} onClick={refresh}>
314
- <RefreshCwIcon className="size-4" />
315
- </L3CodeSurfaceToolbarButton>
316
- ) : null}
317
- </>
318
- )}
329
+ {actions}
319
330
  </div>
320
331
  ) : null}
321
332
  <div className="min-h-0 flex-1 overflow-hidden pb-[env(safe-area-inset-bottom)]">
@@ -0,0 +1,30 @@
1
+ .tree [role='tree'] {
2
+ padding: 0;
3
+ }
4
+
5
+ .tree [role='tree'] > div {
6
+ padding: 0 4px;
7
+ }
8
+
9
+ .tree [data-slot='collapsible-content'] > div {
10
+ border-left-color: color-mix(in oklab, var(--border) 50%, transparent);
11
+ }
12
+
13
+ @media (pointer: fine) {
14
+ .tree [role='treeitem']:not([aria-expanded]),
15
+ .tree [role='treeitem'][aria-expanded] > div:first-child {
16
+ min-height: 24px;
17
+ gap: 4px;
18
+ padding: 0 4px;
19
+ }
20
+
21
+ .tree [role='treeitem'][aria-expanded] > div:first-child > button {
22
+ min-height: 24px;
23
+ gap: 4px;
24
+ }
25
+
26
+ .tree [data-slot='collapsible-content'] > div {
27
+ margin-left: 8px;
28
+ padding-left: 3px;
29
+ }
30
+ }