@jetcrab/pi-desk 1.1.18-dev → 1.1.21

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 (250) hide show
  1. package/docs/plugins.md +82 -2
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/src/app/page.tsx +6 -1
  5. package/src/app/project-preview/page.tsx +10 -0
  6. package/src/client/l1_entry/pages/l1-home-page.tsx +30 -0
  7. package/src/client/l1_entry/pages/l1-project-preview-page.tsx +33 -0
  8. package/src/client/l1_entry/providers/l1-app-providers.tsx +18 -15
  9. package/src/client/l1_entry/providers/l1-app-runtime-provider.tsx +2 -2
  10. package/src/client/l2_biz/auth/l2-login-biz.ts +3 -0
  11. package/src/client/l2_biz/auth/l2-login.tsx +2 -3
  12. package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +6 -1
  13. package/src/client/l2_biz/plugin-host/l2-plugin-contribution-host.tsx +71 -10
  14. package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +9 -0
  15. package/src/client/l2_biz/plugin-host/l2-plugin-session-header-layout.ts +18 -0
  16. package/src/client/l2_biz/plugin-host/l2-plugin-session-header.tsx +247 -0
  17. package/src/client/l2_biz/plugin-host/l2-plugin-widgets.tsx +50 -0
  18. package/src/client/l2_biz/plugin-host/views/l2-plugin-session-header-action.tsx +112 -0
  19. package/src/client/l2_biz/project-preview/hooks/l2-use-project-picker.ts +76 -0
  20. package/src/client/l2_biz/project-preview/hooks/l2-use-project-preview.ts +214 -0
  21. package/src/client/l2_biz/project-preview/l2-project-preview-biz.ts +49 -4
  22. package/src/client/l2_biz/project-preview/l2-project-preview-dialog.tsx +42 -450
  23. package/src/client/l2_biz/project-preview/l2-project-preview-page.tsx +61 -0
  24. package/src/client/l2_biz/project-preview/l2-project-preview.tsx +342 -0
  25. package/src/client/l2_biz/project-preview/runtime/l2-project-history-runtime.ts +9 -3
  26. package/src/client/l2_biz/project-preview/views/l2-project-picker.tsx +168 -0
  27. package/src/client/l2_biz/settings/hooks/l2-use-model-settings.ts +34 -31
  28. package/src/client/l2_biz/settings/views/l2-model-replacement-preview.tsx +7 -1
  29. package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-runtime.ts +1 -1
  30. package/src/client/l2_biz/workbench/file-workspace/l2-project-preview-biz.ts +4 -3
  31. package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-reload.ts +42 -0
  32. package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +28 -21
  33. package/src/client/l2_biz/workbench/l2-work-session-columns.module.css +32 -0
  34. package/src/client/l2_biz/workbench/l2-work-session-columns.tsx +55 -16
  35. package/src/client/l2_biz/workbench/l2-work-session-sidebar.tsx +38 -9
  36. package/src/client/l2_biz/workbench/l2-workbench-chat-composer.tsx +12 -1
  37. package/src/client/l2_biz/workbench/l2-workbench-chat.ts +45 -0
  38. package/src/client/l2_biz/workbench/l2-workbench-initialization-notice.tsx +8 -3
  39. package/src/client/l2_biz/workbench/l2-workbench-plugin-slots.ts +13 -0
  40. package/src/client/l2_biz/workbench/l2-workbench.tsx +19 -2
  41. package/src/client/l3_modules/conversation/l3-conversation-markdown.tsx +10 -1
  42. package/src/client/l3_modules/conversation/l3-conversation-viewport.tsx +12 -6
  43. package/src/client/l3_modules/project-files/l3-markdown-preview.module.css +1 -1
  44. package/src/client/l3_modules/project-files/l3-markdown-preview.tsx +10 -1
  45. package/src/client/l3_modules/project-files/l3-project-document-git.ts +4 -3
  46. package/src/client/l3_modules/project-files/l3-project-document-view.tsx +6 -1
  47. package/src/client/l3_modules/project-files/l3-project-file-browser.tsx +171 -229
  48. package/src/client/l3_modules/project-files/l3-project-file-runtime.ts +133 -70
  49. package/src/client/l3_modules/project-files/l3-project-reader.ts +26 -37
  50. package/src/client/l3_modules/project-files/views/l3-project-search-controls.tsx +201 -0
  51. package/src/client/l3_modules/project-files/views/l3-project-search-results.tsx +333 -0
  52. package/src/client/l4_foundation/lib/l4-client-id.tsx +11 -0
  53. package/src/client/l4_foundation/plugin-host/l4-plugin-host-runtime.ts +316 -35
  54. package/src/client/l4_foundation/ui/code/l4-code-preview.tsx +47 -3
  55. package/src/client/l4_foundation/ui/markdown/l4-markdown-remark.ts +89 -0
  56. package/src/client/l4_foundation/ui/markdown/l4-markdown.module.css +58 -0
  57. package/src/client/l4_foundation/ui/markdown/l4-markdown.tsx +108 -0
  58. package/src/common/l2_biz/auth/l2-auth-return-to.ts +41 -0
  59. package/src/common/l2_biz/model-settings/l2-model-settings-contract.ts +5 -1
  60. package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +4 -0
  61. package/src/common/l2_biz/project-preview/l2-project-history-messages.ts +28 -0
  62. package/src/common/l2_biz/project-preview/l2-project-preview-navigation.ts +5 -0
  63. package/src/common/l2_biz/settings/l2-settings-messages.ts +4 -3
  64. package/src/common/l2_biz/work-session/l2-work-session-file-contract.ts +4 -3
  65. package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +8 -0
  66. package/src/common/l3_modules/project-files/l3-project-files-contract.ts +2 -3
  67. package/src/common/l3_modules/project-files/l3-project-files-locale-messages.ts +56 -10
  68. package/src/common/l4_foundation/file/l4-project-file-contract.ts +51 -1
  69. package/src/common/l4_foundation/locale/l4-host-error-messages.ts +11 -0
  70. package/src/common/l4_foundation/locale/l4-locale-messages.ts +16 -0
  71. package/src/instrumentation.ts +0 -2
  72. package/src/server/l1_entry/api/l1-project-read-routes.ts +6 -4
  73. package/src/server/l1_entry/api/l1-project-route.ts +9 -8
  74. package/src/server/l1_entry/api/l1-work-session-file-error.ts +23 -0
  75. package/src/server/l1_entry/node/l1-server.ts +8 -4
  76. package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +0 -3
  77. package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +30 -58
  78. package/src/server/l2_biz/work-session/l2-work-session-manage.ts +0 -4
  79. package/src/server/l4_foundation/file/l4-project-file-index.ts +276 -0
  80. package/src/server/l4_foundation/file/l4-project-search.ts +399 -0
  81. package/src/server/l4_foundation/file/l4-ripgrep-search.ts +192 -0
  82. package/src/server/l4_foundation/file/l4-work-session-project-file.ts +8 -332
  83. package/src/server/l4_foundation/model-settings/l4-model-settings-store.ts +29 -20
  84. package/src/server/l4_foundation/model-settings/l4-model-settings-types.ts +1 -1
  85. package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +22 -5
  86. package/src/server/l4_foundation/pi/l4-pi-package-maintenance.ts +0 -6
  87. package/src/server/l4_foundation/pi/l4-pi-plugin-capability-probe.ts +1 -3
  88. package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +133 -13
  89. package/src/server/l4_foundation/pi/l4-pi-plugin-probe.ts +1 -3
  90. package/src/server/l4_foundation/pi/l4-pi-runner-process.ts +2 -2
  91. package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +9 -0
  92. package/src/server/l4_foundation/process/l4-process-tree.js +23 -4
  93. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  94. package/temp/build/pi-desk/release/.next/app-path-routes-manifest.json +3 -2
  95. package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
  96. package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
  97. package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -2
  98. package/temp/build/pi-desk/release/.next/required-server-files.js +4 -3
  99. package/temp/build/pi-desk/release/.next/required-server-files.json +4 -3
  100. package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
  101. package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
  102. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  103. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  104. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
  105. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
  106. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
  107. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  108. package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
  109. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  110. package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +1 -1
  111. package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
  112. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
  113. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
  114. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
  115. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
  116. package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
  117. package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
  118. package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
  119. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  120. package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
  121. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
  122. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
  123. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
  124. package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
  125. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
  126. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -1
  127. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
  128. package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
  129. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
  130. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
  131. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
  132. package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
  133. package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
  134. package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
  135. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
  136. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
  137. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
  138. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
  139. package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
  140. package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
  141. package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
  142. package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
  143. package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
  144. package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
  145. package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
  146. package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
  147. package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
  148. package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
  149. package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
  150. package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +2 -1
  151. package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +2 -1
  152. package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
  153. package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +2 -1
  154. package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +2 -1
  155. package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +2 -1
  156. package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +2 -1
  157. package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -1
  158. package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +2 -1
  159. package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -1
  160. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
  161. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
  162. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
  163. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
  164. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
  165. package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
  166. package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
  167. package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
  168. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
  169. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
  170. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
  171. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
  172. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
  173. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
  174. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
  175. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
  176. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
  177. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/sort/route.js +1 -1
  178. package/temp/build/pi-desk/release/.next/server/app/favicon.ico/route.js +1 -1
  179. package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
  180. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  181. package/temp/build/pi-desk/release/.next/server/app/page.js +72 -72
  182. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  183. package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -0
  184. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -0
  185. package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +3 -2
  186. package/temp/build/pi-desk/release/.next/server/chunks/10912.js +2 -2
  187. package/temp/build/pi-desk/release/.next/server/chunks/20460.js +15 -0
  188. package/temp/build/pi-desk/release/.next/server/chunks/23054.js +1 -1
  189. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +10 -10
  190. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +3 -17
  191. package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
  192. package/temp/build/pi-desk/release/.next/server/chunks/48180.js +1 -1
  193. package/temp/build/pi-desk/release/.next/server/chunks/50520.js +2 -4
  194. package/temp/build/pi-desk/release/.next/server/chunks/56830.js +235 -0
  195. package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
  196. package/temp/build/pi-desk/release/.next/server/chunks/70780.js +1 -0
  197. package/temp/build/pi-desk/release/.next/server/chunks/74528.js +1 -1
  198. package/temp/build/pi-desk/release/.next/server/chunks/78262.js +1 -1
  199. package/temp/build/pi-desk/release/.next/server/chunks/79686.js +62 -0
  200. package/temp/build/pi-desk/release/.next/server/chunks/80780.js +1 -0
  201. package/temp/build/pi-desk/release/.next/server/chunks/81363.js +1 -1
  202. package/temp/build/pi-desk/release/.next/server/chunks/82499.js +4 -4
  203. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +8 -8
  204. package/temp/build/pi-desk/release/.next/server/instrumentation.js +1 -1
  205. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  206. package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
  207. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  208. package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
  209. package/temp/build/pi-desk/release/.next/static/8Yd33M0zPi4tbRn4xCaRY/_buildManifest.js +1 -0
  210. package/temp/build/pi-desk/release/.next/static/chunks/11937-1d0a9822f2c7995b.js +1 -0
  211. package/temp/build/pi-desk/release/.next/static/chunks/14825-5579aed34a631ad7.js +62 -0
  212. package/temp/build/pi-desk/release/.next/static/chunks/15302-9c807afc6c60de0c.js +1 -0
  213. package/temp/build/pi-desk/release/.next/static/chunks/19991.8aa2e4b6df15fad6.js +1 -0
  214. package/temp/build/pi-desk/release/.next/static/chunks/22092-6d1bfbefa825ec03.js +1 -0
  215. package/temp/build/pi-desk/release/.next/static/chunks/37313-d5970e2d0c270d22.js +238 -0
  216. package/temp/build/pi-desk/release/.next/static/chunks/{49516-cc4dcd84d64ab4fe.js → 51474-8dc714cb76b2e60a.js} +2 -2
  217. package/temp/build/pi-desk/release/.next/static/chunks/57235-03ba1044f22b6c84.js +1 -0
  218. package/temp/build/pi-desk/release/.next/static/chunks/79267-7d21d7cdbd7cfca1.js +1 -0
  219. package/temp/build/pi-desk/release/.next/static/chunks/82371-f7ba4a07a5dbcf34.js +1 -0
  220. package/temp/build/pi-desk/release/.next/static/chunks/9038-03144ed96d613f75.js +15 -0
  221. package/temp/build/pi-desk/release/.next/static/chunks/app/{error-0296a3b20cd1bf00.js → error-216b14b5da089ed3.js} +1 -1
  222. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-20718f1503910291.js +1 -0
  223. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-0d3d4827802108e5.js +1 -0
  224. package/temp/build/pi-desk/release/.next/static/chunks/app/{not-found-2aab8b33ebbf3257.js → not-found-720cdf6a606b821a.js} +1 -1
  225. package/temp/build/pi-desk/release/.next/static/chunks/app/page-6ebc1c877f6025dd.js +158 -0
  226. package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-d70ee4e9409b970a.js +1 -0
  227. package/temp/build/pi-desk/release/.next/static/chunks/webpack-b60891a214e0925d.js +1 -0
  228. package/temp/build/pi-desk/release/.next/static/css/1e780a4936aa6ba3.css +1 -0
  229. package/temp/build/pi-desk/release/.next/static/css/6dbae2922f88ca31.css +1 -0
  230. package/temp/build/pi-desk/release/.next/static/css/f646ca6c07245c4c.css +1 -0
  231. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  232. package/temp/build/pi-desk/release/.next/server/chunks/33658.js +0 -296
  233. package/temp/build/pi-desk/release/.next/server/chunks/8584.js +0 -2
  234. package/temp/build/pi-desk/release/.next/static/chunks/31087-ba426d09d6f68bfd.js +0 -1
  235. package/temp/build/pi-desk/release/.next/static/chunks/3814-35cfab0ce3aa7c41.js +0 -1
  236. package/temp/build/pi-desk/release/.next/static/chunks/39654-b5fedd762ea0312e.js +0 -238
  237. package/temp/build/pi-desk/release/.next/static/chunks/70669.07f3670bfb7d6dd2.js +0 -1
  238. package/temp/build/pi-desk/release/.next/static/chunks/74220-795a2e7185bd66cb.js +0 -1
  239. package/temp/build/pi-desk/release/.next/static/chunks/75130-d2ca74894aff8085.js +0 -13
  240. package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +0 -1
  241. package/temp/build/pi-desk/release/.next/static/chunks/81881-f11dbe7dc0d04ed9.js +0 -1
  242. package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +0 -62
  243. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-c0e1c5cef8620cf8.js +0 -3
  244. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-84892b06243891f0.js +0 -1
  245. package/temp/build/pi-desk/release/.next/static/chunks/app/page-a984139e99586041.js +0 -158
  246. package/temp/build/pi-desk/release/.next/static/chunks/webpack-40a782fc8a3535b9.js +0 -1
  247. package/temp/build/pi-desk/release/.next/static/css/6427d02b6edf406f.css +0 -1
  248. package/temp/build/pi-desk/release/.next/static/css/79e06f05df9edff0.css +0 -1
  249. package/temp/build/pi-desk/release/.next/static/n2LrXQgq8UkbH_AzhR3fw/_buildManifest.js +0 -1
  250. /package/temp/build/pi-desk/release/.next/static/{n2LrXQgq8UkbH_AzhR3fw → 8Yd33M0zPi4tbRn4xCaRY}/_ssgManifest.js +0 -0
@@ -10,6 +10,12 @@ import { MessageResponse } from '@client/l4_foundation/ui/ai-elements/message'
10
10
  import { copyL4BrowserText } from '@client/l4_foundation/lib/l4-browser-clipboard'
11
11
  import { cn } from '@client/l4_foundation/lib/l4-utils'
12
12
  import { L4ImageViewer } from '@client/l4_foundation/ui/l4-image-viewer'
13
+ import {
14
+ L4_MARKDOWN_ALLOWED_TAGS,
15
+ L4_MARKDOWN_CLASS_NAME,
16
+ L4_MARKDOWN_COMPONENTS,
17
+ L4_MARKDOWN_REMARK_PLUGINS
18
+ } from '@client/l4_foundation/ui/markdown/l4-markdown'
13
19
  import { useL4AppToast } from '@client/l4_foundation/ui/l4-app-toast'
14
20
  import styles from './l3-conversation-markdown.module.css'
15
21
 
@@ -140,11 +146,14 @@ export function L3ConversationMarkdown({
140
146
  return (
141
147
  <div className="contents" onClickCapture={handleCodeCopyFallback}>
142
148
  <MessageResponse
143
- className={cn('space-y-0', styles.markdown, className)}
149
+ className={cn('space-y-0', styles.markdown, L4_MARKDOWN_CLASS_NAME, className)}
144
150
  controls={COMPACT_CODE_CONTROLS}
145
151
  isAnimating={isAnimating}
146
152
  lineNumbers={false}
153
+ allowedTags={L4_MARKDOWN_ALLOWED_TAGS}
154
+ remarkPlugins={L4_MARKDOWN_REMARK_PLUGINS}
147
155
  components={{
156
+ ...L4_MARKDOWN_COMPONENTS,
148
157
  ...components,
149
158
  a: L3ConversationMarkdownLink,
150
159
  img: L3ConversationMarkdownImage
@@ -157,9 +157,11 @@ export function positionL3ConversationMessage(
157
157
  offset,
158
158
  addTailSpace
159
159
  })
160
- clearL3ConversationTailSpacer(scrollElement)
161
-
162
160
  const turn = addTailSpace ? logicalTailTurn(scrollElement) : null
161
+ // 同一轮的定位空间可直接重算,先清空会让浏览器截短 scrollTop。
162
+ if (!turn || activeTailTurn(scrollElement) !== turn) {
163
+ clearL3ConversationTailSpacer(scrollElement)
164
+ }
163
165
  if (!turn) {
164
166
  scrollElement.scrollTop = Math.max(
165
167
  0,
@@ -177,11 +179,15 @@ export function positionL3ConversationMessage(
177
179
  }
178
180
 
179
181
  const { targetScrollTop, minHeight } = measureTailPosition(scrollElement, target, turn, offset)
180
- turn.style.minHeight = `${Math.ceil(minHeight)}px`
181
- turn.dataset.chatTailSpace = ''
182
+ const nextMinHeight = `${Math.ceil(minHeight)}px`
183
+ if (turn.style.minHeight !== nextMinHeight) turn.style.minHeight = nextMinHeight
184
+ if (!turn.hasAttribute('data-chat-tail-space')) turn.dataset.chatTailSpace = ''
182
185
  const anchor = target.dataset.chatScrollAnchor
183
- if (anchor) turn.dataset.chatTailAnchor = anchor
184
- turn.dataset.chatTailOffset = String(offset)
186
+ if (turn.dataset.chatTailAnchor !== anchor) {
187
+ if (anchor) turn.dataset.chatTailAnchor = anchor
188
+ else delete turn.dataset.chatTailAnchor
189
+ }
190
+ if (turn.dataset.chatTailOffset !== String(offset)) turn.dataset.chatTailOffset = String(offset)
185
191
  scrollElement.scrollTop = targetScrollTop
186
192
  logL3ConversationScroll('position:end', scrollElement, {
187
193
  target: target.dataset.chatScrollAnchor ?? null,
@@ -1,7 +1,7 @@
1
1
  .document {
2
2
  box-sizing: border-box;
3
3
  width: 100%;
4
- max-width: 80ch;
4
+ max-width: 1200px;
5
5
  min-width: 0;
6
6
  margin-inline: auto;
7
7
  padding: 1.5rem;
@@ -4,6 +4,12 @@ import { cjk } from '@streamdown/cjk'
4
4
  import { code } from '@streamdown/code'
5
5
  import { memo, useLayoutEffect, useRef } from 'react'
6
6
  import { Streamdown } from 'streamdown'
7
+ import {
8
+ L4_MARKDOWN_ALLOWED_TAGS,
9
+ L4_MARKDOWN_CLASS_NAME,
10
+ L4_MARKDOWN_COMPONENTS,
11
+ L4_MARKDOWN_REMARK_PLUGINS
12
+ } from '@client/l4_foundation/ui/markdown/l4-markdown'
7
13
  import styles from './l3-markdown-preview.module.css'
8
14
 
9
15
  const plugins = { cjk, code }
@@ -37,7 +43,10 @@ export const L3MarkdownPreview = memo(function L3MarkdownPreview({
37
43
  onScroll={(event) => onScroll(event.currentTarget.scrollTop)}
38
44
  >
39
45
  <Streamdown
40
- className={styles.document}
46
+ className={`${styles.document} ${L4_MARKDOWN_CLASS_NAME}`}
47
+ allowedTags={L4_MARKDOWN_ALLOWED_TAGS}
48
+ components={L4_MARKDOWN_COMPONENTS}
49
+ remarkPlugins={L4_MARKDOWN_REMARK_PLUGINS}
41
50
  mode="static"
42
51
  parseIncompleteMarkdown={false}
43
52
  isAnimating={false}
@@ -10,7 +10,8 @@ export function useL3ProjectDocumentGit(
10
10
  git: L3ProjectGitBrowser,
11
11
  host: RefObject<HTMLDivElement | null>,
12
12
  savePosition: () => void,
13
- active: boolean
13
+ active: boolean,
14
+ refreshOnFocus = true
14
15
  ): { refresh: () => void; unavailable: boolean } {
15
16
  useSyncExternalStore(git.subscribe, git.getRevision, git.getRevision)
16
17
  const snapshot = git.getWorkspace(runtime.cwd)?.snapshot
@@ -29,7 +30,7 @@ export function useL3ProjectDocumentGit(
29
30
  }, [diffActive, git, path, runtime])
30
31
 
31
32
  useEffect(() => {
32
- if (!active || !path || diffActive) return
33
+ if (!refreshOnFocus || !active || !path || diffActive) return
33
34
  const visible = (): boolean =>
34
35
  window.document.visibilityState === 'visible' &&
35
36
  Boolean(host.current?.getClientRects().length) &&
@@ -54,7 +55,7 @@ export function useL3ProjectDocumentGit(
54
55
  window.removeEventListener('focus', onFocus)
55
56
  window.document.removeEventListener('visibilitychange', onFocus)
56
57
  }
57
- }, [active, diffActive, git, host, path, refresh, runtime])
58
+ }, [active, diffActive, git, host, path, refresh, refreshOnFocus, runtime])
58
59
 
59
60
  useEffect(() => {
60
61
  if (!active || !path || diffActive || document?.status !== 'text') return
@@ -53,6 +53,7 @@ export function L3ProjectDocumentView({
53
53
  gitRuntime,
54
54
  compact,
55
55
  visible = true,
56
+ refreshOnFocus = true,
56
57
  onEscape,
57
58
  onSwitchPane
58
59
  }: {
@@ -61,6 +62,7 @@ export function L3ProjectDocumentView({
61
62
  gitRuntime: L3ProjectGitBrowser
62
63
  compact: boolean
63
64
  visible?: boolean
65
+ refreshOnFocus?: boolean
64
66
  onEscape: () => void
65
67
  onSwitchPane?: () => void
66
68
  }): React.JSX.Element {
@@ -107,7 +109,8 @@ export function L3ProjectDocumentView({
107
109
  gitRuntime,
108
110
  hostRef,
109
111
  savePosition,
110
- visible
112
+ visible,
113
+ refreshOnFocus
111
114
  )
112
115
  const comparison = document?.status === 'text' ? document.comparison : undefined
113
116
  const [computed, setComputed] = useState<{
@@ -418,6 +421,8 @@ export function L3ProjectDocumentView({
418
421
  }}
419
422
  onOpenDiff={openDiff}
420
423
  savedState={document.editorState}
424
+ navigation={runtime.pendingNavigation}
425
+ onNavigation={(target, exists) => runtime.consumeNavigation(target, exists)}
421
426
  onSaveState={(path, state) => runtime.saveEditorState(path, state)}
422
427
  onEscape={escape}
423
428
  />
@@ -12,10 +12,9 @@ import {
12
12
  ImageIcon,
13
13
  ListFilterIcon,
14
14
  LoaderCircleIcon,
15
- SearchIcon,
16
- XIcon
15
+ SearchIcon
17
16
  } from 'lucide-react'
18
- import { useEffect, useRef, useState, useSyncExternalStore } from 'react'
17
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'
19
18
  import { useTranslation } from 'react-i18next'
20
19
  import type { L4GitRepository } from '@common/l4_foundation/git/l4-git-contract'
21
20
  import { selectL4LocalizedText } from '@common/l4_foundation/locale/l4-localized-text'
@@ -30,12 +29,8 @@ import {
30
29
  } from '@client/l4_foundation/ui/ai-elements/file-tree'
31
30
  import { L4ScrollArea } from '@client/l4_foundation/ui/l4-scroll-area'
32
31
  import { buttonVariants } from '@client/l4_foundation/ui/shadcn/button'
33
- import {
34
- InputGroup,
35
- InputGroupAddon,
36
- InputGroupButton,
37
- InputGroupInput
38
- } from '@client/l4_foundation/ui/shadcn/input-group'
32
+ import { L3ProjectSearchControls } from './views/l3-project-search-controls'
33
+ import { L3ProjectSearchResults } from './views/l3-project-search-results'
39
34
  import type { L3ProjectGitBrowser } from './l3-project-git-runtime'
40
35
  import {
41
36
  getL3GitChangeBadge,
@@ -69,6 +64,7 @@ interface L3ProjectFileBrowserProps {
69
64
  }
70
65
 
71
66
  const EMPTY_GIT_REPOSITORIES: readonly L4GitRepository[] = []
67
+ const composingSearches = new WeakMap<L3ProjectFileRuntime, object>()
72
68
 
73
69
  function compactPath(path: string): string {
74
70
  const parts = path.replaceAll('\\', '/').split('/').filter(Boolean)
@@ -169,8 +165,26 @@ export function L3ProjectFileBrowser({
169
165
  const gitWorkspace = gitRuntime.getWorkspace(cwd)
170
166
  const scrollRef = useRef<HTMLDivElement>(null)
171
167
  const searchInputRef = useRef<HTMLInputElement>(null)
168
+ const searchResultsRef = useRef<HTMLDivElement>(null)
169
+ const searchButtonRef = useRef<HTMLButtonElement>(null)
170
+ const [composing, setComposing] = useState(false)
171
+ const compositionOwner = useRef<object>({})
172
+ const onComposingChange = useCallback(
173
+ (active: boolean): void => {
174
+ if (active) composingSearches.set(runtime, compositionOwner.current)
175
+ else if (composingSearches.get(runtime) === compositionOwner.current)
176
+ composingSearches.delete(runtime)
177
+ setComposing(active)
178
+ },
179
+ [runtime]
180
+ )
172
181
  const searchOpen = workspace.searchOpen
173
182
  const setSearchOpen = (open: boolean): void => runtime.setSearchOpen(open)
183
+ const closeSearch = (): void => {
184
+ setComposing(false)
185
+ setSearchOpen(false)
186
+ requestAnimationFrame(() => searchButtonRef.current?.focus())
187
+ }
174
188
  const [visibleEntries, setVisibleEntries] = useState<Record<string, number>>({})
175
189
  const moreEntries = (key: string, total: number): React.ReactNode => {
176
190
  const count = visibleEntries[key] ?? 300
@@ -200,15 +214,17 @@ export function L3ProjectFileBrowser({
200
214
 
201
215
  useEffect(() => {
202
216
  const scrollElement = scrollRef.current
203
- if (!scrollElement || !workspace) return
217
+ if (!scrollElement || searchOpen) return
204
218
  scrollElement.scrollTop = workspace.treeScrollTop
205
- }, [workspace])
219
+ }, [searchOpen, workspace])
206
220
 
207
221
  useEffect(() => {
208
- if (!searchQuery.trim() || gitWorkspace?.changesOnly) return
209
- const timer = window.setTimeout(() => void runtime.search(searchQuery), 220)
222
+ if (!searchOpen || !searchQuery.trim() || composing) return
223
+ const timer = window.setTimeout(() => {
224
+ if (!composingSearches.has(runtime)) void runtime.search(searchQuery)
225
+ }, 220)
210
226
  return () => window.clearTimeout(timer)
211
- }, [gitWorkspace?.changesOnly, runtime, searchQuery])
227
+ }, [composing, runtime, searchOpen, searchQuery])
212
228
 
213
229
  const expandedRepositoryRootsKey = [...(gitWorkspace?.expandedRepositories ?? [])].join('\u0000')
214
230
  useEffect(() => {
@@ -242,11 +258,20 @@ export function L3ProjectFileBrowser({
242
258
  toggleDirectory(directory, keys.has(keyL3ProjectTree('directory', path)))
243
259
  }
244
260
 
245
- const openPreview = (path: string): void => {
246
- runtime.openFile(path)
261
+ const openPreview = (path: string, line?: number): void => {
262
+ runtime.openFile(path, line)
247
263
  onFileOpened?.()
248
264
  }
249
265
 
266
+ const openFirstSearchResult = (): void => {
267
+ const search = runtime.state.search
268
+ if (search.status !== 'ready') return
269
+ const firstFile = search.fileMatches[0]
270
+ const firstLine = search.contentMatches[0]
271
+ if (firstFile) openPreview(firstFile.path)
272
+ else if (firstLine) openPreview(firstLine.path, firstLine.line)
273
+ }
274
+
250
275
  const openDiff = (change: L3GitProjectChange): void => {
251
276
  runtime.openDiff(change.repositoryRoot, change.path)
252
277
  onFileOpened?.()
@@ -271,16 +296,17 @@ export function L3ProjectFileBrowser({
271
296
  path: string,
272
297
  trigger: React.ReactElement,
273
298
  change: L3GitProjectChange | null = null,
274
- diffOnSelect = false
299
+ diffOnSelect = false,
300
+ line?: number
275
301
  ): React.JSX.Element => (
276
302
  <L3ProjectPathMenu
277
- key={keyL3ProjectTree('file', path)}
303
+ key={line === undefined ? keyL3ProjectTree('file', path) : `${path}:${line}`}
278
304
  cwd={cwd}
279
305
  path={path}
280
306
  type="file"
281
307
  trigger={trigger}
282
308
  openLabel={t(diffOnSelect ? 'viewDiff' : 'openPreview')}
283
- onOpen={() => (diffOnSelect && change ? openDiff(change) : openPreview(path))}
309
+ onOpen={() => (diffOnSelect && change ? openDiff(change) : openPreview(path, line))}
284
310
  oldPath={
285
311
  change?.change.oldPath ? joinPath(change.repositoryRoot, change.change.oldPath) : undefined
286
312
  }
@@ -648,10 +674,6 @@ export function L3ProjectFileBrowser({
648
674
  )
649
675
  }
650
676
 
651
- const changeSearchResults = projectChanges.filter((change) =>
652
- change.projectPath.toLocaleLowerCase().includes(searchQuery.trim().toLocaleLowerCase())
653
- )
654
- const searching = searchOpen && Boolean(searchQuery.trim())
655
677
  const singleProjectRootRepository =
656
678
  repositories.length === 1 && repositories[0]?.root === '' ? repositories[0] : null
657
679
  const showLayoutToggle = repositories.length > 0 && !singleProjectRootRepository
@@ -661,92 +683,82 @@ export function L3ProjectFileBrowser({
661
683
  <section
662
684
  className="flex h-full min-h-0 flex-col bg-sidebar"
663
685
  aria-label={t('projectFiles', { name: projectName })}
686
+ onKeyDown={(event) => {
687
+ if (!searchOpen || event.key !== 'Escape') return
688
+ event.stopPropagation()
689
+ if (event.nativeEvent.isComposing || composing) return
690
+ event.preventDefault()
691
+ closeSearch()
692
+ }}
664
693
  >
665
- <div className="space-y-1.5 border-b px-2.5 py-2">
666
- <div className="flex min-h-7 min-w-0 items-center gap-2">
667
- <L3ProjectPathMenu
668
- cwd={cwd}
669
- path=""
670
- type="directory"
671
- onReveal={
672
- runtime.canReveal ? (path, type) => runtime.revealFile(path, type) : undefined
673
- }
674
- trigger={
675
- rootTitle ? (
676
- <div className="flex min-w-0 flex-1">{rootTitle}</div>
677
- ) : (
678
- <div tabIndex={0} className="flex min-w-0 flex-1 items-baseline gap-1">
679
- <p className="shrink-0 truncate text-xs font-semibold" title={projectName}>
680
- {projectName}
681
- </p>
682
- <span className="text-xs text-muted-foreground">·</span>
683
- <p
684
- className="min-w-0 truncate font-mono text-xs text-muted-foreground"
685
- title={cwd}
686
- >
687
- {compactPath(cwd)}
688
- </p>
689
- </div>
690
- )
691
- }
692
- />
693
- {singleProjectRootRepository ? (
694
- <button
695
- type="button"
696
- disabled={gitReadOnly || singleProjectRootRepository.state !== 'ready'}
697
- className={cn(
698
- buttonVariants({ variant: 'outline', size: 'sm' }),
699
- 'max-w-32 shrink gap-1 font-mono text-xs text-muted-foreground'
700
- )}
701
- title={
702
- singleProjectRootRepository.state === 'ready'
703
- ? t(gitReadOnly ? 'gitBranchReadOnly' : 'gitBranches')
704
- : selectL4LocalizedText(singleProjectRootRepository.message, locale)
694
+ {searchOpen ? (
695
+ <L3ProjectSearchControls
696
+ input={workspace.search}
697
+ inputRef={searchInputRef}
698
+ invalid={workspace.search.status === 'error'}
699
+ onQueryChange={(query) => runtime.setSearchQuery(query)}
700
+ onOptionsChange={(options) => runtime.setSearchOptions(options)}
701
+ onComposingChange={onComposingChange}
702
+ onClose={closeSearch}
703
+ onOpenFirst={openFirstSearchResult}
704
+ onFocusResults={() =>
705
+ searchResultsRef.current?.querySelector<HTMLButtonElement>('button')?.focus()
706
+ }
707
+ />
708
+ ) : (
709
+ <div className="space-y-1.5 border-b px-2.5 py-2">
710
+ <div className="flex min-h-7 min-w-0 items-center gap-2">
711
+ <L3ProjectPathMenu
712
+ cwd={cwd}
713
+ path=""
714
+ type="directory"
715
+ onReveal={
716
+ runtime.canReveal ? (path, type) => runtime.revealFile(path, type) : undefined
717
+ }
718
+ trigger={
719
+ rootTitle ? (
720
+ <div className="flex min-w-0 flex-1">{rootTitle}</div>
721
+ ) : (
722
+ <div tabIndex={0} className="flex min-w-0 flex-1 items-baseline gap-1">
723
+ <p className="shrink-0 truncate text-xs font-semibold" title={projectName}>
724
+ {projectName}
725
+ </p>
726
+ <span className="text-xs text-muted-foreground">·</span>
727
+ <p
728
+ className="min-w-0 truncate font-mono text-xs text-muted-foreground"
729
+ title={cwd}
730
+ >
731
+ {compactPath(cwd)}
732
+ </p>
733
+ </div>
734
+ )
705
735
  }
706
- onClick={() => onOpenGitBranches('')}
707
- >
708
- <GitBranchIcon className="size-3 shrink-0" />
709
- <span className="truncate">
710
- {singleProjectRootRepository.state === 'ready'
711
- ? getL3GitHeadLabel(singleProjectRootRepository.head)
712
- : t('unavailable')}
713
- </span>
714
- </button>
715
- ) : null}
716
- </div>
717
-
718
- {searchOpen ? (
719
- <InputGroup>
720
- <InputGroupAddon>
721
- <SearchIcon className="size-3.5" />
722
- </InputGroupAddon>
723
- <InputGroupInput
724
- ref={searchInputRef}
725
- data-testid="project-file-search"
726
- value={searchQuery}
727
- onChange={(event) => runtime.setSearchQuery(event.target.value)}
728
- onKeyDown={(event) => {
729
- if (event.key !== 'Escape') return
730
- runtime.setSearchQuery('')
731
- setSearchOpen(false)
732
- }}
733
- placeholder={t('searchFiles')}
734
- aria-label={t('searchFiles')}
735
736
  />
736
- <InputGroupAddon align="inline-end">
737
- <InputGroupButton
738
- size="icon-xs"
739
- onClick={() => {
740
- runtime.setSearchQuery('')
741
- setSearchOpen(false)
742
- }}
743
- aria-label={t('closeSearch')}
737
+ {singleProjectRootRepository ? (
738
+ <button
739
+ type="button"
740
+ disabled={gitReadOnly || singleProjectRootRepository.state !== 'ready'}
741
+ className={cn(
742
+ buttonVariants({ variant: 'outline', size: 'sm' }),
743
+ 'max-w-32 shrink gap-1 font-mono text-xs text-muted-foreground'
744
+ )}
745
+ title={
746
+ singleProjectRootRepository.state === 'ready'
747
+ ? t(gitReadOnly ? 'gitBranchReadOnly' : 'gitBranches')
748
+ : selectL4LocalizedText(singleProjectRootRepository.message, locale)
749
+ }
750
+ onClick={() => onOpenGitBranches('')}
744
751
  >
745
- <XIcon />
746
- </InputGroupButton>
747
- </InputGroupAddon>
748
- </InputGroup>
749
- ) : (
752
+ <GitBranchIcon className="size-3 shrink-0" />
753
+ <span className="truncate">
754
+ {singleProjectRootRepository.state === 'ready'
755
+ ? getL3GitHeadLabel(singleProjectRootRepository.head)
756
+ : t('unavailable')}
757
+ </span>
758
+ </button>
759
+ ) : null}
760
+ </div>
761
+
750
762
  <div className="flex min-h-8 min-w-0 items-center gap-1.5">
751
763
  {showLayoutToggle ? (
752
764
  <div
@@ -797,6 +809,7 @@ export function L3ProjectFileBrowser({
797
809
  </button>
798
810
  ) : null}
799
811
  <button
812
+ ref={searchButtonRef}
800
813
  type="button"
801
814
  className={cn(buttonVariants({ variant: 'outline', size: 'icon-sm' }), 'ml-auto')}
802
815
  aria-label={t('searchProject')}
@@ -809,133 +822,62 @@ export function L3ProjectFileBrowser({
809
822
  <SearchIcon className="size-3.5" />
810
823
  </button>
811
824
  </div>
812
- )}
813
- </div>
825
+ </div>
826
+ )}
814
827
 
815
- <L4ScrollArea
816
- className="min-h-0 flex-1"
817
- viewportClassName="py-1.5"
818
- viewportRef={scrollRef}
819
- onViewportScroll={(event) => runtime.setTreeScrollTop(event.currentTarget.scrollTop)}
820
- >
821
- {repositories
822
- .filter((item) => item.state === 'unavailable')
823
- .map((item) =>
824
- item.state === 'unavailable' ? (
825
- <p
826
- key={item.root}
827
- role="alert"
828
- className="mx-2 mb-1 break-words rounded-md bg-muted px-2 py-2 text-sm text-destructive"
829
- >
830
- {item.root || projectName}:{selectL4LocalizedText(item.message, locale)}
831
- </p>
832
- ) : null
833
- )}
834
- {gitWorkspace?.snapshot.status === 'error' ? (
835
- <p className="mx-2 mb-1 rounded-md bg-muted px-2 py-2 text-sm text-destructive">
836
- {gitWorkspace.snapshot.error}
837
- </p>
838
- ) : null}
839
- {searching ? (
840
- <div className="space-y-0.5 px-1">
841
- {gitWorkspace?.changesOnly ? (
842
- changeSearchResults.length > 0 ? (
843
- changeSearchResults.map((change) => {
844
- return fileContextMenu(
845
- change.projectPath,
846
- <button
847
- type="button"
848
- className="flex min-h-10 w-full items-center gap-2 rounded-lg px-2 text-left hover:bg-muted/70"
849
- onClick={() => {
850
- openDiff(change)
851
- }}
852
- title={change.projectPath}
853
- >
854
- {fileIcon(change.projectPath)}
855
- <span className="min-w-0 flex-1">
856
- <span
857
- className={cn(
858
- 'block truncate text-xs font-medium',
859
- changedFileNameClass(change)
860
- )}
861
- >
862
- {change.projectPath.split('/').at(-1)}
863
- </span>
864
- <span className="mt-0.5 block truncate font-mono text-xs text-muted-foreground">
865
- {change.projectPath}
866
- </span>
867
- </span>
868
- </button>,
869
- change,
870
- true
871
- )
872
- })
873
- ) : (
874
- <p className="px-2 py-5 text-center text-xs text-muted-foreground">
875
- {t('noMatches')}
828
+ {searchOpen ? (
829
+ <L3ProjectSearchResults
830
+ key={JSON.stringify([
831
+ workspace.search.query,
832
+ workspace.search.scope,
833
+ workspace.search.caseSensitive,
834
+ workspace.search.wholeWord,
835
+ workspace.search.regex
836
+ ])}
837
+ search={workspace.search}
838
+ resultsRef={searchResultsRef}
839
+ activePath={workspace.activeDiff ? null : workspace.activePath}
840
+ onOpen={openPreview}
841
+ wrapFile={(path, line, trigger) =>
842
+ fileContextMenu(path, trigger, changeByProjectPath.get(path) ?? null, false, line)
843
+ }
844
+ renderFileIcon={fileIcon}
845
+ fileDecoration={(path) => {
846
+ const change = changeByProjectPath.get(path) ?? null
847
+ return {
848
+ className: changedFileNameClass(change),
849
+ badge: change ? getL3GitChangeBadge(change.change) : undefined
850
+ }
851
+ }}
852
+ />
853
+ ) : (
854
+ <L4ScrollArea
855
+ className="min-h-0 flex-1"
856
+ viewportClassName="py-1.5"
857
+ viewportRef={scrollRef}
858
+ onViewportScroll={(event) => runtime.setTreeScrollTop(event.currentTarget.scrollTop)}
859
+ >
860
+ {repositories
861
+ .filter((item) => item.state === 'unavailable')
862
+ .map((item) =>
863
+ item.state === 'unavailable' ? (
864
+ <p
865
+ key={item.root}
866
+ role="alert"
867
+ className="mx-2 mb-1 break-words rounded-md bg-muted px-2 py-2 text-sm text-destructive"
868
+ >
869
+ {item.root || projectName}:{selectL4LocalizedText(item.message, locale)}
876
870
  </p>
877
- )
878
- ) : (
879
- <>
880
- {workspace?.search.status === 'loading' ? (
881
- <div className="flex min-h-10 items-center gap-2 px-2 text-xs text-muted-foreground">
882
- <LoaderCircleIcon className="size-3.5 animate-spin" /> {t('searching')}
883
- </div>
884
- ) : null}
885
- {workspace?.search.status === 'error' ? (
886
- <p className="px-2 py-2 text-xs text-destructive">{workspace.search.error}</p>
887
- ) : null}
888
- {workspace?.search.status === 'ready'
889
- ? workspace.search.matches.map((path) => {
890
- const change = changeByProjectPath.get(path) ?? null
891
- return fileContextMenu(
892
- path,
893
- <button
894
- data-testid={`project-search-result-${path}`}
895
- type="button"
896
- onClick={() => {
897
- openPreview(path)
898
- }}
899
- className="flex min-h-10 w-full items-center gap-2 rounded-lg px-2 text-left hover:bg-muted/70"
900
- title={path}
901
- >
902
- <span className="shrink-0">{fileIcon(path)}</span>
903
- <span className="min-w-0 flex-1">
904
- <span
905
- className={cn(
906
- 'block truncate text-xs font-medium',
907
- changedFileNameClass(change)
908
- )}
909
- >
910
- {path.split('/').at(-1)}
911
- </span>
912
- <span className="mt-0.5 block truncate font-mono text-xs text-muted-foreground">
913
- {path}
914
- </span>
915
- </span>
916
- </button>
917
- )
918
- })
919
- : null}
920
- {workspace?.search.status === 'ready' && workspace.search.matches.length === 0 ? (
921
- <p className="px-2 py-5 text-center text-xs text-muted-foreground">
922
- {t('noMatches')}
923
- </p>
924
- ) : null}
925
- {workspace?.search.status === 'ready' && workspace.search.truncated ? (
926
- <p className="px-2 py-2 text-xs text-amber-600 dark:text-amber-400">
927
- {t('resultLimit')}
928
- </p>
929
- ) : null}
930
- </>
871
+ ) : null
931
872
  )}
932
- </div>
933
- ) : effectiveLayout === 'repository' ? (
934
- renderRepositoryView()
935
- ) : (
936
- renderDirectoryView()
937
- )}
938
- </L4ScrollArea>
873
+ {gitWorkspace?.snapshot.status === 'error' ? (
874
+ <p className="mx-2 mb-1 rounded-md bg-muted px-2 py-2 text-sm text-destructive">
875
+ {gitWorkspace.snapshot.error}
876
+ </p>
877
+ ) : null}
878
+ {effectiveLayout === 'repository' ? renderRepositoryView() : renderDirectoryView()}
879
+ </L4ScrollArea>
880
+ )}
939
881
  </section>
940
882
  )
941
883
  }