@allbluecn/web-app 0.22.4 → 0.23.1

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 (500) hide show
  1. package/package.json +4 -14
  2. package/src/__tests__/integration/login-email-acceptance.test.ts +16 -0
  3. package/src/__tests__/story-view.test.ts +33 -1
  4. package/src/components/agents/__tests__/agent-bindings-tab.test.tsx +221 -0
  5. package/src/components/agents/__tests__/agents-page.test.tsx +284 -0
  6. package/src/components/agents/agent-bindings-tab.tsx +59 -45
  7. package/src/components/agents/agent-form-dialog.tsx +3 -3
  8. package/src/components/agents/agent-selector.tsx +2 -3
  9. package/src/components/agents/agents-page.tsx +66 -31
  10. package/src/components/agents/slash-skill-menu.tsx +2 -3
  11. package/src/components/auth/__tests__/session-guard.test.tsx +74 -0
  12. package/src/components/auth/session-guard.tsx +8 -0
  13. package/src/components/billing/login-prompt-dialog.tsx +1 -1
  14. package/src/components/billing/subscription-provider.tsx +2 -16
  15. package/src/components/bug/__tests__/peek-modes.test.tsx +8 -0
  16. package/src/components/bug/__tests__/subgroup-header.test.tsx +202 -40
  17. package/src/components/bug/peek-panel.tsx +5 -3
  18. package/src/components/bug/views/bug-list/group.tsx +12 -2
  19. package/src/components/bug/views/bug-list/index.tsx +94 -67
  20. package/src/components/command/command-palette.tsx +10 -9
  21. package/src/components/design/canvas/document/frame-custom-presets.ts +9 -1
  22. package/src/components/design/canvas/panels/toolbar.tsx +21 -22
  23. package/src/components/design/canvas/react/__tests__/canvas-context-load-echo.test.tsx +199 -0
  24. package/src/components/design/canvas/react/__tests__/hooks.test.tsx +17 -3
  25. package/src/components/design/canvas/react/canvas-context.tsx +47 -26
  26. package/src/components/design/canvas/react/hooks.ts +39 -34
  27. package/src/components/design/editor/layout/__tests__/collab-param-wiring.test.tsx +17 -0
  28. package/src/components/design/editor/layout/__tests__/context-menu.test.tsx +17 -1
  29. package/src/components/design/editor/layout/__tests__/no-page-save-toast.test.tsx +17 -0
  30. package/src/components/design/editor/layout/__tests__/persistence-restore.test.tsx +17 -1
  31. package/src/components/design/editor/layout/__tests__/ssr-localstorage-smoke.test.tsx +17 -1
  32. package/src/components/design/editor/layout/design-editor-layout.tsx +13 -2
  33. package/src/components/design/editor/layout/hooks/use-design-auto-save.ts +3 -19
  34. package/src/components/devtools/devtools-panel.tsx +56 -0
  35. package/src/components/forms/__tests__/create-app-form.test.tsx +131 -0
  36. package/src/components/forms/app-field-error.tsx +21 -1
  37. package/src/components/forms/app-field.tsx +8 -8
  38. package/src/components/forms/app-submit.tsx +12 -9
  39. package/src/components/forms/create-app-form.tsx +124 -0
  40. package/src/components/forms/index.ts +2 -3
  41. package/src/components/harness/__tests__/gallery-behavior.test.tsx +37 -1
  42. package/src/components/harness/__tests__/harness-workbench-first-send.test.tsx +4 -1
  43. package/src/components/harness/__tests__/message-actions.test.tsx +7 -6
  44. package/src/components/harness/__tests__/use-video-generation.test.tsx +165 -0
  45. package/src/components/harness/ai-chat-history/index.tsx +5 -3
  46. package/src/components/harness/gallery/gallery-card.tsx +57 -35
  47. package/src/components/harness/gallery/gallery-grid.tsx +32 -15
  48. package/src/components/harness/gallery/gallery-types.ts +19 -0
  49. package/src/components/harness/gallery/gallery-video-card.tsx +54 -0
  50. package/src/components/harness/gallery/types.ts +2 -0
  51. package/src/components/harness/gallery/use-video-generation.ts +95 -0
  52. package/src/components/harness/harness-workbench-page.tsx +2 -3
  53. package/src/components/harness/message-actions.tsx +5 -3
  54. package/src/components/hotkeys/shortcuts-panel.tsx +28 -13
  55. package/src/components/iteration/__tests__/burndown-chart.test.tsx +118 -0
  56. package/src/components/iteration/burndown-chart.tsx +54 -23
  57. package/src/components/knowledge/editor/__tests__/knowledge-doc-editor-collab.test.tsx +96 -0
  58. package/src/components/knowledge/editor/knowledge-doc-editor.tsx +56 -1
  59. package/src/components/knowledge/editor/mindmap-doc-editor.tsx +12 -0
  60. package/src/components/knowledge/editor/use-auto-save-channels.ts +3 -1
  61. package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +0 -4
  62. package/src/components/layout/sidebar-03/nav-knowledge.tsx +11 -11
  63. package/src/components/layout/sidebar-03/nav-main.tsx +12 -15
  64. package/src/components/layout/sidebar-03/nav-notifications.tsx +5 -8
  65. package/src/components/layout/sidebar-03/nav-workspace.tsx +11 -9
  66. package/src/components/notifications/__tests__/settings-notifications.test.tsx +158 -0
  67. package/src/components/notifications/settings-notifications.tsx +37 -13
  68. package/src/components/notifications/team-notifications.tsx +10 -14
  69. package/src/components/settings/personal-team/account/__tests__/account-client.test.tsx +170 -0
  70. package/src/components/settings/personal-team/account/account-client.tsx +44 -7
  71. package/src/components/settings/personal-team/team/team-client.tsx +17 -19
  72. package/src/components/shared-kit/list-kit/__tests__/frozen-header.test.ts +210 -41
  73. package/src/components/shared-kit/list-kit/frozen-header.ts +134 -21
  74. package/src/components/shared-kit/list-kit/kanban/frozen-header.ts +20 -27
  75. package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +8 -0
  76. package/src/components/story/__tests__/story-list-table.test.tsx +214 -0
  77. package/src/components/story/columns.tsx +11 -2
  78. package/src/components/story/peek-panel.tsx +4 -2
  79. package/src/components/story/story-list-table.tsx +16 -1
  80. package/src/components/story/views/story-list/__tests__/frozen-headers.test.tsx +186 -0
  81. package/src/components/story/views/story-list/group.tsx +12 -1
  82. package/src/components/story/views/story-list/index.tsx +101 -68
  83. package/src/components/testcase/__tests__/case-row.test.tsx +134 -2
  84. package/src/components/testcase/__tests__/peek-panel.test.tsx +8 -0
  85. package/src/components/testcase/__tests__/step-editor.test.tsx +106 -0
  86. package/src/components/testcase/list/grouped-list.tsx +50 -22
  87. package/src/components/testcase/peek-panel.tsx +7 -4
  88. package/src/components/testcase/step-editor.tsx +196 -150
  89. package/src/components/tiptap/notion-like/editor-extensions.ts +0 -3
  90. package/src/components/tiptap/notion-like/notion-like-editor-header.tsx +1 -31
  91. package/src/components/tiptap/notion-like/notion-like-editor.tsx +32 -1
  92. package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +2 -28
  93. package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +2 -28
  94. package/src/components/workspace/dialogs/create-workspace-dialog.tsx +45 -43
  95. package/src/components/workspace/dialogs/edit-workspace-dialog.tsx +38 -36
  96. package/src/design-engine/engine/engine-decor.ts +2 -0
  97. package/src/design-engine/engine/engine-path.ts +2 -0
  98. package/src/hooks/__tests__/use-unsaved-guard.test.tsx +180 -0
  99. package/src/hooks/use-notifications.ts +9 -16
  100. package/src/hooks/use-unsaved-guard.tsx +87 -0
  101. package/src/lib/__tests__/pacer.test.ts +33 -96
  102. package/src/lib/__tests__/query-defaults.test.ts +169 -0
  103. package/src/lib/__tests__/query-global-error.test.ts +256 -0
  104. package/src/lib/anyhub/anyhub-store.ts +2 -2
  105. package/src/lib/changelog/sdk-versions.ts +2 -10
  106. package/src/lib/collab/presence-store.ts +5 -5
  107. package/src/lib/collab/use-presence-list-refresh.ts +2 -2
  108. package/src/lib/collab/use-share-presence.ts +2 -2
  109. package/src/lib/collections/query-keys.ts +7 -2
  110. package/src/lib/commands/__tests__/command-palette-state.test.ts +12 -14
  111. package/src/lib/commands/__tests__/registry.test.ts +6 -3
  112. package/src/lib/commands/command-palette-state.ts +8 -31
  113. package/src/lib/hotkeys/__tests__/registry.test.ts +71 -18
  114. package/src/lib/hotkeys/registry.ts +188 -85
  115. package/src/lib/hotkeys/types.ts +1 -13
  116. package/src/lib/pacer.ts +1 -79
  117. package/src/lib/queries/README.md +67 -0
  118. package/src/lib/queries/__tests__/agents-queries.test.ts +101 -0
  119. package/src/lib/queries/__tests__/compliance-queries.test.ts +136 -0
  120. package/src/lib/queries/__tests__/notifications-queries.test.ts +175 -0
  121. package/src/lib/queries/__tests__/team-queries.test.ts +87 -0
  122. package/src/lib/queries/agents.ts +40 -0
  123. package/src/lib/queries/compliance.ts +69 -0
  124. package/src/lib/queries/notifications.ts +63 -0
  125. package/src/lib/queries/team.ts +39 -0
  126. package/src/lib/query-defaults.ts +8 -0
  127. package/src/lib/query-global-error.ts +128 -0
  128. package/src/lib/remote-auth-client.ts +16 -0
  129. package/src/paraglide/README.md +1 -1
  130. package/src/paraglide/messages/_index.d.ts +11 -0
  131. package/src/paraglide/messages/_index.js +5659 -88285
  132. package/src/paraglide/messages/common_error_generic.d.ts +16 -0
  133. package/src/paraglide/messages/common_error_generic.js +29 -0
  134. package/src/paraglide/messages/common_unsaved_body.d.ts +16 -0
  135. package/src/paraglide/messages/common_unsaved_body.js +29 -0
  136. package/src/paraglide/messages/common_unsaved_leave.d.ts +16 -0
  137. package/src/paraglide/messages/common_unsaved_leave.js +29 -0
  138. package/src/paraglide/messages/common_unsaved_stay.d.ts +16 -0
  139. package/src/paraglide/messages/common_unsaved_stay.js +29 -0
  140. package/src/paraglide/messages/common_unsaved_title.d.ts +16 -0
  141. package/src/paraglide/messages/common_unsaved_title.js +29 -0
  142. package/src/paraglide/messages/gallery_videofailed1.d.ts +17 -0
  143. package/src/paraglide/messages/gallery_videofailed1.js +30 -0
  144. package/src/paraglide/messages/gallery_videogenerating1.d.ts +17 -0
  145. package/src/paraglide/messages/gallery_videogenerating1.js +30 -0
  146. package/src/paraglide/messages/gallery_videomissingprompt2.d.ts +17 -0
  147. package/src/paraglide/messages/gallery_videomissingprompt2.js +30 -0
  148. package/src/paraglide/messages/gallery_videoprogress1.d.ts +19 -0
  149. package/src/paraglide/messages/gallery_videoprogress1.js +30 -0
  150. package/src/paraglide/messages/gallery_videostop1.d.ts +17 -0
  151. package/src/paraglide/messages/gallery_videostop1.js +30 -0
  152. package/src/paraglide/messages/story_table_resizecolumn1.d.ts +17 -0
  153. package/src/paraglide/messages/story_table_resizecolumn1.js +30 -0
  154. package/src/providers/index.tsx +5 -50
  155. package/src/routeTree.gen.ts +21 -0
  156. package/src/router.tsx +21 -8
  157. package/src/routes/__root.tsx +2 -1
  158. package/src/routes/__tests__/-edition-routing.test.tsx +6 -1
  159. package/src/routes/_login/forgot-password/route.lazy.tsx +7 -6
  160. package/src/routes/_login/login/route.lazy.tsx +10 -9
  161. package/src/routes/_login/register/route.lazy.tsx +19 -18
  162. package/src/routes/_login/reset-password/route.lazy.tsx +10 -9
  163. package/src/routes/_nav/knowledge/compliance/$id.lazy.tsx +9 -12
  164. package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +16 -13
  165. package/src/routes/_nav/knowledge/laws/$id.lazy.tsx +1 -1
  166. package/src/routes/_nav/notifications/route.tsx +6 -16
  167. package/src/routes/_nav/settings/-settings-tabs.tsx +9 -12
  168. package/src/routes/api/generate/__tests__/media.test.ts +378 -0
  169. package/src/routes/api/generate/media.ts +179 -0
  170. package/src/server/__tests__/agent-client-graph.test.ts +0 -1
  171. package/src/server/__tests__/agent-crud.test.ts +37 -4
  172. package/src/server/__tests__/bug-groups-two-level.test.ts +34 -1
  173. package/src/server/__tests__/chat-conversations.test.ts +37 -4
  174. package/src/server/__tests__/chat-list-messages.test.ts +40 -7
  175. package/src/server/__tests__/csv-export.test.ts +36 -3
  176. package/src/server/__tests__/desktop-bootstrap.test.ts +189 -0
  177. package/src/server/__tests__/remote-auth.test.ts +72 -0
  178. package/src/server/__tests__/skill-crud.test.ts +37 -4
  179. package/src/server/__tests__/sso-domain.test.ts +1 -1
  180. package/src/server/__tests__/sso-rate-limit-singleton.test.ts +123 -0
  181. package/src/server/__tests__/task-serverfn-crud.test.ts +35 -2
  182. package/src/server/__tests__/task-serverfn-queries.test.ts +35 -2
  183. package/src/server/__tests__/testcase-board.test.ts +38 -5
  184. package/src/server/__tests__/testcase-bulk.test.ts +38 -5
  185. package/src/server/__tests__/testcase-list-scope.test.ts +38 -5
  186. package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +126 -2
  187. package/src/server/ai-gateway/__tests__/chat-stream-builder-code-mode.test.ts +4 -0
  188. package/src/server/ai-gateway/__tests__/chat-stream-builder-memory.test.ts +4 -0
  189. package/src/server/ai-gateway/__tests__/chat-stream-builder-sandbox.test.ts +4 -0
  190. package/src/server/ai-gateway/__tests__/chat-stream-builder-skills.test.ts +4 -0
  191. package/src/server/ai-gateway/__tests__/pgvector-memory-adapter.test.ts +30 -0
  192. package/src/server/ai-gateway/__tests__/pgvector-memory-conformance.test.ts +92 -0
  193. package/src/server/ai-gateway/__tests__/source-mode.test.ts +49 -8
  194. package/src/server/ai-gateway/ai-output-guard.ts +50 -2
  195. package/src/server/ai-gateway/chat-stream-builder.ts +3 -0
  196. package/src/server/ai-gateway/media/index.ts +36 -29
  197. package/src/server/ai-gateway/media/trim-input.ts +52 -0
  198. package/src/server/ai-gateway/memory/pgvector-adapter.ts +27 -13
  199. package/src/server/ai-gateway/sandbox/__tests__/harness-sandbox.test.ts +252 -41
  200. package/src/server/ai-gateway/sandbox/harness-sandbox.ts +6 -67
  201. package/src/server/ai-gateway/source-mode.ts +24 -8
  202. package/src/server/desktop-bootstrap.ts +74 -0
  203. package/src/server/lib/__tests__/rate-limit.test.ts +0 -1
  204. package/src/server/lib/rate-limit.ts +0 -2
  205. package/src/server/middlewares/__tests__/auth-context.test.ts +156 -0
  206. package/src/server/middlewares/auth-context.ts +62 -0
  207. package/src/server/remote-auth.ts +58 -31
  208. package/src/server/serverFns/__tests__/ai-hosted-models.test.ts +47 -15
  209. package/src/server/serverFns/__tests__/ai-source-mode.test.ts +45 -15
  210. package/src/server/serverFns/__tests__/billing.test.ts +37 -2
  211. package/src/server/serverFns/__tests__/collab-snapshot.test.ts +41 -14
  212. package/src/server/serverFns/__tests__/collab.test.ts +41 -14
  213. package/src/server/serverFns/__tests__/compliance.test.ts +33 -1
  214. package/src/server/serverFns/__tests__/email-verification.test.ts +35 -1
  215. package/src/server/serverFns/__tests__/knowledge-breadcrumb.test.ts +46 -6
  216. package/src/server/serverFns/__tests__/knowledge-delete.test.ts +42 -14
  217. package/src/server/serverFns/__tests__/knowledge-kb-projects.test.ts +46 -6
  218. package/src/server/serverFns/__tests__/knowledge-public-compliance.test.ts +36 -2
  219. package/src/server/serverFns/__tests__/knowledge-readable-kb.test.ts +42 -10
  220. package/src/server/serverFns/__tests__/knowledge-search.test.ts +45 -9
  221. package/src/server/serverFns/__tests__/legal-code-canonical.test.ts +36 -2
  222. package/src/server/serverFns/__tests__/link-preview.test.ts +38 -4
  223. package/src/server/serverFns/__tests__/notifications-list.test.ts +36 -3
  224. package/src/server/serverFns/__tests__/ownership-merge.test.ts +38 -4
  225. package/src/server/serverFns/__tests__/project-delete-transaction.test.ts +39 -2
  226. package/src/server/serverFns/__tests__/project-kb.test.ts +34 -1
  227. package/src/server/serverFns/__tests__/project-member-idor.test.ts +34 -1
  228. package/src/server/serverFns/__tests__/project-team-assign.test.ts +34 -1
  229. package/src/server/serverFns/__tests__/read-write-merge.test.ts +38 -4
  230. package/src/server/serverFns/__tests__/story-comments.test.ts +38 -4
  231. package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +38 -4
  232. package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +37 -3
  233. package/src/server/serverFns/__tests__/story-crud.test.ts +36 -3
  234. package/src/server/serverFns/__tests__/story-links.test.ts +38 -4
  235. package/src/server/serverFns/__tests__/story-reactions.test.ts +38 -4
  236. package/src/server/serverFns/__tests__/story-tree.test.ts +38 -4
  237. package/src/server/serverFns/__tests__/team-accept.test.ts +34 -1
  238. package/src/server/serverFns/__tests__/team-invitation.test.ts +34 -1
  239. package/src/server/serverFns/__tests__/team-join-request.test.ts +34 -1
  240. package/src/server/serverFns/__tests__/team-members.test.ts +34 -1
  241. package/src/server/serverFns/__tests__/team-ownership-merge.test.ts +34 -1
  242. package/src/server/serverFns/__tests__/update-bug-guard.test.ts +37 -3
  243. package/src/server/serverFns/__tests__/update-document-guard.test.ts +38 -4
  244. package/src/server/serverFns/__tests__/update-page-guard.test.ts +40 -4
  245. package/src/server/serverFns/__tests__/update-seats.test.ts +33 -1
  246. package/src/server/serverFns/__tests__/update-story-guard.test.ts +36 -3
  247. package/src/server/serverFns/__tests__/update-testcase-guard.test.ts +37 -3
  248. package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +36 -2
  249. package/src/server/serverFns/__tests__/workspace-kb.test.ts +36 -2
  250. package/src/server/serverFns/__tests__/workspace.test.ts +34 -1
  251. package/src/server/serverFns/activity.ts +10 -14
  252. package/src/server/serverFns/agents/agent-crud.ts +16 -15
  253. package/src/server/serverFns/agents/bindings.ts +12 -13
  254. package/src/server/serverFns/agents/skill-crud.ts +16 -15
  255. package/src/server/serverFns/ai/code-mode-readiness.ts +3 -4
  256. package/src/server/serverFns/ai/search-capability.ts +3 -4
  257. package/src/server/serverFns/anyhub/app.ts +10 -14
  258. package/src/server/serverFns/anyhub/index.ts +7 -10
  259. package/src/server/serverFns/anyhub/install.ts +4 -4
  260. package/src/server/serverFns/anyhub/permissions.ts +10 -14
  261. package/src/server/serverFns/anyhub/proxy.ts +4 -6
  262. package/src/server/serverFns/auth/email-verification.ts +4 -5
  263. package/src/server/serverFns/auth/session.ts +9 -0
  264. package/src/server/serverFns/billing.ts +18 -28
  265. package/src/server/serverFns/blob-upload.ts +6 -9
  266. package/src/server/serverFns/bug/__tests__/bug-access-tokens.test.ts +34 -1
  267. package/src/server/serverFns/bug/__tests__/bug-activities.test.ts +34 -1
  268. package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +34 -1
  269. package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +34 -1
  270. package/src/server/serverFns/bug/__tests__/bug-fix.test.ts +34 -1
  271. package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +34 -1
  272. package/src/server/serverFns/bug/__tests__/bug-reactions.test.ts +34 -1
  273. package/src/server/serverFns/bug/__tests__/bug-reorder.test.ts +39 -5
  274. package/src/server/serverFns/bug/__tests__/bug-repro.test.ts +34 -1
  275. package/src/server/serverFns/bug/__tests__/bug-story.test.ts +34 -1
  276. package/src/server/serverFns/bug/__tests__/bug-webhooks.test.ts +34 -1
  277. package/src/server/serverFns/bug/bug-access-tokens.ts +16 -22
  278. package/src/server/serverFns/bug/bug-activities.ts +4 -6
  279. package/src/server/serverFns/bug/bug-collect-tokens.ts +13 -18
  280. package/src/server/serverFns/bug/bug-comments.ts +13 -18
  281. package/src/server/serverFns/bug/bug-crud/detail.ts +10 -14
  282. package/src/server/serverFns/bug/bug-crud.ts +28 -38
  283. package/src/server/serverFns/bug/bug-display-options.ts +8 -11
  284. package/src/server/serverFns/bug/bug-export.ts +5 -7
  285. package/src/server/serverFns/bug/bug-fix.ts +10 -14
  286. package/src/server/serverFns/bug/bug-groups.ts +10 -14
  287. package/src/server/serverFns/bug/bug-reactions.ts +10 -14
  288. package/src/server/serverFns/bug/bug-reorder.ts +4 -6
  289. package/src/server/serverFns/bug/bug-repro.ts +8 -11
  290. package/src/server/serverFns/bug/bug-story-bulk.ts +13 -18
  291. package/src/server/serverFns/bug/bug-story.ts +10 -14
  292. package/src/server/serverFns/bug/bug-views.ts +12 -18
  293. package/src/server/serverFns/bug/bug-webhooks.ts +13 -18
  294. package/src/server/serverFns/bug/test-bug-confirm.ts +7 -10
  295. package/src/server/serverFns/changelog/__tests__/fetch.test.ts +40 -1
  296. package/src/server/serverFns/changelog/fetch.ts +4 -6
  297. package/src/server/serverFns/chat/conversations.ts +17 -28
  298. package/src/server/serverFns/chat/list-messages.ts +4 -7
  299. package/src/server/serverFns/collab-presence.ts +11 -13
  300. package/src/server/serverFns/collab-snapshot.ts +6 -7
  301. package/src/server/serverFns/collab.ts +7 -7
  302. package/src/server/serverFns/compliance.ts +8 -8
  303. package/src/server/serverFns/design/__tests__/comments.test.ts +54 -10
  304. package/src/server/serverFns/design/__tests__/designs.test.ts +39 -16
  305. package/src/server/serverFns/design/__tests__/page-versions.test.ts +52 -14
  306. package/src/server/serverFns/design/__tests__/shares-password.test.ts +44 -14
  307. package/src/server/serverFns/design/__tests__/shares.test.ts +44 -14
  308. package/src/server/serverFns/design/__tests__/versions.test.ts +44 -14
  309. package/src/server/serverFns/design/ai/__tests__/agent-conversations.test.ts +50 -21
  310. package/src/server/serverFns/design/ai/__tests__/providers.test.ts +42 -8
  311. package/src/server/serverFns/design/ai/agent-conversations.ts +19 -26
  312. package/src/server/serverFns/design/ai/fill-annotation.ts +5 -7
  313. package/src/server/serverFns/design/ai/generate-prototype.ts +5 -7
  314. package/src/server/serverFns/design/ai/hosted-models.ts +4 -5
  315. package/src/server/serverFns/design/ai/providers.ts +30 -39
  316. package/src/server/serverFns/design/ai/source-mode.ts +10 -12
  317. package/src/server/serverFns/design/comments.ts +14 -13
  318. package/src/server/serverFns/design/designs.ts +13 -19
  319. package/src/server/serverFns/design/page-versions.ts +17 -18
  320. package/src/server/serverFns/design/pages.ts +17 -18
  321. package/src/server/serverFns/design/reviews.ts +8 -9
  322. package/src/server/serverFns/design/shares-password.ts +13 -19
  323. package/src/server/serverFns/design/shares.ts +22 -31
  324. package/src/server/serverFns/design/versions.ts +8 -9
  325. package/src/server/serverFns/harness/__tests__/attach-prototype.spec.ts +52 -2
  326. package/src/server/serverFns/harness/__tests__/attach-to-kb.spec.ts +52 -2
  327. package/src/server/serverFns/harness/__tests__/attach-to-story-ssrf.spec.ts +52 -2
  328. package/src/server/serverFns/harness/__tests__/attach-to-story.spec.ts +52 -2
  329. package/src/server/serverFns/harness/__tests__/cancel-run.test.ts +40 -15
  330. package/src/server/serverFns/harness/__tests__/extract-attachment.spec.ts +52 -2
  331. package/src/server/serverFns/harness/__tests__/gallery.spec.ts +76 -12
  332. package/src/server/serverFns/harness/__tests__/github-workspace.spec.ts +38 -6
  333. package/src/server/serverFns/harness/__tests__/media-prototype.spec.ts +50 -6
  334. package/src/server/serverFns/harness/__tests__/media.spec.ts +55 -11
  335. package/src/server/serverFns/harness/__tests__/sandbox-policy.test.ts +41 -16
  336. package/src/server/serverFns/harness/__tests__/workspace-files.test.ts +41 -16
  337. package/src/server/serverFns/harness/attach-prototype.ts +4 -4
  338. package/src/server/serverFns/harness/attach-to-kb.ts +4 -4
  339. package/src/server/serverFns/harness/attach-to-story.ts +4 -4
  340. package/src/server/serverFns/harness/cancel-run.ts +4 -6
  341. package/src/server/serverFns/harness/extract-attachment.ts +3 -4
  342. package/src/server/serverFns/harness/gallery.ts +19 -23
  343. package/src/server/serverFns/harness/github-workspace.ts +14 -21
  344. package/src/server/serverFns/harness/media.ts +18 -117
  345. package/src/server/serverFns/harness/sandbox-policy.ts +11 -10
  346. package/src/server/serverFns/harness/search-mention-candidates.ts +4 -6
  347. package/src/server/serverFns/harness/workspace-files.ts +19 -26
  348. package/src/server/serverFns/iteration/__tests__/iteration-auto-schedule.test.ts +35 -3
  349. package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +35 -3
  350. package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +35 -3
  351. package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +35 -3
  352. package/src/server/serverFns/iteration/iteration-auto-schedule.ts +7 -10
  353. package/src/server/serverFns/iteration/iteration-burndown.ts +4 -6
  354. package/src/server/serverFns/iteration/iteration-crud.ts +26 -35
  355. package/src/server/serverFns/iteration/iteration-stories.ts +10 -14
  356. package/src/server/serverFns/iteration/iteration-transfer.ts +4 -6
  357. package/src/server/serverFns/knowledge/public/bookmarks/categories.ts +11 -15
  358. package/src/server/serverFns/knowledge/public/bookmarks/create.ts +4 -6
  359. package/src/server/serverFns/knowledge/public/bookmarks/delete.ts +5 -7
  360. package/src/server/serverFns/knowledge/public/bookmarks/detail.ts +4 -6
  361. package/src/server/serverFns/knowledge/public/bookmarks/favorite.ts +4 -6
  362. package/src/server/serverFns/knowledge/public/bookmarks/list.ts +4 -6
  363. package/src/server/serverFns/knowledge/public/bookmarks/refresh-favicon.ts +4 -6
  364. package/src/server/serverFns/knowledge/public/bookmarks/share.ts +4 -6
  365. package/src/server/serverFns/knowledge/public/bookmarks/sync-github.ts +5 -7
  366. package/src/server/serverFns/knowledge/public/bookmarks/unshare.ts +4 -6
  367. package/src/server/serverFns/knowledge/public/bookmarks/update.ts +6 -8
  368. package/src/server/serverFns/knowledge/public/compliance/create.ts +4 -6
  369. package/src/server/serverFns/knowledge/public/compliance/delete.ts +5 -7
  370. package/src/server/serverFns/knowledge/public/compliance/detail.ts +4 -6
  371. package/src/server/serverFns/knowledge/public/compliance/list.ts +4 -6
  372. package/src/server/serverFns/knowledge/public/compliance/share.ts +4 -6
  373. package/src/server/serverFns/knowledge/public/compliance/unshare.ts +4 -6
  374. package/src/server/serverFns/knowledge/public/compliance/update.ts +5 -7
  375. package/src/server/serverFns/knowledge/public/dictionary/create.ts +4 -6
  376. package/src/server/serverFns/knowledge/public/dictionary/delete.ts +5 -7
  377. package/src/server/serverFns/knowledge/public/dictionary/detail.ts +4 -6
  378. package/src/server/serverFns/knowledge/public/dictionary/list.ts +4 -6
  379. package/src/server/serverFns/knowledge/public/dictionary/share.ts +4 -6
  380. package/src/server/serverFns/knowledge/public/dictionary/unshare.ts +4 -6
  381. package/src/server/serverFns/knowledge/public/dictionary/update.ts +5 -7
  382. package/src/server/serverFns/knowledge/public/entity-tags.ts +4 -6
  383. package/src/server/serverFns/knowledge/public/favorite.ts +7 -10
  384. package/src/server/serverFns/knowledge/public/legal/create.ts +4 -6
  385. package/src/server/serverFns/knowledge/public/legal/delete.ts +5 -7
  386. package/src/server/serverFns/knowledge/public/legal/detail.ts +4 -6
  387. package/src/server/serverFns/knowledge/public/legal/list.ts +4 -6
  388. package/src/server/serverFns/knowledge/public/legal/share.ts +4 -6
  389. package/src/server/serverFns/knowledge/public/legal/unshare.ts +4 -6
  390. package/src/server/serverFns/knowledge/public/legal/update.ts +5 -7
  391. package/src/server/serverFns/knowledge-index.ts +12 -16
  392. package/src/server/serverFns/knowledge.ts +50 -67
  393. package/src/server/serverFns/notifications.ts +25 -34
  394. package/src/server/serverFns/plugins/harness-plugins.ts +16 -23
  395. package/src/server/serverFns/project/module-overview.ts +7 -10
  396. package/src/server/serverFns/project.ts +48 -68
  397. package/src/server/serverFns/references/create.ts +4 -6
  398. package/src/server/serverFns/references/resolve.ts +4 -6
  399. package/src/server/serverFns/references/search.ts +4 -6
  400. package/src/server/serverFns/share/design/[designId]/events.ts +4 -6
  401. package/src/server/serverFns/sso.ts +1 -1
  402. package/src/server/serverFns/story/__tests__/bulk.test.ts +34 -1
  403. package/src/server/serverFns/story/__tests__/story-assoc.test.ts +34 -1
  404. package/src/server/serverFns/story/__tests__/story-attachments.test.ts +34 -1
  405. package/src/server/serverFns/story/__tests__/story-crud-date-key.test.ts +42 -17
  406. package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +34 -1
  407. package/src/server/serverFns/story/__tests__/story-gantt-deps.test.ts +34 -1
  408. package/src/server/serverFns/story/__tests__/story-groups.test.ts +34 -1
  409. package/src/server/serverFns/story/__tests__/story-read-path-authz.test.ts +40 -4
  410. package/src/server/serverFns/story/__tests__/story-search.test.ts +34 -1
  411. package/src/server/serverFns/story/__tests__/story-transfer.test.ts +34 -1
  412. package/src/server/serverFns/story/link-preview.ts +5 -7
  413. package/src/server/serverFns/story/story-assoc.ts +28 -38
  414. package/src/server/serverFns/story/story-attachments.ts +7 -10
  415. package/src/server/serverFns/story/story-children.ts +4 -6
  416. package/src/server/serverFns/story/story-comments.ts +13 -18
  417. package/src/server/serverFns/story/story-crud/activities.ts +4 -6
  418. package/src/server/serverFns/story/story-crud/archive.ts +7 -10
  419. package/src/server/serverFns/story/story-crud/bulk.ts +10 -14
  420. package/src/server/serverFns/story/story-crud/detail.ts +7 -10
  421. package/src/server/serverFns/story/story-crud/label-suggest.ts +4 -6
  422. package/src/server/serverFns/story/story-crud/labels.ts +6 -9
  423. package/src/server/serverFns/story/story-crud/user-brief.ts +4 -6
  424. package/src/server/serverFns/story/story-crud.ts +13 -18
  425. package/src/server/serverFns/story/story-dependencies.ts +16 -22
  426. package/src/server/serverFns/story/story-groups.ts +10 -14
  427. package/src/server/serverFns/story/story-links.ts +13 -18
  428. package/src/server/serverFns/story/story-reactions.ts +13 -18
  429. package/src/server/serverFns/story/story-search.ts +7 -10
  430. package/src/server/serverFns/story/story-transfer.ts +10 -14
  431. package/src/server/serverFns/story/story-tree.ts +7 -10
  432. package/src/server/serverFns/story-display-options.ts +7 -8
  433. package/src/server/serverFns/story-view.ts +12 -18
  434. package/src/server/serverFns/tasks/task-crud.ts +13 -17
  435. package/src/server/serverFns/tasks/task-queries.ts +12 -18
  436. package/src/server/serverFns/team.ts +46 -66
  437. package/src/server/serverFns/testcase/__tests__/automation.test.ts +38 -5
  438. package/src/server/serverFns/testcase/__tests__/evidence-upload.test.ts +37 -3
  439. package/src/server/serverFns/testcase/__tests__/generation.test.ts +38 -5
  440. package/src/server/serverFns/testcase/__tests__/plans.test.ts +36 -3
  441. package/src/server/serverFns/testcase/__tests__/regression.test.ts +36 -3
  442. package/src/server/serverFns/testcase/__tests__/run-cases.test.ts +36 -3
  443. package/src/server/serverFns/testcase/__tests__/runs.test.ts +36 -3
  444. package/src/server/serverFns/testcase/__tests__/stats.test.ts +36 -3
  445. package/src/server/serverFns/testcase/__tests__/testcase-board.test.ts +39 -5
  446. package/src/server/serverFns/testcase/__tests__/testcase-crud.test.ts +36 -3
  447. package/src/server/serverFns/testcase/__tests__/testcase-folders.test.ts +36 -3
  448. package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +38 -5
  449. package/src/server/serverFns/testcase/automation.ts +13 -17
  450. package/src/server/serverFns/testcase/bulk.ts +10 -14
  451. package/src/server/serverFns/testcase/evidence-upload.ts +5 -7
  452. package/src/server/serverFns/testcase/generation.ts +10 -13
  453. package/src/server/serverFns/testcase/plans.ts +23 -31
  454. package/src/server/serverFns/testcase/regression.ts +7 -10
  455. package/src/server/serverFns/testcase/run-cases.ts +13 -18
  456. package/src/server/serverFns/testcase/runs.ts +13 -18
  457. package/src/server/serverFns/testcase/stats.ts +13 -18
  458. package/src/server/serverFns/testcase/testcase-activities.ts +4 -6
  459. package/src/server/serverFns/testcase/testcase-board.ts +4 -6
  460. package/src/server/serverFns/testcase/testcase-crud.ts +20 -27
  461. package/src/server/serverFns/testcase/testcase-display-options.ts +8 -11
  462. package/src/server/serverFns/testcase/testcase-export.ts +5 -7
  463. package/src/server/serverFns/testcase/testcase-filter-views.ts +12 -18
  464. package/src/server/serverFns/testcase/testcase-folders.ts +16 -22
  465. package/src/server/serverFns/testcase/testcase-groups.ts +4 -6
  466. package/src/server/serverFns/testcase/testcase-reorder.ts +4 -6
  467. package/src/server/serverFns/translation.ts +4 -6
  468. package/src/server/serverFns/user/avatar.ts +4 -6
  469. package/src/server/serverFns/user/settings.ts +4 -6
  470. package/src/server/serverFns/user/update-locale.ts +4 -6
  471. package/src/server/serverFns/workspace-kb.ts +26 -35
  472. package/src/server/serverFns/workspace.ts +43 -59
  473. package/src/server/serverFns/ws.ts +4 -6
  474. package/src/server/serverfn-public-allowlist.ts +117 -0
  475. package/src/server/story/types.ts +0 -1
  476. package/src/server/sync/__tests__/scopes.test.ts +64 -0
  477. package/src/server/sync/scopes.ts +7 -0
  478. package/src/server/testcase/__tests__/auto-bug.test.ts +44 -12
  479. package/src/__tests__/bench/baselines/format-timestamp.json +0 -44
  480. package/src/__typeprobe__.d.ts +0 -4
  481. package/src/components/forms/app-form-dialog.tsx +0 -126
  482. package/src/components/forms/hooks/use-form-submit.ts +0 -57
  483. package/src/components/forms/types.ts +0 -34
  484. package/src/components/harness/code/workspace-picker.tsx +0 -239
  485. package/src/components/tiptap/extensions/local-file/insert-local-file.ts +0 -35
  486. package/src/components/tiptap/extensions/local-file/local-file-node.ts +0 -71
  487. package/src/devframe-docks/ai-chat-iframe.tsx +0 -29
  488. package/src/devframe-docks/session-iframe.tsx +0 -90
  489. package/src/lib/__tests__/desktop-detect.test.ts +0 -19
  490. package/src/lib/collections/index.ts +0 -22
  491. package/src/lib/collections/knowledge/index.ts +0 -18
  492. package/src/lib/collections/knowledge/knowledge-bases-collection.ts +0 -34
  493. package/src/lib/collections/reference/bookmark-categories-collection.ts +0 -31
  494. package/src/lib/collections/reference/labels-collection.ts +0 -31
  495. package/src/lib/collections/reference/projects-collection.ts +0 -31
  496. package/src/lib/collections/reference/users-brief-collection.ts +0 -35
  497. package/src/lib/collections/reference/workspaces-collection.ts +0 -34
  498. package/src/lib/desktop-detect.ts +0 -29
  499. package/src/paraglide/messages/en.js +0 -28260
  500. package/src/paraglide/messages/zh.js +0 -28258
@@ -15,16 +15,33 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
- // BUG 列表双层分组(任务 40):子组头 depth 缩进/折叠回调 + 冻结组头改写
19
- // computeFrozenHeader 的同构回归断言(吸顶判定与 story 同一实现,防再次漂移)。
20
- import { describe, it, expect, vi, beforeEach } from "vitest";
21
- import { render, screen, fireEvent, cleanup } from "@testing-library/react";
18
+ // BUG 列表双层分组(任务 40):子组头 depth 缩进/折叠回调 + 吸附组头回归断言
19
+ // (任务 22 起判定走共享 computeFrozenHeader——等价原生 sticky:活跃才渲染副本、
20
+ // 被下个组头逐像素顶出、边界不留容差;旧「常驻冻结层 + threshold+1 容差」已迁离)。
21
+ import { afterEach, describe, expect, it, vi } from "vitest";
22
+ import { render, screen, fireEvent, cleanup, act } from "@testing-library/react";
22
23
  import { readFileSync } from "node:fs";
23
24
  import { resolve } from "node:path";
24
25
  import { TooltipProvider } from "@/components/ui/tooltip";
25
26
  import { BugGroupHeader } from "../views/bug-list/group";
26
27
  import { computeFrozenHeader } from "@/components/shared-kit/list-kit/frozen-header";
27
- import type { BugGroupNode } from "../display/use-paged-bug-groups";
28
+ import type { BugGroupNode, PagedBugGroupNode } from "../display/use-paged-bug-groups";
29
+ import type { BugListItem } from "@/shared/bug/types";
30
+
31
+ // BugList 渲染链裁剪:serverFn 桩(createServerFn 需 tanstackStart 插件编译)、
32
+ // 行与批量工具栏裁掉(各自重 import 链);组头/组尾保持真实渲染
33
+ vi.mock("@/server/serverFns/bug", () => ({
34
+ bulkArchiveBugsFn: vi.fn(),
35
+ bulkDeleteBugsFn: vi.fn(),
36
+ bulkUpdateBugsFn: vi.fn(),
37
+ createBugFn: vi.fn(),
38
+ updateBugFn: vi.fn(),
39
+ }));
40
+ vi.mock("../views/bug-list/row", () => ({ BugListRow: () => null }));
41
+ vi.mock("../views/bug-list/batch-toolbar", () => ({ BugBatchToolbar: () => null }));
42
+
43
+ import { BugList } from "../views/bug-list";
44
+ import { DEFAULT_BUG_DISPLAY_OPTIONS } from "@/shared/bug/display-types";
28
45
 
29
46
  const renderWithProviders = (ui: React.ReactElement) =>
30
47
  render(<TooltipProvider>{ui}</TooltipProvider>);
@@ -51,9 +68,6 @@ const baseHeaderProps = {
51
68
  };
52
69
 
53
70
  describe("BugGroupHeader 子组头(depth)", () => {
54
- beforeEach(() => {
55
- vi.clearAllMocks();
56
- });
57
71
  afterEach(() => cleanup());
58
72
 
59
73
  it("depth=1 渲染缩进(pl-4)与子组底色(bg-muted/35)", () => {
@@ -97,15 +111,14 @@ describe("BugGroupHeader 子组头(depth)", () => {
97
111
  });
98
112
 
99
113
  // ---------------------------------------------------------------------------
100
- // 冻结组头同构回归:bug 列表吸顶判定必须消费共享 computeFrozenHeader
101
- // (任务 40 内联吸顶改写,与 story-list 同一实现;读源码断言防漂移——
102
- // install-commands-contract.test.ts 先例)。
114
+ // 吸附组头共享实现契约(bug ↔ story ↔ kanban 三处统一,任务 22):
115
+ // 读源码断言防漂移(install-commands-contract.test.ts 先例)。
103
116
  // ---------------------------------------------------------------------------
104
117
 
105
118
  const indexSource = readFileSync(resolve(import.meta.dirname, "../views/bug-list/index.tsx"), "utf8");
106
119
 
107
- describe("冻结组头共享实现契约(bug ↔ story 同构)", () => {
108
- it("bug-list/index.tsx 从 shared-kit 导入 computeFrozenHeader(不再内联 rangeExtractor+activeStickyRef 吸顶)", () => {
120
+ describe("吸附组头共享实现契约(bug ↔ story 同构)", () => {
121
+ it("bug-list/index.tsx 从 shared-kit 导入 computeFrozenHeader(共享判定,不内联吸顶)", () => {
109
122
  expect(indexSource).toContain(
110
123
  'from "@/components/shared-kit/list-kit/frozen-header"',
111
124
  );
@@ -115,36 +128,185 @@ describe("冻结组头共享实现契约(bug ↔ story 同构)", () => {
115
128
  expect(indexSource).not.toContain("position: sticky");
116
129
  });
117
130
 
118
- it("computeFrozenHeader 吸顶判定与 story 同构(threshold=scrollTop+1、子组 parent 归属)", () => {
119
- // bug 双层扁平行布局(index → start):
120
- // 0 header(state=open) 0
121
- // 1 subHeader(severity) 36
122
- // 2 bug 80
123
- // 3 header(state=fixed) 200
124
- const measurements = [{ start: 0 }, { start: 36 }, { start: 80 }, { start: 200 }];
125
- const groupIdx = [0, 3];
126
- const subIdx = [1];
127
- const parentOfSub = new Map([[1, 0]]);
128
-
129
- // 未滚动:激活首个主组、无子组
130
- expect(computeFrozenHeader(0, measurements, groupIdx, subIdx, parentOfSub)).toEqual({
131
- groupIdx: 0,
132
- subIdx: null,
131
+ it("旧「常驻冻结层」形态必须清零:无固定 top 双层覆盖渲染(subIdx/groupIdx 旧字段)", () => {
132
+ // 任务 22 前的常驻冻结层:frozenHeader.subIdx/groupIdx + 固定 top 覆盖渲染
133
+ expect(indexSource).not.toContain("subIdx");
134
+ expect(indexSource).not.toContain("groupIdx");
135
+ // 新吸附层:数据槽存在 + 子组头吸附位钉在吸附主组头正下方(subPinTop=HEADER_ROW_HEIGHT)
136
+ expect(indexSource).toContain('data-slot="bug-frozen-headers"');
137
+ expect(indexSource).toContain("subPinTop: HEADER_ROW_HEIGHT");
138
+ });
139
+
140
+ it("computeFrozenHeader 双层判定与 bug 扁平布局对表(吸附/顶出/区段随动/切换)", () => {
141
+ // bug 双层扁平行布局(estimateSize 估高:header/subHeader 36、bug 44、tail 40)
142
+ // 0 header(open) 0
143
+ // 1 subHeader(critical) 36
144
+ // 2 bug 72
145
+ // 3 tail 116
146
+ // 4 subHeader(low) 156
147
+ // 5 bug 192
148
+ // 6 tail 236
149
+ // 7 header(fixed) 276
150
+ // 8 subHeader(critical) 312
151
+ // 9 bug 356
152
+ // 10 tail 400
153
+ const measurements = [
154
+ { start: 0, size: 36 },
155
+ { start: 36, size: 36 },
156
+ { start: 72, size: 44 },
157
+ { start: 116, size: 40 },
158
+ { start: 156, size: 36 },
159
+ { start: 192, size: 44 },
160
+ { start: 236, size: 40 },
161
+ { start: 276, size: 36 },
162
+ { start: 312, size: 36 },
163
+ { start: 356, size: 44 },
164
+ { start: 400, size: 40 },
165
+ ];
166
+ const levels = {
167
+ groupIndexes: [0, 7],
168
+ subIndexes: [1, 4, 8],
169
+ parentGroupOfSub: new Map<number, number>([
170
+ [1, 0],
171
+ [4, 0],
172
+ [8, 7],
173
+ ]),
174
+ pad: 0,
175
+ subPinTop: 36,
176
+ };
177
+ const compute = (scrollTop: number) => computeFrozenHeader(scrollTop, measurements, levels);
178
+
179
+ // 首屏:首个组头容器坐标恰为 0,边界不留容差地吸附;子组头吸附位 36 恰在组头正下方
180
+ expect(compute(0)).toEqual({
181
+ group: { index: 0, offset: 0 },
182
+ sub: { index: 1, offset: 0 },
133
183
  });
134
- // +1 容差:start == scrollTop + 1 视为已到达(与 story 同一语义)
135
- expect(computeFrozenHeader(35, measurements, groupIdx, subIdx, parentOfSub)).toEqual({
136
- groupIdx: 0,
137
- subIdx: 1,
184
+ // 下一个子组头(start 156)逼近:活跃子组头被逐像素顶出(56 - 36 - 36 = -16)
185
+ expect(compute(100)).toEqual({
186
+ group: { index: 0, offset: 0 },
187
+ sub: { index: 1, offset: -16 },
138
188
  });
139
- // 跨到第二个主组:子组重置(不属于该主组)
140
- expect(computeFrozenHeader(200, measurements, groupIdx, subIdx, parentOfSub)).toEqual({
141
- groupIdx: 3,
142
- subIdx: null,
189
+ // 组切换前一瞬(st=275):主组头被 G2 顶出 -35;子组头随区段结束(G2 上缘)滑出 -71
190
+ expect(compute(275)).toEqual({
191
+ group: { index: 0, offset: -35 },
192
+ sub: { index: 4, offset: -71 },
143
193
  });
144
- // 负 scrollTop(overscroll)钳制
145
- expect(computeFrozenHeader(-5, measurements, groupIdx, subIdx, parentOfSub)).toEqual({
146
- groupIdx: 0,
147
- subIdx: null,
194
+ // 组切换边界(st=276):新组头零跳变就位,子组头切换为其首个子组(归属门生效)
195
+ expect(compute(276)).toEqual({
196
+ group: { index: 7, offset: 0 },
197
+ sub: { index: 8, offset: 0 },
148
198
  });
199
+ // 滚过末尾:双层停在最后一组(无顶出源,offset 恒 0)
200
+ expect(compute(10000)).toEqual({
201
+ group: { index: 7, offset: 0 },
202
+ sub: { index: 8, offset: 0 },
203
+ });
204
+ });
205
+ });
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // BugList 吸附层行为(jsdom:行高走 estimateSize 精确值,scrollTop 直写 + rAF 节流)
209
+ // ---------------------------------------------------------------------------
210
+
211
+ function makeBugItem(id: string): BugListItem {
212
+ return { id } as unknown as BugListItem;
213
+ }
214
+
215
+ /** 双层分组夹具:布局与上方「对表」用例一致(starts 0/36/72/116/156/192/236/276/312/356/400) */
216
+ const twoLevelGroups: PagedBugGroupNode[] = [
217
+ makeNode({
218
+ key: "open",
219
+ label: "待处理",
220
+ totalCount: 2,
221
+ items: [makeBugItem("b1"), makeBugItem("b2")],
222
+ subGroups: [
223
+ makeNode({ key: "critical", label: "严重", totalCount: 1, items: [makeBugItem("b1")] }),
224
+ makeNode({ key: "low", label: "较低", totalCount: 1, items: [makeBugItem("b2")] }),
225
+ ],
226
+ }),
227
+ makeNode({
228
+ key: "fixed",
229
+ label: "已修复",
230
+ totalCount: 1,
231
+ items: [makeBugItem("b3")],
232
+ subGroups: [
233
+ makeNode({ key: "critical", label: "严重", totalCount: 1, items: [makeBugItem("b3")] }),
234
+ ],
235
+ }),
236
+ ];
237
+
238
+ const baseListProps = {
239
+ groups: twoLevelGroups,
240
+ isLoading: false,
241
+ displayFilters: DEFAULT_BUG_DISPLAY_OPTIONS,
242
+ users: [],
243
+ labels: [],
244
+ onRefresh: () => {},
245
+ onPatchBug: () => {},
246
+ onOpenBug: () => {},
247
+ };
248
+
249
+ async function scrollBugList(container: HTMLElement, top: number) {
250
+ const el = container.querySelector<HTMLElement>(".overflow-y-auto");
251
+ if (!el) throw new Error("scroll container not found");
252
+ Object.defineProperty(el, "scrollTop", { configurable: true, value: top });
253
+ await act(async () => {
254
+ fireEvent.scroll(el);
255
+ // 组件内 rAF 节流:排进同帧队列,等下一帧回调执行完
256
+ await new Promise((r) => requestAnimationFrame(r));
257
+ });
258
+ }
259
+
260
+ const frozenLayer = (container: HTMLElement) =>
261
+ container.querySelector<HTMLElement>('[data-slot="bug-frozen-headers"]');
262
+
263
+ describe("BugList 吸附组头(共享 computeFrozenHeader)", () => {
264
+ afterEach(() => cleanup());
265
+
266
+ it("首屏即吸附(首个组头容器坐标恰为 0):主/子双层满宽实色底色盒 offset 0", () => {
267
+ const { container } = renderWithProviders(<BugList {...baseListProps} />);
268
+ const layer = frozenLayer(container);
269
+ expect(layer).toBeTruthy();
270
+ const bands = layer!.querySelectorAll<HTMLElement>(":scope > div");
271
+ expect(bands.length).toBe(2);
272
+ // 子组头带在下(top 36 = HEADER_ROW_HEIGHT)、主组头带在上(top 0)
273
+ expect(bands[0]!.textContent).toContain("严重");
274
+ expect(bands[0]!.style.transform).toBe("translateY(0px)");
275
+ expect(bands[0]!.style.top).toBe("36px");
276
+ expect(bands[1]!.textContent).toContain("待处理");
277
+ expect(bands[1]!.style.transform).toBe("translateY(0px)");
278
+ // 满宽实色底色盒(与 BugGroupHeader sticky 变体同源,任务 22 单一来源)
279
+ expect(bands[0]!.className).toContain("bg-[color-mix(in_oklch,var(--muted)_35%,var(--card)_65%)]");
280
+ expect(bands[1]!.className).toContain("bg-[color-mix(in_oklch,var(--muted)_50%,var(--card)_50%)]");
281
+ });
282
+
283
+ it("下个组头逼近:主组头被顶出负值、子组头随区段结束滑出", async () => {
284
+ const { container } = renderWithProviders(<BugList {...baseListProps} />);
285
+ await scrollBugList(container, 275);
286
+ const bands = frozenLayer(container)!.querySelectorAll<HTMLElement>(":scope > div");
287
+ expect(bands[1]!.textContent).toContain("待处理");
288
+ expect(bands[1]!.style.transform).toBe("translateY(-35px)");
289
+ expect(bands[0]!.textContent).toContain("较低");
290
+ expect(bands[0]!.style.transform).toBe("translateY(-71px)");
291
+ });
292
+
293
+ it("组切换边界零跳变:新组头 offset 0 就位,子组头切换为新组首个子组", async () => {
294
+ const { container } = renderWithProviders(<BugList {...baseListProps} />);
295
+ await scrollBugList(container, 276);
296
+ const bands = frozenLayer(container)!.querySelectorAll<HTMLElement>(":scope > div");
297
+ expect(bands[1]!.textContent).toContain("已修复");
298
+ expect(bands[1]!.style.transform).toBe("translateY(0px)");
299
+ expect(bands[0]!.textContent).toContain("严重");
300
+ expect(bands[0]!.style.transform).toBe("translateY(0px)");
301
+ });
302
+
303
+ it("子组头被下个同级子组头顶出(组内滚动)", async () => {
304
+ const { container } = renderWithProviders(<BugList {...baseListProps} />);
305
+ await scrollBugList(container, 100);
306
+ const bands = frozenLayer(container)!.querySelectorAll<HTMLElement>(":scope > div");
307
+ expect(bands[1]!.textContent).toContain("待处理");
308
+ expect(bands[1]!.style.transform).toBe("translateY(0px)");
309
+ expect(bands[0]!.textContent).toContain("严重");
310
+ expect(bands[0]!.style.transform).toBe("translateY(-16px)");
149
311
  });
150
312
  });
@@ -23,7 +23,7 @@
23
23
  // 与独立路由共用 BugDetailBody。
24
24
  import { useCallback, useEffect, useRef, useState } from "react";
25
25
  import { createPortal } from "react-dom";
26
- import { useHotkey } from "@tanstack/react-hotkeys";
26
+ import { useAppHotkeys, useHotkey } from "@tanstack/react-hotkeys";
27
27
  import { Skeleton } from "@/components/ui/skeleton";
28
28
  import { Dialog, DialogContent } from "@/components/ui/dialog";
29
29
  import { CalendarClock, Folder } from "lucide-react";
@@ -144,8 +144,10 @@ export function BugPeekPanel({
144
144
  }, [bugId]);
145
145
 
146
146
  // Esc 仅 side-peek 面板注册(side-dock 与 story/testcase 口径一致不注册;
147
- // modal/full-screen 由 Dialog 自身 Esc → onOpenChange 关闭)
148
- useHotkey("Escape", onClose, { enabled: isSidePeek });
147
+ // modal/full-screen 由 Dialog 自身 Esc → onOpenChange 关闭)。
148
+ // 声明即绑定:close-panel 条目单一来源(lib/hotkeys/registry.ts)
149
+ const hk = useAppHotkeys();
150
+ useHotkey(hk["close-panel"], onClose, { enabled: isSidePeek });
149
151
 
150
152
  const handleBugUpdate = useCallback(
151
153
  (next: BugDetail) => {
@@ -44,12 +44,22 @@ interface BugGroupHeaderProps {
44
44
  showSelect: boolean;
45
45
  }
46
46
 
47
+ /**
48
+ * 组头吸附层底色单一来源(任务 22):吸附层满宽底色盒(index.tsx)与
49
+ * BugGroupHeader 的 sticky 变体共用,防漂移。必须是实色(color-mix 两实色
50
+ * 变量):吸附层要完全遮挡其下滚动的行,半透明底会透出下层内容。
51
+ */
52
+ export const bugHeaderSurfaceClass = (depth: number) =>
53
+ depth === 0
54
+ ? "bg-[color-mix(in_oklch,var(--muted)_50%,var(--card)_50%)]"
55
+ : "bg-[color-mix(in_oklch,var(--muted)_35%,var(--card)_65%)]";
56
+
47
57
  export const BugGroupHeader = memo(function BugGroupHeader({ node, depth, collapsed, collapseKey, onToggle, onCreate, sticky, selectState, onToggleSelect, showSelect }: BugGroupHeaderProps) {
48
58
  const textColor = node.color?.split(" ").find((c) => c.startsWith("text")) ?? "text-muted-foreground";
49
59
 
50
60
  return (
51
- // 冻结层用实色混底(50%/35% muted×card),原位行用透明度底——与 story 双层一致
52
- <div className={cn("flex h-full items-center", sticky ? (depth === 0 ? "bg-[color-mix(in_oklch,var(--muted)_50%,var(--card)_50%)]" : "bg-[color-mix(in_oklch,var(--muted)_35%,var(--card)_65%)]") : depth === 0 ? "bg-muted/50" : "bg-muted/35", depth > 0 && "pl-4")}>
61
+ // 吸附层用实色混底(50%/35% muted×card),原位行用透明度底——与 story 双层一致
62
+ <div className={cn("flex h-full items-center", sticky ? bugHeaderSurfaceClass(depth) : depth === 0 ? "bg-muted/50" : "bg-muted/35", depth > 0 && "pl-4")}>
53
63
  {showSelect && (
54
64
  <Checkbox
55
65
  checked={selectState === true}
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
19
- import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
19
+ import { useVirtualizer } from "@tanstack/react-virtual";
20
20
  import {
21
21
  bulkArchiveBugsFn,
22
22
  bulkDeleteBugsFn,
@@ -26,18 +26,19 @@ import { toast } from "@/components/ui/toast";
26
26
  import type { UserBrief } from "@/shared/story/types";
27
27
  import type { BugDisplayOptions, BugDisplayProperty } from "@/shared/bug/display-types";
28
28
  import type { BugGroupNode, BugPatch, PagedBugGroupNode } from "../../display/use-paged-bug-groups";
29
- import { BugGroupHeader, BugGroupTailRow } from "./group";
29
+ import { BugGroupHeader, BugGroupTailRow, bugHeaderSurfaceClass } from "./group";
30
30
  import { BugListRow } from "./row";
31
31
  import { BugBatchToolbar } from "./batch-toolbar";
32
32
  import { computeGroupCheckState, toggleGroupSelection } from "./group-select";
33
- // 任务 40:吸顶判定消费共享 computeFrozenHeader(bug 侧首次与 story 统一冻结
34
- // 组头机制;对照 story-list/index.tsx 消费方式——吸附语义 threshold=scrollTop+1、
35
- // 冻结层覆盖式渲染在滚动容器外)。
36
- import { computeFrozenHeader, type FrozenHeader } from "@/components/shared-kit/list-kit/frozen-header";
33
+ // 任务 22:吸附组头判定统一走共享 computeFrozenHeader(等价原生 sticky:活跃才
34
+ // 渲染副本、被下个组头逐像素顶出、边界不留容差),替代旧的「滚动容器外常驻
35
+ // 冻结层」(固定 top、无顶出、threshold+1 容差——违规存量;任务 40 为 story 同构引入)。
36
+ import { computeFrozenHeader, type FrozenHeaderResult } from "@/components/shared-kit/list-kit/frozen-header";
37
37
  import { WavePhysicsLoader } from "@/components/shared-kit/loading/wave-physics-loader";
38
38
  import { useDeferredFlag } from "@/components/shared-kit/loading/use-deferred-flag";
39
39
  import { m } from "@/paraglide/messages.js";
40
40
  import { toastError } from "@/lib/toast-error";
41
+ import { cn } from "@/lib/utils";
41
42
 
42
43
  const HEADER_ROW_HEIGHT = 36;
43
44
  const BUG_ROW_HEIGHT = 44;
@@ -280,13 +281,13 @@ export function BugList({ groups, isLoading = false, displayFilters, users, labe
280
281
  return tailHeights.current[row.key] ?? 40;
281
282
  }, [flatRows]);
282
283
 
283
- // 吸顶判定(任务 40 前的内联 ref 自吸顶 + 行内 sticky 定位机制已移除,改
284
- // 共享 computeFrozenHeader + 滚动容器外冻结层覆盖渲染,与 story 同构)。
285
- // 冻结组头渲染在滚动容器外层,位置固定(组头 top:0、子组头 top:36),滚动时
286
- // 唯一变化的是「当前活跃组头索引」——仅在跨越分组边界时改变。因此不存每帧
287
- // 变化的 scrollTop,只存派生结果并去重:组内连续滚动零 setState 零整表重渲染。
288
- const [frozenHeader, setFrozenHeader] = useState<FrozenHeader | null>(null);
289
- const frozenHeaderRef = useRef<FrozenHeader | null>(null);
284
+ // 吸附组头(任务 22 前为滚动容器外常驻冻结层,任务 40 前为内联 ref 自吸顶):
285
+ // computeFrozenHeader 派生活跃(主/子)组头及顶出位移。副本仍渲染在滚动容器
286
+ // 外层,但仅在组头越过吸附位后才出现,并被下一个组头逐像素顶出——与原生
287
+ // sticky 等价。不存每帧变化的 scrollTop,只存派生结果(index+offset 双值),
288
+ // ref 去重:组内连续滚动零 setState 零整表重渲染。
289
+ const [frozen, setFrozen] = useState<FrozenHeaderResult | null>(null);
290
+ const frozenRef = useRef<FrozenHeaderResult | null>(null);
290
291
  const frozenRafRef = useRef<number | null>(null);
291
292
 
292
293
  const rowVirtualizer = useVirtualizer({
@@ -294,49 +295,42 @@ export function BugList({ groups, isLoading = false, displayFilters, users, labe
294
295
  getScrollElement: () => scrollRef.current,
295
296
  estimateSize,
296
297
  overscan: OVERSCAN,
297
- rangeExtractor: useCallback(
298
- (range) => {
299
- // 活跃(主/子)组头必须保持在虚拟化渲染窗口内,否则滚动容器外的冻结层
300
- // 无内容可覆盖渲染(story 同款;此处只决定渲染集合,不承载吸顶判定)
301
- const activeGroupIdx = [...groupHeaderIndexes].reverse().find((i) => range.startIndex >= i);
302
- let activeSubIdx: number | null = null;
303
- if (activeGroupIdx != null) {
304
- const candidate = [...subGroupHeaderIndexes].reverse().find((i) => i <= range.startIndex);
305
- if (candidate != null && parentGroupOfSub.get(candidate) === activeGroupIdx) {
306
- activeSubIdx = candidate;
307
- }
308
- }
309
- const idx = new Set(defaultRangeExtractor(range));
310
- if (activeGroupIdx != null) idx.add(activeGroupIdx);
311
- if (activeSubIdx != null) idx.add(activeSubIdx);
312
- return [...idx].sort((a, b) => a - b);
313
- },
314
- [groupHeaderIndexes, subGroupHeaderIndexes, parentGroupOfSub],
315
- ),
298
+ // 任务 22:不再用 rangeExtractor 强留活跃组头行——吸附层从 flatRows 数据
299
+ // 直接渲染(与 kanban 同构),在流行按虚拟窗口自然进出
316
300
  enabled: flatRows.length > 0,
317
301
  });
318
302
  const virtualRows = rowVirtualizer.getVirtualItems();
319
303
  const totalHeight = rowVirtualizer.getTotalSize();
320
304
  const remeasureRows = useCallback(() => rowVirtualizer.measure(), [rowVirtualizer]);
321
305
 
322
- // 冻结组头派生:读取当前滚动位置算出活跃(主/子)组头,仅在结果变化时提交 state
306
+ // 吸附组头派生:读取当前滚动位置算出活跃(主/子)组头与顶出位移,仅在结果
307
+ // (index+offset 双值)变化时提交 state
323
308
  const recomputeFrozenHeader = useCallback(() => {
324
309
  frozenRafRef.current = null;
325
310
  const el = scrollRef.current;
326
311
  if (!el) return;
327
312
  // measurementsCache ?? []:虚拟化桩(测试/极端首帧)可能未提供测量缓存,
328
- // computeFrozenHeader 对缺失测量返回 null(无冻结层),不抛错
329
- const next = computeFrozenHeader(
330
- el.scrollTop,
331
- rowVirtualizer.measurementsCache ?? [],
332
- groupHeaderIndexes,
333
- subGroupHeaderIndexes,
313
+ // computeFrozenHeader 对缺失测量返回 null(无吸附层),不抛错
314
+ const next = computeFrozenHeader(el.scrollTop, rowVirtualizer.measurementsCache ?? [], {
315
+ groupIndexes: groupHeaderIndexes,
316
+ subIndexes: subGroupHeaderIndexes,
334
317
  parentGroupOfSub,
335
- );
336
- const prev = frozenHeaderRef.current;
337
- if (prev?.groupIdx === next?.groupIdx && prev?.subIdx === next?.subIdx) return;
338
- frozenHeaderRef.current = next;
339
- setFrozenHeader(next);
318
+ // 滚动容器 overflow-y-auto 无上内边距:测量原点 = 滚动口顶
319
+ pad: 0,
320
+ // 子组头吸附在吸附主组头正下方(组头行高 36)
321
+ subPinTop: HEADER_ROW_HEIGHT,
322
+ });
323
+ const prev = frozenRef.current;
324
+ if (
325
+ prev?.group?.index === next.group?.index &&
326
+ prev?.group?.offset === next.group?.offset &&
327
+ prev?.sub?.index === next.sub?.index &&
328
+ prev?.sub?.offset === next.sub?.offset
329
+ ) {
330
+ return;
331
+ }
332
+ frozenRef.current = next;
333
+ setFrozen(next);
340
334
  }, [rowVirtualizer, groupHeaderIndexes, subGroupHeaderIndexes, parentGroupOfSub]);
341
335
 
342
336
  // rAF 节流:每帧最多重算一次(滚动事件可能一帧多次触发)
@@ -441,27 +435,23 @@ export function BugList({ groups, isLoading = false, displayFilters, users, labe
441
435
  return null;
442
436
  };
443
437
 
444
- const renderFrozenHeader = (idx: number, top: number, depth: number, zIndex: number) => {
438
+ // 吸附层组头内容渲染:与在流组头同一渲染单元(BugGroupHeader sticky 实色变体),
439
+ // 保证吸附态与在流态视觉与交互完全一致。快捷创建不入吸附层(旧常驻层同口径)
440
+ const renderFrozenHeaderRow = (idx: number, depth: 0 | 1) => {
445
441
  const row = flatRows[idx];
446
442
  if (!row || (row.kind !== "header" && row.kind !== "subHeader")) return null;
447
443
  return (
448
- <div
449
- key={row.key}
450
- className="absolute inset-x-0 overflow-hidden"
451
- style={{ top, zIndex, height: HEADER_ROW_HEIGHT }}
452
- >
453
- <BugGroupHeader
454
- node={row.node}
455
- depth={depth}
456
- collapsed={row.isCollapsed}
457
- collapseKey={row.key}
458
- onToggle={toggleCollapse}
459
- sticky
460
- selectState={computeGroupCheckState(groupBugIds.get(row.key) ?? [], selectedIds)}
461
- onToggleSelect={() => toggleGroup(row.key)}
462
- showSelect={selectedIds.size > 0}
463
- />
464
- </div>
444
+ <BugGroupHeader
445
+ node={row.node}
446
+ depth={depth}
447
+ collapsed={row.isCollapsed}
448
+ collapseKey={row.key}
449
+ onToggle={toggleCollapse}
450
+ sticky
451
+ selectState={computeGroupCheckState(groupBugIds.get(row.key) ?? [], selectedIds)}
452
+ onToggleSelect={() => toggleGroup(row.key)}
453
+ showSelect={selectedIds.size > 0}
454
+ />
465
455
  );
466
456
  };
467
457
 
@@ -483,11 +473,48 @@ export function BugList({ groups, isLoading = false, displayFilters, users, labe
483
473
  </div>
484
474
  )}
485
475
  </div>
486
- {frozenHeader && (
487
- <>
488
- {frozenHeader.subIdx != null && renderFrozenHeader(frozenHeader.subIdx, HEADER_ROW_HEIGHT, 1, 9)}
489
- {renderFrozenHeader(frozenHeader.groupIdx, 0, 0, 10)}
490
- </>
476
+ {/* 吸附组头副本(任务 22,替代旧常驻冻结层):仅在组头越过吸附位后渲染
477
+ (computeFrozenHeader),并被下一个组头逐像素顶出(translateY 负值),
478
+ 与原生 sticky 等价。结构照 kanban 吸附层同款:
479
+ - 外层 pointer-events-none + overflow-hidden:顶出越界部分裁在滚动口顶
480
+ - 满宽实色底色盒(bugHeaderSurfaceClass,与 BugGroupHeader sticky 变体
481
+ 同源)无纵向内边距——纵向范围 = 组头行自身盒子(高 HEADER_ROW_HEIGHT)
482
+ - 内容包 pointer-events-auto:组头可点(折叠/勾选),带外点击穿透
483
+ - 子组头层在下、主组头层在上(z 9/10):组切换顶出阶段两层同带滑出,
484
+ 露出主组头(旧常驻层 z 序,行为保持) */}
485
+ {frozen && (frozen.group || frozen.sub) && (
486
+ <div
487
+ data-slot="bug-frozen-headers"
488
+ className="pointer-events-none absolute inset-0 z-10 overflow-hidden"
489
+ >
490
+ {frozen.sub && (
491
+ <div
492
+ className={cn("absolute inset-x-0 z-[9]", bugHeaderSurfaceClass(1))}
493
+ style={{
494
+ top: HEADER_ROW_HEIGHT,
495
+ height: HEADER_ROW_HEIGHT,
496
+ transform: `translateY(${frozen.sub.offset}px)`,
497
+ }}
498
+ >
499
+ <div className="pointer-events-auto h-full">
500
+ {renderFrozenHeaderRow(frozen.sub.index, 1)}
501
+ </div>
502
+ </div>
503
+ )}
504
+ {frozen.group && (
505
+ <div
506
+ className={cn("absolute inset-x-0 top-0 z-10", bugHeaderSurfaceClass(0))}
507
+ style={{
508
+ height: HEADER_ROW_HEIGHT,
509
+ transform: `translateY(${frozen.group.offset}px)`,
510
+ }}
511
+ >
512
+ <div className="pointer-events-auto h-full">
513
+ {renderFrozenHeaderRow(frozen.group.index, 0)}
514
+ </div>
515
+ </div>
516
+ )}
517
+ </div>
491
518
  )}
492
519
  <BugBatchToolbar
493
520
  selectedCount={selectedIds.size}
@@ -15,9 +15,10 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
- import { useState, useSyncExternalStore } from "react"
18
+ import { useState } from "react"
19
+ import { useSelector } from "@tanstack/react-store"
19
20
  import { useThemeMode } from "@/providers/theme-mode-provider"
20
- import { useHotkey, useHotkeys } from "@tanstack/react-hotkeys"
21
+ import { useAppHotkeys, useHotkey, useHotkeys } from "@tanstack/react-hotkeys"
21
22
  import type { HotkeySequence } from "@tanstack/hotkeys"
22
23
  import {
23
24
  CommandMenu,
@@ -84,11 +85,10 @@ export function CommandPalette() {
84
85
  const { toggle } = useThemeMode()
85
86
  const [designDialogOpen, setDesignDialogOpen] = useState(false)
86
87
 
87
- const open = useSyncExternalStore(
88
- commandPaletteStore.subscribe,
89
- commandPaletteStore.getSnapshot,
90
- () => false,
91
- )
88
+ // 任务 25:手写 store 迁官方 Store(见 command-palette-state.ts)。store
89
+ // 初始值模块级 false 且 SSR 期间不可变更,useSelector 以 get() 同时充当
90
+ // server snapshot,与旧 useSyncExternalStore(subscribe, get, () => false) 等价
91
+ const open = useSelector(commandPaletteStore)
92
92
 
93
93
  const { commands, execute } = useCommands({
94
94
  onAction: (id) => {
@@ -104,9 +104,10 @@ export function CommandPalette() {
104
104
  execute(cmd)
105
105
  }
106
106
 
107
- useHotkey("Mod+K", toggleCommandPalette, { ignoreInputs: false })
107
+ const hk = useAppHotkeys()
108
+ useHotkey(hk["command-palette"], toggleCommandPalette, { ignoreInputs: false })
108
109
  useHotkey(
109
- "Mod+Shift+L",
110
+ hk["toggle-theme"],
110
111
  () => {
111
112
  // FS-5.4:设计画布编辑器内 Mod+Shift+L 是「锁定」命令(canvas useEditCommands),
112
113
  // 全局主题切换在画布挂载时让位,避免双 handler 同时触发(锁定同时翻转主题)
@@ -71,7 +71,15 @@ function subscribe(listener: () => void): () => void {
71
71
  }
72
72
  }
73
73
 
74
- /** 订阅自定义画板预设列表;SSR 返回空数组避免 hydration 差异 */
74
+ /**
75
+ * 订阅自定义画板预设列表;SSR 返回空数组避免 hydration 差异。
76
+ *
77
+ * 保留 useSyncExternalStore(任务 25 审计裁决):getFrameCustomPresets 走
78
+ * ensureLoaded 读 window.localStorage,不能充当服务端快照;而官方
79
+ * useSelector 以 get() 同时充当 getServerSnapshot、不支持独立的
80
+ * getServerSnapshot——故此处无法迁 useSelector,必须保留原生
81
+ * useSyncExternalStore 以显式注入 `() => []` 服务端快照。
82
+ */
75
83
  export function useFrameCustomPresets(): FrameCustomPreset[] {
76
84
  return useSyncExternalStore(subscribe, getFrameCustomPresets, () => [])
77
85
  }