@jetcrab/pi-desk 1.1.28-dev → 1.1.29-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 (337) hide show
  1. package/package.json +1 -1
  2. package/src/app/api/work-sessions/search/route.ts +1 -0
  3. package/src/app/project-preview/page.tsx +1 -3
  4. package/src/client/l2_biz/project-preview/hooks/l2-use-project-preview.ts +1 -14
  5. package/src/client/l2_biz/project-preview/l2-project-preview-dialog.tsx +0 -1
  6. package/src/client/l2_biz/project-preview/l2-project-preview-page.tsx +0 -1
  7. package/src/client/l2_biz/project-preview/l2-project-preview.tsx +1 -4
  8. package/src/client/l2_biz/project-preview/views/l2-project-branch-select.tsx +42 -3
  9. package/src/client/l2_biz/workbench/git-workspace/l2-git-workspace-runtime.ts +11 -10
  10. package/src/client/l2_biz/workbench/hooks/l2-work-session-search.ts +52 -0
  11. package/src/client/l2_biz/workbench/hooks/l2-workbench-branch-picker.ts +42 -7
  12. package/src/client/l2_biz/workbench/hooks/l2-workbench-session-history.ts +143 -131
  13. package/src/client/l2_biz/workbench/hooks/l2-workbench-session-search.ts +97 -0
  14. package/src/client/l2_biz/workbench/l2-work-session-columns.tsx +8 -0
  15. package/src/client/l2_biz/workbench/l2-work-session-sidebar.tsx +129 -10
  16. package/src/client/l2_biz/workbench/l2-workbench-biz.ts +20 -2
  17. package/src/client/l2_biz/workbench/l2-workbench-branch-filter-preferences.ts +34 -0
  18. package/src/client/l2_biz/workbench/l2-workbench-branch-picker.tsx +34 -71
  19. package/src/client/l2_biz/workbench/l2-workbench-chat-input-runtime.ts +38 -1
  20. package/src/client/l2_biz/workbench/l2-workbench-chat-view.tsx +11 -1
  21. package/src/client/l2_biz/workbench/l2-workbench-picker-dialog.tsx +55 -215
  22. package/src/client/l2_biz/workbench/l2-workbench.tsx +20 -38
  23. package/src/client/l2_biz/workbench/views/l2-workbench-session-search.tsx +123 -0
  24. package/src/client/l3_modules/conversation/l3-conversation-messages.tsx +43 -4
  25. package/src/client/l3_modules/project-files/l3-markdown-preview.module.css +1 -1
  26. package/src/client/l3_modules/project-files/l3-project-document-git.ts +13 -29
  27. package/src/client/l3_modules/project-files/l3-project-document-view.tsx +1 -4
  28. package/src/client/l3_modules/project-files/l3-project-file-browser.module.css +14 -18
  29. package/src/client/l3_modules/project-files/l3-project-file-browser.tsx +52 -95
  30. package/src/client/l3_modules/project-files/l3-project-file-runtime.ts +52 -21
  31. package/src/client/l3_modules/project-files/l3-project-file-tabs.tsx +41 -62
  32. package/src/client/l3_modules/project-files/l3-project-git-runtime.ts +5 -4
  33. package/src/client/l3_modules/project-files/l3-use-project-refresh.ts +36 -0
  34. package/src/client/l3_modules/project-files/views/l3-project-search-results.tsx +2 -2
  35. package/src/client/l3_modules/project-files/views/l3-project-tree-folder.tsx +86 -0
  36. package/src/client/l4_foundation/ui/file-icons/README.md +1 -1
  37. package/src/client/l4_foundation/ui/file-icons/l4-file-icon.tsx +49 -2
  38. package/src/common/l2_biz/pi-session/l2-pi-session-contract.ts +11 -24
  39. package/src/common/l2_biz/project-preview/l2-project-preview-navigation.ts +5 -12
  40. package/src/common/l2_biz/work-session/l2-work-session-search-contract.ts +26 -0
  41. package/src/common/l2_biz/work-session/l2-work-session-tree-contract.ts +9 -1
  42. package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +12 -0
  43. package/src/common/l2_biz/workbench/l2-workbench-picker-messages.ts +6 -2
  44. package/src/common/l3_modules/conversation/l3-conversation-locale-messages.ts +2 -0
  45. package/src/common/l3_modules/pi/l3-pi-session-search-contract.ts +26 -0
  46. package/src/common/l4_foundation/locale/l4-host-error-messages.ts +2 -0
  47. package/src/server/l1_entry/api/l1-work-session-branch-route.ts +21 -15
  48. package/src/server/l1_entry/api/l1-work-session-search-route.ts +38 -0
  49. package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +4 -2
  50. package/src/server/l2_biz/work-session/l2-work-session-manage.ts +46 -10
  51. package/src/server/l3_modules/work-session/l3-work-session.ts +11 -6
  52. package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +35 -2
  53. package/src/server/l4_foundation/pi/l4-pi-session-history-catalog.ts +62 -113
  54. package/src/server/l4_foundation/pi/l4-pi-session-search.ts +166 -0
  55. package/src/server/l4_foundation/pi/l4-pi-work-session-runtime.ts +66 -5
  56. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  57. package/temp/build/pi-desk/release/.next/app-path-routes-manifest.json +1 -0
  58. package/temp/build/pi-desk/release/.next/build-manifest.json +2 -2
  59. package/temp/build/pi-desk/release/.next/required-server-files.js +2 -2
  60. package/temp/build/pi-desk/release/.next/required-server-files.json +2 -2
  61. package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
  62. package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
  63. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  64. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  65. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +1 -1
  66. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  67. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  68. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  69. package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
  70. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  71. package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +2 -2
  72. package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
  73. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
  74. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
  75. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
  76. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
  77. package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
  78. package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
  79. package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
  80. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  81. package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
  82. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
  83. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
  84. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
  85. package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
  86. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
  87. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -1
  88. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
  89. package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
  90. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
  91. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
  92. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
  93. package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
  94. package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
  95. package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
  96. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
  97. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
  98. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
  99. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
  100. package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
  101. package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
  102. package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
  103. package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
  104. package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
  105. package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
  106. package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
  107. package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
  108. package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
  109. package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
  110. package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
  111. package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +2 -2
  112. package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +1 -1
  113. package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
  114. package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +1 -1
  115. package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +2 -2
  116. package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +2 -2
  117. package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +1 -1
  118. package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -2
  119. package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +1 -1
  120. package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -2
  121. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
  122. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
  123. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
  124. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
  125. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
  126. package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
  127. package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
  128. package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
  129. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
  130. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
  131. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
  132. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
  133. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
  134. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
  135. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
  136. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
  137. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
  138. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/search/route.js +1 -0
  139. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/search/route_client-reference-manifest.js +1 -0
  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 +138 -138
  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 -1
  147. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -1
  148. package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +1 -0
  149. package/temp/build/pi-desk/release/.next/server/chunks/23054.js +2 -2
  150. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +8 -8
  151. package/temp/build/pi-desk/release/.next/server/chunks/27344.js +3 -3
  152. package/temp/build/pi-desk/release/.next/server/chunks/3108.js +3 -3
  153. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +2 -2
  154. package/temp/build/pi-desk/release/.next/server/chunks/41856.js +1 -0
  155. package/temp/build/pi-desk/release/.next/server/chunks/62900.js +235 -0
  156. package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
  157. package/temp/build/pi-desk/release/.next/server/chunks/81363.js +1 -1
  158. package/temp/build/pi-desk/release/.next/server/chunks/81912.js +3 -0
  159. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +2 -2
  160. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  161. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  162. package/temp/build/pi-desk/release/.next/static/Yyfg-r17aYmEzlYYlIwDo/_buildManifest.js +1 -0
  163. package/temp/build/pi-desk/release/.next/static/chunks/10043-9d6c3735418ccaf9.js +238 -0
  164. package/temp/build/pi-desk/release/.next/static/chunks/11937-ac71fb0f7cef482a.js +1 -0
  165. package/temp/build/pi-desk/release/.next/static/chunks/30669-ffa76d39e0e25194.js +1 -0
  166. package/temp/build/pi-desk/release/.next/static/chunks/36643-f60e98278740073c.js +3 -0
  167. package/temp/build/pi-desk/release/.next/static/chunks/65232-a8571f78020eede2.js +15 -0
  168. package/temp/build/pi-desk/release/.next/static/chunks/{51474-8dc714cb76b2e60a.js → 65350-23bb961d87addf14.js} +3 -3
  169. package/temp/build/pi-desk/release/.next/static/chunks/app/_global-error/page-295cb63da5f7262f.js +1 -0
  170. package/temp/build/pi-desk/release/.next/static/chunks/app/_not-found/page-295cb63da5f7262f.js +1 -0
  171. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth/login/route-295cb63da5f7262f.js +1 -0
  172. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth/logout/route-295cb63da5f7262f.js +1 -0
  173. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth-settings/get/route-295cb63da5f7262f.js +1 -0
  174. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth-settings/replace/route-295cb63da5f7262f.js +1 -0
  175. package/temp/build/pi-desk/release/.next/static/chunks/app/api/capability-modes/get/route-295cb63da5f7262f.js +1 -0
  176. package/temp/build/pi-desk/release/.next/static/chunks/app/api/capability-modes/replace/route-295cb63da5f7262f.js +1 -0
  177. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/context/get/route-295cb63da5f7262f.js +1 -0
  178. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/images/get/route-295cb63da5f7262f.js +1 -0
  179. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/messages/get/route-295cb63da5f7262f.js +1 -0
  180. package/temp/build/pi-desk/release/.next/static/chunks/app/api/health/route-295cb63da5f7262f.js +1 -0
  181. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-check/get/route-295cb63da5f7262f.js +1 -0
  182. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/del/route-295cb63da5f7262f.js +1 -0
  183. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/get/route-295cb63da5f7262f.js +1 -0
  184. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/replace/route-295cb63da5f7262f.js +1 -0
  185. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-catalog/list/route-295cb63da5f7262f.js +1 -0
  186. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/get/route-295cb63da5f7262f.js +1 -0
  187. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/preview/route-295cb63da5f7262f.js +1 -0
  188. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/replace/route-295cb63da5f7262f.js +1 -0
  189. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directories/list/route-295cb63da5f7262f.js +1 -0
  190. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-entries/list/route-295cb63da5f7262f.js +1 -0
  191. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-ignores/list/route-295cb63da5f7262f.js +1 -0
  192. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-ignores/replace/route-295cb63da5f7262f.js +1 -0
  193. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/models/list/route-295cb63da5f7262f.js +1 -0
  194. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/session-user-messages/list/route-295cb63da5f7262f.js +1 -0
  195. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/sessions/list/route-295cb63da5f7262f.js +1 -0
  196. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/get/route-295cb63da5f7262f.js +1 -0
  197. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/search/route-295cb63da5f7262f.js +1 -0
  198. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/settings-get/route-295cb63da5f7262f.js +1 -0
  199. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/settings-replace/route-295cb63da5f7262f.js +1 -0
  200. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/add/route-295cb63da5f7262f.js +1 -0
  201. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/apply/route-295cb63da5f7262f.js +1 -0
  202. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/batch/route-295cb63da5f7262f.js +1 -0
  203. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route-295cb63da5f7262f.js +1 -0
  204. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/del/route-295cb63da5f7262f.js +1 -0
  205. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/enabled-set/route-295cb63da5f7262f.js +1 -0
  206. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/get/route-295cb63da5f7262f.js +1 -0
  207. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route-295cb63da5f7262f.js +1 -0
  208. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/list/route-295cb63da5f7262f.js +1 -0
  209. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/reload/route-295cb63da5f7262f.js +1 -0
  210. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/update/route-295cb63da5f7262f.js +1 -0
  211. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/get/route-295cb63da5f7262f.js +1 -0
  212. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/list/route-295cb63da5f7262f.js +1 -0
  213. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/reveal/route-295cb63da5f7262f.js +1 -0
  214. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/search/route-295cb63da5f7262f.js +1 -0
  215. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git/get/route-295cb63da5f7262f.js +1 -0
  216. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-branches/list/route-295cb63da5f7262f.js +1 -0
  217. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-changes/list/route-295cb63da5f7262f.js +1 -0
  218. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-diff/get/route-295cb63da5f7262f.js +1 -0
  219. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-heads/get/route-295cb63da5f7262f.js +1 -0
  220. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-log/list/route-295cb63da5f7262f.js +1 -0
  221. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-model-default/get/route-295cb63da5f7262f.js +1 -0
  222. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-model-default/replace/route-295cb63da5f7262f.js +1 -0
  223. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/get/route-295cb63da5f7262f.js +1 -0
  224. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/list/route-295cb63da5f7262f.js +1 -0
  225. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/replace/route-295cb63da5f7262f.js +1 -0
  226. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skills/list/route-295cb63da5f7262f.js +1 -0
  227. package/temp/build/pi-desk/release/.next/static/chunks/app/api/task-center/images/get/route-295cb63da5f7262f.js +1 -0
  228. package/temp/build/pi-desk/release/.next/static/chunks/app/api/task-center/messages/get/route-295cb63da5f7262f.js +1 -0
  229. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-git-branch/add/route-295cb63da5f7262f.js +1 -0
  230. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-git-branch/replace/route-295cb63da5f7262f.js +1 -0
  231. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-tree/get/route-295cb63da5f7262f.js +1 -0
  232. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-tree-entry/get/route-295cb63da5f7262f.js +1 -0
  233. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/add/route-295cb63da5f7262f.js +1 -0
  234. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/branch/route-295cb63da5f7262f.js +1 -0
  235. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/del/route-295cb63da5f7262f.js +1 -0
  236. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/rename/route-295cb63da5f7262f.js +1 -0
  237. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/replace/route-295cb63da5f7262f.js +1 -0
  238. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/search/route-295cb63da5f7262f.js +1 -0
  239. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/sort/route-295cb63da5f7262f.js +1 -0
  240. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-2c6a573d3004516e.js +1 -0
  241. package/temp/build/pi-desk/release/.next/static/chunks/app/login/{page-0d3d4827802108e5.js → page-0df9df996aa68060.js} +1 -1
  242. package/temp/build/pi-desk/release/.next/static/chunks/app/page-f78be007d97f0d95.js +158 -0
  243. package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/{page-038b6c14e03ed878.js → page-e1dbfcfc0f333e01.js} +1 -1
  244. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/app-error-295cb63da5f7262f.js +1 -0
  245. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/forbidden-295cb63da5f7262f.js +1 -0
  246. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/unauthorized-295cb63da5f7262f.js +1 -0
  247. package/temp/build/pi-desk/release/.next/static/css/{1e780a4936aa6ba3.css → 2bb393927ba8f71d.css} +1 -1
  248. package/temp/build/pi-desk/release/.next/static/css/3a07dcc86ffdf88c.css +1 -0
  249. package/temp/build/pi-desk/release/.next/static/css/a677e7fbf6e2173e.css +1 -0
  250. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  251. package/temp/build/pi-desk/release/.next/server/chunks/35348.js +0 -3
  252. package/temp/build/pi-desk/release/.next/server/chunks/56830.js +0 -235
  253. package/temp/build/pi-desk/release/.next/server/chunks/80780.js +0 -1
  254. package/temp/build/pi-desk/release/.next/static/bj2dB_Z10YB_CqK_VNhDx/_buildManifest.js +0 -1
  255. package/temp/build/pi-desk/release/.next/static/chunks/11937-1d0a9822f2c7995b.js +0 -1
  256. package/temp/build/pi-desk/release/.next/static/chunks/14197-3d3ddd426ac06c9c.js +0 -3
  257. package/temp/build/pi-desk/release/.next/static/chunks/37313-d5970e2d0c270d22.js +0 -238
  258. package/temp/build/pi-desk/release/.next/static/chunks/65232-ef4e5a4174f62faf.js +0 -15
  259. package/temp/build/pi-desk/release/.next/static/chunks/79267-7d21d7cdbd7cfca1.js +0 -1
  260. package/temp/build/pi-desk/release/.next/static/chunks/app/_global-error/page-9969fe0baa238786.js +0 -1
  261. package/temp/build/pi-desk/release/.next/static/chunks/app/_not-found/page-9969fe0baa238786.js +0 -1
  262. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth/login/route-9969fe0baa238786.js +0 -1
  263. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth/logout/route-9969fe0baa238786.js +0 -1
  264. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth-settings/get/route-9969fe0baa238786.js +0 -1
  265. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth-settings/replace/route-9969fe0baa238786.js +0 -1
  266. package/temp/build/pi-desk/release/.next/static/chunks/app/api/capability-modes/get/route-9969fe0baa238786.js +0 -1
  267. package/temp/build/pi-desk/release/.next/static/chunks/app/api/capability-modes/replace/route-9969fe0baa238786.js +0 -1
  268. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/context/get/route-9969fe0baa238786.js +0 -1
  269. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/images/get/route-9969fe0baa238786.js +0 -1
  270. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/messages/get/route-9969fe0baa238786.js +0 -1
  271. package/temp/build/pi-desk/release/.next/static/chunks/app/api/health/route-9969fe0baa238786.js +0 -1
  272. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-check/get/route-9969fe0baa238786.js +0 -1
  273. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/del/route-9969fe0baa238786.js +0 -1
  274. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/get/route-9969fe0baa238786.js +0 -1
  275. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/replace/route-9969fe0baa238786.js +0 -1
  276. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-catalog/list/route-9969fe0baa238786.js +0 -1
  277. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/get/route-9969fe0baa238786.js +0 -1
  278. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/preview/route-9969fe0baa238786.js +0 -1
  279. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/replace/route-9969fe0baa238786.js +0 -1
  280. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directories/list/route-9969fe0baa238786.js +0 -1
  281. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-entries/list/route-9969fe0baa238786.js +0 -1
  282. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-ignores/list/route-9969fe0baa238786.js +0 -1
  283. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-ignores/replace/route-9969fe0baa238786.js +0 -1
  284. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/models/list/route-9969fe0baa238786.js +0 -1
  285. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/session-user-messages/list/route-9969fe0baa238786.js +0 -1
  286. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/sessions/list/route-9969fe0baa238786.js +0 -1
  287. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/get/route-9969fe0baa238786.js +0 -1
  288. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/search/route-9969fe0baa238786.js +0 -1
  289. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/settings-get/route-9969fe0baa238786.js +0 -1
  290. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/settings-replace/route-9969fe0baa238786.js +0 -1
  291. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/add/route-9969fe0baa238786.js +0 -1
  292. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/apply/route-9969fe0baa238786.js +0 -1
  293. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/batch/route-9969fe0baa238786.js +0 -1
  294. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route-9969fe0baa238786.js +0 -1
  295. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/del/route-9969fe0baa238786.js +0 -1
  296. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/enabled-set/route-9969fe0baa238786.js +0 -1
  297. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/get/route-9969fe0baa238786.js +0 -1
  298. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route-9969fe0baa238786.js +0 -1
  299. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/list/route-9969fe0baa238786.js +0 -1
  300. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/reload/route-9969fe0baa238786.js +0 -1
  301. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/update/route-9969fe0baa238786.js +0 -1
  302. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/get/route-9969fe0baa238786.js +0 -1
  303. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/list/route-9969fe0baa238786.js +0 -1
  304. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/reveal/route-9969fe0baa238786.js +0 -1
  305. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/search/route-9969fe0baa238786.js +0 -1
  306. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git/get/route-9969fe0baa238786.js +0 -1
  307. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-branches/list/route-9969fe0baa238786.js +0 -1
  308. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-changes/list/route-9969fe0baa238786.js +0 -1
  309. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-diff/get/route-9969fe0baa238786.js +0 -1
  310. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-heads/get/route-9969fe0baa238786.js +0 -1
  311. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-log/list/route-9969fe0baa238786.js +0 -1
  312. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-model-default/get/route-9969fe0baa238786.js +0 -1
  313. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-model-default/replace/route-9969fe0baa238786.js +0 -1
  314. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/get/route-9969fe0baa238786.js +0 -1
  315. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/list/route-9969fe0baa238786.js +0 -1
  316. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/replace/route-9969fe0baa238786.js +0 -1
  317. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skills/list/route-9969fe0baa238786.js +0 -1
  318. package/temp/build/pi-desk/release/.next/static/chunks/app/api/task-center/images/get/route-9969fe0baa238786.js +0 -1
  319. package/temp/build/pi-desk/release/.next/static/chunks/app/api/task-center/messages/get/route-9969fe0baa238786.js +0 -1
  320. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-git-branch/add/route-9969fe0baa238786.js +0 -1
  321. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-git-branch/replace/route-9969fe0baa238786.js +0 -1
  322. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-tree/get/route-9969fe0baa238786.js +0 -1
  323. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-tree-entry/get/route-9969fe0baa238786.js +0 -1
  324. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/add/route-9969fe0baa238786.js +0 -1
  325. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/branch/route-9969fe0baa238786.js +0 -1
  326. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/del/route-9969fe0baa238786.js +0 -1
  327. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/rename/route-9969fe0baa238786.js +0 -1
  328. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/replace/route-9969fe0baa238786.js +0 -1
  329. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/sort/route-9969fe0baa238786.js +0 -1
  330. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-20718f1503910291.js +0 -1
  331. package/temp/build/pi-desk/release/.next/static/chunks/app/page-ce2ebf7d8ea7a330.js +0 -158
  332. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/app-error-9969fe0baa238786.js +0 -1
  333. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/forbidden-9969fe0baa238786.js +0 -1
  334. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/unauthorized-9969fe0baa238786.js +0 -1
  335. package/temp/build/pi-desk/release/.next/static/css/2c3b0c0ff1f49c68.css +0 -1
  336. package/temp/build/pi-desk/release/.next/static/css/c43b04c244a64fb4.css +0 -1
  337. /package/temp/build/pi-desk/release/.next/static/{bj2dB_Z10YB_CqK_VNhDx → Yyfg-r17aYmEzlYYlIwDo}/_ssgManifest.js +0 -0
@@ -50,7 +50,13 @@ export type L3FileDocument =
50
50
  previewScrollTop: number
51
51
  comparison?: L3FileComparison
52
52
  }
53
- | { status: 'image'; mimeType: L4ProjectFileImageMimeType; size: number; objectUrl: string }
53
+ | {
54
+ status: 'image'
55
+ mimeType: L4ProjectFileImageMimeType
56
+ size: number
57
+ objectUrl: string
58
+ blob: Blob
59
+ }
54
60
  | { status: 'error'; error: string }
55
61
  export interface L3FileSearch extends L4ProjectSearchInput, L4ProjectFileSearch {
56
62
  status: 'idle' | 'loading' | 'ready' | 'error'
@@ -157,6 +163,13 @@ function normalizePath(cwd: string, path: string): string {
157
163
  return inside ? normalized.slice(prefix.length) : normalized
158
164
  }
159
165
 
166
+ async function sameImage(left: Blob, right: Blob): Promise<boolean> {
167
+ if (left.type !== right.type || left.size !== right.size) return false
168
+ const [before, after] = await Promise.all([left.arrayBuffer(), right.arrayBuffer()])
169
+ const bytes = new Uint8Array(after)
170
+ return new Uint8Array(before).every((value, index) => value === bytes[index])
171
+ }
172
+
160
173
  // 单个目录阅读实例;会话身份、窗口和聊天布局由调用方拥有。
161
174
  export class L3ProjectFileRuntime {
162
175
  readonly state: L3ProjectFileState = {
@@ -286,9 +299,9 @@ export class L3ProjectFileRuntime {
286
299
  ensureRoot(): Promise<void> {
287
300
  return this.ensureDirectory('')
288
301
  }
289
- ensureDirectory(path: string): Promise<void> {
290
- if (this.disposed || this.state.directories.get(path)?.status === 'ready')
291
- return Promise.resolve()
302
+ ensureDirectory(path: string, refresh = false): Promise<void> {
303
+ const current = this.state.directories.get(path)
304
+ if (this.disposed || (!refresh && current?.status === 'ready')) return Promise.resolve()
292
305
  const existing = this.directoryRequests.get(path)
293
306
  if (existing) return existing.promise
294
307
  const controller = new AbortController()
@@ -307,10 +320,15 @@ export class L3ProjectFileRuntime {
307
320
  this.publish()
308
321
  })
309
322
  this.directoryRequests.set(path, { controller, promise })
310
- this.state.directories.set(path, { status: 'loading' })
311
- this.publish()
323
+ if (current?.status !== 'ready') {
324
+ this.state.directories.set(path, { status: 'loading' })
325
+ this.publish()
326
+ }
312
327
  return promise
313
328
  }
329
+ refreshDirectories(paths: Iterable<string>): void {
330
+ for (const path of new Set(paths)) void this.ensureDirectory(path, true)
331
+ }
314
332
  setExpandedPaths(paths: Set<string>, load = true): void {
315
333
  const closed = [...this.state.expandedPaths].filter((path) => !paths.has(path))
316
334
  this.state.expandedPaths = new Set(paths)
@@ -319,7 +337,7 @@ export class L3ProjectFileRuntime {
319
337
  this.compactRequests.delete(path)
320
338
  }
321
339
  this.publish()
322
- if (load) for (const path of paths) void this.ensureDirectory(path)
340
+ if (load) for (const path of paths) void this.ensureDirectory(path, true)
323
341
  }
324
342
  cancelDirectoryExpansion(root = ''): void {
325
343
  for (const path of this.compactRequests.keys()) {
@@ -553,20 +571,25 @@ export class L3ProjectFileRuntime {
553
571
  if (!refresh && current && ['loading', 'text', 'image'].includes(current.status)) return
554
572
  const request = new AbortController()
555
573
  this.documentRequests.set(path, request)
556
- // 搜索定位刷新正文时保留已挂载的代码视图,内容未变时不重建编辑器。
557
- if (current?.status !== 'text') this.state.documents.set(path, { status: 'loading' })
558
- this.publish()
574
+ // 自动重读保留已挂载的视图,文本或图片未变时不重建阅读实例。
575
+ if (current?.status !== 'text' && current?.status !== 'image') {
576
+ this.state.documents.set(path, { status: 'loading' })
577
+ this.publish()
578
+ }
559
579
  try {
560
580
  const result = await this.reader.get(path, this.options.imagePreviewMode(), request.signal)
561
581
  if (this.documentRequests.get(path) !== request) return
582
+ const blob = result.kind === 'image' ? createL4Base64ImageBlob(result) : null
583
+ const unchanged =
584
+ (current?.status === 'text' &&
585
+ result.kind === 'text' &&
586
+ current.content === result.content &&
587
+ current.size === result.size) ||
588
+ (current?.status === 'image' && blob !== null && (await sameImage(current.blob, blob)))
589
+ if (this.documentRequests.get(path) !== request) return
562
590
  this.documentRequests.delete(path)
563
591
  this.closeMissing.delete(path)
564
592
  this.restoredTabs.delete(path)
565
- const unchanged =
566
- current?.status === 'text' &&
567
- result.kind === 'text' &&
568
- current.content === result.content &&
569
- current.size === result.size
570
593
  if (!unchanged)
571
594
  this.state.documents.set(
572
595
  path,
@@ -598,11 +621,12 @@ export class L3ProjectFileRuntime {
598
621
  status: 'image',
599
622
  mimeType: result.mimeType,
600
623
  size: result.size,
601
- objectUrl: URL.createObjectURL(createL4Base64ImageBlob(result))
624
+ objectUrl: URL.createObjectURL(blob!),
625
+ blob: blob!
602
626
  }
603
627
  )
604
628
  if (this.state.activePath === path && result.kind !== 'text') this.navigation = null
605
- this.releaseDocument(current)
629
+ if (!unchanged) this.releaseDocument(current)
606
630
  } catch (cause) {
607
631
  if (this.documentRequests.get(path) !== request) return
608
632
  this.documentRequests.delete(path)
@@ -741,6 +765,10 @@ export class L3ProjectFileRuntime {
741
765
  this.publish()
742
766
  void this.ensureDiff(diff)
743
767
  }
768
+ refreshDiff(): void {
769
+ const diff = this.state.activeDiff
770
+ if (diff && !diff.comparison) void this.ensureDiff(diff, true)
771
+ }
744
772
  saveDiffState(expected: L3FileDiff, state: L4CodeDiffState | null): void {
745
773
  // 卸载回调仍属于原标签;尚未创建编辑器时的空状态不能抹掉已有位置。
746
774
  if (this.disposed || !this.state.diffs.includes(expected) || !state) return
@@ -805,13 +833,16 @@ export class L3ProjectFileRuntime {
805
833
  this.restoredPreviewScrollTops.clear()
806
834
  this.listeners.clear()
807
835
  }
808
- private async ensureDiff(diff: L3FileDiff): Promise<void> {
836
+ private async ensureDiff(diff: L3FileDiff, refresh = false): Promise<void> {
809
837
  const key = getL3FileDiffKey(diff)
810
- if (this.disposed || diff.status !== 'unloaded' || this.diffRequests.has(key)) return
838
+ if (this.disposed || (!refresh && diff.status !== 'unloaded') || this.diffRequests.has(key))
839
+ return
811
840
  const request = new AbortController()
812
841
  this.diffRequests.set(key, request)
813
- Object.assign(diff, { status: 'loading' })
814
- this.publish()
842
+ if (diff.status !== 'text' && diff.status !== 'metadata') {
843
+ Object.assign(diff, { status: 'loading' })
844
+ this.publish()
845
+ }
815
846
  try {
816
847
  const result = await this.reader.diff(
817
848
  diff.repositoryRoot,
@@ -1,10 +1,11 @@
1
1
  'use client'
2
2
 
3
- import { ChevronDownIcon, ChevronUpIcon, FileCode2Icon, XIcon } from 'lucide-react'
4
- import { useLayoutEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react'
3
+ import { XIcon } from 'lucide-react'
4
+ import { useLayoutEffect, useRef, useSyncExternalStore, type ReactNode } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
6
  import { cn } from '@client/l4_foundation/lib/l4-utils'
7
7
  import { Button } from '@client/l4_foundation/ui/shadcn/button'
8
+ import { L4FileIcon } from '@client/l4_foundation/ui/file-icons/l4-file-icon'
8
9
  import {
9
10
  ContextMenu,
10
11
  ContextMenuItem,
@@ -44,9 +45,21 @@ const CLOSE_ACTIONS: readonly { scope: L3FileTabCloseScope; label: string }[] =
44
45
 
45
46
  function fileLabel(path: string, paths: readonly string[]): string {
46
47
  const segments = path.replaceAll('\\', '/').split('/')
47
- const name = segments.at(-1) ?? path
48
- const sameName = paths.some((other) => other !== path && other.split('/').at(-1) === name)
49
- return sameName && segments.length > 1 ? segments.slice(-2).join('/') : name
48
+ const others = paths
49
+ .map((other) => other.replaceAll('\\', '/'))
50
+ .filter((other) => other !== segments.join('/'))
51
+ for (let length = 1; length < segments.length; length++) {
52
+ const label = segments.slice(-length).join('/')
53
+ if (!others.some((other) => other.split('/').slice(-length).join('/') === label)) return label
54
+ }
55
+ return segments.join('/')
56
+ }
57
+
58
+ function revealTab(rows: HTMLDivElement, tab: HTMLElement): void {
59
+ const left = tab.offsetLeft
60
+ const right = left + tab.offsetWidth
61
+ if (left < rows.scrollLeft) rows.scrollLeft = left
62
+ else if (right > rows.scrollLeft + rows.clientWidth) rows.scrollLeft = right - rows.clientWidth
50
63
  }
51
64
 
52
65
  export function L3ProjectFileTabs({
@@ -66,12 +79,6 @@ export function L3ProjectFileTabs({
66
79
  const snapshot = gitRuntime.getWorkspace(cwd)?.snapshot
67
80
  const rowsRef = useRef<HTMLDivElement>(null)
68
81
  const suppressTouchClickRef = useRef(false)
69
- const [expanded, setExpanded] = useState(false)
70
- const [overflow, setOverflow] = useState(false)
71
- const [clippedIndexes, setClippedIndexes] = useState<readonly number[]>([])
72
- const merged = actions !== undefined
73
- const rowHeight = merged && !mobile ? 32 : 36
74
- const maxHeight = (mobile ? 2 : 3) * rowHeight
75
82
  const allPaths = [
76
83
  ...paths,
77
84
  ...diffs.map((diff) => [diff.repositoryRoot, diff.path].filter(Boolean).join('/'))
@@ -121,43 +128,32 @@ export function L3ProjectFileTabs({
121
128
  const rows = rowsRef.current
122
129
  if (!rows) return
123
130
  const inspect = (): void => {
124
- const items = Array.from(rows.children).filter(
125
- (child): child is HTMLElement => child instanceof HTMLElement
126
- )
127
- const clipped = items.flatMap((item, index) => (item.offsetTop >= maxHeight ? [index] : []))
128
- setClippedIndexes((previous) =>
129
- previous.join(',') === clipped.join(',') ? previous : clipped
130
- )
131
- setOverflow(rows.scrollHeight > maxHeight)
132
131
  const selected = rows
133
132
  .querySelector<HTMLElement>('[aria-selected="true"]')
134
133
  ?.closest<HTMLElement>('[data-file-tab]')
135
- if (selected && selected.offsetTop >= maxHeight) setExpanded(true)
134
+ if (selected) revealTab(rows, selected)
136
135
  }
137
136
  inspect()
138
137
  const observer = new ResizeObserver(inspect)
139
138
  observer.observe(rows)
140
139
  return () => observer.disconnect()
141
- }, [active, diffs, maxHeight, paths])
140
+ }, [active, diffs, paths])
142
141
 
143
142
  if (tabs.length === 0) return null
144
143
  return (
145
144
  <div
146
- className={cn('shrink-0 bg-muted', merged ? 'flex flex-wrap items-start' : 'border-b')}
145
+ className="flex min-w-0 max-w-full shrink-0 flex-col border-b bg-muted"
147
146
  data-testid="file-tabs"
148
147
  >
149
- <div
150
- className={cn('overflow-hidden', merged && 'min-w-0 flex-[1_1_12rem]')}
151
- style={{ maxHeight: expanded ? undefined : maxHeight }}
152
- >
148
+ <div className="min-w-0 max-w-full">
153
149
  <div
154
150
  ref={rowsRef}
155
151
  role="tablist"
156
152
  aria-label={t('openFiles')}
157
- className="relative flex flex-wrap content-start items-start"
153
+ aria-orientation="horizontal"
154
+ className="relative flex min-w-0 flex-nowrap items-stretch overflow-x-auto overflow-y-hidden overscroll-x-contain [scrollbar-color:var(--border)_transparent] [scrollbar-width:thin]"
158
155
  >
159
156
  {tabs.map((tab, index) => {
160
- const clipped = !expanded && clippedIndexes.includes(index)
161
157
  const selected = tab.key === active
162
158
  const isDiff = typeof tab.target !== 'string'
163
159
  const change =
@@ -186,23 +182,14 @@ export function L3ProjectFileTabs({
186
182
  <div
187
183
  data-file-tab
188
184
  data-testid={tab.testId}
189
- aria-hidden={clipped || undefined}
190
- inert={clipped || undefined}
191
185
  title={tab.title}
192
186
  className={cn(
193
- 'flex max-w-full items-center text-sm',
194
- merged ? (mobile ? 'h-9' : 'h-8') : 'h-9 border-r border-b',
195
- mobile
196
- ? tab.revisions
197
- ? 'w-full'
198
- : 'w-[min(50%,12rem)]'
199
- : tab.revisions
200
- ? 'max-w-80'
201
- : 'max-w-60',
202
- clipped && 'invisible',
187
+ 'flex shrink-0 items-center border-r text-sm transition-colors',
188
+ mobile ? 'h-9' : 'h-8',
189
+ tab.revisions ? 'max-w-[min(100%,20rem)]' : 'max-w-[min(100%,15rem)]',
203
190
  selected
204
- ? 'bg-background text-foreground shadow-[inset_0_-2px_0_var(--primary)]'
205
- : 'text-muted-foreground'
191
+ ? 'bg-background text-foreground shadow-[inset_0_-2px_0_var(--foreground)]'
192
+ : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
206
193
  )}
207
194
  onPointerDown={(event) => {
208
195
  if (event.pointerType === 'touch') suppressTouchClickRef.current = false
@@ -215,6 +202,10 @@ export function L3ProjectFileTabs({
215
202
  role="tab"
216
203
  aria-selected={selected}
217
204
  className="flex h-full min-w-0 flex-1 cursor-pointer items-center gap-2 pl-3 pr-1 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
205
+ onFocus={(event) => {
206
+ const tab = event.currentTarget.closest<HTMLElement>('[data-file-tab]')
207
+ if (rowsRef.current && tab) revealTab(rowsRef.current, tab)
208
+ }}
218
209
  onClick={() => {
219
210
  if (suppressTouchClickRef.current) {
220
211
  suppressTouchClickRef.current = false
@@ -251,18 +242,17 @@ export function L3ProjectFileTabs({
251
242
  if (!direction) return
252
243
  event.preventDefault()
253
244
  const next = (index + direction + tabs.length) % tabs.length
254
- setExpanded(true)
255
245
  const buttons =
256
246
  rowsRef.current?.querySelectorAll<HTMLButtonElement>('[role="tab"]')
257
247
  buttons?.[next]?.focus({ preventScroll: true })
258
248
  }}
259
249
  >
260
- <FileCode2Icon className="size-3.5 shrink-0" />
250
+ <L4FileIcon path={tab.path} className="size-4 shrink-0" />
261
251
  <span className={cn('truncate', change && getL3GitChangeClass(change))}>
262
252
  {tab.label}
263
253
  </span>
264
254
  {tab.revisions ? (
265
- <span className="shrink-0 font-mono text-xs">{tab.revisions}</span>
255
+ <span className="max-w-36 truncate font-mono text-xs">{tab.revisions}</span>
266
256
  ) : null}
267
257
  {change ? (
268
258
  <span
@@ -276,6 +266,7 @@ export function L3ProjectFileTabs({
276
266
  <Button
277
267
  variant="ghost"
278
268
  size="icon"
269
+ className={cn('shrink-0 rounded-none', mobile && 'size-9')}
279
270
  data-testid={
280
271
  isDiff
281
272
  ? tab.testId.replace('file-diff-tab-', 'file-diff-tab-close-')
@@ -323,30 +314,18 @@ export function L3ProjectFileTabs({
323
314
  })}
324
315
  </div>
325
316
  </div>
326
- {merged && actions ? (
317
+ {actions ? (
327
318
  <div
328
- className="ml-auto flex min-h-8 max-w-full flex-wrap items-center justify-end gap-1 px-1"
319
+ className={cn(
320
+ 'flex min-h-9 min-w-0 max-w-full flex-wrap items-center justify-end gap-1 overflow-x-auto border-t bg-background p-1 [&>*]:min-w-0 [&>*]:max-w-full [&>span]:break-all [&>[role=group]]:flex-wrap',
321
+ mobile && '[&_button]:min-h-9 [&_button]:min-w-9'
322
+ )}
329
323
  role="group"
330
324
  aria-label={t('fileActions')}
331
325
  >
332
326
  {actions}
333
327
  </div>
334
328
  ) : null}
335
- {overflow ? (
336
- <Button
337
- variant="ghost"
338
- className="w-full rounded-none text-muted-foreground"
339
- aria-expanded={expanded}
340
- onClick={() => setExpanded((value) => !value)}
341
- >
342
- {expanded ? (
343
- <ChevronUpIcon className="size-3.5" />
344
- ) : (
345
- <ChevronDownIcon className="size-3.5" />
346
- )}
347
- {expanded ? t('collapseTabs') : t('expandTabs', { count: tabs.length })}
348
- </Button>
349
- ) : null}
350
329
  </div>
351
330
  )
352
331
  }
@@ -75,12 +75,13 @@ export class L3ProjectGitRuntime implements L3ProjectGitBrowser {
75
75
  this.listeners.clear()
76
76
  }
77
77
  private async load(refresh: boolean): Promise<void> {
78
- if (this.disposed) return
79
- this.request?.abort()
78
+ if (this.disposed || this.request) return
80
79
  const request = new AbortController()
81
80
  this.request = request
82
- this.state.snapshot = { ...this.state.snapshot, status: 'loading', error: null }
83
- this.publish()
81
+ if (this.state.snapshot.status !== 'ready') {
82
+ this.state.snapshot = { ...this.state.snapshot, status: 'loading', error: null }
83
+ this.publish()
84
+ }
84
85
  try {
85
86
  const result = await this.reader.snapshot(request.signal, refresh)
86
87
  if (this.request !== request) return
@@ -0,0 +1,36 @@
1
+ 'use client'
2
+
3
+ import { useEffect, type RefObject } from 'react'
4
+
5
+ // 只在进入可见预览或窗口重新激活时读取;计时器仅合并同一次激活事件。
6
+ export function useL3ProjectRefresh(
7
+ host: RefObject<HTMLElement | null>,
8
+ refresh: () => void,
9
+ active = true
10
+ ): void {
11
+ useEffect(() => {
12
+ if (!active) return
13
+ let timer: ReturnType<typeof setTimeout> | undefined
14
+ const visible = (): boolean =>
15
+ document.visibilityState === 'visible' &&
16
+ Boolean(host.current?.getClientRects().length) &&
17
+ !host.current?.closest('[inert], [aria-hidden="true"]')
18
+ const run = (): void => {
19
+ timer = undefined
20
+ if (visible()) refresh()
21
+ }
22
+ const onFocus = (): void => {
23
+ if (timer !== undefined) clearTimeout(timer)
24
+ timer = undefined
25
+ if (visible()) timer = setTimeout(run, 150)
26
+ }
27
+ run()
28
+ window.addEventListener('focus', onFocus)
29
+ document.addEventListener('visibilitychange', onFocus)
30
+ return () => {
31
+ if (timer !== undefined) clearTimeout(timer)
32
+ window.removeEventListener('focus', onFocus)
33
+ document.removeEventListener('visibilitychange', onFocus)
34
+ }
35
+ }, [active, host, refresh])
36
+ }
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { ChevronRightIcon, FileIcon, LoaderCircleIcon, SearchIcon } from 'lucide-react'
3
+ import { ChevronRightIcon, LoaderCircleIcon, SearchIcon } from 'lucide-react'
4
4
  import { useId, useState, type ReactElement, type ReactNode, type Ref } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
6
  import type {
@@ -268,7 +268,7 @@ export function L3ProjectSearchResults({
268
268
  expanded && 'rotate-90'
269
269
  )}
270
270
  />
271
- <FileIcon className="size-3.5 shrink-0 text-muted-foreground" />
271
+ <span className="shrink-0">{renderFileIcon(path)}</span>
272
272
  <span className="min-w-0 truncate text-sm">
273
273
  {path.slice(slash + 1)}
274
274
  <span className="ml-1.5 text-xs text-muted-foreground">
@@ -0,0 +1,86 @@
1
+ 'use client'
2
+
3
+ import { ChevronRightIcon, FolderIcon, FolderOpenIcon } from 'lucide-react'
4
+ import type { ComponentPropsWithRef } from 'react'
5
+ import { cn } from '@client/l4_foundation/lib/l4-utils'
6
+ import {
7
+ Collapsible,
8
+ CollapsibleContent,
9
+ CollapsibleTrigger
10
+ } from '@client/l4_foundation/ui/shadcn/collapsible'
11
+ import type { L3CompactDirectory } from '../l3-project-path-tree'
12
+
13
+ export function L3ProjectTreeFolder({
14
+ directory,
15
+ expanded,
16
+ onToggle,
17
+ children,
18
+ ...props
19
+ }: ComponentPropsWithRef<'div'> & {
20
+ directory: L3CompactDirectory
21
+ expanded: boolean
22
+ onToggle: () => void
23
+ }): React.JSX.Element {
24
+ const parts = directory.label.split('/')
25
+ const leaf = parts.at(-1)
26
+ const prefix = parts.slice(0, -1).join(' \\ ')
27
+ const label = parts.join(' \\ ')
28
+ const Icon = expanded ? FolderOpenIcon : FolderIcon
29
+
30
+ return (
31
+ <Collapsible open={expanded} onOpenChange={onToggle}>
32
+ <div
33
+ role="treeitem"
34
+ aria-expanded={expanded}
35
+ aria-selected={false}
36
+ title={directory.targetPath}
37
+ {...props}
38
+ >
39
+ <div className="flex min-h-9 items-center rounded-sm px-2 py-1 transition-colors hover:bg-foreground/8">
40
+ <CollapsibleTrigger
41
+ className="flex min-h-7 min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-sm bg-transparent p-0 text-left text-sm outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
42
+ aria-label={label}
43
+ onKeyDown={(event) => {
44
+ if (
45
+ (event.key === 'ArrowLeft' && expanded) ||
46
+ (event.key === 'ArrowRight' && !expanded)
47
+ ) {
48
+ event.preventDefault()
49
+ event.stopPropagation()
50
+ onToggle()
51
+ }
52
+ }}
53
+ >
54
+ <span className="flex size-6 shrink-0 items-center justify-center">
55
+ <ChevronRightIcon
56
+ className={cn(
57
+ 'size-4 text-muted-foreground transition-transform',
58
+ expanded && 'rotate-90'
59
+ )}
60
+ />
61
+ </span>
62
+ <Icon className="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
63
+ <span className="flex min-w-0 items-baseline">
64
+ {prefix ? (
65
+ <>
66
+ <span className="min-w-0 shrink-[2] truncate text-muted-foreground">
67
+ {prefix}
68
+ </span>
69
+ <span className="shrink-0 px-1 text-muted-foreground" aria-hidden="true">
70
+ {'\\'}
71
+ </span>
72
+ </>
73
+ ) : null}
74
+ <span className="min-w-0 truncate">{leaf}</span>
75
+ </span>
76
+ </CollapsibleTrigger>
77
+ </div>
78
+ <CollapsibleContent>
79
+ <div role="group" className="ml-4 border-l pl-2">
80
+ {children}
81
+ </div>
82
+ </CollapsibleContent>
83
+ </div>
84
+ </Collapsible>
85
+ )
86
+ }
@@ -1,6 +1,6 @@
1
1
  # 文件类型图标
2
2
 
3
- `l4-file-icon.tsx` 是宿主文件类型图标入口。特殊文件名优先于扩展名,未知类型回退 Lucide 文档图标;操作图标继续使用 Lucide。
3
+ `l4-file-icon.tsx` 是文件树、搜索、预览页签与聊天读取消息共用的文件类型图标入口。常用类型保留彩色图标;环境与工具配置共用中性配置图标,其他已识别代码类型回退通用代码图标,未知文件回退文档图标。目录与操作图标继续使用中性色 Lucide。
4
4
 
5
5
  `material/` 保留 [Material Icon Theme](https://github.com/material-extensions/vscode-material-icon-theme) 的 18 个原始 SVG,来源版本为 `cb1dfb6d9cb73b15681a93939983d75dbba7bf5b`,采用 MIT 许可,完整许可见 [`material/LICENSE`](./material/LICENSE)。
6
6
 
@@ -1,4 +1,4 @@
1
- import { FileTextIcon } from 'lucide-react'
1
+ import { FileCode2Icon, FileTextIcon, Settings2Icon } from 'lucide-react'
2
2
  import Image, { type StaticImageData } from 'next/image'
3
3
  import agentIcon from './material/agent.svg'
4
4
  import consoleIcon from './material/console.svg'
@@ -74,6 +74,40 @@ const EXTENSION_ICONS = new Map<string, StaticImageData>(
74
74
  })
75
75
  )
76
76
 
77
+ const CONFIG_EXTENSIONS = new Set(['env', 'toml', 'ini', 'conf', 'cfg', 'properties'])
78
+ const CONFIG_NAMES = new Set([
79
+ '.editorconfig',
80
+ '.npmrc',
81
+ '.yarnrc',
82
+ '.prettierignore',
83
+ '.eslintignore'
84
+ ])
85
+ const CODE_EXTENSIONS = new Set([
86
+ 'c',
87
+ 'cpp',
88
+ 'cc',
89
+ 'cxx',
90
+ 'h',
91
+ 'hpp',
92
+ 'cs',
93
+ 'go',
94
+ 'rs',
95
+ 'java',
96
+ 'kt',
97
+ 'kts',
98
+ 'rb',
99
+ 'php',
100
+ 'swift',
101
+ 'dart',
102
+ 'vue',
103
+ 'svelte',
104
+ 'scss',
105
+ 'sass',
106
+ 'less',
107
+ 'sql',
108
+ 'xml'
109
+ ])
110
+
77
111
  export function L4FileIcon({
78
112
  path,
79
113
  className
@@ -83,13 +117,26 @@ export function L4FileIcon({
83
117
  }): React.JSX.Element {
84
118
  const fileName = path.replaceAll('\\', '/').split('/').at(-1)?.toLowerCase() ?? ''
85
119
  const extension = fileName.includes('.') ? fileName.slice(fileName.lastIndexOf('.') + 1) : ''
120
+ const config =
121
+ CONFIG_EXTENSIONS.has(extension) ||
122
+ CONFIG_NAMES.has(fileName) ||
123
+ fileName.startsWith('.env.') ||
124
+ fileName.startsWith('.eslintrc') ||
125
+ fileName.startsWith('.prettierrc') ||
126
+ fileName.startsWith('eslint.config.') ||
127
+ fileName.startsWith('prettier.config.')
128
+ if (config) return <Settings2Icon className={className} aria-hidden="true" />
129
+
86
130
  // 特殊文件名优先于扩展名;只查静态子集,不在卡片内生成完整主题映射。
87
131
  const icon =
88
132
  FILE_NAME_ICONS.get(fileName) ??
89
133
  (fileName.startsWith('dockerfile.') ? dockerIcon : undefined) ??
90
134
  EXTENSION_ICONS.get(extension)
91
135
 
92
- if (!icon) return <FileTextIcon className={className} aria-hidden="true" />
136
+ if (!icon) {
137
+ const Fallback = CODE_EXTENSIONS.has(extension) ? FileCode2Icon : FileTextIcon
138
+ return <Fallback className={className} aria-hidden="true" />
139
+ }
93
140
 
94
141
  return (
95
142
  <Image
@@ -5,6 +5,11 @@ import {
5
5
  L3PiSessionIdSchema
6
6
  } from '@common/l3_modules/pi/l3-pi-contract'
7
7
  import { L4TimestampMsSchema } from '@common/l4_foundation/l4-timestamp-contract'
8
+ import {
9
+ L3PiSessionSearchScopeSchema,
10
+ L3PiSessionSearchMatchSchema,
11
+ L3PiSessionSearchQuerySchema
12
+ } from '@common/l3_modules/pi/l3-pi-session-search-contract'
8
13
 
9
14
  export const L2PiDirectorySchema = z
10
15
  .object({
@@ -59,22 +64,8 @@ export const L2PiDirectoryEntryListResponseSchema = z
59
64
  })
60
65
  .strict()
61
66
 
62
- export const L2PiSessionHistorySearchScopeSchema = z.enum([
63
- 'title',
64
- 'user',
65
- 'assistant',
66
- 'sessionId'
67
- ])
68
-
69
- export const L2PiSessionHistoryMatchSchema = z
70
- .object({
71
- scope: L2PiSessionHistorySearchScopeSchema,
72
- count: z.number().int().positive(),
73
- entryId: L3PiEntryIdSchema.nullable(),
74
- timestampMs: L4TimestampMsSchema.nullable(),
75
- preview: z.string().min(1).max(160)
76
- })
77
- .strict()
67
+ export const L2PiSessionHistorySearchScopeSchema = L3PiSessionSearchScopeSchema
68
+ export const L2PiSessionHistoryMatchSchema = L3PiSessionSearchMatchSchema
78
69
 
79
70
  export const L2PiSessionHistoryItemSchema = z
80
71
  .object({
@@ -90,14 +81,10 @@ export const L2PiSessionHistoryItemSchema = z
90
81
  })
91
82
  .strict()
92
83
 
93
- export const L2PiSessionHistoryQuerySchema = z
94
- .object({
95
- cwd: L3PiCwdSchema,
96
- query: z.string().trim().max(120).default(''),
97
- forceRefresh: z.boolean().default(false),
98
- searchIn: z.array(L2PiSessionHistorySearchScopeSchema).min(1).max(4).default(['title', 'user'])
99
- })
100
- .strict()
84
+ export const L2PiSessionHistoryQuerySchema = L3PiSessionSearchQuerySchema.extend({
85
+ cwd: L3PiCwdSchema,
86
+ forceRefresh: z.boolean().default(false)
87
+ })
101
88
 
102
89
  export const L2PiSessionHistoryResponseSchema = z
103
90
  .object({