@allbluecn/web-app 0.19.5 → 0.21.0

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 (235) hide show
  1. package/package.json +3 -3
  2. package/src/components/bug/__tests__/board-subgroups.test.tsx +1 -1
  3. package/src/components/bug/__tests__/board-view.test.tsx +1 -1
  4. package/src/components/bug/__tests__/comment-reactions.test.tsx +1 -1
  5. package/src/components/bug/__tests__/create-bug-dialog.test.tsx +2 -2
  6. package/src/components/bug/__tests__/filters-compat.test.tsx +1 -1
  7. package/src/components/bug/__tests__/fix-request-race.test.tsx +1 -1
  8. package/src/components/bug/__tests__/peek-modes.test.tsx +1 -1
  9. package/src/components/bug/__tests__/use-paged-bug-groups.test.tsx +1 -1
  10. package/src/components/bug/__tests__/view-type.test.tsx +1 -1
  11. package/src/components/bug/bug-list-view.tsx +2 -2
  12. package/src/components/bug/bug-toolbar.tsx +1 -1
  13. package/src/components/bug/create-bug-dialog.tsx +3 -3
  14. package/src/components/bug/detail/__tests__/activity-timeline.test.tsx +1 -1
  15. package/src/components/bug/detail/__tests__/bug-detail-collab.test.tsx +1 -1
  16. package/src/components/bug/detail/__tests__/repro-report-card.test.tsx +1 -1
  17. package/src/components/bug/detail/activity-tabs.tsx +34 -3
  18. package/src/components/bug/detail/activity-timeline.tsx +1 -1
  19. package/src/components/bug/detail/bug-detail-body.tsx +3 -3
  20. package/src/components/bug/detail/comment-input.tsx +1 -1
  21. package/src/components/bug/detail/comment-item.tsx +1 -1
  22. package/src/components/bug/detail/comment-reactions.tsx +1 -1
  23. package/src/components/bug/detail/comment-thread.tsx +1 -1
  24. package/src/components/bug/detail/props-section.tsx +2 -2
  25. package/src/components/bug/detail/repro-report-card.tsx +1 -1
  26. package/src/components/bug/detail/reproduce-steps-view.tsx +1 -1
  27. package/src/components/bug/display/use-paged-bug-groups.ts +2 -2
  28. package/src/components/bug/filters/field-configs.tsx +1 -1
  29. package/src/components/bug/inline-editors/assignee-editor.tsx +1 -1
  30. package/src/components/bug/peek-panel.tsx +2 -2
  31. package/src/components/bug/reproduce-steps-editor.tsx +1 -1
  32. package/src/components/bug/story-bugs-panel.tsx +10 -6
  33. package/src/components/bug/views/board-view.tsx +2 -2
  34. package/src/components/bug/views/bug-list/batch-toolbar.tsx +1 -1
  35. package/src/components/bug/views/bug-list/group.tsx +1 -1
  36. package/src/components/bug/views/bug-list/index.tsx +1 -1
  37. package/src/components/bug/views/bug-list/inline-bug-creator.tsx +1 -1
  38. package/src/components/bug/views/bug-list/row.tsx +2 -2
  39. package/src/components/bug/views/create-view-dialog.tsx +1 -1
  40. package/src/components/design/editor/versions/hooks/use-design-versions.ts +11 -4
  41. package/src/components/design/editor/versions/page-version-panel.tsx +30 -11
  42. package/src/components/harness/code/workspace-context.tsx +5 -1
  43. package/src/components/harness/code/workspace-picker.tsx +19 -6
  44. package/src/components/harness/composer/harness-composer.tsx +25 -3
  45. package/src/components/harness/composer/mention/mention-resolvers.ts +2 -0
  46. package/src/components/harness/composer/mention/mention-suggestion-menu.tsx +6 -3
  47. package/src/components/harness/composer/mention/use-mention-sources.ts +1 -1
  48. package/src/components/iteration/iteration-progress-bar.tsx +2 -2
  49. package/src/components/knowledge/public/legal-compliance-editor.tsx +1 -1
  50. package/src/components/layout/sidebar-03/nav-knowledge.tsx +1 -1
  51. package/src/components/layout/sidebar-03/nav-workspace.tsx +1 -1
  52. package/src/components/project/invite-member-dialog.tsx +2 -2
  53. package/src/components/project/project-member-manager.tsx +0 -3
  54. package/src/components/shared-kit/list-kit/kanban/__tests__/instance-key.test.ts +1 -1
  55. package/src/components/story/__tests__/constants.test.ts +1 -1
  56. package/src/components/story/__tests__/dependency-type-badge.test.ts +1 -1
  57. package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +1 -1
  58. package/src/components/story/__tests__/story-list-view-peek-sync.test.tsx +1 -1
  59. package/src/components/story/ai/story-ai-panel.tsx +1 -1
  60. package/src/components/story/ai/story-ai-preview-list.tsx +2 -2
  61. package/src/components/story/ai/use-story-ai-model.ts +17 -4
  62. package/src/components/story/columns.tsx +2 -2
  63. package/src/components/story/create-story-dialog.tsx +4 -4
  64. package/src/components/story/detail/__tests__/activity-timeline.test.tsx +1 -1
  65. package/src/components/story/detail/__tests__/associated-attachments-panel.test.tsx +1 -1
  66. package/src/components/story/detail/__tests__/story-collab.test.tsx +1 -1
  67. package/src/components/story/detail/activity-tabs.tsx +1 -1
  68. package/src/components/story/detail/activity-timeline.tsx +36 -17
  69. package/src/components/story/detail/comment-input.tsx +1 -1
  70. package/src/components/story/detail/comment-item.tsx +1 -1
  71. package/src/components/story/detail/comment-thread.tsx +1 -1
  72. package/src/components/story/detail/reaction-bar.tsx +1 -1
  73. package/src/components/story/detail/story-detail-body.tsx +4 -4
  74. package/src/components/story/detail/story-detail-toolbar/__tests__/attachment-cache.test.ts +83 -0
  75. package/src/components/story/detail/story-detail-toolbar/associated-attachments-panel.tsx +50 -9
  76. package/src/components/story/detail/story-detail-toolbar/children.tsx +3 -3
  77. package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +9 -8
  78. package/src/components/story/detail/story-detail-toolbar/index.tsx +8 -8
  79. package/src/components/story/detail/story-detail-toolbar/related-test-cases.tsx +2 -2
  80. package/src/components/story/detail/story-properties.tsx +2 -2
  81. package/src/components/story/display/__tests__/flatten-story-groups.test.ts +1 -1
  82. package/src/components/story/display/__tests__/group-fields.test.ts +1 -1
  83. package/src/components/story/display/__tests__/group-resolver.test.ts +2 -2
  84. package/src/components/story/display/__tests__/order-resolver.test.ts +1 -1
  85. package/src/components/story/display/__tests__/use-grouped-stories.test.ts +1 -1
  86. package/src/components/story/display/__tests__/use-paged-story-groups.test.ts +1 -1
  87. package/src/components/story/display/flatten-story-groups.ts +1 -1
  88. package/src/components/story/display/group-fields.ts +2 -2
  89. package/src/components/story/display/group-resolver.tsx +4 -4
  90. package/src/components/story/display/order-resolver.ts +3 -3
  91. package/src/components/story/display/types.ts +5 -4
  92. package/src/components/story/display/use-grouped-stories.ts +5 -5
  93. package/src/components/story/display/use-paged-story-groups.ts +11 -4
  94. package/src/components/story/entity-overlay.ts +24 -0
  95. package/src/components/story/filters/field-configs.tsx +2 -2
  96. package/src/components/story/inline-editors/assignee-editor.tsx +1 -1
  97. package/src/components/story/inline-editors/dependency-type-select.tsx +1 -1
  98. package/src/components/story/inline-editors/parent-story-editor.tsx +2 -2
  99. package/src/components/story/inline-editors/priority-editor.tsx +2 -2
  100. package/src/components/story/inline-editors/state-editor.tsx +2 -2
  101. package/src/components/story/inline-editors/story-select-dialog.tsx +2 -2
  102. package/src/components/story/peek-panel.tsx +6 -5
  103. package/src/components/story/priority-icon.tsx +2 -2
  104. package/src/components/story/relations/__tests__/attachment-manage-dialog.test.tsx +1 -1
  105. package/src/components/story/relations/__tests__/design-select-dialog.test.tsx +1 -1
  106. package/src/components/story/relations/__tests__/knowledge-select-dialog.test.tsx +1 -1
  107. package/src/components/story/relations/__tests__/link-manage-dialog.test.tsx +1 -1
  108. package/src/components/story/relations/attachment-manage-dialog.tsx +4 -4
  109. package/src/components/story/relations/design-select-dialog.tsx +2 -2
  110. package/src/components/story/relations/knowledge-select-dialog.tsx +2 -2
  111. package/src/components/story/relations/link-manage-dialog.tsx +2 -2
  112. package/src/components/story/relations/story-ref-row.tsx +3 -3
  113. package/src/components/story/story-list-table.tsx +1 -1
  114. package/src/components/story/story-list-view.tsx +1 -1
  115. package/src/components/story/story-toolbar.tsx +1 -1
  116. package/src/components/story/transfer-story-popover.tsx +3 -2
  117. package/src/components/story/views/__tests__/calendar-view.test.tsx +1 -1
  118. package/src/components/story/views/__tests__/kanban-drop-subgroup.test.ts +1 -1
  119. package/src/components/story/views/__tests__/kanban-peek-sync.test.tsx +2 -2
  120. package/src/components/story/views/__tests__/kanban-view-membership-sync.test.tsx +1 -1
  121. package/src/components/story/views/__tests__/timeline-view.test.tsx +1 -1
  122. package/src/components/story/views/calendar/__tests__/mappers.test.ts +1 -1
  123. package/src/components/story/views/calendar/mappers.ts +1 -1
  124. package/src/components/story/views/calendar-view.tsx +1 -1
  125. package/src/components/story/views/create-view-dialog.tsx +1 -1
  126. package/src/components/story/views/kanban-view.tsx +2 -2
  127. package/src/components/story/views/story-list/batch-preview-dialog.tsx +2 -2
  128. package/src/components/story/views/story-list/batch-toolbar.tsx +3 -3
  129. package/src/components/story/views/story-list/group.tsx +18 -5
  130. package/src/components/story/views/story-list/index.tsx +4 -2
  131. package/src/components/story/views/story-list/inline-story-creator.tsx +2 -2
  132. package/src/components/story/views/story-list/row.tsx +2 -2
  133. package/src/components/story/views/story-list/use-flat-rows.ts +1 -1
  134. package/src/components/story/views/timeline/__tests__/mappers.test.ts +2 -2
  135. package/src/components/story/views/timeline/mappers.ts +2 -2
  136. package/src/components/story/views/timeline/use-date-commit.ts +1 -1
  137. package/src/components/story/views/timeline-view.tsx +2 -2
  138. package/src/components/testcase/__tests__/case-row.test.tsx +1 -1
  139. package/src/components/testcase/__tests__/create-dialog-scope.test.tsx +1 -1
  140. package/src/components/testcase/__tests__/display-options.test.tsx +1 -1
  141. package/src/components/testcase/__tests__/export-entry.test.tsx +1 -1
  142. package/src/components/testcase/__tests__/list-view.test.tsx +1 -1
  143. package/src/components/testcase/__tests__/step-editor.test.tsx +4 -2
  144. package/src/components/testcase/__tests__/toolbar.test.tsx +1 -1
  145. package/src/components/testcase/create-testcase-dialog.tsx +6 -5
  146. package/src/components/testcase/detail/case-detail-body.tsx +1 -1
  147. package/src/components/testcase/detail/properties.tsx +1 -1
  148. package/src/components/testcase/display/display-dropdown.tsx +2 -2
  149. package/src/components/testcase/display/use-testcase-display-options.ts +1 -1
  150. package/src/components/testcase/filters/field-configs.tsx +1 -1
  151. package/src/components/testcase/generation/draft-card.tsx +2 -2
  152. package/src/components/testcase/inline-editors/assignee-editor.tsx +1 -1
  153. package/src/components/testcase/list/case-row.tsx +1 -1
  154. package/src/components/testcase/list/grouped-list.tsx +1 -1
  155. package/src/components/testcase/list-view.tsx +2 -2
  156. package/src/components/testcase/runs/convert-bug-button.tsx +2 -2
  157. package/src/components/testcase/step-editor.tsx +13 -2
  158. package/src/components/testcase/testcase-toolbar.tsx +2 -2
  159. package/src/components/testcase/views/batch-toolbar.tsx +1 -1
  160. package/src/components/testcase/views/board-view.tsx +2 -2
  161. package/src/lib/changelog/sdk-versions.ts +2 -2
  162. package/src/lib/collections/knowledge/knowledge-bases-collection.ts +2 -1
  163. package/src/lib/collections/query-keys.ts +18 -0
  164. package/src/lib/collections/reference/users-brief-collection.ts +2 -1
  165. package/src/lib/collections/reference/workspaces-collection.ts +2 -1
  166. package/src/lib/story/__tests__/activity-grouper.test.ts +1 -1
  167. package/src/lib/story/activity-grouper.ts +1 -1
  168. package/src/paraglide/messages/_index.d.ts +2 -0
  169. package/src/paraglide/messages/_index.js +88269 -5642
  170. package/src/paraglide/messages/en.js +28255 -0
  171. package/src/paraglide/messages/story_group_loadfailed1.d.ts +17 -0
  172. package/src/paraglide/messages/story_group_loadfailed1.js +30 -0
  173. package/src/paraglide/messages/story_group_retry.d.ts +16 -0
  174. package/src/paraglide/messages/story_group_retry.js +29 -0
  175. package/src/paraglide/messages/zh.js +28253 -0
  176. package/src/routes/_nav/bugs/$bugId/route.lazy.tsx +2 -2
  177. package/src/routes/_nav/projects/$projectId/iterations/$iterationId/route.lazy.tsx +1 -1
  178. package/src/routes/_nav/route.tsx +1 -2
  179. package/src/routes/_nav/stories/$storyId/route.lazy.tsx +2 -2
  180. package/src/routes/_nav/stories/$storyId/route.tsx +1 -1
  181. package/src/routes/api/design/prototype/__tests__/route.test.ts +4 -1
  182. package/src/server/__tests__/task-executor.test.ts +7 -0
  183. package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +4 -4
  184. package/src/server/ai-gateway/__tests__/tools-authorization.test.ts +25 -0
  185. package/src/server/ai-gateway/tools/server.ts +24 -15
  186. package/src/server/knowledge/reindex-guard.ts +35 -0
  187. package/src/server/lib/__tests__/cron-auth.test.ts +1 -1
  188. package/src/server/serverFns/__tests__/legal-code-canonical.test.ts +169 -0
  189. package/src/server/serverFns/__tests__/story-tree.test.ts +0 -1
  190. package/src/server/serverFns/__tests__/story-types.test.ts +2 -1
  191. package/src/server/serverFns/__tests__/update-bug-guard.test.ts +1 -1
  192. package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +95 -44
  193. package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +46 -18
  194. package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +1 -1
  195. package/src/server/serverFns/bug/bug-activities.ts +2 -2
  196. package/src/server/serverFns/bug/bug-comments.ts +44 -19
  197. package/src/server/serverFns/bug/bug-crud/detail-select.ts +1 -1
  198. package/src/server/serverFns/bug/bug-crud/detail.ts +2 -2
  199. package/src/server/serverFns/bug/bug-crud.ts +142 -110
  200. package/src/server/serverFns/bug/bug-group-query.ts +1 -1
  201. package/src/server/serverFns/bug/bug-groups.ts +2 -2
  202. package/src/server/serverFns/bug/bug-story-utils.ts +1 -1
  203. package/src/server/serverFns/chat/conversations.ts +27 -13
  204. package/src/server/serverFns/harness/github-workspace.ts +32 -25
  205. package/src/server/serverFns/harness/search-mention-candidates.ts +3 -1
  206. package/src/server/serverFns/iteration/iteration-crud.ts +1 -1
  207. package/src/server/serverFns/knowledge/public/legal/create.ts +3 -0
  208. package/src/server/serverFns/knowledge/public/legal/update.ts +8 -8
  209. package/src/server/serverFns/knowledge-index.ts +12 -5
  210. package/src/server/serverFns/project.ts +5 -11
  211. package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +15 -4
  212. package/src/server/serverFns/story/__tests__/story-transfer.test.ts +20 -0
  213. package/src/server/serverFns/story/link-preview.ts +3 -2
  214. package/src/server/serverFns/story/story-children.ts +1 -1
  215. package/src/server/serverFns/story/story-comments.ts +1 -1
  216. package/src/server/serverFns/story/story-crud/bulk.ts +1 -1
  217. package/src/server/serverFns/story/story-crud.ts +1 -1
  218. package/src/server/serverFns/story/story-dependencies.ts +61 -69
  219. package/src/server/serverFns/story/story-groups.ts +1 -1
  220. package/src/server/serverFns/story/story-transfer.ts +8 -20
  221. package/src/server/serverFns/testcase/__tests__/automation.test.ts +21 -0
  222. package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +1 -1
  223. package/src/server/serverFns/testcase/automation.ts +24 -1
  224. package/src/server/serverFns/testcase/testcase-board.ts +1 -1
  225. package/src/server/serverFns/testcase/testcase-crud.ts +1 -1
  226. package/src/server/serverFns/testcase/testcase-display-options.ts +1 -1
  227. package/src/server/serverFns/testcase/testcase-filter-views.ts +1 -1
  228. package/src/server/serverFns/testcase/types.ts +1 -1
  229. package/src/server/story/types.ts +7 -3
  230. package/src/shared/bug/__tests__/constants.test.ts +1 -1
  231. package/src/{components → shared}/bug/types.ts +4 -3
  232. package/src/{components → shared}/story/constants.ts +1 -1
  233. package/src/{components → shared}/story/types.ts +1 -7
  234. package/src/types/project.ts +1 -2
  235. /package/src/{components/testcase/display → shared/testcase}/field-configs.ts +0 -0
@@ -58,6 +58,8 @@ const mocks = vi.hoisted(() => ({
58
58
  bugCount: vi.fn(),
59
59
  bugDeleteMany: vi.fn(),
60
60
  bugActivityCreate: vi.fn(),
61
+ bugActivityCreateMany: vi.fn(),
62
+ prismaTransaction: vi.fn(),
61
63
  projectActivityCreate: vi.fn(),
62
64
  projectFindUnique: vi.fn(),
63
65
  projectMemberFindUnique: vi.fn(),
@@ -86,7 +88,7 @@ vi.mock("@allbluecn/database", () => {
86
88
  count: mocks.bugCount,
87
89
  deleteMany: mocks.bugDeleteMany,
88
90
  },
89
- bugActivity: { create: mocks.bugActivityCreate },
91
+ bugActivity: { create: mocks.bugActivityCreate, createMany: mocks.bugActivityCreateMany },
90
92
  projectActivity: { create: mocks.projectActivityCreate },
91
93
  project: { findUnique: mocks.projectFindUnique },
92
94
  projectMember: { findUnique: mocks.projectMemberFindUnique, findMany: mocks.projectMemberFindMany },
@@ -102,10 +104,15 @@ vi.mock("@allbluecn/database", () => {
102
104
  updateMany: mocks.collabDocumentUpdateMany,
103
105
  deleteMany: mocks.collabDocumentDeleteMany,
104
106
  },
105
- // $transaction 双形态:交互式(fn 回调传入同一 prisma 形状)与数组(Promise.all)
106
- $transaction: (arg: any) =>
107
- Array.isArray(arg) ? Promise.all(arg) : arg(prisma),
107
+ // $transaction spy(默认实现见下方 mockImplementation;S40 批回滚用例可 once 覆写)
108
+ $transaction: mocks.prismaTransaction,
108
109
  };
110
+ // $transaction 默认实现:数组形态 Promise.all;交互式形态回调传同一 prisma
111
+ mocks.prismaTransaction.mockImplementation((arg: unknown) =>
112
+ Array.isArray(arg)
113
+ ? Promise.all(arg as Promise<unknown>[])
114
+ : (arg as (tx: typeof prisma) => unknown)(prisma),
115
+ );
109
116
  return { prisma, Prisma: {} };
110
117
  });
111
118
 
@@ -713,7 +720,7 @@ describe("bulkArchiveBugsFn", () => {
713
720
  ]);
714
721
  mocks.projectMemberFindMany.mockResolvedValue([{ projectId: "p1" }]);
715
722
  const out = await bulkArchiveBugsFn({ data: { bugIds: ["a", "b", "c"] } });
716
- expect(out).toEqual({ archived: 2, skipped: 1 });
723
+ expect(out).toEqual({ archived: 2, skipped: 1, failed: 0 });
717
724
  expect(mocks.projectMemberFindMany).toHaveBeenCalledWith({
718
725
  where: { userId: "u1", projectId: { in: ["p1"] }, status: { not: "suspended" } },
719
726
  select: { projectId: true },
@@ -723,11 +730,32 @@ describe("bulkArchiveBugsFn", () => {
723
730
  select: { id: true, archived: true },
724
731
  });
725
732
  expect(mocks.bugUpdate).toHaveBeenCalledTimes(2);
726
- expect(mocks.bugActivityCreate).toHaveBeenCalledWith(
727
- expect.objectContaining({
728
- data: expect.objectContaining({ bugId: "a", action: "archived" }),
729
- }),
730
- );
733
+ // S40:活动随批 createMany(一条语句承载整批流水)
734
+ expect(mocks.bugActivityCreateMany).toHaveBeenCalledWith({
735
+ data: expect.arrayContaining([
736
+ expect.objectContaining({ bugId: "a", action: "archived" }),
737
+ expect.objectContaining({ bugId: "b", action: "archived" }),
738
+ ]),
739
+ });
740
+ });
741
+
742
+ it("S40:某批事务失败不中断后续批,failed 计数返回", async () => {
743
+ // 101 条构出两个批次(100+1,批大小不可导出——serverFn 纯净性口径)
744
+ const rows = Array.from({ length: 101 }, (_, i) => ({
745
+ id: `bug-${i}`,
746
+ projectId: "p1",
747
+ archived: false,
748
+ }));
749
+ mockBugFindManyByScope(rows);
750
+ mocks.projectMemberFindMany.mockResolvedValue([{ projectId: "p1" }]);
751
+ mocks.bugUpdate.mockResolvedValue({ id: "x", updatedAt: new Date("2026-01-01T00:00:00Z") });
752
+ // 首批事务回滚;次批走默认实现(真执行回调)
753
+ mocks.prismaTransaction.mockRejectedValueOnce(new Error("tx fail"));
754
+
755
+ const out = await bulkArchiveBugsFn({ data: { bugIds: rows.map((r) => r.id) } });
756
+ expect(out).toEqual({ archived: 1, skipped: 0, failed: 100 });
757
+ // 首批 reject 即短路(回调不执行),仅次批的 1 条走到 update
758
+ expect(mocks.bugUpdate).toHaveBeenCalledTimes(1);
731
759
  });
732
760
 
733
761
  it("非项目成员批量归档被拒且不写库", async () => {
@@ -795,7 +823,7 @@ describe("bulkUpdateBugsFn", () => {
795
823
  ]);
796
824
  mocks.projectMemberFindMany.mockResolvedValue([{ projectId: "p1" }]);
797
825
  const out = await bulkUpdateBugsFn({ data: { bugIds: ["a", "b"], patch: { state: "in_progress" } } });
798
- expect(out).toEqual({ updated: 2 });
826
+ expect(out).toEqual({ updated: 2, failed: 0 });
799
827
  expect(mocks.bugUpdate).toHaveBeenCalledTimes(2);
800
828
  expect(mocks.bugUpdate).toHaveBeenCalledWith(
801
829
  expect.objectContaining({
@@ -803,7 +831,7 @@ describe("bulkUpdateBugsFn", () => {
803
831
  data: expect.objectContaining({ state: "in_progress" }),
804
832
  }),
805
833
  );
806
- const actions = mocks.bugActivityCreate.mock.calls.map((c) => c[0].data);
834
+ const actions = mocks.bugActivityCreateMany.mock.calls.flatMap((c) => c[0].data);
807
835
  expect(actions).toEqual([
808
836
  expect.objectContaining({ bugId: "a", action: "state_changed", oldValue: "open", newValue: "in_progress" }),
809
837
  expect.objectContaining({ bugId: "b", action: "state_changed", oldValue: "fixed", newValue: "in_progress" }),
@@ -949,7 +977,7 @@ describe("bulkUpdateBugsFn × 协同门卫三态", () => {
949
977
  try {
950
978
  const out = await bulkUpdateBugsFn({ data: { bugIds: ["a", "b"], patch: { state: "in_progress" } } });
951
979
 
952
- expect(out).toEqual({ updated: 2 });
980
+ expect(out).toEqual({ updated: 2, failed: 0 });
953
981
  // 直写与活动剥离(collab 落库侧产出)——互吞防务本体
954
982
  expect(mocks.bugUpdate).not.toHaveBeenCalled();
955
983
  expect(mocks.bugActivityCreate).not.toHaveBeenCalled();
@@ -976,9 +1004,9 @@ describe("bulkUpdateBugsFn × 协同门卫三态", () => {
976
1004
  try {
977
1005
  const out = await bulkUpdateBugsFn({ data: { bugIds: ["a", "b"], patch: { state: "in_progress" } } });
978
1006
 
979
- expect(out).toEqual({ updated: 2 });
1007
+ expect(out).toEqual({ updated: 2, failed: 0 });
980
1008
  expect(mocks.bugUpdate).toHaveBeenCalledTimes(2);
981
- expect(mocks.bugActivityCreate).toHaveBeenCalledTimes(2);
1009
+ expect(mocks.bugActivityCreateMany).toHaveBeenCalledTimes(2);
982
1010
  expect(vi.mocked(globalThis.fetch)).not.toHaveBeenCalled();
983
1011
  // 直写全为 Y 属主面 → 不 touch(R11 旁路反超语义)
984
1012
  expect(mocks.collabDocumentUpdateMany).not.toHaveBeenCalled();
@@ -996,9 +1024,9 @@ describe("bulkUpdateBugsFn × 协同门卫三态", () => {
996
1024
  try {
997
1025
  const out = await bulkUpdateBugsFn({ data: { bugIds: ["a", "b"], patch: { state: "in_progress" } } });
998
1026
 
999
- expect(out).toEqual({ updated: 2 });
1027
+ expect(out).toEqual({ updated: 2, failed: 0 });
1000
1028
  expect(mocks.bugUpdate).toHaveBeenCalledTimes(2);
1001
- expect(mocks.bugActivityCreate).toHaveBeenCalledTimes(2);
1029
+ expect(mocks.bugActivityCreateMany).toHaveBeenCalledTimes(2);
1002
1030
  expect(vi.mocked(globalThis.fetch)).not.toHaveBeenCalled();
1003
1031
  } finally {
1004
1032
  vi.unstubAllGlobals();
@@ -1018,7 +1046,7 @@ describe("bulkUpdateBugsFn × 协同门卫三态", () => {
1018
1046
  try {
1019
1047
  const out = await bulkUpdateBugsFn({ data: { bugIds: ["a"], patch: { state: "in_progress" } } });
1020
1048
 
1021
- expect(out).toEqual({ updated: 0 });
1049
+ expect(out).toEqual({ updated: 0, failed: 0 });
1022
1050
  expect(fetchMock).not.toHaveBeenCalled();
1023
1051
  expect(mocks.collabDocumentFindUnique).not.toHaveBeenCalled();
1024
1052
  expect(mocks.bugUpdate).not.toHaveBeenCalled();
@@ -108,7 +108,7 @@ import {
108
108
  DEFAULT_BUG_DISPLAY_OPTIONS,
109
109
  } from "@/shared/bug/display-types";
110
110
  import { BUG_STATES } from "@/shared/bug/constants";
111
- import { BUG_LIST_SELECT } from "@/components/bug/types";
111
+ import { BUG_LIST_SELECT } from "@/shared/bug/types";
112
112
  import type { BugDisplayOptions } from "@/shared/bug/display-types";
113
113
 
114
114
  const DATE = new Date("2026-01-01T00:00:00Z");
@@ -21,8 +21,8 @@ import { getRequest } from "@tanstack/react-start/server";
21
21
  import { z } from "zod";
22
22
  import { requireAuth } from "@/server/auth-helpers";
23
23
  import type { AvatarConfig } from "@allbluecn/kernel";
24
- import { BUG_USER_BRIEF_SELECT } from "@/components/bug/types";
25
- import type { BugActivityItem } from "@/components/bug/types";
24
+ import { BUG_USER_BRIEF_SELECT } from "@/shared/bug/types";
25
+ import type { BugActivityItem } from "@/shared/bug/types";
26
26
  import { assertBugProjectMember } from "@/server/bug/bug-permissions";
27
27
 
28
28
  // BugActivity.userId 可空(SDK/agent 产生),user 关系 onDelete: SetNull
@@ -21,9 +21,9 @@ import { getRequest } from "@tanstack/react-start/server";
21
21
  import { z } from "zod";
22
22
  import { requireAuth } from "@/server/auth-helpers";
23
23
  import type { AvatarConfig } from "@allbluecn/kernel";
24
- import { BUG_USER_BRIEF_SELECT } from "@/components/bug/types";
25
- import type { BugCommentWithReplies } from "@/components/bug/types";
26
- import type { ReactionGroup } from "@/components/story/types";
24
+ import { BUG_USER_BRIEF_SELECT } from "@/shared/bug/types";
25
+ import type { BugCommentWithReplies } from "@/shared/bug/types";
26
+ import type { ReactionGroup } from "@/shared/story/types";
27
27
  import { assertBugProjectMember } from "@/server/bug/bug-permissions";
28
28
  import { aggregateReactions } from "@/server/story/types";
29
29
 
@@ -38,8 +38,18 @@ function castCommentUser(u: {
38
38
  return { ...u, avatarConfig: (u.avatarConfig as AvatarConfig | null) ?? null };
39
39
  }
40
40
 
41
+ // S39(审计 A2x ⚪):根评论游标分页——长串 bug 全量评论 + reactions 响应膨胀。
42
+ // 契约从裸数组扩展为 { items, nextCursor }(对齐 listBugActivitiesFn 口径);
43
+ // 回复树不单独分页:根页内所有回复整树随根返回,客户端渲染契约不变。
41
44
  export const listCommentsFn = createServerFn({ method: "GET" })
42
- .validator(z.object({ bugId: z.string() }))
45
+ .validator(
46
+ z.object({
47
+ bugId: z.string(),
48
+ take: z.number().int().min(1).max(100).default(50),
49
+ /** 上一页最后一个根评论 id(keyset 锚点;缺省 = 第一页) */
50
+ cursor: z.string().optional(),
51
+ }),
52
+ )
43
53
  .handler(async ({ data }) => {
44
54
  const prisma = await getPrisma();
45
55
  const request = getRequest();
@@ -48,21 +58,36 @@ export const listCommentsFn = createServerFn({ method: "GET" })
48
58
 
49
59
  await assertBugProjectMember(data.bugId, userId);
50
60
 
51
- const comments = await prisma.bugComment.findMany({
52
- where: { bugId: data.bugId },
53
- orderBy: { createdAt: "asc" },
54
- select: {
55
- id: true,
56
- content: true,
57
- parentId: true,
58
- createdAt: true,
59
- updatedAt: true,
60
- user: { select: BUG_USER_BRIEF_SELECT },
61
- reactions: { select: { emoji: true, userId: true } },
62
- },
61
+ const COMMENT_SELECT = {
62
+ id: true,
63
+ content: true,
64
+ parentId: true,
65
+ createdAt: true,
66
+ updatedAt: true,
67
+ user: { select: BUG_USER_BRIEF_SELECT },
68
+ reactions: { select: { emoji: true, userId: true } },
69
+ } as const;
70
+
71
+ // take+1 探测下一页;cursor 行用 skip:1 跳过(Prisma cursor 含锚行)
72
+ const roots = await prisma.bugComment.findMany({
73
+ where: { bugId: data.bugId, parentId: null },
74
+ orderBy: [{ createdAt: "asc" }, { id: "asc" }],
75
+ take: data.take + 1,
76
+ ...(data.cursor ? { cursor: { id: data.cursor }, skip: 1 } : {}),
77
+ select: COMMENT_SELECT,
63
78
  });
64
-
65
- const enriched = comments.map((c) => ({
79
+ const nextCursor = roots.length > data.take ? roots[data.take].id : null;
80
+ const pageRoots = roots.slice(0, data.take);
81
+ const rootIds = pageRoots.map((r) => r.id);
82
+ const replies = rootIds.length
83
+ ? await prisma.bugComment.findMany({
84
+ where: { bugId: data.bugId, parentId: { in: rootIds } },
85
+ orderBy: [{ createdAt: "asc" }, { id: "asc" }],
86
+ select: COMMENT_SELECT,
87
+ })
88
+ : [];
89
+
90
+ const enriched = [...pageRoots, ...replies].map((c) => ({
66
91
  id: c.id,
67
92
  content: c.content,
68
93
  parentId: c.parentId,
@@ -87,7 +112,7 @@ export const listCommentsFn = createServerFn({ method: "GET" })
87
112
  replies: (replyMap.get(c.id) ?? []).map(buildTree),
88
113
  });
89
114
 
90
- return enriched.filter((c) => !c.parentId).map(buildTree);
115
+ return { items: enriched.filter((c) => !c.parentId).map(buildTree), nextCursor };
91
116
  });
92
117
 
93
118
  const commentCreateSchema = z.object({
@@ -21,7 +21,7 @@
21
21
  // 任务 2 裁定:详情扩展字段在 serverFns/bug 侧扩展,不改 components/bug/types.ts
22
22
  // 的 BUG_LIST_SELECT——列表查询不多拉大字段。
23
23
  import type { Prisma } from "@allbluecn/database";
24
- import { BUG_LIST_SELECT } from "@/components/bug/types";
24
+ import { BUG_LIST_SELECT } from "@/shared/bug/types";
25
25
 
26
26
  export const BUG_DETAIL_SELECT = {
27
27
  ...BUG_LIST_SELECT,
@@ -20,8 +20,8 @@ import { createServerFn } from "@tanstack/react-start";
20
20
  import { getRequest } from "@tanstack/react-start/server";
21
21
  import { z } from "zod";
22
22
  import { requireAuth } from "@/server/auth-helpers";
23
- import { castBugUsers } from "@/components/bug/types";
24
- import type { BugDetail, ReproduceSteps } from "@/components/bug/types";
23
+ import { castBugUsers } from "@/shared/bug/types";
24
+ import type { BugDetail, ReproduceSteps } from "@/shared/bug/types";
25
25
  import { assertBugProjectMember } from "@/server/bug/bug-permissions";
26
26
  import { BUG_DETAIL_SELECT, extractScreenshotBlobId } from "./detail-select";
27
27
 
@@ -31,7 +31,7 @@ import {
31
31
  type BugSeverity,
32
32
  type BugSource,
33
33
  } from "@/shared/bug/constants";
34
- import { BUG_LIST_SELECT, BUG_USER_BRIEF_SELECT, castBugUsers } from "@/components/bug/types";
34
+ import { BUG_LIST_SELECT, BUG_USER_BRIEF_SELECT, castBugUsers } from "@/shared/bug/types";
35
35
  import { buildListWhere } from "./bug-list-query";
36
36
  import type { AvatarConfig } from "@allbluecn/kernel";
37
37
  import type { Prisma } from "@allbluecn/database";
@@ -768,6 +768,12 @@ export const unarchiveBugFn = createServerFn({ method: "POST" })
768
768
 
769
769
  const idListSchema = z.object({ bugIds: z.array(z.string()).min(1).max(500) });
770
770
 
771
+ // S40(审计 A2x 待决策)分批事务语义裁定:**按批 all-or-nothing、批间 best-effort
772
+ // 继续**——活动流水与实体写同批原子(批内失败活动不孤儿);某批失败不中断后续批,
773
+ // failed 计数随返回值交客户端提示。理由:500 条 × 3+ 语句的全量单事务长事务锁持
774
+ // 风险高;批间失败全量回滚会迫使用户重试整个大批。批大小 100。
775
+ const BULK_TX_BATCH_SIZE = 100;
776
+
771
777
  export const bulkArchiveBugsFn = createServerFn({ method: "POST" })
772
778
  .validator(idListSchema)
773
779
  .handler(async ({ data }) => {
@@ -778,36 +784,47 @@ export const bulkArchiveBugsFn = createServerFn({ method: "POST" })
778
784
 
779
785
  await assertBugsInMemberProjects(data.bugIds, userId);
780
786
 
781
- return prisma.$transaction(async (tx) => {
782
- const rows = await tx.bug.findMany({
783
- where: { id: { in: data.bugIds } },
784
- select: { id: true, archived: true },
785
- });
786
- const toArchive = rows.filter((r) => !r.archived);
787
- for (const r of toArchive) {
788
- const updated = await tx.bug.update({
789
- where: { id: r.id },
790
- data: { archived: true, archivedAt: new Date() },
791
- });
792
- // R11 touch:批量归档同不变式(逐条 touch,事务内)
793
- await touchCollabSnapshot(
794
- tx,
795
- buildRoomName({ kind: "entity-bug", bugId: r.id }),
796
- updated.updatedAt,
797
- );
798
- await tx.bugActivity.create({
799
- data: {
800
- bugId: r.id,
801
- userId,
802
- action: "archived",
803
- field: "archived",
804
- oldValue: "false",
805
- newValue: "true",
806
- },
787
+ const rows = await prisma.bug.findMany({
788
+ where: { id: { in: data.bugIds } },
789
+ select: { id: true, archived: true },
790
+ });
791
+ const toArchiveIds = rows.filter((r) => !r.archived).map((r) => r.id);
792
+ let archived = 0;
793
+ let failed = 0;
794
+ for (let i = 0; i < toArchiveIds.length; i += BULK_TX_BATCH_SIZE) {
795
+ const chunk = toArchiveIds.slice(i, i + BULK_TX_BATCH_SIZE);
796
+ try {
797
+ await prisma.$transaction(async (tx) => {
798
+ for (const id of chunk) {
799
+ const updated = await tx.bug.update({
800
+ where: { id },
801
+ data: { archived: true, archivedAt: new Date() },
802
+ });
803
+ // R11 touch:批量归档同不变式(逐条 touch,事务内)
804
+ await touchCollabSnapshot(
805
+ tx,
806
+ buildRoomName({ kind: "entity-bug", bugId: id }),
807
+ updated.updatedAt,
808
+ );
809
+ }
810
+ await tx.bugActivity.createMany({
811
+ data: chunk.map((id) => ({
812
+ bugId: id,
813
+ userId,
814
+ action: "archived",
815
+ field: "archived",
816
+ oldValue: "false",
817
+ newValue: "true",
818
+ })),
819
+ });
807
820
  });
821
+ archived += chunk.length;
822
+ } catch (e) {
823
+ failed += chunk.length;
824
+ console.warn(`[bug-bulk] archive 批次失败(${chunk.length} 条)`, e);
808
825
  }
809
- return { archived: toArchive.length, skipped: data.bugIds.length - toArchive.length };
810
- });
826
+ }
827
+ return { archived, skipped: data.bugIds.length - toArchiveIds.length, failed };
811
828
  });
812
829
 
813
830
  export const bulkDeleteBugsFn = createServerFn({ method: "POST" })
@@ -883,6 +900,16 @@ export const bulkUpdateBugsFn = createServerFn({ method: "POST" })
883
900
  }
884
901
  }
885
902
 
903
+ // 标签名解析提升出循环(patch.labelIds 对所有 bug 同一值,S40 顺带收敛)
904
+ const patchLabelNames = new Map<string, string>();
905
+ if (data.patch.labelIds !== undefined && data.patch.labelIds.length > 0) {
906
+ const labelRows = await prisma.label.findMany({
907
+ where: { id: { in: data.patch.labelIds } },
908
+ select: { id: true, name: true },
909
+ });
910
+ for (const l of labelRows) patchLabelNames.set(l.id, l.name);
911
+ }
912
+
886
913
  // per-bug 门卫:仅对「相对现值有变化」的 bug 转发(无变化零写零转发零
887
914
  // 查询);变更面即门卫 fields 面(updateBugFn 同口径,键值与客户端 Y 写
888
915
  // 端同形)。C1 属主预检:批量入口已 assertBugsInMemberProjects 圈定,
@@ -920,88 +947,93 @@ export const bulkUpdateBugsFn = createServerFn({ method: "POST" })
920
947
  if (guard.kind === "forwarded") forwardedIds.add(bug.id);
921
948
  }
922
949
 
923
- return prisma.$transaction(async (tx) => {
924
- let updated = 0;
925
- for (const bug of rows) {
926
- const updates: Prisma.BugUpdateInput = {};
927
- const activities: Array<{ action: string; field: string; oldValue: string; newValue: string }> = [];
928
-
929
- if (data.patch.state !== undefined && data.patch.state !== bug.state) {
930
- activities.push({
931
- action: "state_changed",
932
- field: "state",
933
- oldValue: bug.state,
934
- newValue: data.patch.state,
935
- });
936
- updates.state = data.patch.state;
937
- }
938
- if (data.patch.severity !== undefined && data.patch.severity !== bug.severity) {
939
- activities.push({
940
- action: "severity_changed",
941
- field: "severity",
942
- oldValue: bug.severity,
943
- newValue: data.patch.severity,
944
- });
945
- updates.severity = data.patch.severity;
946
- }
947
- if (data.patch.assigneeId !== undefined) {
948
- const newId = data.patch.assigneeId;
949
- const oldId = bug.assignee?.id ?? null;
950
- if (newId !== oldId) {
951
- updates.assignee = newId ? { connect: { id: newId } } : { disconnect: true };
952
- activities.push({
953
- action: "assigned",
954
- field: "assigneeId",
955
- oldValue: oldId ?? "",
956
- newValue: newId ?? "",
957
- });
950
+ // 写入分批(S40 语义见 bulkArchiveBugsFn 上方裁定):实体写与活动流水同批原子
951
+ let updated = 0;
952
+ let failed = 0;
953
+ for (let i = 0; i < rows.length; i += BULK_TX_BATCH_SIZE) {
954
+ const chunkRows = rows.slice(i, i + BULK_TX_BATCH_SIZE);
955
+ let chunkUpdated = 0;
956
+ try {
957
+ await prisma.$transaction(async (tx) => {
958
+ for (const bug of chunkRows) {
959
+ const updates: Prisma.BugUpdateInput = {};
960
+ const activities: Array<{ action: string; field: string; oldValue: string; newValue: string }> = [];
961
+
962
+ if (data.patch.state !== undefined && data.patch.state !== bug.state) {
963
+ activities.push({
964
+ action: "state_changed",
965
+ field: "state",
966
+ oldValue: bug.state,
967
+ newValue: data.patch.state,
968
+ });
969
+ updates.state = data.patch.state;
970
+ }
971
+ if (data.patch.severity !== undefined && data.patch.severity !== bug.severity) {
972
+ activities.push({
973
+ action: "severity_changed",
974
+ field: "severity",
975
+ oldValue: bug.severity,
976
+ newValue: data.patch.severity,
977
+ });
978
+ updates.severity = data.patch.severity;
979
+ }
980
+ if (data.patch.assigneeId !== undefined) {
981
+ const newId = data.patch.assigneeId;
982
+ const oldId = bug.assignee?.id ?? null;
983
+ if (newId !== oldId) {
984
+ updates.assignee = newId ? { connect: { id: newId } } : { disconnect: true };
985
+ activities.push({
986
+ action: "assigned",
987
+ field: "assigneeId",
988
+ oldValue: oldId ?? "",
989
+ newValue: newId ?? "",
990
+ });
991
+ }
992
+ }
993
+ if (data.patch.labelIds !== undefined) {
994
+ const oldIds = bug.labels.map((bl) => bl.labelId).sort().join(",");
995
+ const newIds = [...data.patch.labelIds].sort().join(",");
996
+ if (oldIds !== newIds) {
997
+ activities.push({
998
+ action: "label_changed",
999
+ field: "labelIds",
1000
+ oldValue: bug.labels.map((bl) => bl.label.name).join(", "),
1001
+ newValue: data.patch.labelIds.map((id) => patchLabelNames.get(id) ?? id).join(", "),
1002
+ });
1003
+ updates.labels = {
1004
+ deleteMany: {},
1005
+ create: data.patch.labelIds.map((labelId) => ({ labelId })),
1006
+ };
1007
+ }
1008
+ }
1009
+
1010
+ // 无字段变化跳过写(不触碰 updatedBy/updatedAt)
1011
+ if (Object.keys(updates).length === 0) continue;
1012
+
1013
+ // forwarded:批量 patch 全为 Y 属主字段,写入已由 collab 接管——剥离
1014
+ // 直写(防互吞)后零剩余写入:不 bump updatedAt、不写活动(collab
1015
+ // 落库侧产出,web 再写即重复流水)。编辑已经 collab 应用,计入 updated。
1016
+ if (forwardedIds.has(bug.id)) {
1017
+ chunkUpdated++;
1018
+ continue;
1019
+ }
1020
+
1021
+ updates.updatedBy = { connect: { id: userId } };
1022
+ await tx.bug.update({ where: { id: bug.id }, data: updates });
1023
+ if (activities.length > 0) {
1024
+ await tx.bugActivity.createMany({
1025
+ data: activities.map((a) => ({ bugId: bug.id, userId, ...a })),
1026
+ });
1027
+ }
1028
+ chunkUpdated++;
958
1029
  }
959
- }
960
- if (data.patch.labelIds !== undefined) {
961
- const oldIds = bug.labels.map((bl) => bl.labelId).sort().join(",");
962
- const newIds = [...data.patch.labelIds].sort().join(",");
963
- if (oldIds !== newIds) {
964
- const labelRows = data.patch.labelIds.length
965
- ? await tx.label.findMany({
966
- where: { id: { in: data.patch.labelIds } },
967
- select: { id: true, name: true },
968
- })
969
- : [];
970
- const idToName = new Map(labelRows.map((l) => [l.id, l.name]));
971
- activities.push({
972
- action: "label_changed",
973
- field: "labelIds",
974
- oldValue: bug.labels.map((bl) => bl.label.name).join(", "),
975
- newValue: data.patch.labelIds.map((id) => idToName.get(id) ?? id).join(", "),
976
- });
977
- updates.labels = {
978
- deleteMany: {},
979
- create: data.patch.labelIds.map((labelId) => ({ labelId })),
980
- };
981
- }
982
- }
983
-
984
- // 无字段变化跳过写(不触碰 updatedBy/updatedAt)
985
- if (Object.keys(updates).length === 0) continue;
986
-
987
- // forwarded:批量 patch 全为 Y 属主字段,写入已由 collab 接管——剥离
988
- // 直写(防互吞)后零剩余写入:不 bump updatedAt、不写活动(collab
989
- // 落库侧产出,web 再写即重复流水)。编辑已经 collab 应用,计入 updated。
990
- if (forwardedIds.has(bug.id)) {
991
- updated++;
992
- continue;
993
- }
994
-
995
- updates.updatedBy = { connect: { id: userId } };
996
- await tx.bug.update({ where: { id: bug.id }, data: updates });
997
- for (const a of activities) {
998
- await tx.bugActivity.create({
999
- data: { bugId: bug.id, userId, action: a.action, field: a.field, oldValue: a.oldValue, newValue: a.newValue },
1000
- });
1001
- }
1002
- updated++;
1030
+ });
1031
+ updated += chunkUpdated;
1032
+ } catch (e) {
1033
+ failed += chunkUpdated;
1034
+ console.warn(`[bug-bulk] update 批次失败(${chunkRows.length} 条)`, e);
1003
1035
  }
1036
+ }
1004
1037
 
1005
- return { updated };
1006
- });
1038
+ return { updated, failed };
1007
1039
  });
@@ -25,7 +25,7 @@ import {
25
25
  type BugGroupKey,
26
26
  type BugOrderKey,
27
27
  } from "@/shared/bug/display-types";
28
- import { BUG_LIST_SELECT, type BugListRow } from "@/components/bug/types";
28
+ import { BUG_LIST_SELECT, type BugListRow } from "@/shared/bug/types";
29
29
 
30
30
  /** prisma.groupBy 的 by 字段映射(assignee/createdBy 用外键列分组计数) */
31
31
  export const BUG_GROUP_BY_FIELD: Record<Exclude<BugGroupKey, "none">, Prisma.BugScalarFieldEnum> = {
@@ -31,7 +31,7 @@ import {
31
31
  BUG_ORDER_DIRECTIONS,
32
32
  type BugGroupKey,
33
33
  } from "@/shared/bug/display-types";
34
- import { BUG_LIST_SELECT, castBugUsers } from "@/components/bug/types";
34
+ import { BUG_LIST_SELECT, castBugUsers } from "@/shared/bug/types";
35
35
  import { buildListWhere } from "./bug-list-query";
36
36
  import {
37
37
  BUG_GROUP_BY_FIELD,
@@ -44,7 +44,7 @@ import {
44
44
  fetchBugsInSeverityRankOrder,
45
45
  sortBugGroupKeys,
46
46
  } from "./bug-group-query";
47
- import type { BugListItem } from "@/components/bug/types";
47
+ import type { BugListItem } from "@/shared/bug/types";
48
48
  import type { Prisma } from "@allbluecn/database";
49
49
 
50
50
  const baseFilterSchema = {
@@ -20,7 +20,7 @@
20
20
  // (历史数据可能是裸 HTML/markdown,见 lib/story/description-diff.ts 的兼容口径)。
21
21
  // 转需求时把 Bug.reproduceSteps 序列化为「## 复现步骤」节追加到描述尾部,避免信息丢失
22
22
  // (规格 §7:附件不复制,复现步骤必须随描述带走)。
23
- import type { ReproduceSteps } from "@/components/bug/types";
23
+ import type { ReproduceSteps } from "@/shared/bug/types";
24
24
 
25
25
  function escapeHtml(text: string): string {
26
26
  return text