@jetcrab/pi-desk 1.1.20-dev → 1.1.22-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 (202) hide show
  1. package/docs/plugins.md +81 -1
  2. package/package.json +3 -3
  3. package/src/app/page.tsx +6 -1
  4. package/src/app/project-preview/page.tsx +10 -0
  5. package/src/client/l1_entry/pages/l1-home-page.tsx +30 -0
  6. package/src/client/l1_entry/pages/l1-project-preview-page.tsx +33 -0
  7. package/src/client/l1_entry/providers/l1-app-providers.tsx +18 -15
  8. package/src/client/l1_entry/providers/l1-app-runtime-provider.tsx +2 -2
  9. package/src/client/l2_biz/auth/l2-login-biz.ts +3 -0
  10. package/src/client/l2_biz/auth/l2-login.tsx +2 -3
  11. package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +6 -1
  12. package/src/client/l2_biz/plugin-host/l2-plugin-contribution-host.tsx +71 -10
  13. package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +9 -0
  14. package/src/client/l2_biz/plugin-host/l2-plugin-session-header-layout.ts +18 -0
  15. package/src/client/l2_biz/plugin-host/l2-plugin-session-header.tsx +247 -0
  16. package/src/client/l2_biz/plugin-host/l2-plugin-widgets.tsx +50 -0
  17. package/src/client/l2_biz/plugin-host/views/l2-plugin-session-header-action.tsx +112 -0
  18. package/src/client/l2_biz/project-preview/hooks/l2-use-project-picker.ts +76 -0
  19. package/src/client/l2_biz/project-preview/hooks/l2-use-project-preview.ts +214 -0
  20. package/src/client/l2_biz/project-preview/l2-project-preview-biz.ts +49 -4
  21. package/src/client/l2_biz/project-preview/l2-project-preview-dialog.tsx +42 -450
  22. package/src/client/l2_biz/project-preview/l2-project-preview-page.tsx +61 -0
  23. package/src/client/l2_biz/project-preview/l2-project-preview.tsx +342 -0
  24. package/src/client/l2_biz/project-preview/runtime/l2-project-history-runtime.ts +9 -3
  25. package/src/client/l2_biz/project-preview/views/l2-project-picker.tsx +168 -0
  26. package/src/client/l2_biz/settings/hooks/l2-use-model-settings.ts +34 -31
  27. package/src/client/l2_biz/settings/views/l2-model-replacement-preview.tsx +7 -1
  28. package/src/client/l2_biz/workbench/file-workspace/l2-project-preview-biz.ts +4 -3
  29. package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +28 -21
  30. package/src/client/l2_biz/workbench/l2-work-session-columns.module.css +32 -0
  31. package/src/client/l2_biz/workbench/l2-work-session-columns.tsx +55 -16
  32. package/src/client/l2_biz/workbench/l2-work-session-sidebar.tsx +19 -8
  33. package/src/client/l2_biz/workbench/l2-workbench-chat-composer.tsx +12 -1
  34. package/src/client/l2_biz/workbench/l2-workbench-initialization-notice.tsx +8 -3
  35. package/src/client/l2_biz/workbench/l2-workbench-plugin-slots.ts +13 -0
  36. package/src/client/l2_biz/workbench/l2-workbench.tsx +15 -2
  37. package/src/client/l3_modules/conversation/l3-conversation-viewport.tsx +12 -6
  38. package/src/client/l3_modules/project-files/l3-markdown-preview.module.css +1 -1
  39. package/src/client/l3_modules/project-files/l3-project-document-git.ts +4 -3
  40. package/src/client/l3_modules/project-files/l3-project-document-view.tsx +4 -1
  41. package/src/client/l3_modules/project-files/l3-project-reader.ts +23 -35
  42. package/src/client/l4_foundation/lib/l4-client-id.tsx +11 -0
  43. package/src/client/l4_foundation/plugin-host/l4-plugin-host-runtime.ts +316 -35
  44. package/src/common/l2_biz/auth/l2-auth-return-to.ts +41 -0
  45. package/src/common/l2_biz/model-settings/l2-model-settings-contract.ts +5 -1
  46. package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +4 -0
  47. package/src/common/l2_biz/project-preview/l2-project-history-messages.ts +28 -0
  48. package/src/common/l2_biz/project-preview/l2-project-preview-navigation.ts +5 -0
  49. package/src/common/l2_biz/settings/l2-settings-messages.ts +4 -3
  50. package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +2 -0
  51. package/src/server/l1_entry/node/l1-server.ts +8 -4
  52. package/src/server/l4_foundation/model-settings/l4-model-settings-store.ts +29 -20
  53. package/src/server/l4_foundation/model-settings/l4-model-settings-types.ts +1 -1
  54. package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +22 -5
  55. package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +133 -13
  56. package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +9 -0
  57. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  58. package/temp/build/pi-desk/release/.next/app-path-routes-manifest.json +3 -2
  59. package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
  60. package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -2
  61. package/temp/build/pi-desk/release/.next/required-server-files.js +2 -2
  62. package/temp/build/pi-desk/release/.next/required-server-files.json +2 -2
  63. package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
  64. package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
  65. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  66. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  67. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
  68. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
  69. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
  70. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  71. package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
  72. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  73. package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +1 -1
  74. package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
  75. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
  76. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
  77. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
  78. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
  79. package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
  80. package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
  81. package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
  82. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  83. package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
  84. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
  85. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
  86. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
  87. package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
  88. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
  89. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -1
  90. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
  91. package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
  92. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
  93. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
  94. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
  95. package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
  96. package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
  97. package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
  98. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
  99. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
  100. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
  101. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
  102. package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
  103. package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
  104. package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
  105. package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
  106. package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
  107. package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
  108. package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
  109. package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
  110. package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
  111. package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
  112. package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
  113. package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +1 -1
  114. package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +1 -1
  115. package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
  116. package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +2 -2
  117. package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +1 -1
  118. package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +1 -1
  119. package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +1 -1
  120. package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -2
  121. package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +1 -1
  122. package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -2
  123. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
  124. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
  125. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
  126. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
  127. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
  128. package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
  129. package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
  130. package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
  131. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
  132. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
  133. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
  134. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
  135. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
  136. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
  137. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
  138. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
  139. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
  140. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/sort/route.js +1 -1
  141. package/temp/build/pi-desk/release/.next/server/app/favicon.ico/route.js +1 -1
  142. package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
  143. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  144. package/temp/build/pi-desk/release/.next/server/app/page.js +140 -140
  145. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -0
  147. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -0
  148. package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +3 -2
  149. package/temp/build/pi-desk/release/.next/server/chunks/10912.js +2 -2
  150. package/temp/build/pi-desk/release/.next/server/chunks/20460.js +15 -0
  151. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +8 -8
  152. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +3 -17
  153. package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
  154. package/temp/build/pi-desk/release/.next/server/chunks/48180.js +1 -1
  155. package/temp/build/pi-desk/release/.next/server/chunks/56830.js +235 -0
  156. package/temp/build/pi-desk/release/.next/server/chunks/78262.js +1 -1
  157. package/temp/build/pi-desk/release/.next/server/chunks/79686.js +62 -0
  158. package/temp/build/pi-desk/release/.next/server/chunks/80780.js +1 -0
  159. package/temp/build/pi-desk/release/.next/server/chunks/81363.js +1 -1
  160. package/temp/build/pi-desk/release/.next/server/chunks/82499.js +4 -4
  161. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +8 -8
  162. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  163. package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
  164. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  165. package/temp/build/pi-desk/release/.next/static/MV6_lBt7Mzmi1rBNI-3dx/_buildManifest.js +1 -0
  166. package/temp/build/pi-desk/release/.next/static/chunks/11937-1d0a9822f2c7995b.js +1 -0
  167. package/temp/build/pi-desk/release/.next/static/chunks/14825-5579aed34a631ad7.js +62 -0
  168. package/temp/build/pi-desk/release/.next/static/chunks/15302-9c807afc6c60de0c.js +1 -0
  169. package/temp/build/pi-desk/release/.next/static/chunks/19991.8aa2e4b6df15fad6.js +1 -0
  170. package/temp/build/pi-desk/release/.next/static/chunks/22092-6d1bfbefa825ec03.js +1 -0
  171. package/temp/build/pi-desk/release/.next/static/chunks/37313-d5970e2d0c270d22.js +238 -0
  172. package/temp/build/pi-desk/release/.next/static/chunks/57235-03ba1044f22b6c84.js +1 -0
  173. package/temp/build/pi-desk/release/.next/static/chunks/79267-7d21d7cdbd7cfca1.js +1 -0
  174. package/temp/build/pi-desk/release/.next/static/chunks/82371-f7ba4a07a5dbcf34.js +1 -0
  175. package/temp/build/pi-desk/release/.next/static/chunks/9038-03144ed96d613f75.js +15 -0
  176. package/temp/build/pi-desk/release/.next/static/chunks/app/{error-0296a3b20cd1bf00.js → error-216b14b5da089ed3.js} +1 -1
  177. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-20718f1503910291.js +1 -0
  178. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-0d3d4827802108e5.js +1 -0
  179. package/temp/build/pi-desk/release/.next/static/chunks/app/{not-found-2aab8b33ebbf3257.js → not-found-720cdf6a606b821a.js} +1 -1
  180. package/temp/build/pi-desk/release/.next/static/chunks/app/page-e8a5274c115f528d.js +158 -0
  181. package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-d70ee4e9409b970a.js +1 -0
  182. package/temp/build/pi-desk/release/.next/static/chunks/webpack-b60891a214e0925d.js +1 -0
  183. package/temp/build/pi-desk/release/.next/static/css/{250a6b93cc2222d1.css → 1e780a4936aa6ba3.css} +1 -1
  184. package/temp/build/pi-desk/release/.next/static/css/f646ca6c07245c4c.css +1 -0
  185. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  186. package/temp/build/pi-desk/release/.next/server/chunks/10881.js +0 -296
  187. package/temp/build/pi-desk/release/.next/static/RLKcK-RQF5FvEeDmS9uih/_buildManifest.js +0 -1
  188. package/temp/build/pi-desk/release/.next/static/chunks/27611-0890482fe76bd9d6.js +0 -238
  189. package/temp/build/pi-desk/release/.next/static/chunks/31087-d8e5765d541d5c26.js +0 -1
  190. package/temp/build/pi-desk/release/.next/static/chunks/3814-35cfab0ce3aa7c41.js +0 -1
  191. package/temp/build/pi-desk/release/.next/static/chunks/70669.07f3670bfb7d6dd2.js +0 -1
  192. package/temp/build/pi-desk/release/.next/static/chunks/74220-795a2e7185bd66cb.js +0 -1
  193. package/temp/build/pi-desk/release/.next/static/chunks/77592-5bef6acb8e7c4b96.js +0 -13
  194. package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +0 -1
  195. package/temp/build/pi-desk/release/.next/static/chunks/81881-f11dbe7dc0d04ed9.js +0 -1
  196. package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +0 -62
  197. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-a50c456648537afb.js +0 -3
  198. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-84892b06243891f0.js +0 -1
  199. package/temp/build/pi-desk/release/.next/static/chunks/app/page-0789c43551b29dd2.js +0 -158
  200. package/temp/build/pi-desk/release/.next/static/chunks/webpack-d7248206b7fc7ba8.js +0 -1
  201. package/temp/build/pi-desk/release/.next/static/css/a4cf28fd76c70165.css +0 -1
  202. /package/temp/build/pi-desk/release/.next/static/{RLKcK-RQF5FvEeDmS9uih → MV6_lBt7Mzmi1rBNI-3dx}/_ssgManifest.js +0 -0
@@ -1,11 +1,55 @@
1
1
  import { revealL3ProjectPath } from '@client/l3_modules/project-files/l3-project-file-actions'
2
- import { createL3ProjectReader } from '@client/l3_modules/project-files/l3-project-reader'
2
+ import {
3
+ createL3ProjectReader,
4
+ requestL3Project
5
+ } from '@client/l3_modules/project-files/l3-project-reader'
6
+ import {
7
+ L2PiDirectoryListRequestSchema,
8
+ L2PiDirectoryListResponseSchema,
9
+ L2PiDirectoryEntryListRequestSchema,
10
+ L2PiDirectoryEntryListResponseSchema,
11
+ type L2PiDirectoryListResponse,
12
+ type L2PiDirectoryEntryListResponse
13
+ } from '@common/l2_biz/pi-session/l2-pi-session-contract'
3
14
  import { L3ProjectFileRuntime } from '@client/l3_modules/project-files/l3-project-file-runtime'
4
15
  import { L3ProjectGitRuntime } from '@client/l3_modules/project-files/l3-project-git-runtime'
5
16
  import { readL3ImagePreviewMode } from '@client/l3_modules/preferences/l3-work-session-file-preferences'
6
17
  import { L2ProjectHistoryRuntime } from './runtime/l2-project-history-runtime'
7
18
  import type { L2ProjectPreviewGlobal, L2ProjectPreviewProject } from './l2-project-preview-storage'
8
19
 
20
+ export function listL2PreviewProjects(
21
+ clientId: string,
22
+ signal: AbortSignal
23
+ ): Promise<L2PiDirectoryListResponse> {
24
+ return requestL3Project(
25
+ clientId,
26
+ {
27
+ path: '/api/pi/directories/list',
28
+ request: L2PiDirectoryListRequestSchema,
29
+ response: L2PiDirectoryListResponseSchema
30
+ },
31
+ { forceRefresh: false },
32
+ signal
33
+ )
34
+ }
35
+
36
+ export function browseL2PreviewDirectory(
37
+ clientId: string,
38
+ cwd: string | null,
39
+ signal: AbortSignal
40
+ ): Promise<L2PiDirectoryEntryListResponse> {
41
+ return requestL3Project(
42
+ clientId,
43
+ {
44
+ path: '/api/pi/directory-entries/list',
45
+ request: L2PiDirectoryEntryListRequestSchema,
46
+ response: L2PiDirectoryEntryListResponseSchema
47
+ },
48
+ { cwd },
49
+ signal
50
+ )
51
+ }
52
+
9
53
  export interface L2ProjectPreviewOwner {
10
54
  files: L3ProjectFileRuntime
11
55
  git: L3ProjectGitRuntime
@@ -18,9 +62,10 @@ export function createL2ProjectPreviewOwner(
18
62
  cwd: string,
19
63
  onNotice: (message: string) => void,
20
64
  initial: L2ProjectPreviewProject | null,
21
- global: L2ProjectPreviewGlobal
65
+ global: L2ProjectPreviewGlobal,
66
+ options: { lazyHistory?: boolean; onUnauthorized?: () => void } = {}
22
67
  ): L2ProjectPreviewOwner {
23
- const reader = createL3ProjectReader(clientId, { cwd })
68
+ const reader = createL3ProjectReader(clientId, { cwd }, options.onUnauthorized)
24
69
  const files = new L3ProjectFileRuntime(cwd, reader, {
25
70
  imagePreviewMode: readL3ImagePreviewMode,
26
71
  onNotice,
@@ -42,7 +87,7 @@ export function createL2ProjectPreviewOwner(
42
87
  git.setChangesOnly(cwd, initial.changesOnly)
43
88
  for (const root of initial.expandedRepositories) git.toggleRepository(cwd, root)
44
89
  files.restoreBrowsing(initial)
45
- history.restoreBrowsing(initial.history)
90
+ history.restoreBrowsing(initial.history, !options.lazyHistory)
46
91
  },
47
92
  dispose(): void {
48
93
  history.dispose()
@@ -1,46 +1,20 @@
1
1
  'use client'
2
- import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'
2
+
3
+ import { useState } from 'react'
3
4
  import { useTranslation } from 'react-i18next'
4
- import {
5
- Columns2Icon,
6
- FileCode2Icon,
7
- FolderTreeIcon,
8
- GitBranchIcon,
9
- Maximize2Icon,
10
- Minimize2Icon,
11
- PanelBottomCloseIcon,
12
- PanelLeftCloseIcon,
13
- PanelLeftOpenIcon,
14
- RefreshCwIcon,
15
- XIcon
16
- } from 'lucide-react'
5
+ import { Maximize2Icon, Minimize2Icon, XIcon } from 'lucide-react'
17
6
  import {
18
7
  L4AppDialogRoot,
19
8
  L4AppDialogContent,
20
9
  L4AppDialogTitle,
21
10
  L4AppDialogDescription
22
11
  } from '@client/l4_foundation/ui/l4-app-dialog'
23
- import {
24
- ResizablePanelGroup,
25
- ResizablePanel,
26
- ResizableHandle
27
- } from '@client/l4_foundation/ui/shadcn/resizable'
28
12
  import { Button } from '@client/l4_foundation/ui/shadcn/button'
29
- import { L3ProjectFileBrowser } from '@client/l3_modules/project-files/l3-project-file-browser'
30
- import { L3ProjectFileTabs } from '@client/l3_modules/project-files/l3-project-file-tabs'
31
- import { L3ProjectDocumentView } from '@client/l3_modules/project-files/l3-project-document-view'
32
- import { useL4AppToast } from '@client/l4_foundation/ui/l4-app-toast'
33
- import { copyL4BrowserText } from '@client/l4_foundation/lib/l4-browser-clipboard'
34
13
  import { cn } from '@client/l4_foundation/lib/l4-utils'
35
- import { createL2ProjectPreviewOwner } from './l2-project-preview-biz'
36
- import { L2ProjectHistoryView } from './views/l2-project-history-view'
14
+ import { L2ProjectPreview } from './l2-project-preview'
37
15
  import {
38
16
  loadL2ProjectPreviewGlobal,
39
- openL2ProjectPreviewProject,
40
- saveL2ProjectPreviewGlobal,
41
- saveL2ProjectPreviewProject,
42
- type L2ProjectPreviewGlobal,
43
- type L2ProjectPreviewProject
17
+ saveL2ProjectPreviewGlobal
44
18
  } from './l2-project-preview-storage'
45
19
 
46
20
  export function L2ProjectPreviewDialog({
@@ -53,306 +27,8 @@ export function L2ProjectPreviewDialog({
53
27
  onClose: () => void
54
28
  }): React.JSX.Element {
55
29
  const { t } = useTranslation('projectHistory')
56
- const toast = useL4AppToast()
57
- const [initialGlobal] = useState(loadL2ProjectPreviewGlobal)
58
- const [global, setGlobal] = useState(initialGlobal)
59
- const updateGlobal = useCallback((change: Partial<L2ProjectPreviewGlobal>): void => {
60
- setGlobal((current) => ({ ...current, ...change }))
61
- saveL2ProjectPreviewGlobal({ ...loadL2ProjectPreviewGlobal(), ...change })
62
- }, [])
63
- const [saved] = useState(() => openL2ProjectPreviewProject(cwd))
64
- const [owner] = useState(() =>
65
- createL2ProjectPreviewOwner(
66
- clientId,
67
- cwd,
68
- (text) => toast.show({ level: 'info', title: text }),
69
- saved,
70
- initialGlobal
71
- )
72
- )
73
- const { files, git, history } = owner
74
- useSyncExternalStore(files.subscribe, files.getRevision, files.getRevision)
75
- const gitRevision = useSyncExternalStore(git.subscribe, git.getRevision, git.getRevision)
76
- useSyncExternalStore(history.subscribe, history.getRevision, history.getRevision)
77
- const { directoryOpen, gitOpen, gitMaximized, maximized } = global
78
- const [page, setPage] = useState<'files' | 'code' | 'git'>(saved?.page ?? 'files')
79
- const [size, setSize] = useState({ width: 1200, height: 800 })
80
- const [container, setContainer] = useState<HTMLDivElement | null>(null)
81
- const [historyPane, setHistoryPane] = useState<'log' | 'files'>(saved?.historyPane ?? 'log')
82
- const live = useRef<object | null>(null)
83
- const saveCurrent = useRef<() => void>(() => undefined)
84
- const narrow = size.width < 900
85
- const stacked = narrow || size.height < 680
30
+ const [maximized, setMaximized] = useState(() => loadL2ProjectPreviewGlobal().maximized)
86
31
  const projectName = cwd.replaceAll('\\', '/').split('/').filter(Boolean).at(-1) ?? cwd
87
- const gitState = git.getWorkspace(cwd)!
88
- const repositories = gitState.snapshot.repositories
89
- const state = files.state
90
- const diffPath = state.activeDiff
91
- ? [state.activeDiff.repositoryRoot, state.activeDiff.path].filter(Boolean).join('/')
92
- : null
93
- useEffect(() => {
94
- saveCurrent.current = (): void => {
95
- const currentGit = git.getWorkspace(cwd)!
96
- const currentHistory = history.state
97
- const snapshot: L2ProjectPreviewProject = {
98
- page,
99
- historyPane,
100
- tabs: [...files.state.tabs],
101
- activePath: files.state.activePath,
102
- ...files.getBrowsingDocumentSettings(),
103
- expandedPaths: [...files.state.expandedPaths].slice(0, 500),
104
- treeScrollTop: files.state.treeScrollTop,
105
- searchQuery: files.state.search.query.slice(0, 200),
106
- changesOnly: currentGit.changesOnly,
107
- expandedRepositories: [...currentGit.expandedRepositories].slice(0, 100),
108
- history: {
109
- repositoryRoot: currentHistory.repositoryRoot,
110
- mode: currentHistory.mode,
111
- tip: currentHistory.tip,
112
- base: currentHistory.base,
113
- target: currentHistory.target,
114
- strategy: currentHistory.strategy,
115
- query: currentHistory.query.slice(0, 200),
116
- author: currentHistory.author.slice(0, 200),
117
- selectedCommit: currentHistory.selectedCommit,
118
- selectedChangePath: currentHistory.selectedChangePath,
119
- logScroll: history.scroll.log,
120
- changesScroll: history.scroll.changes
121
- },
122
- activeDiff: files.state.activeDiff
123
- ? {
124
- repositoryRoot: files.state.activeDiff.repositoryRoot,
125
- path: files.state.activeDiff.path,
126
- ...(files.state.activeDiff.comparison
127
- ? { comparison: files.state.activeDiff.comparison }
128
- : {})
129
- }
130
- : null
131
- }
132
- saveL2ProjectPreviewProject(cwd, snapshot)
133
- }
134
- }, [cwd, files, git, history, historyPane, page])
135
-
136
- useEffect(() => {
137
- const token = {}
138
- live.current = token
139
- owner.restore()
140
- void files.ensureRoot()
141
- void git.ensureSnapshot(cwd)
142
- let lastFocus = Date.now()
143
- const focus = (): void => {
144
- if (Date.now() - lastFocus < 5000) return
145
- lastFocus = Date.now()
146
- void git.refreshSnapshot(cwd)
147
- }
148
- window.addEventListener('focus', focus)
149
- const saveOnLeave = (): void => saveCurrent.current()
150
- window.addEventListener('pagehide', saveOnLeave)
151
- return () => {
152
- live.current = null
153
- window.removeEventListener('focus', focus)
154
- window.removeEventListener('pagehide', saveOnLeave)
155
- queueMicrotask(() => {
156
- if (live.current === null) {
157
- saveCurrent.current()
158
- owner.dispose()
159
- }
160
- })
161
- }
162
- }, [cwd, files, git, owner])
163
- useEffect(() => {
164
- if (!container) return
165
- // Portal 的 DOM 可能晚于外层 effect;跟随实际节点绑定与释放。
166
- const observer = new ResizeObserver(() =>
167
- setSize({ width: container.clientWidth, height: container.clientHeight })
168
- )
169
- observer.observe(container)
170
- return () => observer.disconnect()
171
- }, [container])
172
- useEffect(() => {
173
- if (!gitOpen || !repositories.length) return
174
- if (repositories.some((repository) => repository.root === history.state.repositoryRoot)) return
175
- const active = state.activeDiff
176
- ? [state.activeDiff.repositoryRoot, state.activeDiff.path].filter(Boolean).join('/')
177
- : state.activePath
178
- const belonging = active
179
- ? [...repositories]
180
- .sort((left, right) => right.root.length - left.root.length)
181
- .find((repository) => !repository.root || active.startsWith(`${repository.root}/`))
182
- : null
183
- history.setRepository(
184
- belonging?.root ??
185
- repositories.find((item) => item.root === '')?.root ??
186
- repositories[0]!.root
187
- )
188
- }, [gitOpen, gitRevision, history, repositories, state.activeDiff, state.activePath])
189
-
190
- useEffect(
191
- () =>
192
- git.subscribe(() => {
193
- const treeLayout = git.getWorkspace(cwd)?.layout
194
- if (treeLayout && treeLayout !== global.treeLayout) updateGlobal({ treeLayout })
195
- }),
196
- [cwd, git, global.treeLayout, updateGlobal]
197
- )
198
-
199
- const openGit = (root?: string): void => {
200
- updateGlobal({ gitOpen: true })
201
- setPage('git')
202
- if (root !== undefined) history.setRepository(root)
203
- }
204
- const refresh = (): void => {
205
- files.refreshTree()
206
- void git.refreshSnapshot(cwd)
207
- if (state.activeDiff) files.reloadDiff()
208
- else if (state.activePath) files.reloadFile(state.activePath)
209
- if (gitOpen) history.refresh()
210
- }
211
- const fileTree = (
212
- <L3ProjectFileBrowser
213
- cwd={cwd}
214
- projectName={projectName}
215
- runtime={files}
216
- gitRuntime={git}
217
- onOpenGitBranches={openGit}
218
- onFileOpened={() => setPage('code')}
219
- />
220
- )
221
- const code = (
222
- <div className="flex size-full min-h-0 min-w-0 flex-col">
223
- <L3ProjectFileTabs
224
- cwd={cwd}
225
- gitRuntime={git}
226
- paths={state.tabs}
227
- activePath={state.activePath}
228
- diffPath={diffPath}
229
- mobile={narrow}
230
- onActivate={(path) => files.openFile(path)}
231
- onClose={(path, scope) => files.closeTabs(path, scope)}
232
- />
233
- <L3ProjectDocumentView
234
- runtime={files}
235
- gitRuntime={git}
236
- compact={size.width < 1100 || narrow}
237
- onEscape={onClose}
238
- />
239
- </div>
240
- )
241
- const gitView = (
242
- <div className="flex size-full min-h-0 min-w-0 flex-col">
243
- <div className="flex h-9 shrink-0 items-center gap-2 border-b bg-muted px-3">
244
- <GitBranchIcon className="size-4" />
245
- <span className="flex-1 text-sm font-medium">Git</span>
246
- {!stacked ? (
247
- <Button
248
- size="icon-sm"
249
- variant="ghost"
250
- aria-label={t(gitMaximized ? 'restoreGit' : 'maximizeGit')}
251
- onClick={() => updateGlobal({ gitMaximized: !gitMaximized })}
252
- >
253
- {gitMaximized ? (
254
- <Minimize2Icon className="size-4" />
255
- ) : (
256
- <Maximize2Icon className="size-4" />
257
- )}
258
- </Button>
259
- ) : null}
260
- <Button
261
- size="icon-sm"
262
- variant="ghost"
263
- aria-label={t('collapseGit')}
264
- onClick={() => {
265
- updateGlobal({ gitOpen: false, gitMaximized: false })
266
- setPage('code')
267
- }}
268
- >
269
- <PanelBottomCloseIcon className="size-4" />
270
- </Button>
271
- </div>
272
- {repositories.length ? (
273
- <L2ProjectHistoryView
274
- state={history.state}
275
- cwd={cwd}
276
- changeRows={history.getChangeRows()}
277
- onSelectChange={(path) => history.selectChange(path)}
278
- onToggleDirectory={(path) => history.toggleChangeDirectory(path)}
279
- onReveal={(path, type) => files.revealFile(path, type)}
280
- repositories={repositories}
281
- projectName={projectName}
282
- narrow={narrow}
283
- mobilePane={historyPane}
284
- onMobilePane={setHistoryPane}
285
- scroll={history.scroll}
286
- onScroll={(pane, value) => history.setScroll(pane, value)}
287
- panelSizes={{ log: global.logSize, changes: global.changeSize }}
288
- onPanelSize={(panel, value) =>
289
- updateGlobal(panel === 'log' ? { logSize: value } : { changeSize: value })
290
- }
291
- onRepository={(root) => {
292
- history.setRepository(root)
293
- setHistoryPane('log')
294
- }}
295
- onMode={(mode) => history.setMode(mode)}
296
- onRevision={(field, value) => history.setRevision(field, value)}
297
- onStrategy={(strategy) => history.setStrategy(strategy)}
298
- onQuery={(query) => history.setQuery(query)}
299
- onAuthor={(author) => history.setAuthor(author)}
300
- onCommit={(oid, parent) => history.selectCommit(oid, parent)}
301
- onAggregate={() => history.showAggregate()}
302
- onMoreLog={() => history.loadMoreLog()}
303
- onMoreChanges={() => history.loadMoreChanges()}
304
- onOpenFile={(path) => {
305
- history.openChange(path)
306
- setPage('code')
307
- updateGlobal({ gitMaximized: false })
308
- }}
309
- onRefresh={() => history.refresh()}
310
- onCopy={(text) => {
311
- void copyL4BrowserText(text).then((ok) => {
312
- if (!ok) toast.error(t('copyFailed'))
313
- })
314
- }}
315
- />
316
- ) : (
317
- <p className="p-4 text-sm text-muted-foreground">
318
- {gitState.snapshot.status === 'loading' || gitState.snapshot.status === 'idle'
319
- ? t('findingGit')
320
- : (gitState.snapshot.error ?? t('noGit'))}
321
- </p>
322
- )}
323
- </div>
324
- )
325
- const upper = (
326
- <ResizablePanelGroup
327
- key={directoryOpen ? 'tree-visible' : 'tree-hidden'}
328
- orientation="horizontal"
329
- defaultLayout={
330
- directoryOpen
331
- ? { 'project-tree': global.directorySize, 'project-code': 100 - global.directorySize }
332
- : undefined
333
- }
334
- onLayoutChanged={(layout, meta) => {
335
- if (meta.isUserInteraction && layout['project-tree'] !== undefined)
336
- updateGlobal({ directorySize: layout['project-tree'] })
337
- }}
338
- >
339
- {directoryOpen ? (
340
- <ResizablePanel
341
- id="project-tree"
342
- defaultSize={`${global.directorySize}%`}
343
- minSize="180px"
344
- maxSize="40%"
345
- >
346
- {fileTree}
347
- </ResizablePanel>
348
- ) : null}
349
- {directoryOpen ? <ResizableHandle /> : null}
350
- <ResizablePanel id="project-code" minSize="40%">
351
- {code}
352
- </ResizablePanel>
353
- </ResizablePanelGroup>
354
- )
355
-
356
32
  return (
357
33
  <L4AppDialogRoot
358
34
  open
@@ -361,7 +37,6 @@ export function L2ProjectPreviewDialog({
361
37
  }}
362
38
  >
363
39
  <L4AppDialogContent
364
- ref={setContainer}
365
40
  showCloseButton={false}
366
41
  data-testid="project-preview-dialog"
367
42
  className={cn(
@@ -370,134 +45,51 @@ export function L2ProjectPreviewDialog({
370
45
  'max-sm:h-dvh max-sm:max-h-none max-sm:w-dvw max-sm:rounded-none'
371
46
  )}
372
47
  >
373
- <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))]">
374
- {!stacked ? (
375
- <Button
376
- size="icon-sm"
377
- variant="ghost"
378
- aria-label={t(directoryOpen ? 'collapseDirectory' : 'expandDirectory')}
379
- onClick={() => updateGlobal({ directoryOpen: !directoryOpen })}
380
- >
381
- {directoryOpen ? (
382
- <PanelLeftCloseIcon className="size-4" />
383
- ) : (
384
- <PanelLeftOpenIcon className="size-4" />
385
- )}
386
- </Button>
387
- ) : null}
388
- <div className="min-w-0 flex-1">
48
+ <L4AppDialogDescription className="sr-only">
49
+ {t('previewDescription')}
50
+ </L4AppDialogDescription>
51
+ <L2ProjectPreview
52
+ clientId={clientId}
53
+ cwd={cwd}
54
+ refreshOnFocus
55
+ onEscape={onClose}
56
+ title={
389
57
  <L4AppDialogTitle className="truncate" title={cwd}>
390
58
  {projectName}
391
59
  </L4AppDialogTitle>
392
- <p className="truncate text-xs text-muted-foreground" title={cwd}>
393
- {cwd}
394
- </p>
395
- </div>
396
- <L4AppDialogDescription className="sr-only">
397
- {t('previewDescription')}
398
- </L4AppDialogDescription>
399
- <Button
400
- size="sm"
401
- variant={gitOpen ? 'secondary' : 'ghost'}
402
- aria-pressed={gitOpen}
403
- onClick={() => openGit()}
404
- >
405
- <GitBranchIcon className="size-4" />
406
- <span className="max-sm:hidden">{t('gitRecords')}</span>
407
- </Button>
408
- <Button size="icon-sm" variant="ghost" aria-label={t('refreshPreview')} onClick={refresh}>
409
- <RefreshCwIcon className="size-4" />
410
- </Button>
411
- {!narrow ? (
412
- <Button
413
- size="icon-sm"
414
- variant="ghost"
415
- aria-label={t(maximized ? 'restoreWindow' : 'maximizeWindow')}
416
- onClick={() => updateGlobal({ maximized: !maximized })}
417
- >
418
- {maximized ? (
419
- <Minimize2Icon className="size-4" />
420
- ) : (
421
- <Maximize2Icon className="size-4" />
422
- )}
423
- </Button>
424
- ) : null}
425
- <Button size="icon-sm" variant="ghost" aria-label={t('closePreview')} onClick={onClose}>
426
- <XIcon className="size-4" />
427
- </Button>
428
- </header>
429
- {gitState.snapshot.error ? (
430
- <p role="alert" className="shrink-0 border-b px-3 py-2 text-sm text-destructive">
431
- {gitState.snapshot.error}
432
- </p>
433
- ) : null}
434
- {stacked ? (
435
- <>
436
- <div
437
- className="flex shrink-0 gap-1 border-b px-2 py-1"
438
- role="group"
439
- aria-label={t('previewArea')}
440
- >
441
- <Button
442
- size="sm"
443
- variant={page === 'files' ? 'secondary' : 'ghost'}
444
- onClick={() => setPage('files')}
445
- >
446
- <FolderTreeIcon className="size-4" />
447
- {t('files')}
448
- </Button>
60
+ }
61
+ actions={
62
+ <>
449
63
  <Button
450
- size="sm"
451
- variant={page === 'code' ? 'secondary' : 'ghost'}
452
- onClick={() => setPage('code')}
64
+ size="icon-sm"
65
+ variant="ghost"
66
+ className="max-md:hidden"
67
+ aria-label={t(maximized ? 'restoreWindow' : 'maximizeWindow')}
68
+ onClick={() => {
69
+ setMaximized(!maximized)
70
+ saveL2ProjectPreviewGlobal({
71
+ ...loadL2ProjectPreviewGlobal(),
72
+ maximized: !maximized
73
+ })
74
+ }}
453
75
  >
454
- <FileCode2Icon className="size-4" />
455
- {t('code')}
76
+ {maximized ? (
77
+ <Minimize2Icon className="size-4" />
78
+ ) : (
79
+ <Maximize2Icon className="size-4" />
80
+ )}
456
81
  </Button>
457
82
  <Button
458
- size="sm"
459
- variant={page === 'git' ? 'secondary' : 'ghost'}
460
- onClick={() => openGit()}
83
+ size="icon-sm"
84
+ variant="ghost"
85
+ aria-label={t('closePreview')}
86
+ onClick={onClose}
461
87
  >
462
- <Columns2Icon className="size-4" />
463
- Git
88
+ <XIcon className="size-4" />
464
89
  </Button>
465
- </div>
466
- <div className="min-h-0 flex-1">
467
- {page === 'files' ? fileTree : page === 'git' ? gitView : code}
468
- </div>
469
- </>
470
- ) : gitMaximized && gitOpen ? (
471
- gitView
472
- ) : (
473
- <ResizablePanelGroup
474
- key={gitOpen ? 'git-visible' : 'git-hidden'}
475
- orientation="vertical"
476
- defaultLayout={
477
- gitOpen
478
- ? { 'project-main': 100 - global.gitSize, 'project-git': global.gitSize }
479
- : undefined
480
- }
481
- onLayoutChanged={(layout, meta) => {
482
- if (meta.isUserInteraction && layout['project-git'] !== undefined)
483
- updateGlobal({ gitSize: layout['project-git'] })
484
- }}
485
- >
486
- <ResizablePanel
487
- id="project-main"
488
- defaultSize={gitOpen ? `${100 - global.gitSize}%` : '100%'}
489
- minSize="35%"
490
- >
491
- {upper}
492
- </ResizablePanel>
493
- {gitOpen ? <ResizableHandle /> : null}
494
- {gitOpen ? (
495
- <ResizablePanel id="project-git" defaultSize={`${global.gitSize}%`} minSize="300px">
496
- {gitView}
497
- </ResizablePanel>
498
- ) : null}
499
- </ResizablePanelGroup>
500
- )}
90
+ </>
91
+ }
92
+ />
501
93
  </L4AppDialogContent>
502
94
  </L4AppDialogRoot>
503
95
  )
@@ -0,0 +1,61 @@
1
+ 'use client'
2
+
3
+ import { FolderOpenIcon } from 'lucide-react'
4
+ import { useTranslation } from 'react-i18next'
5
+ import { Button } from '@client/l4_foundation/ui/shadcn/button'
6
+ import { L2ProjectPreview } from './l2-project-preview'
7
+ import { useL2ProjectPicker } from './hooks/l2-use-project-picker'
8
+ import { L2ProjectPickerView } from './views/l2-project-picker'
9
+
10
+ function ProjectPicker({
11
+ clientId,
12
+ onSelect,
13
+ onUnauthorized
14
+ }: {
15
+ clientId: string
16
+ onSelect: (cwd: string) => void
17
+ onUnauthorized: () => void
18
+ }): React.JSX.Element {
19
+ const picker = useL2ProjectPicker(clientId, onUnauthorized)
20
+ return <L2ProjectPickerView picker={picker} onSelect={onSelect} />
21
+ }
22
+
23
+ export function L2ProjectPreviewPage({
24
+ clientId,
25
+ cwd,
26
+ onSelect,
27
+ onUnauthorized
28
+ }: {
29
+ clientId: string
30
+ cwd: string | null
31
+ onSelect: (cwd: string | null) => void
32
+ onUnauthorized: () => void
33
+ }): React.JSX.Element {
34
+ const { t } = useTranslation('projectHistory')
35
+ return (
36
+ <main
37
+ className="flex h-dvh min-w-0 flex-col overflow-hidden bg-background text-foreground"
38
+ data-testid="project-preview-page"
39
+ >
40
+ {cwd ? (
41
+ <L2ProjectPreview
42
+ key={cwd}
43
+ clientId={clientId}
44
+ cwd={cwd}
45
+ refreshOnFocus={false}
46
+ onUnauthorized={onUnauthorized}
47
+ actions={
48
+ <Button size="sm" variant="outline" onClick={() => onSelect(null)}>
49
+ <FolderOpenIcon className="size-4" />
50
+ <span>{t('switchProject')}</span>
51
+ </Button>
52
+ }
53
+ />
54
+ ) : (
55
+ <div className="min-h-0 flex-1 overflow-auto">
56
+ <ProjectPicker clientId={clientId} onSelect={onSelect} onUnauthorized={onUnauthorized} />
57
+ </div>
58
+ )}
59
+ </main>
60
+ )
61
+ }