@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
@@ -75,7 +75,22 @@ vi.mock("@/server/serverFns/design/pages", () => ({
75
75
  vi.mock("@/server/serverFns/design/designs", () => ({
76
76
  updateDesignFn: vi.fn(),
77
77
  }));
78
- vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {} }));
78
+ vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {}, useAppHotkeys: () => appHotkeys }));
79
+
80
+ // DesignEditorLayout 挂载 use-unsaved-guard(任务 2):无 RouterProvider 的
81
+ // 单测环境把 useBlocker 替换为 idle resolver(真实 blocker 注册属 router
82
+ // 集成行为,见 hooks/__tests__/use-unsaved-guard.test.tsx)
83
+ vi.mock("@tanstack/react-router", async (importOriginal) => ({
84
+ ...(await importOriginal<Record<string, unknown>>()),
85
+ useBlocker: () => ({
86
+ status: "idle" as const,
87
+ current: undefined,
88
+ next: undefined,
89
+ action: undefined,
90
+ proceed: undefined,
91
+ reset: undefined,
92
+ }),
93
+ }));
79
94
  vi.mock("@/components/design/editor/versions/page-version-panel", () => ({
80
95
  PageVersionPanel: () => null,
81
96
  }));
@@ -88,6 +103,7 @@ vi.mock("@/components/design/editor/layout/design-doc-panel", () => ({
88
103
 
89
104
  import { DesignRightSidebar } from "../design-right-sidebar";
90
105
  import { DesignEditorLayout } from "../design-editor-layout";
106
+ import { appHotkeys } from "@/lib/hotkeys/registry";
91
107
  import type { Design } from "@allbluecn/kernel";
92
108
 
93
109
  const designFixture = {
@@ -72,10 +72,26 @@ vi.mock("@/server/serverFns/design/pages", () => ({
72
72
  vi.mock("@/server/serverFns/design/designs", () => ({
73
73
  updateDesignFn: vi.fn(),
74
74
  }));
75
- vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {} }));
75
+ vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {}, useAppHotkeys: () => appHotkeys }));
76
+
77
+ // DesignEditorLayout 挂载 use-unsaved-guard(任务 2):本冒烟测试无
78
+ // RouterProvider(node + renderToString),把 useBlocker 替换为 idle
79
+ // resolver——真实 blocker 注册属 router 集成行为,SSR 渲染期本就零 window 访问
80
+ vi.mock("@tanstack/react-router", async (importOriginal) => ({
81
+ ...(await importOriginal<Record<string, unknown>>()),
82
+ useBlocker: () => ({
83
+ status: "idle" as const,
84
+ current: undefined,
85
+ next: undefined,
86
+ action: undefined,
87
+ proceed: undefined,
88
+ reset: undefined,
89
+ }),
90
+ }));
76
91
 
77
92
  import { DesignRightSidebar } from "../design-right-sidebar";
78
93
  import { DesignEditorLayout } from "../design-editor-layout";
94
+ import { appHotkeys } from "@/lib/hotkeys/registry";
79
95
  import { useTrackColumns } from "@/components/harness/workbench/use-track-columns";
80
96
 
81
97
  const designFixture = {
@@ -21,7 +21,7 @@ import { emitEvent, AppEvents } from "@/lib/events";
21
21
  import { DesignLeftSidebar } from "./design-left-sidebar";
22
22
  import { DesignRightSidebar } from "./design-right-sidebar";
23
23
  import { DesignNavigation } from "./design-navigation";
24
- import { useHotkey } from "@tanstack/react-hotkeys";
24
+ import { useAppHotkeys, useHotkey } from "@tanstack/react-hotkeys";
25
25
  import type { Editor } from "@tiptap/react";
26
26
  import { AnnotationOverlay } from "@/components/design/editor/annotation/overlay/annotation-overlay";
27
27
  import { ElementContext } from "./element-context";
@@ -64,6 +64,7 @@ import {
64
64
  import type { PrototypeScene } from "@/server/serverFns/design/ai/generate-prototype";
65
65
  import { useDebouncer } from "@/lib/pacer";
66
66
  import { isCollabEnabled } from "@/lib/collab/env";
67
+ import { useUnsavedGuard } from "@/hooks/use-unsaved-guard";
67
68
  import { useDesignLeftPanelPreference } from "./hooks/use-design-left-panel-preference";
68
69
  import { useDesignPageLoad } from "./hooks/use-design-page-load";
69
70
  import { usePageVersionEvents } from "./hooks/use-page-version-events";
@@ -569,6 +570,13 @@ export function DesignEditorLayout({ prd: initialDesign }: DesignEditorLayoutPro
569
570
  collab: collabEnabled,
570
571
  });
571
572
 
573
+ // 未保存拦截(任务 2):SPA 导航 dirty 弹 AlertDialog,刷新/关闭走
574
+ // enableBeforeUnload 原生警告。协同态豁免:collab 下双通道保存让位(T29)
575
+ // dirty 恒真不代表未落库(Y.Doc 是权威副本),拦截反成误报
576
+ const unsavedGuard = useUnsavedGuard({
577
+ isDirty: () => !collabEnabled && (loaded.canvasDirty || loaded.docDirty),
578
+ });
579
+
572
580
  const switchPage = useCallback(async (pageId: string) => {
573
581
  if (loaded.canvasDirty && loaded.activePageId) await autoSave.saveCanvasRef.current();
574
582
  if (loaded.docDirty && loaded.activePageId) await autoSave.saveDoc();
@@ -588,7 +596,9 @@ export function DesignEditorLayout({ prd: initialDesign }: DesignEditorLayoutPro
588
596
  }
589
597
  }, [prd.id]);
590
598
 
591
- useHotkey("Mod+S", autoSave.saveAll);
599
+ // 声明即绑定:save 条目单一来源(lib/hotkeys/registry.ts),禁止手写 chord
600
+ const hk = useAppHotkeys();
601
+ useHotkey(hk["save"], autoSave.saveAll);
592
602
 
593
603
  const [aiModel, setAiModel] = useState("");
594
604
 
@@ -669,6 +679,7 @@ export function DesignEditorLayout({ prd: initialDesign }: DesignEditorLayoutPro
669
679
  </CanvasProvider>
670
680
  </div>
671
681
  </div>
682
+ {unsavedGuard.dialog}
672
683
  </div>
673
684
  </ElementContext.Provider>
674
685
  );
@@ -182,30 +182,14 @@ export function useDesignAutoSave({
182
182
  await Promise.all([saveCanvasRef.current("manual"), saveDoc("manual")]);
183
183
  }, [canvasDebouncer, saveDoc]);
184
184
 
185
- const docDirtyRef = useRef(docDirty);
186
- docDirtyRef.current = docDirty;
187
- const canvasDirtyRef = useRef(canvasDirty);
188
- canvasDirtyRef.current = canvasDirty;
185
+ // 未保存拦截(任务 2):手写 beforeunload preventDefault 已迁
186
+ // use-unsaved-guard(useBlocker + enableBeforeUnload 函数形态,挂载于
187
+ // design-editor-layout),SPA 导航与刷新统一由该 hook 拦截
189
188
 
190
189
  useEffect(() => {
191
190
  if (activePageId) noPageToastShownRef.current = false;
192
191
  }, [activePageId]);
193
192
 
194
- useEffect(() => {
195
- const handleBeforeUnload = (e: BeforeUnloadEvent) => {
196
- canvasDebouncer.cancel();
197
- docDebouncer.cancel();
198
- // 关闭/刷新前若有未落库编辑(防抖窗口内的内容已被 cancel 丢弃),
199
- // 触发浏览器原生「未保存更改」确认,给用户阻止误关的机会
200
- if (canvasDirtyRef.current || docDirtyRef.current) {
201
- e.preventDefault();
202
- e.returnValue = "";
203
- }
204
- };
205
- window.addEventListener("beforeunload", handleBeforeUnload);
206
- return () => window.removeEventListener("beforeunload", handleBeforeUnload);
207
- }, [canvasDebouncer, docDebouncer]);
208
-
209
193
  const handleDocContentChange = useCallback((content: DocContent) => {
210
194
  setDocContent(content);
211
195
  setDocDirty(true);
@@ -0,0 +1,56 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import { Suspense, lazy, useEffect, useState } from "react";
19
+
20
+ // DEV 门控放在模块顶层三元上:生产构建时 import.meta.env.DEV 被静态替换为
21
+ // false,动态 import 落入死分支被 tree-shake,devtools chunk 不进生产产物。
22
+ const ReactQueryDevtools = import.meta.env.DEV
23
+ ? lazy(() =>
24
+ import("@tanstack/react-query-devtools").then((m) => ({
25
+ default: m.ReactQueryDevtools,
26
+ })),
27
+ )
28
+ : null;
29
+
30
+ const TanStackRouterDevtools = import.meta.env.DEV
31
+ ? lazy(() =>
32
+ import("@tanstack/react-router-devtools").then((m) => ({
33
+ default: m.TanStackRouterDevtools,
34
+ })),
35
+ )
36
+ : null;
37
+
38
+ export function DevtoolsPanel() {
39
+ // SSR 安全:dev 模式下 SSR 渲染期(mounted=false)返回 null,挂载后再
40
+ // 加载面板——lazy 组件只在客户端渲染,hydration 前后零差异。
41
+ const [mounted, setMounted] = useState(false);
42
+ useEffect(() => {
43
+ setMounted(true);
44
+ }, []);
45
+
46
+ if (!import.meta.env.DEV || !mounted) return null;
47
+
48
+ return (
49
+ <Suspense fallback={null}>
50
+ {ReactQueryDevtools ? <ReactQueryDevtools initialIsOpen={false} /> : null}
51
+ {TanStackRouterDevtools ? (
52
+ <TanStackRouterDevtools initialIsOpen={false} />
53
+ ) : null}
54
+ </Suspense>
55
+ );
56
+ }
@@ -0,0 +1,131 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import { describe, it, expect, vi } from "vitest";
19
+ import { render, screen, fireEvent, waitFor } from "@testing-library/react";
20
+ import { z } from "zod";
21
+ import { useAppForm } from "../create-app-form";
22
+
23
+ const schema = z.object({
24
+ name: z.string().min(2, "名称至少2个字"),
25
+ });
26
+
27
+ type SubmitValues = { name: string };
28
+
29
+ type SubmitFn = (value: SubmitValues) => Promise<void> | void;
30
+
31
+ function Harness({ onSubmit, initialValue = "" }: {
32
+ onSubmit: SubmitFn;
33
+ initialValue?: string;
34
+ }) {
35
+ const form = useAppForm({
36
+ defaultValues: { name: initialValue },
37
+ validators: [{ run: schema, triggers: ["change"] }],
38
+ onSubmit: ({ value }) => onSubmit(value),
39
+ });
40
+ return (
41
+ <form.AppForm>
42
+ <form
43
+ onSubmit={(e) => {
44
+ e.preventDefault();
45
+ e.stopPropagation();
46
+ form.handleSubmit();
47
+ }}
48
+ >
49
+ <form.AppField name="name" label="名称" />
50
+ <button type="submit">提交</button>
51
+ </form>
52
+ </form.AppForm>
53
+ );
54
+ }
55
+
56
+ function SubmitHarness({ onSubmit }: { onSubmit: SubmitFn }) {
57
+ const form = useAppForm({
58
+ defaultValues: { name: "有效名称" },
59
+ validators: [{ run: schema, triggers: ["change"] }],
60
+ onSubmit: ({ value }) => onSubmit(value),
61
+ });
62
+ return (
63
+ <form.AppForm>
64
+ <form
65
+ onSubmit={(e) => {
66
+ e.preventDefault();
67
+ e.stopPropagation();
68
+ form.handleSubmit();
69
+ }}
70
+ >
71
+ <form.AppField name="name" />
72
+ <form.AppSubmit>提交</form.AppSubmit>
73
+ </form>
74
+ </form.AppForm>
75
+ );
76
+ }
77
+
78
+ describe("create-app-form 工厂", () => {
79
+ it("useAppForm 渲染 form.AppField 默认输入与 AppSubmit 按钮", () => {
80
+ render(<SubmitHarness onSubmit={vi.fn()} />);
81
+ expect(screen.getByRole("textbox")).toBeInTheDocument();
82
+ expect(screen.getByRole("button", { name: "提交" })).toBeInTheDocument();
83
+ });
84
+
85
+ it("校验错误经 AppFieldError 在 touch 后显示(isTouched 守卫)", async () => {
86
+ render(<Harness onSubmit={vi.fn()} />);
87
+ const input = screen.getByRole("textbox");
88
+ fireEvent.change(input, { target: { value: "A" } });
89
+ // 未 touch:isTouched 守卫生效,错误不显示
90
+ expect(screen.queryByText("名称至少2个字")).not.toBeInTheDocument();
91
+ fireEvent.blur(input);
92
+ await waitFor(() =>
93
+ expect(screen.getByText("名称至少2个字")).toBeInTheDocument(),
94
+ );
95
+ });
96
+
97
+ it("提交被校验拦截时字段已 touch、错误可见;有效值提交透传 values", async () => {
98
+ const onSubmit = vi.fn();
99
+ render(<Harness onSubmit={onSubmit} initialValue="A" />);
100
+ const input = screen.getByRole("textbox");
101
+ // 无效值直接提交:form-core 提交流程先 touch 全部字段再拦截 onSubmit
102
+ fireEvent.click(screen.getByRole("button", { name: "提交" }));
103
+ await waitFor(() =>
104
+ expect(screen.getByText("名称至少2个字")).toBeInTheDocument(),
105
+ );
106
+ expect(onSubmit).not.toHaveBeenCalled();
107
+
108
+ fireEvent.change(input, { target: { value: "有效名称" } });
109
+ fireEvent.click(screen.getByRole("button", { name: "提交" }));
110
+ await waitFor(() =>
111
+ expect(onSubmit).toHaveBeenCalledWith({ name: "有效名称" }),
112
+ );
113
+ });
114
+
115
+ it("form.AppSubmit 走 form.Subscribe isSubmitting 单通道:提交中禁用", async () => {
116
+ let resolveSubmit: () => void = () => {};
117
+ const deferred = new Promise<void>((resolve) => {
118
+ resolveSubmit = resolve;
119
+ });
120
+ const onSubmit = vi.fn(() => deferred);
121
+
122
+ render(<SubmitHarness onSubmit={onSubmit} />);
123
+ const button = screen.getByRole("button", { name: "提交" });
124
+ expect(button).not.toBeDisabled();
125
+ fireEvent.click(button);
126
+ await waitFor(() => expect(button).toBeDisabled());
127
+ resolveSubmit();
128
+ await waitFor(() => expect(button).not.toBeDisabled());
129
+ expect(onSubmit).toHaveBeenCalledWith({ name: "有效名称" });
130
+ });
131
+ });
@@ -17,8 +17,28 @@
17
17
 
18
18
  import type { AnyFieldApi } from "@tanstack/react-form"
19
19
 
20
+ /**
21
+ * 字段错误可见性的唯一口径:touch 守卫(blur 或提交尝试后)+ 校验未过。
22
+ * 表单库在每次提交尝试会 touch 全部字段,故提交被校验拦截时错误必然可见。
23
+ */
24
+ export function hasVisibleFieldError(field: AnyFieldApi): boolean {
25
+ return field.meta.isTouched && !field.meta.isValid
26
+ }
27
+
28
+ /**
29
+ * 首条可见错误文本,供自绘错误 UI(IconInput/PasswordInput 等)消费。
30
+ * 与 AppFieldError 共享同一守卫语义。
31
+ */
32
+ export function firstFieldErrorMessage(field: AnyFieldApi): string | undefined {
33
+ if (!hasVisibleFieldError(field)) return undefined
34
+ const first = field.errors
35
+ .map((e) => (typeof e === "string" ? e : e?.message ?? String(e)))
36
+ .find(Boolean)
37
+ return first ?? undefined
38
+ }
39
+
20
40
  export function AppFieldError({ field }: { field: AnyFieldApi }) {
21
- if (!field.meta.isTouched || field.meta.isValid) return null
41
+ if (!hasVisibleFieldError(field)) return null
22
42
  return (
23
43
  <span className="block text-sm text-destructive mt-1">
24
44
  {/* 校验错误可为纯字符串(无 message 属性),直接渲染会输出字面量 undefined */}
@@ -18,6 +18,7 @@
18
18
  import type {
19
19
  AnyFieldApi,
20
20
  AnyReactFormApi,
21
+ FieldValidators,
21
22
  } from "@tanstack/react-form"
22
23
  import type { ReactNode } from "react"
23
24
  import {
@@ -29,19 +30,18 @@ import {
29
30
  import { Input } from "@/components/ui/input"
30
31
  import { AppFieldError } from "./app-field-error"
31
32
 
32
- /**
33
- * TODO(待决策):TanStack Form v2(alpha)的 validators 泛型尚未稳定,
34
- * 精确映射 FieldValidators 需随 v2 正式版补全;当前经 AnyReactFormApi 通道透传。
35
- */
36
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
- export type AppFieldValidators<TValue> = any
38
-
39
33
  export interface AppFieldProps<TValue> {
40
34
  name: string
41
35
  form: AnyReactFormApi
42
36
  label?: string
43
37
  description?: string
44
- validators?: AppFieldValidators<TValue>
38
+ /**
39
+ * 字段级校验器(TanStack Form v2 Validator 配置数组)。值类型经 TValue 约束;
40
+ * 所属表单在 App* 封装层为 any 擦除(AnyReactFormApi 通道),由 useAppForm
41
+ * 的表单级 validators 承担精确类型。
42
+ */
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- 表单数据形状在封装层擦除,与 AnyFieldApi 同口径
44
+ validators?: FieldValidators<any, string, TValue>
45
45
  children?: (field: AnyFieldApi) => ReactNode
46
46
  renderError?: boolean
47
47
  }
@@ -20,22 +20,25 @@ import type { ReactNode } from "react"
20
20
  import type { AnyReactFormApi } from "@tanstack/react-form"
21
21
  import { Button } from "@/components/ui/button"
22
22
 
23
+ export interface AppSubmitProps {
24
+ /** 显式传 form 走 Subscribe isSubmitting 单通道;免传时用工厂产物 form.AppSubmit */
25
+ form?: AnyReactFormApi
26
+ isSubmitting?: boolean
27
+ children: ReactNode
28
+ disabled?: boolean
29
+ }
30
+
23
31
  export function AppSubmit({
24
32
  form,
25
33
  isSubmitting,
26
34
  children,
27
35
  disabled = false,
28
- }: {
29
- form?: AnyReactFormApi
30
- isSubmitting?: boolean
31
- children: ReactNode
32
- disabled?: boolean
33
- }) {
36
+ }: AppSubmitProps) {
34
37
  if (form && isSubmitting === undefined) {
35
38
  return (
36
- <form.Subscribe<boolean>
37
- selector={(state: any) => state.isSubmitting}
38
- children={(isSub: boolean) => (
39
+ <form.Subscribe
40
+ selector={(state) => state.isSubmitting}
41
+ children={(isSub) => (
39
42
  <Button type="submit" disabled={isSub || disabled}>
40
43
  {isSub ? (
41
44
  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
@@ -0,0 +1,124 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import { createFormHook, getFormHookHelpers } from "@tanstack/react-form"
19
+ import { AppCheckbox, type AppCheckboxProps } from "./app-checkbox"
20
+ import { AppCombobox, type AppComboboxProps } from "./app-combobox"
21
+ import { AppDatePicker, type AppDatePickerProps } from "./app-date-picker"
22
+ import { AppField, type AppFieldProps } from "./app-field"
23
+ import { AppFieldError } from "./app-field-error"
24
+ import { AppInput, type AppInputProps } from "./app-input"
25
+ import { AppArrayField, type AppArrayFieldProps } from "./app-array-field"
26
+ import { AppSection } from "./app-section"
27
+ import { AppSelect, type AppSelectProps } from "./app-select"
28
+ import { AppSubmit, type AppSubmitProps } from "./app-submit"
29
+ import { AppSwitch, type AppSwitchProps } from "./app-switch"
30
+ import { AppTextarea, type AppTextareaProps } from "./app-textarea"
31
+
32
+ /**
33
+ * App* 表单套件的唯一消费入口(TanStack Form v2 createFormHook 工厂)。
34
+ *
35
+ * - `useAppForm`:创建携带已注册组件树的表单 API;表单树内经
36
+ * `form.AppField` / `field.AppFieldError` 等按名消费,无需再显式传 `form`。
37
+ * - `formComponents`:挂在表单 API 上的组件(`form.AppXxx`)。它们经由
38
+ * useFormContext 从最近的 `form.AppForm` 取表单——因此使用这些组件的
39
+ * 子树必须包在 `<form.AppForm>` 内。
40
+ * - `fieldComponents`:挂在字段 API 上的组件(`field.AppXxx`),由
41
+ * fieldComponent 助手从字段上下文注入当前 field,仅在 `form.Field`
42
+ * 渲染回调内可用。
43
+ * - 提交状态单通道:一切提交态消费 `form.Subscribe isSubmitting`
44
+ * (form.AppSubmit 内部同源),禁止另立 useState loading 通道。
45
+ */
46
+ const { fieldComponent } = getFormHookHelpers()
47
+
48
+ /** AppFieldError 免传 field:由字段上下文注入(isTouched 守卫语义见其实现) */
49
+ const AppFieldErrorBound = fieldComponent.loose(AppFieldError, "field")
50
+
51
+ // useFormContext 仅由 createFormHook 返回(包内不单独导出),绑定层组件在
52
+ // 渲染期才调用它;函数声明提升保证上方 options 求值时绑定组件已可用。
53
+ const { useAppForm, appFormOptions, useFormContext } = createFormHook({
54
+ formComponents: {
55
+ AppField: AppFieldBound,
56
+ AppInput: AppInputBound,
57
+ AppTextarea: AppTextareaBound,
58
+ AppCheckbox: AppCheckboxBound,
59
+ AppSwitch: AppSwitchBound,
60
+ AppSelect: AppSelectBound,
61
+ AppCombobox: AppComboboxBound,
62
+ AppDatePicker: AppDatePickerBound,
63
+ AppArrayField: AppArrayFieldBound,
64
+ AppSubmit: AppSubmitBound,
65
+ AppSection,
66
+ },
67
+ fieldComponents: {
68
+ AppFieldError: AppFieldErrorBound,
69
+ },
70
+ })
71
+
72
+ export { useAppForm, appFormOptions }
73
+
74
+ function AppFieldBound<TValue>(props: Omit<AppFieldProps<TValue>, "form">) {
75
+ const form = useFormContext()
76
+ return <AppField {...props} form={form} />
77
+ }
78
+
79
+ function AppInputBound(props: Omit<AppInputProps, "form">) {
80
+ const form = useFormContext()
81
+ return <AppInput {...props} form={form} />
82
+ }
83
+
84
+ function AppTextareaBound(props: Omit<AppTextareaProps, "form">) {
85
+ const form = useFormContext()
86
+ return <AppTextarea {...props} form={form} />
87
+ }
88
+
89
+ function AppCheckboxBound(props: Omit<AppCheckboxProps, "form">) {
90
+ const form = useFormContext()
91
+ return <AppCheckbox {...props} form={form} />
92
+ }
93
+
94
+ function AppSwitchBound(props: Omit<AppSwitchProps, "form">) {
95
+ const form = useFormContext()
96
+ return <AppSwitch {...props} form={form} />
97
+ }
98
+
99
+ function AppSelectBound(props: Omit<AppSelectProps, "form">) {
100
+ const form = useFormContext()
101
+ return <AppSelect {...props} form={form} />
102
+ }
103
+
104
+ function AppComboboxBound(props: Omit<AppComboboxProps, "form">) {
105
+ const form = useFormContext()
106
+ return <AppCombobox {...props} form={form} />
107
+ }
108
+
109
+ function AppDatePickerBound(props: Omit<AppDatePickerProps, "form">) {
110
+ const form = useFormContext()
111
+ return <AppDatePicker {...props} form={form} />
112
+ }
113
+
114
+ function AppArrayFieldBound<TItem>(
115
+ props: Omit<AppArrayFieldProps<TItem>, "form">,
116
+ ) {
117
+ const form = useFormContext()
118
+ return <AppArrayField {...props} form={form} />
119
+ }
120
+
121
+ function AppSubmitBound(props: Omit<AppSubmitProps, "form">) {
122
+ const form = useFormContext()
123
+ return <AppSubmit {...props} form={form} />
124
+ }
@@ -26,6 +26,5 @@ export { AppDatePicker } from "./app-date-picker"
26
26
  export { AppArrayField, type AppArrayFieldProps } from "./app-array-field"
27
27
  export { AppSection } from "./app-section"
28
28
  export { AppSubmit } from "./app-submit"
29
- export { AppFormDialog } from "./app-form-dialog"
30
- export { useFormSubmit } from "./hooks/use-form-submit"
31
- export type { FormSubmitConfig, AsyncValidator } from "./types"
29
+ export { AppFieldError, firstFieldErrorMessage } from "./app-field-error"
30
+ export { useAppForm, appFormOptions } from "./create-app-form"
@@ -9,7 +9,14 @@ import { Route as HarnessRoute } from "@/routes/_nav/harness/route.lazy"
9
9
  import type { GalleryItem } from "../gallery/gallery-types"
10
10
  import { m } from "@/paraglide/messages.js"
11
11
 
12
- const { listGenerationRunsFn, createVariantFn, attachToStoryFn, attachToKbFn, attachPrototypeToDesignFn, listStoriesFn, listKnowledgeFn, listDesignsFn } = vi.hoisted(() => ({
12
+ type VideoGenerationState = {
13
+ isActive: boolean
14
+ progress: number | undefined
15
+ status: string | null
16
+ start: (item: GalleryItem, referenceImage?: string) => boolean
17
+ stop: () => void
18
+ }
19
+ const { listGenerationRunsFn, createVariantFn, attachToStoryFn, attachToKbFn, attachPrototypeToDesignFn, listStoriesFn, listKnowledgeFn, listDesignsFn, videoGeneration } = vi.hoisted(() => ({
13
20
  listGenerationRunsFn: vi.fn<() => Promise<{ runs: GalleryItem[]; nextCursor: string | null }>>(),
14
21
  createVariantFn: vi.fn<() => Promise<{ runId: string; artifacts: unknown[]; images: unknown[] }>>(),
15
22
  attachToStoryFn: vi.fn(),
@@ -18,9 +25,11 @@ const { listGenerationRunsFn, createVariantFn, attachToStoryFn, attachToKbFn, at
18
25
  listStoriesFn: vi.fn(),
19
26
  listKnowledgeFn: vi.fn(),
20
27
  listDesignsFn: vi.fn(),
28
+ videoGeneration: vi.fn<() => VideoGenerationState>(),
21
29
  }))
22
30
  vi.mock("@/components/billing/feature-gate", () => ({ FeatureGate: ({ children }: { children: React.ReactNode }) => children }))
23
31
  vi.mock("@/server/serverFns/harness/gallery", () => ({ listGenerationRunsFn, createVariantFn }))
32
+ vi.mock("@/components/harness/gallery/use-video-generation", () => ({ useVideoGeneration: videoGeneration }))
24
33
  vi.mock("@/server/serverFns/harness/attach-to-story", () => ({ attachToStoryFn }))
25
34
  vi.mock("@/server/serverFns/harness/attach-to-kb", () => ({ attachToKbFn }))
26
35
  vi.mock("@/server/serverFns/harness/attach-prototype", () => ({ attachPrototypeToDesignFn }))
@@ -56,6 +65,8 @@ beforeEach(() => {
56
65
  vi.mocked(listStoriesFn).mockReset()
57
66
  vi.mocked(listKnowledgeFn).mockReset()
58
67
  vi.mocked(listDesignsFn).mockReset()
68
+ videoGeneration.mockClear()
69
+ videoGeneration.mockImplementation(() => ({ isActive: false, progress: undefined, status: null, start: vi.fn(() => true), stop: vi.fn() }))
59
70
  })
60
71
  afterEach(() => { cleanup(); clients.splice(0).forEach((value) => value.clear()) })
61
72
 
@@ -118,6 +129,31 @@ describe("gallery behavior", () => {
118
129
  expect(screen.getAllByRole("button", { name: "生成变体" })).toHaveLength(2)
119
130
  })
120
131
 
132
+ it("shows streaming progress on the generating video card and stops from it", async () => {
133
+ const start = vi.fn(() => true)
134
+ const stop = vi.fn()
135
+ videoGeneration.mockImplementation(() => ({ isActive: true, progress: 42, status: "processing", start, stop }))
136
+ vi.mocked(listGenerationRunsFn).mockResolvedValue({ runs: [item("a"), item("b", "video")], nextCursor: null })
137
+ grid()
138
+ const stopButtons = await screen.findAllByRole("button", { name: m.gallery_videoStop() })
139
+ expect(stopButtons).toHaveLength(1) // 仅生成中的视频卡
140
+ expect(screen.getByText(m.gallery_videoProgress({ progress: 42 }))).toBeInTheDocument()
141
+ // 生成中的卡片不再提供变体入口,图片卡不受影响
142
+ expect(screen.getAllByRole("button", { name: m.gallery_createVariant() })).toHaveLength(1)
143
+ fireEvent.click(stopButtons[0])
144
+ expect(stop).toHaveBeenCalledTimes(1)
145
+ expect(start).not.toHaveBeenCalled()
146
+ })
147
+
148
+ it("routes video variant clicks to the streaming hook (toast covered by hook tests)", async () => {
149
+ const start = vi.fn((_item: GalleryItem, _referenceImage?: string) => false)
150
+ videoGeneration.mockImplementation(() => ({ isActive: false, progress: undefined, status: null, start, stop: vi.fn() }))
151
+ vi.mocked(listGenerationRunsFn).mockResolvedValue({ runs: [item("b", "video")], nextCursor: null })
152
+ grid()
153
+ fireEvent.click(await screen.findByRole("button", { name: m.gallery_createVariant() }))
154
+ expect(start.mock.calls[0]?.[0]).toMatchObject({ id: "b" })
155
+ })
156
+
121
157
  it("keeps the gallery and localizes variant failures", async () => {
122
158
  vi.mocked(listGenerationRunsFn).mockResolvedValue({ runs: [item("a")], nextCursor: null })
123
159
  vi.mocked(createVariantFn).mockRejectedValue(new Error("provider failure"))
@@ -79,7 +79,10 @@ vi.mock("@tanstack/react-router", () => ({
79
79
  ),
80
80
  }))
81
81
 
82
- vi.mock("@tanstack/react-query", () => ({
82
+ vi.mock("@tanstack/react-query", async (importOriginal) => ({
83
+ // queryOptions 工厂(lib/queries/agents)经组件 import 链在此求值,
84
+ // 需要真实 queryOptions;useQuery 仍按本测试口径桩化
85
+ ...(await importOriginal<typeof import("@tanstack/react-query")>()),
83
86
  useQuery: () => ({ data: undefined }),
84
87
  }))
85
88