@allbluecn/web-app 0.18.2 → 0.19.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 (761) hide show
  1. package/package.json +66 -51
  2. package/src/__tests__/components/auth/auth-layout.test.tsx +13 -1
  3. package/src/__tests__/{knowledge-public → knowledge/public}/canonical-id.test.ts +1 -1
  4. package/src/__tests__/{knowledge-public → knowledge/public}/favicon.test.ts +1 -1
  5. package/src/__tests__/{knowledge-public → knowledge/public}/github-sync.test.ts +1 -1
  6. package/src/__tests__/{knowledge-public → knowledge/public}/share-merge.test.ts +7 -7
  7. package/src/__tests__/security/xss-sanitize.test.ts +9 -8
  8. package/src/components/ai/__tests__/chat-thread-id-stability.test.tsx +125 -0
  9. package/src/components/ai/ai-chat-prompt.tsx +8 -2
  10. package/src/components/ai/ai-prompt-input/index.tsx +2 -1
  11. package/src/components/ai/global-harness-sidebar/__tests__/global-ai-prompts.test.ts +1 -2
  12. package/src/components/ai/global-harness-sidebar/index.tsx +22 -10
  13. package/src/components/bug/__tests__/board-subgroups.test.tsx +512 -0
  14. package/src/components/bug/__tests__/board-view.test.tsx +350 -0
  15. package/src/components/bug/__tests__/bug-list-loading-defer.test.tsx +85 -0
  16. package/src/components/bug/__tests__/bug-toolbar.test.tsx +7 -1
  17. package/src/components/bug/__tests__/comment-reactions.test.tsx +182 -0
  18. package/src/components/bug/__tests__/date-and-lightbox.test.tsx +96 -0
  19. package/src/components/bug/__tests__/export-entry.test.tsx +217 -0
  20. package/src/components/bug/__tests__/filters-compat.test.tsx +157 -0
  21. package/src/components/bug/__tests__/fix-request-race.test.tsx +396 -0
  22. package/src/components/bug/__tests__/peek-modes.test.tsx +365 -0
  23. package/src/components/bug/__tests__/saved-views.test.tsx +309 -0
  24. package/src/components/bug/__tests__/story-bugs-panel.test.tsx +146 -0
  25. package/src/components/bug/__tests__/subgroup-header.test.tsx +150 -0
  26. package/src/components/bug/__tests__/toolbar-shell.test.tsx +369 -0
  27. package/src/components/bug/__tests__/use-paged-bug-groups.test.tsx +58 -0
  28. package/src/components/bug/__tests__/view-type.test.tsx +247 -0
  29. package/src/components/bug/bug-list-view.tsx +310 -13
  30. package/src/components/bug/bug-panel-header.tsx +84 -8
  31. package/src/components/bug/bug-toolbar.tsx +268 -116
  32. package/src/components/bug/create-bug-dialog.tsx +1 -1
  33. package/src/components/bug/detail/__tests__/activity-timeline.test.tsx +1 -0
  34. package/src/components/bug/detail/__tests__/bug-detail-collab.test.tsx +236 -0
  35. package/src/components/bug/detail/__tests__/repro-report-card.test.tsx +2 -1
  36. package/src/components/bug/detail/activity-timeline.tsx +1 -1
  37. package/src/components/bug/detail/bug-detail-body.tsx +316 -18
  38. package/src/components/bug/detail/comment-item.tsx +7 -2
  39. package/src/components/bug/detail/comment-reactions.tsx +138 -0
  40. package/src/components/bug/detail/error-context.tsx +29 -9
  41. package/src/components/bug/detail/props-section.tsx +31 -3
  42. package/src/components/bug/detail/repro-report-card.tsx +1 -1
  43. package/src/components/bug/display/constants.ts +4 -2
  44. package/src/components/bug/display/display-dropdown.tsx +76 -280
  45. package/src/components/bug/display/types.ts +24 -8
  46. package/src/components/bug/display/use-paged-bug-groups.ts +183 -32
  47. package/src/components/bug/filters/{filter-value-input.tsx → field-configs.tsx} +218 -106
  48. package/src/components/bug/filters/index.tsx +14 -5
  49. package/src/components/bug/inline-editors/assignee-editor.tsx +2 -2
  50. package/src/components/bug/inline-editors/labels-editor.tsx +1 -1
  51. package/src/components/bug/inline-editors/severity-editor.tsx +37 -18
  52. package/src/components/bug/inline-editors/state-editor.tsx +1 -1
  53. package/src/components/bug/peek-panel.tsx +153 -35
  54. package/src/components/bug/select-bug-dialog.tsx +500 -0
  55. package/src/components/bug/story-bugs-panel.tsx +1090 -0
  56. package/src/components/bug/types.ts +9 -2
  57. package/src/components/bug/views/board-view.tsx +661 -0
  58. package/src/components/bug/views/bug-list/batch-toolbar.tsx +25 -34
  59. package/src/components/bug/views/bug-list/group.tsx +28 -13
  60. package/src/components/bug/views/bug-list/index.tsx +223 -59
  61. package/src/components/bug/views/bug-list/row.tsx +7 -1
  62. package/src/components/bug/views/create-view-dialog.tsx +160 -0
  63. package/src/components/bug/views/view-list-section.tsx +140 -0
  64. package/src/components/bug/views/view-selector-button.tsx +145 -0
  65. package/src/components/bug/views/view-switcher.tsx +8 -6
  66. package/src/components/bug/views/view-types.ts +162 -0
  67. package/src/components/collab/__tests__/activity-attribution.test.tsx +211 -0
  68. package/src/components/collab/__tests__/canvas-collab-panel.test.tsx +118 -0
  69. package/src/components/collab/__tests__/collab-presence-header.test.tsx +335 -0
  70. package/src/components/collab/__tests__/entity-presence-badge.test.tsx +226 -0
  71. package/src/components/collab/activity-attribution.tsx +221 -0
  72. package/src/components/collab/canvas-collab-panel.tsx +104 -0
  73. package/src/components/collab/collab-presence-header.tsx +220 -0
  74. package/src/components/collab/entity-presence-badge.tsx +130 -0
  75. package/src/components/collab/field-editing-pill.tsx +137 -0
  76. package/src/components/command/command-palette.tsx +6 -6
  77. package/src/components/design/canvas/document/__tests__/canvas-tree-fixture-snapshot.ts +589 -0
  78. package/src/components/design/canvas/document/__tests__/canvas-tree-reconcile.test.ts +159 -0
  79. package/src/components/design/canvas/panels/toolbar.tsx +3 -2
  80. package/src/components/design/canvas/plugins/presence/__tests__/canvas-collab-wiring.test.tsx +660 -0
  81. package/src/components/design/canvas/plugins/presence/__tests__/layout.test.ts +292 -0
  82. package/src/components/design/canvas/plugins/presence/__tests__/presence-overlay.test.tsx +139 -0
  83. package/src/components/design/canvas/plugins/presence/layout.ts +222 -0
  84. package/src/components/design/canvas/plugins/presence/presence-layer.tsx +353 -0
  85. package/src/components/design/canvas/plugins/presence/presence-overlay.tsx +155 -0
  86. package/src/components/design/canvas/plugins/presence/viewer-badge.tsx +80 -0
  87. package/src/components/design/canvas/react/__tests__/hooks.test.tsx +4 -4
  88. package/src/components/design/editor/collaboration/__tests__/design-agent-panel-regenerate.test.tsx +3 -1
  89. package/src/components/design/editor/collaboration/__tests__/design-agent-panel-stream.test.tsx +3 -1
  90. package/src/components/design/editor/collaboration/design-agent-panel.tsx +3 -0
  91. package/src/components/design/editor/collaboration/design-comment-review-panel.tsx +5 -5
  92. package/src/components/design/editor/dashboard/create-design-dialog.tsx +1 -1
  93. package/src/components/design/editor/dashboard/design-dashboard.tsx +1 -10
  94. package/src/components/design/editor/layout/__tests__/collab-param-wiring.test.tsx +153 -0
  95. package/src/components/design/editor/layout/__tests__/no-page-save-toast.test.tsx +8 -0
  96. package/src/components/design/editor/layout/design-editor-layout.tsx +28 -1
  97. package/src/components/design/editor/layout/design-navigation.tsx +0 -4
  98. package/src/components/design/editor/layout/hooks/__tests__/use-design-auto-save-collab.test.tsx +130 -0
  99. package/src/components/design/editor/layout/hooks/use-design-auto-save.ts +18 -1
  100. package/src/components/design/editor/share/share-comment-panel.tsx +37 -20
  101. package/src/components/design/editor/share/share-layout.tsx +3 -0
  102. package/src/components/design/editor/share/share-nav-bar.tsx +7 -1
  103. package/src/components/design/editor/share/share-theme-toggle.tsx +10 -15
  104. package/src/components/design/editor/tiptap/node/paragraph-node/paragraph-node.scss +1 -1
  105. package/src/components/harness/__tests__/composer-draft.test.ts +126 -0
  106. package/src/components/harness/composer/composer-editor.tsx +64 -6
  107. package/src/components/harness/composer/harness-composer.tsx +1 -1
  108. package/src/components/harness/harness-layout.tsx +1 -1
  109. package/src/components/harness/harness-workbench-page.tsx +6 -1
  110. package/src/components/harness/turn-group.tsx +1 -1
  111. package/src/components/hotkeys/shortcut-badge.tsx +4 -4
  112. package/src/components/knowledge/dialog/knowledge-form-dialog.tsx +1 -1
  113. package/src/components/knowledge/editor/__tests__/knowledge-doc-editor-collab.test.tsx +542 -0
  114. package/src/components/knowledge/editor/doc-header.tsx +7 -49
  115. package/src/components/knowledge/editor/knowledge-doc-editor.tsx +264 -51
  116. package/src/components/knowledge/editor/mindmap-doc-editor.tsx +3 -19
  117. package/src/components/knowledge/knowledge-homepage.tsx +3 -3
  118. package/src/components/knowledge/knowledge-toolbar.tsx +8 -8
  119. package/src/components/{knowledge-public → knowledge/public}/bookmark-form-dialog.tsx +4 -4
  120. package/src/components/{knowledge-public → knowledge/public}/knowledge-card.tsx +1 -15
  121. package/src/components/{knowledge-public → knowledge/public}/knowledge-form-dialog.tsx +9 -9
  122. package/src/components/{knowledge-public → knowledge/public}/legal-compliance-editor.tsx +35 -28
  123. package/src/components/{knowledge-public → knowledge/public}/sharers-avatar-group.tsx +3 -2
  124. package/src/components/layout/layout-responsive-provider.tsx +79 -0
  125. package/src/components/layout/sidebar-03/__tests__/presence-avatar-stack.test.tsx +148 -0
  126. package/src/components/layout/sidebar-03/app-sidebar.tsx +9 -3
  127. package/src/components/layout/sidebar-03/index.tsx +19 -4
  128. package/src/components/layout/sidebar-03/nav-main.tsx +28 -10
  129. package/src/components/layout/sidebar-03/presence-avatar-stack.tsx +112 -0
  130. package/src/components/layout/sidebar-03/user-menu.tsx +3 -3
  131. package/src/components/project/create-project-dialog.tsx +1 -1
  132. package/src/components/project/invite-member-dialog.tsx +1 -1
  133. package/src/components/project/project-activity-list.tsx +2 -2
  134. package/src/components/project/project-member-manager.tsx +2 -2
  135. package/src/components/reui/__tests__/kanban-drop-indicator.test.ts +88 -0
  136. package/src/components/reui/__tests__/kanban-keyboard.test.ts +262 -0
  137. package/src/components/reui/event-calendar/event-calendar-agenda-view.tsx +236 -0
  138. package/src/components/reui/event-calendar/event-calendar-content.tsx +80 -0
  139. package/src/components/reui/event-calendar/event-calendar-dnd.tsx +1062 -0
  140. package/src/components/reui/event-calendar/event-calendar-event.tsx +555 -0
  141. package/src/components/reui/event-calendar/event-calendar-i18n.tsx +291 -0
  142. package/src/components/reui/event-calendar/event-calendar-lib.tsx +677 -0
  143. package/src/components/reui/event-calendar/event-calendar-month-view.tsx +1287 -0
  144. package/src/components/reui/event-calendar/event-calendar-nav.tsx +615 -0
  145. package/src/components/reui/event-calendar/event-calendar-recurrence.tsx +592 -0
  146. package/src/components/reui/event-calendar/event-calendar-resource-view.tsx +810 -0
  147. package/src/components/reui/event-calendar/event-calendar-time-grid.tsx +1366 -0
  148. package/src/components/reui/event-calendar/event-calendar-types.tsx +240 -0
  149. package/src/components/reui/event-calendar/event-calendar.tsx +1916 -0
  150. package/src/components/reui/gantt/__tests__/gantt-row-window.test.ts +243 -0
  151. package/src/components/reui/gantt/__tests__/gantt-view-virtualize.test.tsx +243 -0
  152. package/src/components/reui/gantt/gantt-bar.tsx +439 -0
  153. package/src/components/reui/gantt/gantt-dnd.tsx +1000 -0
  154. package/src/components/reui/gantt/gantt-i18n.tsx +287 -0
  155. package/src/components/reui/gantt/gantt-lib.tsx +695 -0
  156. package/src/components/reui/gantt/gantt-nav.tsx +446 -0
  157. package/src/components/reui/gantt/gantt-recurrence.tsx +402 -0
  158. package/src/components/reui/gantt/gantt-row-window.ts +119 -0
  159. package/src/components/reui/gantt/gantt-types.tsx +283 -0
  160. package/src/components/reui/gantt/gantt-view.tsx +4466 -0
  161. package/src/components/reui/gantt/gantt.tsx +1822 -0
  162. package/src/components/reui/gantt/use-gantt-row-window.ts +159 -0
  163. package/src/components/reui/icon-stack.tsx +90 -0
  164. package/src/components/reui/kanban-keyboard.ts +268 -0
  165. package/src/components/reui/kanban.tsx +1227 -0
  166. package/src/components/settings/personal-team/account/account-client.tsx +3 -2
  167. package/src/components/settings/personal-team/team/invitation-cards/invitation-history-list.tsx +5 -5
  168. package/src/components/settings/personal-team/team/team-client.tsx +2 -1
  169. package/src/components/settings/personal-team/team/team-member-list.tsx +4 -4
  170. package/src/components/share/share-presence-bar.tsx +182 -0
  171. package/src/components/shared-kit/auth/auth-client.ts +5 -0
  172. package/src/components/shared-kit/avatars/__tests__/avatar-shape.test.ts +44 -0
  173. package/src/components/shared-kit/avatars/__tests__/user-avatar.test.tsx +46 -0
  174. package/src/components/{tags/tags-collection.ts → shared-kit/avatars/avatar-shape.ts} +14 -20
  175. package/src/components/shared-kit/avatars/notion-avatar.tsx +62 -11
  176. package/src/components/shared-kit/avatars/user-avatar.tsx +7 -4
  177. package/src/components/shared-kit/list-kit/__tests__/batch-toolbar-shell.test.tsx +133 -0
  178. package/src/components/shared-kit/list-kit/__tests__/frozen-header.test.ts +89 -0
  179. package/src/components/shared-kit/list-kit/__tests__/level-badge.test.tsx +85 -0
  180. package/src/components/shared-kit/list-kit/__tests__/toolbar-shell.test.tsx +178 -0
  181. package/src/components/shared-kit/list-kit/batch-toolbar-shell.tsx +111 -0
  182. package/src/components/shared-kit/list-kit/csv-download.ts +46 -0
  183. package/src/components/shared-kit/list-kit/display/__tests__/dropdown.test.tsx +215 -0
  184. package/src/components/shared-kit/list-kit/display/dropdown.tsx +582 -0
  185. package/src/components/shared-kit/list-kit/display/types.ts +64 -0
  186. package/src/components/shared-kit/list-kit/display/values.ts +60 -0
  187. package/src/components/shared-kit/list-kit/filters/__tests__/create-filters-module.test.tsx +181 -0
  188. package/src/components/shared-kit/list-kit/filters/__tests__/types.test.ts +133 -0
  189. package/src/components/shared-kit/list-kit/filters/index.tsx +757 -0
  190. package/src/components/shared-kit/list-kit/filters/types.ts +68 -0
  191. package/src/components/shared-kit/list-kit/frozen-header.ts +45 -0
  192. package/src/components/shared-kit/list-kit/icon-semantics.ts +50 -0
  193. package/src/components/shared-kit/list-kit/kanban/__tests__/drop-layout.test.ts +139 -0
  194. package/src/components/shared-kit/list-kit/kanban/__tests__/frozen-header.test.ts +111 -0
  195. package/src/components/shared-kit/list-kit/kanban/__tests__/instance-key.test.ts +130 -0
  196. package/src/components/shared-kit/list-kit/kanban/__tests__/kanban-board-view.test.tsx +901 -0
  197. package/src/components/shared-kit/list-kit/kanban/__tests__/manual-order.test.ts +35 -0
  198. package/src/components/shared-kit/list-kit/kanban/drop-commit.ts +212 -0
  199. package/src/components/shared-kit/list-kit/kanban/drop-layout.ts +98 -0
  200. package/src/components/shared-kit/list-kit/kanban/frozen-header.ts +88 -0
  201. package/src/components/shared-kit/list-kit/kanban/instance-key.ts +83 -0
  202. package/src/components/shared-kit/list-kit/kanban/kanban-board-view.tsx +1414 -0
  203. package/src/components/shared-kit/list-kit/kanban/manual-order.ts +39 -0
  204. package/src/components/shared-kit/list-kit/level-badge.tsx +71 -0
  205. package/src/components/shared-kit/list-kit/toolbar-shell.tsx +103 -0
  206. package/src/components/shared-kit/loading/__tests__/use-deferred-flag.test.tsx +115 -0
  207. package/src/components/shared-kit/loading/use-deferred-flag.ts +35 -0
  208. package/src/components/shared-kit/peek/dock-slot.ts +62 -0
  209. package/src/components/shared-kit/peek/peek-mode.ts +34 -0
  210. package/src/components/shared-kit/theme/animated-theme-toggler.tsx +13 -7
  211. package/src/components/shared-kit/theme/theme-toggle.tsx +7 -18
  212. package/src/components/shared-kit/views/view-icon.tsx +49 -0
  213. package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +215 -0
  214. package/src/components/story/__tests__/story-list-loading-defer.test.tsx +88 -0
  215. package/src/components/story/__tests__/story-list-view-peek-sync.test.tsx +242 -0
  216. package/src/components/story/__tests__/story-list-view-restore.test.tsx +45 -3
  217. package/src/components/story/__tests__/story-list-view-ssr.test.tsx +0 -1
  218. package/src/components/story/ai/__tests__/action-registry.test.ts +11 -11
  219. package/src/components/story/ai/action-registry.ts +5 -7
  220. package/src/components/story/ai/handlers/stream-text.ts +1 -3
  221. package/src/components/story/ai/story-ai-toolbar.tsx +58 -16
  222. package/src/components/story/ai/use-toolbar-overflow.ts +4 -7
  223. package/src/components/story/columns.tsx +2 -2
  224. package/src/components/story/constants.ts +17 -1
  225. package/src/components/story/create-story-dialog.tsx +8 -7
  226. package/src/components/story/detail/__tests__/associated-attachments-panel.test.tsx +38 -4
  227. package/src/components/story/detail/__tests__/categories.test.tsx +52 -26
  228. package/src/components/story/detail/__tests__/story-collab.test.tsx +897 -0
  229. package/src/components/story/detail/activity-tabs.tsx +5 -4
  230. package/src/components/story/detail/activity-timeline.tsx +15 -3
  231. package/src/components/story/detail/comment-input.tsx +2 -2
  232. package/src/components/story/detail/comment-item.tsx +6 -3
  233. package/src/components/story/detail/comment-thread.tsx +1 -0
  234. package/src/components/story/detail/reaction-bar.tsx +1 -1
  235. package/src/components/story/detail/story-detail-body.tsx +501 -76
  236. package/src/components/story/detail/story-detail-toolbar/__tests__/related-test-cases.test.tsx +173 -10
  237. package/src/components/story/detail/story-detail-toolbar/associated-attachments-panel.tsx +144 -8
  238. package/src/components/story/detail/story-detail-toolbar/categories.tsx +82 -38
  239. package/src/components/story/detail/story-detail-toolbar/children.tsx +254 -174
  240. package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +140 -58
  241. package/src/components/story/detail/story-detail-toolbar/index.tsx +613 -0
  242. package/src/components/story/detail/story-detail-toolbar/related-test-cases.tsx +1010 -87
  243. package/src/components/story/detail/story-detail-toolbar/scroll-hint.tsx +57 -0
  244. package/src/components/story/detail/story-detail-toolbar/use-panel-frozen-groups.ts +97 -0
  245. package/src/components/story/detail/story-properties.tsx +9 -5
  246. package/src/components/story/display/__tests__/display-dropdown-helpers.test.ts +17 -1
  247. package/src/components/story/display/__tests__/display-dropdown.test.tsx +9 -0
  248. package/src/components/story/display/__tests__/flatten-story-groups.test.ts +42 -0
  249. package/src/components/story/display/__tests__/group-fields.test.ts +81 -1
  250. package/src/components/story/display/__tests__/group-resolver.test.ts +153 -1
  251. package/src/components/story/display/__tests__/use-collapsed-subgroups.test.ts +158 -0
  252. package/src/components/story/display/__tests__/use-display-options.test.ts +32 -3
  253. package/src/components/story/display/__tests__/use-fit-count.test.ts +198 -0
  254. package/src/components/story/display/__tests__/use-paged-story-groups.test.ts +77 -3
  255. package/src/components/story/display/constants.ts +23 -2
  256. package/src/components/story/display/display-dropdown.tsx +95 -397
  257. package/src/components/story/display/flatten-story-groups.ts +39 -0
  258. package/src/components/story/display/group-fields.ts +32 -1
  259. package/src/components/story/display/group-resolver.tsx +128 -5
  260. package/src/components/story/display/types.ts +23 -42
  261. package/src/components/story/display/use-collapsed-subgroups.ts +109 -0
  262. package/src/components/story/display/use-display-options.ts +21 -5
  263. package/src/components/story/display/use-fit-count.ts +124 -0
  264. package/src/components/story/display/use-paged-story-groups.ts +174 -31
  265. package/src/components/story/filters/field-configs.tsx +263 -0
  266. package/src/components/story/filters/index.tsx +14 -5
  267. package/src/components/story/inline-editors/__tests__/labels-editor.test.tsx +2 -0
  268. package/src/components/story/inline-editors/__tests__/priority-editor.test.tsx +15 -0
  269. package/src/components/story/inline-editors/assignee-editor.tsx +7 -5
  270. package/src/components/story/inline-editors/date-editor.tsx +7 -2
  271. package/src/components/story/inline-editors/inline-trigger.tsx +17 -4
  272. package/src/components/story/inline-editors/labels-editor.tsx +243 -35
  273. package/src/components/story/inline-editors/parent-story-editor.tsx +1 -1
  274. package/src/components/story/inline-editors/priority-editor.tsx +6 -4
  275. package/src/components/story/inline-editors/state-editor.tsx +6 -4
  276. package/src/components/story/inline-editors/story-select-dialog.tsx +56 -19
  277. package/src/components/story/peek-panel.tsx +234 -51
  278. package/src/components/story/relations/__tests__/design-select-dialog.test.tsx +131 -0
  279. package/src/components/story/relations/attachment-manage-dialog.tsx +1 -1
  280. package/src/components/story/relations/design-select-dialog.tsx +285 -0
  281. package/src/components/story/relations/knowledge-select-dialog.tsx +3 -3
  282. package/src/components/story/relations/link-manage-dialog.tsx +2 -2
  283. package/src/components/story/story-list-view.tsx +175 -17
  284. package/src/components/story/story-panel-header.tsx +18 -11
  285. package/src/components/story/story-toolbar.tsx +177 -126
  286. package/src/components/story/transfer-story-popover.tsx +1 -1
  287. package/src/components/story/types.ts +11 -2
  288. package/src/components/story/views/__tests__/calendar-view.test.tsx +262 -0
  289. package/src/components/story/views/__tests__/kanban-drop-subgroup.test.ts +68 -0
  290. package/src/components/story/views/__tests__/kanban-peek-sync.test.tsx +184 -0
  291. package/src/components/story/views/__tests__/kanban-view-membership-sync.test.tsx +179 -0
  292. package/src/components/story/views/__tests__/timeline-view.test.tsx +453 -0
  293. package/src/components/story/views/__tests__/view-icon.test.tsx +45 -0
  294. package/src/components/story/views/__tests__/view-types.test.ts +0 -8
  295. package/src/components/story/views/calendar/__tests__/mappers.test.ts +201 -0
  296. package/src/components/story/views/calendar/__tests__/slot-draft.test.ts +78 -0
  297. package/src/components/story/views/calendar/drag-blocked.ts +41 -0
  298. package/src/components/story/views/calendar/i18n.ts +91 -0
  299. package/src/components/story/views/calendar/mappers.ts +119 -0
  300. package/src/components/story/views/calendar/slot-draft.ts +48 -0
  301. package/src/components/story/views/calendar-view.tsx +490 -223
  302. package/src/components/story/views/create-view-dialog.tsx +6 -6
  303. package/src/components/story/views/kanban-view.tsx +618 -0
  304. package/src/components/story/views/{__tests__ → story-list/__tests__}/use-flat-rows.test.ts +1 -1
  305. package/src/components/story/views/story-list/batch-preview-dialog.tsx +3 -3
  306. package/src/components/story/views/story-list/batch-toolbar.tsx +17 -34
  307. package/src/components/story/views/story-list/group.tsx +4 -2
  308. package/src/components/story/views/story-list/index.tsx +142 -34
  309. package/src/components/story/views/story-list/row.tsx +4 -0
  310. package/src/components/story/views/{use-flat-rows.ts → story-list/use-flat-rows.ts} +2 -2
  311. package/src/components/story/views/timeline/__tests__/mappers.test.ts +742 -0
  312. package/src/components/story/views/timeline/__tests__/sentinel-geometry.test.ts +76 -0
  313. package/src/components/story/views/timeline/__tests__/use-date-commit.test.tsx +372 -0
  314. package/src/components/story/views/timeline/i18n.ts +96 -0
  315. package/src/components/story/views/timeline/mappers.ts +506 -0
  316. package/src/components/story/views/timeline/sentinel-geometry.ts +43 -0
  317. package/src/components/story/views/timeline/use-date-commit.ts +169 -0
  318. package/src/components/story/views/timeline-view.tsx +593 -222
  319. package/src/components/story/views/view-list-section.tsx +4 -4
  320. package/src/components/story/views/view-selector-button.tsx +9 -10
  321. package/src/components/story/views/view-switcher.tsx +7 -1
  322. package/src/components/story/views/view-types.ts +1 -5
  323. package/src/components/testcase/__tests__/batch-toolbar.test.tsx +245 -0
  324. package/src/components/testcase/__tests__/board-view.test.tsx +241 -0
  325. package/src/components/testcase/__tests__/case-row.test.tsx +24 -10
  326. package/src/components/testcase/__tests__/constants.test.ts +151 -0
  327. package/src/components/testcase/__tests__/create-dialog-scope.test.tsx +173 -0
  328. package/src/components/testcase/__tests__/detail-properties.test.tsx +19 -0
  329. package/src/components/testcase/__tests__/display-options.test.tsx +154 -0
  330. package/src/components/testcase/__tests__/export-entry.test.tsx +279 -0
  331. package/src/components/testcase/__tests__/filters.test.tsx +280 -0
  332. package/src/components/testcase/__tests__/list-view-scope.test.tsx +290 -0
  333. package/src/components/testcase/__tests__/list-view.test.tsx +552 -7
  334. package/src/components/testcase/__tests__/manual-move.test.ts +69 -0
  335. package/src/components/testcase/__tests__/peek-panel.test.tsx +89 -8
  336. package/src/components/testcase/__tests__/toolbar.test.tsx +302 -0
  337. package/src/components/testcase/__tests__/view-fallback.test.tsx +272 -0
  338. package/src/components/testcase/__tests__/view-management.test.tsx +179 -0
  339. package/src/components/testcase/ai/generate-dialog.tsx +1 -1
  340. package/src/components/testcase/constants.ts +83 -0
  341. package/src/components/testcase/create-testcase-dialog.tsx +76 -8
  342. package/src/components/testcase/detail/__tests__/select-cases-dialog.test.tsx +163 -0
  343. package/src/components/testcase/detail/case-detail-body.tsx +311 -30
  344. package/src/components/testcase/detail/select-cases-dialog.tsx +465 -0
  345. package/src/components/testcase/detail/story-link-editor.tsx +1 -1
  346. package/src/components/testcase/display/display-dropdown.tsx +147 -0
  347. package/src/components/testcase/display/field-configs.ts +80 -0
  348. package/src/components/testcase/display/use-testcase-display-options.ts +59 -0
  349. package/src/components/testcase/filters/field-configs.tsx +748 -0
  350. package/src/components/testcase/filters/index.tsx +35 -127
  351. package/src/components/testcase/inline-editors/assignee-editor.tsx +6 -6
  352. package/src/components/testcase/inline-editors/folder-editor.tsx +9 -9
  353. package/src/components/testcase/inline-editors/priority-editor.tsx +47 -31
  354. package/src/components/testcase/inline-editors/state-editor.tsx +14 -12
  355. package/src/components/testcase/inline-editors/type-editor.tsx +31 -20
  356. package/src/components/testcase/list/case-row.tsx +60 -2
  357. package/src/components/testcase/list/grouped-list.tsx +3 -2
  358. package/src/components/testcase/list-view.tsx +633 -137
  359. package/src/components/testcase/peek-panel.tsx +69 -11
  360. package/src/components/testcase/plans/plan-list.tsx +6 -1
  361. package/src/components/testcase/step-editor.tsx +34 -3
  362. package/src/components/testcase/testcase-toolbar.tsx +349 -94
  363. package/src/components/testcase/views/batch-toolbar.tsx +203 -0
  364. package/src/components/testcase/views/board-view.tsx +546 -0
  365. package/src/components/testcase/views/create-view-dialog.tsx +26 -14
  366. package/src/components/testcase/views/move.ts +56 -0
  367. package/src/components/testcase/views/view-selector-button.tsx +82 -30
  368. package/src/components/testcase/views/view-switcher.tsx +12 -5
  369. package/src/components/testcase/views/view-types.ts +50 -27
  370. package/src/components/tiptap/mindmap/lib/__tests__/shortcuts.test.ts +5 -4
  371. package/src/components/tiptap/mindmap/lib/shortcuts.ts +4 -3
  372. package/src/components/tiptap/node/doc-card-node-view.tsx +2 -1
  373. package/src/components/tiptap/notion-like/editor-extensions.ts +231 -0
  374. package/src/components/tiptap/notion-like/notion-like-editor.tsx +29 -156
  375. package/src/components/tiptap/ui/ai-menu/ai-menu.tsx +6 -1
  376. package/src/components/tiptap/ui/doc-reference-interaction/doc-reference-interaction.tsx +2 -1
  377. package/src/components/ui/checkbox.tsx +4 -4
  378. package/src/components/ui/popover.tsx +1 -1
  379. package/src/components/workspace/dialogs/edit-workspace-dialog.tsx +1 -1
  380. package/src/components/workspace/kb/workspace-kb-manage-dialog.tsx +1 -1
  381. package/src/components/workspace/kb/workspace-quick-doc-dialog.tsx +1 -1
  382. package/src/components/workspace/members/workspace-member-item.tsx +2 -2
  383. package/src/components/workspace/members/workspace-members-manager.tsx +2 -2
  384. package/src/config/sidebar-routes.tsx +11 -13
  385. package/src/design-engine/document/serialize-leafer-json.ts +2 -1
  386. package/src/design-engine/engine/__tests__/engine-split.test.ts +1 -1
  387. package/src/design-engine/engine/__tests__/engine.test.ts +20 -0
  388. package/src/design-engine/engine/app.ts +5 -0
  389. package/src/design-engine/engine/engine-base.ts +17 -0
  390. package/src/design-engine/engine/engine-doc.ts +32 -0
  391. package/src/hooks/__tests__/use-commands.test.tsx +17 -5
  392. package/src/hooks/use-commands.ts +4 -4
  393. package/src/hooks/use-toolbar-search.ts +7 -2
  394. package/src/lib/__tests__/datetime-local.test.ts +46 -0
  395. package/src/lib/__tests__/datetime.test.ts +7 -0
  396. package/src/lib/__tests__/layout-responsive.test.ts +123 -0
  397. package/src/lib/__tests__/sidebar-utils.test.ts +4 -4
  398. package/src/lib/ai/story-ai-stream.ts +1 -1
  399. package/src/lib/ai/use-selected-model.ts +15 -5
  400. package/src/lib/changelog/sdk-versions.ts +22 -22
  401. package/src/lib/collab/__tests__/bindings-fields.test.ts +496 -0
  402. package/src/lib/collab/__tests__/bindings-steps.test.ts +332 -0
  403. package/src/lib/collab/__tests__/presence-store.test.ts +414 -0
  404. package/src/lib/collab/__tests__/provider-handle-contract.test.ts +89 -0
  405. package/src/lib/collab/__tests__/provider.test.ts +504 -0
  406. package/src/lib/collab/__tests__/use-presence-list-refresh.test.tsx +309 -0
  407. package/src/lib/collab/__tests__/use-presence-report.test.ts +356 -0
  408. package/src/lib/collab/__tests__/use-share-presence.test.ts +555 -0
  409. package/src/lib/collab/__tests__/user-color.test.ts +53 -0
  410. package/src/lib/collab/bindings/__tests__/canvas-local.test.ts +583 -0
  411. package/src/lib/collab/bindings/__tests__/canvas-presence.test.ts +378 -0
  412. package/src/lib/collab/bindings/__tests__/canvas-remote.test.ts +454 -0
  413. package/src/lib/collab/bindings/__tests__/canvas-tree-perf.test.ts +134 -0
  414. package/src/lib/collab/bindings/canvas-local.ts +415 -0
  415. package/src/lib/collab/bindings/canvas-presence.ts +420 -0
  416. package/src/lib/collab/bindings/canvas-remote.ts +226 -0
  417. package/src/lib/collab/bindings/fields.ts +463 -0
  418. package/src/lib/collab/bindings/steps.ts +332 -0
  419. package/src/lib/collab/env.ts +33 -0
  420. package/src/lib/collab/module-map.ts +131 -0
  421. package/src/lib/collab/presence-store.ts +350 -0
  422. package/src/lib/collab/provider.ts +378 -0
  423. package/src/lib/collab/use-collab-doc.ts +100 -0
  424. package/src/lib/collab/use-presence-list-refresh.ts +142 -0
  425. package/src/lib/collab/use-presence-report.ts +176 -0
  426. package/src/lib/collab/use-self-profile-broadcast.ts +113 -0
  427. package/src/lib/collab/use-share-presence.ts +561 -0
  428. package/src/lib/collab/user-color.ts +40 -0
  429. package/src/lib/collections/index.ts +0 -1
  430. package/src/lib/collections/reference/bookmark-categories-collection.ts +1 -1
  431. package/src/lib/commands/__tests__/registry.test.ts +2 -5
  432. package/src/lib/commands/registry.ts +0 -3
  433. package/src/lib/commands/types.ts +1 -1
  434. package/src/lib/datetime.ts +20 -3
  435. package/src/lib/hooks/__tests__/use-synced-state.test.tsx +54 -0
  436. package/src/lib/hooks/use-story-panel-open.ts +134 -0
  437. package/src/lib/hooks/use-synced-state.ts +17 -0
  438. package/src/lib/hotkeys/__tests__/registry.test.ts +1 -1
  439. package/src/lib/hotkeys/registry.ts +0 -3
  440. package/src/lib/layout-responsive.ts +140 -0
  441. package/src/lib/pacer.ts +10 -1
  442. package/src/lib/peek-dock.ts +32 -0
  443. package/src/lib/plugin-runtime/__tests__/plugin-tool-schema-parity.test.ts +140 -0
  444. package/src/lib/plugin-runtime/plugin-client-tools.ts +28 -46
  445. package/src/lib/plugin-runtime/plugin-tool-defs.ts +152 -0
  446. package/src/lib/project-module-meta.ts +1 -8
  447. package/src/lib/rate-limit.ts +15 -6
  448. package/src/lib/sanitize.ts +122 -10
  449. package/src/lib/share-events.ts +20 -11
  450. package/src/lib/sidebar-utils.ts +1 -1
  451. package/src/lib/theme/__tests__/theme-mode.test.ts +96 -0
  452. package/src/lib/theme/theme-mode.ts +77 -0
  453. package/src/paraglide/messages/_index.js +5312 -1591
  454. package/src/paraglide/messages/en.js +1880 -685
  455. package/src/paraglide/messages/zh.js +1877 -682
  456. package/src/providers/index.tsx +4 -1
  457. package/src/providers/theme-mode-provider.tsx +135 -0
  458. package/src/routeTree.gen.ts +78 -101
  459. package/src/routes/$lang/docs/$/route.lazy.tsx +7 -2
  460. package/src/routes/-tests/chat-body-validation.test.ts +61 -3
  461. package/src/routes/__tests__/-edition-routing.test.tsx +12 -8
  462. package/src/routes/_nav/-components/nav-not-found.tsx +2 -2
  463. package/src/routes/_nav/bugs/$bugId/route.lazy.tsx +16 -2
  464. package/src/routes/_nav/bugs/$bugId/route.tsx +11 -2
  465. package/src/routes/_nav/dashboard/-components/avatar-group.tsx +2 -2
  466. package/src/routes/_nav/knowledge/$kbId/doc/$docId/route.lazy.tsx +31 -17
  467. package/src/routes/_nav/knowledge/$kbId/doc/$docId/route.tsx +12 -1
  468. package/src/routes/_nav/knowledge/$kbId/index.lazy.tsx +4 -2
  469. package/src/routes/_nav/knowledge/$kbId/route.lazy.tsx +7 -3
  470. package/src/routes/_nav/knowledge/bookmarks/$id.lazy.tsx +6 -6
  471. package/src/routes/_nav/knowledge/bookmarks/index.lazy.tsx +10 -10
  472. package/src/routes/_nav/knowledge/compliance/$id.lazy.tsx +9 -9
  473. package/src/routes/_nav/knowledge/compliance/edit/$id.lazy.tsx +1 -1
  474. package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +8 -8
  475. package/src/routes/_nav/knowledge/dictionaries/$id.lazy.tsx +4 -4
  476. package/src/routes/_nav/knowledge/dictionaries/index.lazy.tsx +6 -6
  477. package/src/routes/_nav/knowledge/index.lazy.tsx +1 -1
  478. package/src/routes/_nav/knowledge/laws/$id.lazy.tsx +9 -9
  479. package/src/routes/_nav/knowledge/laws/edit/$id.lazy.tsx +1 -1
  480. package/src/routes/_nav/knowledge/laws/index.lazy.tsx +11 -11
  481. package/src/routes/_nav/knowledge/summary/index.lazy.tsx +1 -1
  482. package/src/routes/_nav/projects/$projectId/index.lazy.tsx +14 -6
  483. package/src/routes/_nav/route.lazy.tsx +11 -6
  484. package/src/routes/_nav/settings/account/route.tsx +3 -2
  485. package/src/routes/_nav/stories/$storyId/route.lazy.tsx +52 -8
  486. package/src/routes/_nav/stories/$storyId/route.tsx +11 -0
  487. package/src/routes/_nav/stories/-components/story-detail-skeleton.tsx +1 -1
  488. package/src/{components/tags/pages/tag-detail-page.tsx → routes/_nav/tasks/$taskId/route.lazy.tsx} +20 -10
  489. package/src/routes/_nav/tasks/$taskId/route.tsx +43 -0
  490. package/src/routes/_nav/tasks/-components/ai-task-detail.tsx +307 -0
  491. package/src/routes/_nav/tasks/-components/ai-task-list.tsx +217 -0
  492. package/src/routes/_nav/tasks/-components/ai-task-status-badge.tsx +46 -0
  493. package/src/routes/_nav/tasks/-components/assigned-list.tsx +157 -0
  494. package/src/routes/_nav/tasks/-components/create-ai-task-dialog.tsx +228 -0
  495. package/src/routes/_nav/tasks/-components/source-ai-exec-dialog.tsx +175 -0
  496. package/src/routes/_nav/tasks/-components/task-constants.ts +115 -0
  497. package/src/routes/_nav/tasks/-components/use-task-filter.ts +64 -0
  498. package/src/routes/_nav/tasks/-tests/create-ai-task-dialog.test.tsx +175 -0
  499. package/src/routes/_nav/tasks/-tests/task-filter.test.ts +134 -0
  500. package/src/routes/_nav/tasks/-tests/task-styles.test.tsx +119 -0
  501. package/src/routes/_nav/tasks/index.lazy.tsx +114 -0
  502. package/src/routes/_nav/tasks/index.tsx +47 -0
  503. package/src/routes/_nav/{tags → tasks}/route.tsx +3 -3
  504. package/src/routes/_nav/test-cases/$caseId/route.lazy.tsx +3 -1
  505. package/src/routes/_nav/test-cases/$caseId/route.tsx +11 -2
  506. package/src/routes/api/chat.ts +23 -19
  507. package/src/routes/api/design/prototype/route.ts +2 -4
  508. package/src/routes/{_nav/acceptance-criteria/route.tsx → api/internal/collab-reindex.ts} +11 -8
  509. package/src/routes/api/knowledge/sync-github.ts +1 -1
  510. package/src/routes/design-files/$projectId/$artboardId/$filename.ts +9 -0
  511. package/src/routes/invite/$token/route.lazy.tsx +2 -2
  512. package/src/routes/share/$versionId/route.lazy.tsx +8 -0
  513. package/src/routes/share/compliance/$id/route.lazy.tsx +2 -2
  514. package/src/routes/share/compliance/$id/route.tsx +1 -1
  515. package/src/routes/share/law/$id/route.lazy.tsx +2 -2
  516. package/src/routes/share/law/$id/route.tsx +1 -1
  517. package/src/server/__tests__/bug-groups-two-level.test.ts +263 -0
  518. package/src/server/__tests__/builtin-project.test.ts +45 -1
  519. package/src/server/__tests__/csv-export.test.ts +319 -0
  520. package/src/server/__tests__/share-version.test.ts +7 -2
  521. package/src/server/__tests__/task-assigned-query.test.ts +232 -0
  522. package/src/server/__tests__/task-context-builder.test.ts +175 -0
  523. package/src/server/__tests__/task-executor.test.ts +349 -0
  524. package/src/server/__tests__/task-priority-map.test.ts +41 -0
  525. package/src/server/__tests__/task-serverfn-crud.test.ts +639 -0
  526. package/src/server/__tests__/task-serverfn-queries.test.ts +403 -0
  527. package/src/server/__tests__/testcase-board.test.ts +301 -0
  528. package/src/server/__tests__/testcase-bulk.test.ts +155 -0
  529. package/src/server/__tests__/testcase-list-scope.test.ts +215 -0
  530. package/src/server/ai-gateway/__tests__/chat-handler-prompt-key.test.ts +106 -0
  531. package/src/server/ai-gateway/__tests__/chat-stream-builder-sandbox.test.ts +9 -3
  532. package/src/server/ai-gateway/__tests__/conformance.test.ts +4 -3
  533. package/src/server/ai-gateway/adapter.ts +4 -2
  534. package/src/server/ai-gateway/chat-handler.ts +13 -5
  535. package/src/server/ai-gateway/chat-stream-builder.ts +12 -7
  536. package/src/server/ai-gateway/sandbox/__tests__/harness-sandbox.test.ts +88 -0
  537. package/src/server/ai-gateway/sandbox/harness-sandbox.ts +1 -1
  538. package/src/server/ai-gateway/story-handler.ts +7 -2
  539. package/src/server/ai-gateway/tools/plugin-lifecycle-defs.ts +8 -99
  540. package/src/server/ai-gateway/tools/plugin-tools.ts +6 -10
  541. package/src/server/ai-task/assigned-query.ts +167 -0
  542. package/src/server/ai-task/context-builder.ts +182 -0
  543. package/src/server/ai-task/executor.ts +296 -0
  544. package/src/server/ai-task/priority-map.ts +37 -0
  545. package/src/server/ai-task/types.ts +15 -0
  546. package/src/server/bug-report/__tests__/report-handler.test.ts +55 -4
  547. package/src/server/bug-report/report-handler.ts +54 -8
  548. package/src/server/builtin-project.ts +24 -0
  549. package/src/server/cloudflare/prisma-wasm-base64-stub.ts +5 -0
  550. package/src/server/collab/__tests__/collab-permissions.test.ts +412 -0
  551. package/src/server/collab/__tests__/knowledge-additions.test.ts +121 -0
  552. package/src/server/collab/__tests__/reindex-endpoint.test.ts +168 -0
  553. package/src/server/collab/__tests__/resolve.test.ts +249 -0
  554. package/src/server/collab/__tests__/touch-snapshot.test.ts +58 -0
  555. package/src/server/collab/__tests__/write-guard-sdk.test.ts +501 -0
  556. package/src/server/collab/__tests__/write-guard.test.ts +373 -0
  557. package/src/server/collab/knowledge-additions.ts +37 -0
  558. package/src/server/collab/reindex-endpoint.ts +75 -0
  559. package/src/server/collab/resolve.ts +122 -0
  560. package/src/server/collab/seed.ts +408 -0
  561. package/src/server/collab/ticket-utils.ts +107 -0
  562. package/src/server/collab/touch-snapshot.ts +55 -0
  563. package/src/server/collab/write-guard.ts +258 -0
  564. package/src/server/collab-permissions.ts +248 -0
  565. package/src/server/csv.ts +61 -0
  566. package/src/server/{knowledge-public → knowledge/public}/github-sync.ts +12 -2
  567. package/src/server/{knowledge-public → knowledge/public}/share-merge.ts +31 -8
  568. package/src/server/lib/__tests__/sequence-number-retry.test.ts +160 -0
  569. package/src/server/lib/rate-limit.ts +13 -2
  570. package/src/server/lib/sequence-number-retry.ts +85 -0
  571. package/src/server/mcp/__tests__/tools.test.ts +10 -0
  572. package/src/server/mcp/tools.ts +26 -2
  573. package/src/server/middlewares/auth-rate-limit.ts +18 -9
  574. package/src/server/notifications/event-bus.ts +3 -0
  575. package/src/server/serverFns/__tests__/collab-snapshot.test.ts +665 -0
  576. package/src/server/serverFns/__tests__/collab.test.ts +459 -0
  577. package/src/server/serverFns/__tests__/knowledge-delete.test.ts +167 -0
  578. package/src/server/serverFns/__tests__/ownership-merge.test.ts +34 -2
  579. package/src/server/serverFns/__tests__/read-write-merge.test.ts +2 -2
  580. package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +12 -2
  581. package/src/server/serverFns/__tests__/story-crud.test.ts +51 -11
  582. package/src/server/serverFns/__tests__/story-tree.test.ts +66 -34
  583. package/src/server/serverFns/__tests__/story-types.test.ts +14 -9
  584. package/src/server/serverFns/__tests__/update-bug-guard.test.ts +325 -0
  585. package/src/server/serverFns/__tests__/update-document-guard.test.ts +285 -0
  586. package/src/server/serverFns/__tests__/update-page-guard.test.ts +276 -0
  587. package/src/server/serverFns/__tests__/update-story-guard.test.ts +436 -0
  588. package/src/server/serverFns/__tests__/update-testcase-guard.test.ts +293 -0
  589. package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +25 -0
  590. package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +315 -15
  591. package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +36 -18
  592. package/src/server/serverFns/bug/__tests__/bug-list-query.test.ts +21 -0
  593. package/src/server/serverFns/bug/__tests__/bug-reactions.test.ts +335 -0
  594. package/src/server/serverFns/bug/__tests__/bug-reorder.test.ts +165 -0
  595. package/src/server/serverFns/bug/__tests__/bug-story.test.ts +4 -1
  596. package/src/server/serverFns/bug/bug-comments.ts +5 -0
  597. package/src/server/serverFns/bug/bug-crud.ts +321 -72
  598. package/src/server/serverFns/bug/bug-display-options.ts +11 -0
  599. package/src/server/serverFns/bug/bug-export.ts +137 -0
  600. package/src/server/serverFns/bug/bug-group-query.ts +16 -5
  601. package/src/server/serverFns/bug/bug-groups.ts +39 -15
  602. package/src/server/serverFns/bug/bug-list-query.ts +8 -1
  603. package/src/server/serverFns/bug/bug-reactions.ts +123 -0
  604. package/src/server/serverFns/bug/bug-reorder.ts +64 -0
  605. package/src/server/serverFns/bug/bug-story-bulk.ts +280 -0
  606. package/src/server/serverFns/bug/bug-story.ts +47 -39
  607. package/src/server/serverFns/bug/index.ts +6 -0
  608. package/src/server/serverFns/collab-presence.ts +119 -0
  609. package/src/server/serverFns/collab-snapshot.ts +201 -0
  610. package/src/server/serverFns/collab.ts +153 -0
  611. package/src/server/serverFns/design/__tests__/designs.test.ts +0 -2
  612. package/src/server/serverFns/design/designs.ts +0 -64
  613. package/src/server/serverFns/design/detail.ts +36 -2
  614. package/src/server/serverFns/design/pages.ts +73 -10
  615. package/src/server/serverFns/iteration/iteration-crud.ts +25 -1
  616. package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/create.ts +3 -3
  617. package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/detail.ts +2 -2
  618. package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/list.ts +3 -2
  619. package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/refresh-favicon.ts +1 -1
  620. package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/share.ts +1 -1
  621. package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/sync-github.ts +1 -1
  622. package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/update.ts +5 -4
  623. package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/create.ts +1 -1
  624. package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/detail.ts +2 -2
  625. package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/list.ts +3 -2
  626. package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/share.ts +1 -1
  627. package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/update.ts +3 -2
  628. package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/create.ts +1 -1
  629. package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/detail.ts +2 -2
  630. package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/list.ts +3 -2
  631. package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/share.ts +1 -1
  632. package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/update.ts +3 -2
  633. package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/create.ts +1 -1
  634. package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/detail.ts +2 -2
  635. package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/list.ts +3 -2
  636. package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/share.ts +1 -1
  637. package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/update.ts +3 -2
  638. package/src/server/serverFns/knowledge.ts +135 -19
  639. package/src/server/serverFns/project/module-overview.ts +1 -2
  640. package/src/server/serverFns/share/version.ts +7 -1
  641. package/src/server/serverFns/story/__tests__/bulk.test.ts +6 -0
  642. package/src/server/serverFns/story/__tests__/create-story-internal.test.ts +112 -3
  643. package/src/server/serverFns/story/__tests__/story-assoc.test.ts +77 -6
  644. package/src/server/serverFns/story/__tests__/story-crud-date-key.test.ts +187 -0
  645. package/src/server/serverFns/story/__tests__/story-gantt-deps.test.ts +163 -0
  646. package/src/server/serverFns/story/__tests__/story-groups.test.ts +4 -0
  647. package/src/server/serverFns/story/__tests__/story-list-query.test.ts +20 -0
  648. package/src/server/serverFns/story/__tests__/story-search.test.ts +5 -0
  649. package/src/server/serverFns/story/__tests__/story-transfer.test.ts +64 -1
  650. package/src/server/serverFns/story/index.ts +2 -2
  651. package/src/server/serverFns/story/story-assoc.ts +44 -14
  652. package/src/server/serverFns/story/story-crud/archive.ts +52 -28
  653. package/src/server/serverFns/story/story-crud/bulk.ts +14 -4
  654. package/src/server/serverFns/story/story-crud/create-story-internal.ts +39 -21
  655. package/src/server/serverFns/story/story-crud.ts +283 -116
  656. package/src/server/serverFns/story/story-dependencies.ts +31 -0
  657. package/src/server/serverFns/story/story-groups.ts +15 -11
  658. package/src/server/serverFns/story/story-list-query.ts +16 -4
  659. package/src/server/serverFns/story/story-search.ts +9 -6
  660. package/src/server/serverFns/story/story-transfer.ts +99 -42
  661. package/src/server/serverFns/story/story-tree.ts +70 -48
  662. package/src/server/serverFns/story-display-options.ts +21 -10
  663. package/src/server/serverFns/story-view.ts +2 -2
  664. package/src/server/serverFns/tasks/task-crud.ts +264 -0
  665. package/src/server/serverFns/tasks/task-queries.ts +172 -0
  666. package/src/server/serverFns/testcase/__tests__/generation.test.ts +3 -0
  667. package/src/server/serverFns/testcase/__tests__/stats.test.ts +18 -4
  668. package/src/server/serverFns/testcase/__tests__/testcase-board.test.ts +113 -0
  669. package/src/server/serverFns/testcase/__tests__/testcase-crud.test.ts +62 -14
  670. package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +32 -10
  671. package/src/server/serverFns/testcase/bulk.ts +195 -0
  672. package/src/server/serverFns/testcase/generation.ts +10 -0
  673. package/src/server/serverFns/testcase/stats.ts +22 -8
  674. package/src/server/serverFns/testcase/testcase-board.ts +198 -0
  675. package/src/server/serverFns/testcase/testcase-crud.ts +165 -33
  676. package/src/server/serverFns/testcase/testcase-display-options.ts +166 -0
  677. package/src/server/serverFns/testcase/testcase-export.ts +125 -0
  678. package/src/server/serverFns/testcase/testcase-filter-views.ts +36 -9
  679. package/src/server/serverFns/testcase/testcase-groups.ts +27 -23
  680. package/src/server/serverFns/testcase/testcase-reorder.ts +66 -0
  681. package/src/server/story/__tests__/date-key.test.ts +57 -0
  682. package/src/server/story/date-key.ts +36 -0
  683. package/src/server/story/types.ts +3 -7
  684. package/src/server/testcase/__tests__/list-query.test.ts +269 -0
  685. package/src/server/testcase/list-query.ts +145 -0
  686. package/src/server-entry.ts +32 -0
  687. package/src/shared/bug/__tests__/constants.test.ts +44 -0
  688. package/src/shared/bug/constants.ts +37 -1
  689. package/src/start.ts +7 -2
  690. package/src/styles/globals.css +24 -3
  691. package/src/types/project.ts +2 -3
  692. package/vite.shared.ts +110 -3
  693. package/src/__tests__/tag-badge.test.tsx +0 -41
  694. package/src/components/bug/__tests__/bug-list-table.test.tsx +0 -289
  695. package/src/components/bug/bug-list-table.tsx +0 -187
  696. package/src/components/bug/columns.tsx +0 -193
  697. package/src/components/bug/filters/add-filter-dropdown.tsx +0 -70
  698. package/src/components/bug/filters/applied-filters.tsx +0 -214
  699. package/src/components/bug/filters/filter-condition.tsx +0 -151
  700. package/src/components/bug/filters/filter-row.tsx +0 -104
  701. package/src/components/bug/filters/filter-toggle.tsx +0 -61
  702. package/src/components/bug/filters/filter-types.ts +0 -146
  703. package/src/components/story/detail/story-detail-toolbar.tsx +0 -382
  704. package/src/components/story/filters/add-filter-dropdown.tsx +0 -70
  705. package/src/components/story/filters/applied-filters.tsx +0 -225
  706. package/src/components/story/filters/filter-condition.tsx +0 -152
  707. package/src/components/story/filters/filter-row.tsx +0 -116
  708. package/src/components/story/filters/filter-toggle.tsx +0 -60
  709. package/src/components/story/filters/filter-types.ts +0 -120
  710. package/src/components/story/filters/filter-value-input.tsx +0 -182
  711. package/src/components/story/views/kanban-board.tsx +0 -464
  712. package/src/components/story/views/kanban-card.tsx +0 -100
  713. package/src/components/tags/__tests__/tags.test.ts +0 -388
  714. package/src/components/tags/nav-tag.tsx +0 -121
  715. package/src/components/tags/pages/tag-settings-page.tsx +0 -32
  716. package/src/components/tags/pages/tags-index-page.tsx +0 -32
  717. package/src/components/tags/tag-badge.tsx +0 -56
  718. package/src/components/tags/tag-color-classes.ts +0 -61
  719. package/src/components/tags/tag-color-picker.tsx +0 -54
  720. package/src/components/tags/tag-components-index.ts +0 -23
  721. package/src/components/tags/tag-manager.tsx +0 -208
  722. package/src/components/tags/tag-selector.tsx +0 -164
  723. package/src/components/testcase/__tests__/table-view.test.tsx +0 -328
  724. package/src/components/testcase/plans/add-to-plan-bar.tsx +0 -64
  725. package/src/components/testcase/table/columns.tsx +0 -170
  726. package/src/components/testcase/table/table-view.tsx +0 -152
  727. package/src/lib/tag-colors.ts +0 -35
  728. package/src/routes/_nav/tags/$id/route.lazy.tsx +0 -23
  729. package/src/routes/_nav/tags/$id/route.tsx +0 -20
  730. package/src/routes/_nav/tags/index.lazy.tsx +0 -23
  731. package/src/routes/_nav/tags/index.tsx +0 -20
  732. package/src/routes/_nav/tags/settings/route.lazy.tsx +0 -23
  733. package/src/routes/_nav/tags/settings/route.tsx +0 -23
  734. package/src/server/serverFns/tags.ts +0 -138
  735. /package/src/components/{knowledge-public → knowledge/public}/bookmark-card.tsx +0 -0
  736. /package/src/components/{knowledge-public → knowledge/public}/category-icon.tsx +0 -0
  737. /package/src/components/{knowledge-public → knowledge/public}/chip-utils.ts +0 -0
  738. /package/src/components/{knowledge-public → knowledge/public}/knowledge-card-grid.tsx +0 -0
  739. /package/src/components/{knowledge-public → knowledge/public}/knowledge-filters.tsx +0 -0
  740. /package/src/components/{knowledge-public → knowledge/public}/knowledge-search-bar.tsx +0 -0
  741. /package/src/components/{knowledge-public → knowledge/public}/markdown-editor.tsx +0 -0
  742. /package/src/components/{knowledge-public → knowledge/public}/markdown-view.tsx +0 -0
  743. /package/src/components/{knowledge-public → knowledge/public}/tag-filter.tsx +0 -0
  744. /package/src/components/{story/detail → shared-kit/comments}/emoji-picker.tsx +0 -0
  745. /package/src/components/story/{schemas/story-create.ts → story-create-schema.ts} +0 -0
  746. /package/src/server/{knowledge-public → knowledge/public}/canonical-id.ts +0 -0
  747. /package/src/server/{knowledge-public → knowledge/public}/favicon.ts +0 -0
  748. /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/categories.ts +0 -0
  749. /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/delete.ts +0 -0
  750. /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/favorite.ts +0 -0
  751. /package/src/server/serverFns/{knowledge-public → knowledge/public}/bookmarks/unshare.ts +0 -0
  752. /package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/delete.ts +0 -0
  753. /package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/public.ts +0 -0
  754. /package/src/server/serverFns/{knowledge-public → knowledge/public}/compliance/unshare.ts +0 -0
  755. /package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/delete.ts +0 -0
  756. /package/src/server/serverFns/{knowledge-public → knowledge/public}/dictionary/unshare.ts +0 -0
  757. /package/src/server/serverFns/{knowledge-public → knowledge/public}/entity-tags.ts +0 -0
  758. /package/src/server/serverFns/{knowledge-public → knowledge/public}/favorite.ts +0 -0
  759. /package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/delete.ts +0 -0
  760. /package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/public.ts +0 -0
  761. /package/src/server/serverFns/{knowledge-public → knowledge/public}/legal/unshare.ts +0 -0
@@ -20,37 +20,52 @@
20
20
  // signature 防过期(过期响应丢弃),禁止 react-query。
21
21
  // 任务 8:分组虚拟列表(GroupedList)+ 行内编辑乐观 patch(三档)+ 无限滚动 +
22
22
  // ↑↓ 键选中(selectedId 由本组件持有,任务 10 peek 消费)。
23
- // 任务 9:视图切换(list↔table 保持筛选,表格走 TableView)+ 保存筛选视图
24
- // (内置 __default__ 默认视图不落库;applyView 应用 filters+displayOptions;
25
- // 手动改动筛选/搜索/模块树/分组即回落默认视图;保存时 filters 键集收敛在前端)。
23
+ // 任务 9:视图切换 + 保存筛选视图(内置 __default__ 默认视图不落库;applyView
24
+ // 应用 filters+displayOptions;手动改动筛选/搜索/模块树/分组即回落默认视图;
25
+ // 保存时 filters 键集收敛在前端)。PR-7b 任务 27:视图定版 list|board,表格视图
26
+ // 移除(存量 view:"table" 反序列化回落 list)。
26
27
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
27
28
  import { PanelLeftClose, PanelLeftOpen, RotateCcw } from "lucide-react";
28
29
  import { Button } from "@/components/ui/button";
30
+ import { WavePhysicsLoader } from "@/components/shared-kit/loading/wave-physics-loader";
31
+ import { useDeferredFlag } from "@/components/shared-kit/loading/use-deferred-flag";
29
32
  import { FolderTree, type FolderNodeDto, type FolderSelection } from "./folder-tree";
30
33
  import { TestCaseToolbar } from "./testcase-toolbar";
31
34
  import {
32
- hasActiveFilters,
33
- priorityLabel,
34
- serializeTestCaseFilters,
35
- stateLabel,
36
- typeLabel,
37
- type TestCaseFilterState,
35
+ filterConditionSummary,
36
+ serializeFilters,
37
+ type TestcaseFilterCondition,
38
38
  } from "./filters";
39
39
  import { listTestCasesFn } from "@/server/serverFns/testcase/testcase-crud";
40
40
  import { listGroupsFn } from "@/server/serverFns/testcase/testcase-groups";
41
41
  import { listFoldersFn } from "@/server/serverFns/testcase/testcase-folders";
42
+ import { listTestCaseBoardColumnsFn } from "@/server/serverFns/testcase/testcase-board";
43
+ import { reorderTestCasesFn } from "@/server/serverFns/testcase/testcase-reorder";
44
+ import {
45
+ TEST_CASE_PRIORITY_VALUES,
46
+ TEST_CASE_STATE_VALUES,
47
+ } from "@/server/serverFns/testcase/constants";
48
+ import { listProjectsBriefFn } from "@/server/serverFns/project";
42
49
  import {
43
50
  createFilterViewFn,
51
+ deleteFilterViewFn,
44
52
  listFilterViewsFn,
53
+ updateFilterViewFn,
45
54
  } from "@/server/serverFns/testcase/testcase-filter-views";
46
55
  import type { TestCaseSummary } from "@/server/serverFns/testcase/types";
47
56
  import type { UserBrief } from "@/components/story/types";
48
- import { GroupedList, type TestCaseGroupBy, type TestCaseGroupInfo } from "./list/grouped-list";
49
- import { TableView } from "./table/table-view";
57
+ import { GroupedList, groupKeyOf, type TestCaseGroupInfo } from "./list/grouped-list";
58
+ import { computeMove, canMove } from "./views/move";
59
+ import { TestCaseBoardView } from "./views/board-view";
60
+ import type { ReorderEntry } from "@/components/shared-kit/list-kit/kanban/drop-commit";
61
+ import { manualOrderSwitchPatch } from "@/components/shared-kit/list-kit/kanban/manual-order";
62
+ import { toast } from "@/components/ui/toast";
50
63
  import { TestCasePeekPanel, type PeekMode } from "./peek-panel";
64
+ import { useTestcaseDisplayOptions } from "./display/use-testcase-display-options";
65
+ import type { TestCaseGroupKey } from "./display/field-configs";
51
66
  import { CreateViewDialog, type CreateViewDialogPayload } from "./views/create-view-dialog";
52
67
  import { CreateTestCaseDialog } from "./create-testcase-dialog";
53
- import { AddToPlanBar } from "./plans/add-to-plan-bar";
68
+ import { TestCaseBatchToolbar } from "./views/batch-toolbar";
54
69
  import { AddToPlanDialog } from "./plans/add-to-plan-dialog";
55
70
  import {
56
71
  buildDefaultView,
@@ -62,6 +77,8 @@ import {
62
77
  type TestCaseViewType,
63
78
  } from "./views/view-types";
64
79
  import { BUILTIN_INDEPENDENT_DISPLAY_ID } from "@/lib/builtin-project";
80
+ import { usePresenceListRefresh } from "@/lib/collab/use-presence-list-refresh";
81
+ import { toastError } from "@/lib/toast-error";
65
82
  import { m } from "@/paraglide/messages.js";
66
83
 
67
84
  interface TestCaseListViewProps {
@@ -75,30 +92,47 @@ interface CaseListResult {
75
92
  nextCursor: string | null;
76
93
  }
77
94
 
78
- export function TestCaseListView({ projects, initialFolders, initialUsers }: TestCaseListViewProps) {
79
- // P1 作用域:用户「独立需求」内置项目(displayId=STORY,与 stories/bugs 的
80
- // serverFn 静默回退同一项目)。项目选择器不在任务 7 范围。
81
- // 回退口径对齐说明(任务 7 审查遗留):服务端 resolveStoryScopeProjectId 对
82
- // 未传/无效/非成员 projectId 静默回退内置项目;客户端 projects 为空时 projectId
83
- // 为 undefined 直接跳过拉取(fetchCases 的 !projectId 守卫),不会发出无效请求,
84
- // 因此保持「取内置项目 → 首个项目」的确定性回退即可,与前者互补不冲突。
85
- const projectId = useMemo(
95
+ export function TestCaseListView({ projects: initialProjects, initialFolders, initialUsers }: TestCaseListViewProps) {
96
+ // P1 作用域(任务 17 改造):项目选择器落在工具栏 Popover(story-toolbar 同款),
97
+ // scopeProjectId=undefined 表示「全部项目」聚合 scope(后端任务 16 已支持缺省
98
+ // 聚合读路径)。默认仍落内置「独立需求」项目(首屏行为不变);回退口径对齐
99
+ // 说明(任务 7 审查遗留):服务端对未传/无效/非成员 projectId 静默回退内置
100
+ // 项目,客户端仅决定「聚合 or 单项目」,双端互补不冲突。
101
+ // projects 改本地状态(story-list-view 同款):Popover 内新建项目后即时刷新花名册。
102
+ const [projects, setProjects] = useState(initialProjects);
103
+ const defaultProjectId = useMemo(
86
104
  () => projects.find((p) => p.displayId === BUILTIN_INDEPENDENT_DISPLAY_ID)?.id ?? projects[0]?.id,
87
105
  [projects],
88
106
  );
107
+ const [projectId, setProjectId] = useState<string | undefined>(defaultProjectId);
89
108
 
90
109
  const [folders, setFolders] = useState(initialFolders);
91
110
  const [users] = useState(initialUsers);
92
111
  const [cases, setCases] = useState<CaseListResult>({ items: [], nextCursor: null });
93
112
  const [selection, setSelection] = useState<FolderSelection>({ kind: "all" });
94
113
  const [searchText, setSearchText] = useState<string | undefined>(undefined);
95
- const [filters, setFilters] = useState<TestCaseFilterState>({});
114
+ // 筛选条件(任务 21):FilterCondition 三段式取代旧三 Select 快速筛选,
115
+ // serializeFilters 序列化后经 requestParams.filters 传 serverFn
116
+ const [filterConditions, setFilterConditions] = useState<TestcaseFilterCondition[]>([]);
96
117
  const [view, setView] = useState<TestCaseViewType>("list");
97
- const [groupBy, setGroupBy] = useState<TestCaseGroupBy>("none");
118
+ // 显示选项(任务 20):groupBy 自 hook 来(per-user 持久化偏好,首屏异步
119
+ // 拉取覆盖默认 none),变更经 updateFilters 落库;orderBy/orderDir/properties/
120
+ // pageSize 已入域,消费接线随任务 21/25 列表排序入参跟进
121
+ const {
122
+ filters: displayOptions,
123
+ updateFilters: updateDisplayOptions,
124
+ } = useTestcaseDisplayOptions();
125
+ const groupBy = displayOptions.groupBy;
98
126
  // ── 保存筛选视图(任务 9):内置默认视图在首位;手动改筛选回落默认视图 ──
127
+ // 任务 22:对话框状态带 mode + 被编辑视图(编辑/删除入口接线,story-list-view
128
+ // 的 dialogState 同构)
99
129
  const [views, setViews] = useState<TestCaseSavedView[]>([]);
100
130
  const [activeViewId, setActiveViewId] = useState<string>(DEFAULT_VIEW_ID);
101
- const [createViewOpen, setCreateViewOpen] = useState(false);
131
+ const [viewDialog, setViewDialog] = useState<{
132
+ mode: "create" | "edit";
133
+ view: TestCaseSavedView | null;
134
+ }>({ mode: "create", view: null });
135
+ const [viewDialogOpen, setViewDialogOpen] = useState(false);
102
136
  const [groups, setGroups] = useState<TestCaseGroupInfo[]>([]);
103
137
  const [selectedId, setSelectedId] = useState<string | null>(null);
104
138
  // ── Peek 侧滑预览(任务 10):peekId 与选中分离持有;↑↓ 移动选中时若面板
@@ -123,19 +157,23 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
123
157
 
124
158
  const allViews = useMemo(() => [buildDefaultView(), ...views], [views]);
125
159
 
126
- // applyView:仅置状态——查询参数变化由 signature 机制自动触发重拉(A7 口径)
160
+ // applyView:仅置状态——查询参数变化由 signature 机制自动触发重拉(A7 口径)。
161
+ // scope=all(projectId 缺省)下 folder 条件不合法,应用时剔除(与分组 clamp 同口径)
127
162
  const applyView = useCallback((target: TestCaseSavedView) => {
128
163
  setActiveViewId(target.id);
129
- const { selection: nextSelection, searchText: nextSearch, filters: nextFilters } =
164
+ const { selection: nextSelection, searchText: nextSearch, conditions: nextConditions } =
130
165
  viewFiltersToState(target.filters);
131
166
  setSelection(nextSelection);
132
167
  setSearchText(nextSearch);
133
- setFilters(nextFilters);
134
- setGroupBy(target.displayOptions.groupBy ?? "none");
168
+ setFilterConditions(
169
+ projectId ? nextConditions : nextConditions.filter((c) => c.property !== "folder"),
170
+ );
171
+ updateDisplayOptions({ groupBy: target.displayOptions.groupBy ?? "none" });
135
172
  setView(target.displayOptions.view ?? "list");
136
- }, []);
173
+ }, [updateDisplayOptions, projectId]);
137
174
 
138
175
  // ── signature 防过期(A7):任一查询参数变化生成新签名,过期响应丢弃 ──
176
+ // 任务 29:排序入参入签名(manual 批量移动依赖列表按 boardOrder 重拉)
139
177
  const signature = useMemo(
140
178
  () =>
141
179
  JSON.stringify([
@@ -143,11 +181,11 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
143
181
  selection.kind,
144
182
  selection.kind === "folder" ? selection.id : "",
145
183
  searchText ?? "",
146
- filters.state ?? "",
147
- filters.type ?? "",
148
- filters.priority ?? "",
184
+ serializeFilters(filterConditions) ?? "",
185
+ displayOptions.orderBy,
186
+ displayOptions.orderDir,
149
187
  ]),
150
- [projectId, selection, searchText, filters],
188
+ [projectId, selection, searchText, filterConditions, displayOptions.orderBy, displayOptions.orderDir],
151
189
  );
152
190
  const signatureRef = useRef(signature);
153
191
  signatureRef.current = signature;
@@ -161,7 +199,7 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
161
199
 
162
200
  const requestParams = useCallback(
163
201
  (cursor?: string) => ({
164
- projectId: projectId as string,
202
+ projectId,
165
203
  folderId:
166
204
  selection.kind === "folder"
167
205
  ? selection.id
@@ -169,15 +207,27 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
169
207
  ? null
170
208
  : undefined,
171
209
  searchText,
172
- ...serializeTestCaseFilters(filters),
210
+ // FilterCondition 三段式(任务 21):序列化串由 serverFn buildListWhere
211
+ // 翻译为 where(story/bug 同构 filters 传参形态)
212
+ filters: serializeFilters(filterConditions),
213
+ // 排序入参(任务 29 补接线:listSchema 任务 25 已收 orderBy/orderDir,
214
+ // 列表此前漏传导致显示选项排序只对面板生效;manual = boardOrder,
215
+ // 批量移动后重拉即按新序渲染)
216
+ orderBy: displayOptions.orderBy,
217
+ orderDir: displayOptions.orderDir,
218
+ // 每页数量接线(PR-8 审查 B):DisplayDropdown pageSize 此前仅看板
219
+ // 生效(列级 take),列表恒吃服务端 default 50——补 take,封顶 200
220
+ // = listSchema max 不放宽
221
+ take: Math.min(displayOptions.pageSize, 200),
173
222
  ...(cursor ? { cursor } : {}),
174
223
  }),
175
- [projectId, selection, searchText, filters],
224
+ [projectId, selection, searchText, filterConditions, displayOptions.orderBy, displayOptions.orderDir, displayOptions.pageSize],
176
225
  );
177
226
 
178
227
  const fetchCases = useCallback(
179
228
  async (cursor?: string) => {
180
- if (!projectId) return;
229
+ // projectId=undefined(全部项目聚合 scope)照常拉取(任务 16 读路径已支持
230
+ // 缺省聚合;服务端对无效值仍有静默回退兜底)
181
231
  if (cursor && loadingRef.current) return;
182
232
  const requestSignature = signatureRef.current;
183
233
  if (cursor) loadingRef.current = true;
@@ -225,17 +275,16 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
225
275
  selection.kind,
226
276
  selection.kind === "folder" ? selection.id : "",
227
277
  searchText ?? "",
228
- filters.state ?? "",
229
- filters.type ?? "",
230
- filters.priority ?? "",
278
+ serializeFilters(filterConditions) ?? "",
231
279
  ]),
232
- [projectId, groupBy, selection, searchText, filters],
280
+ [projectId, groupBy, selection, searchText, filterConditions],
233
281
  );
234
282
  const groupsSignatureRef = useRef(groupsSignature);
235
283
  groupsSignatureRef.current = groupsSignature;
236
284
 
237
285
  const fetchGroups = useCallback(async () => {
238
- if (!projectId || groupBy === "none") return;
286
+ // 全部项目 scope(projectId 缺省)同样支持聚合分组计数(任务 16)
287
+ if (groupBy === "none") return;
239
288
  const requestSignature = groupsSignatureRef.current;
240
289
  try {
241
290
  const { groups: rows } = await listGroupsFn({ data: { ...requestParams(), groupBy } });
@@ -250,6 +299,188 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
250
299
  void fetchGroups();
251
300
  }, [fetchGroups]);
252
301
 
302
+ // ── 看板(view=board,PR-7b 任务 26):列级 cursor 分页 ──
303
+ // 看板维度收敛:看板无单列形态,groupBy=none 一律按 state 渲染;folder 仅
304
+ // 单项目 scope 有效(scope=all 同步收敛为 state,不依赖任务 20 的异步 clamp)。
305
+ // 收敛只落 folder 最小面:scope=all 下 state/priority 列表本就支持聚合分组
306
+ // (resolveStoryScopeProjectIds),看板保持同维度不静默换轴。
307
+ const boardGroupBy: Exclude<TestCaseGroupKey, "none"> =
308
+ groupBy === "none" || (projectId == null && groupBy === "folder")
309
+ ? "state"
310
+ : groupBy;
311
+ // 列全集显式传全(含空列,键序 = 列序):任务 25 约束 columns 必传
312
+ const boardColumnKeys = useMemo(() => {
313
+ switch (boardGroupBy) {
314
+ case "state":
315
+ return [...TEST_CASE_STATE_VALUES];
316
+ case "priority":
317
+ return [...TEST_CASE_PRIORITY_VALUES];
318
+ case "folder":
319
+ return [...folders.map((f) => f.id), "unassigned"];
320
+ }
321
+ }, [boardGroupBy, folders]);
322
+
323
+ const [boardData, setBoardData] = useState<Record<string, TestCaseSummary[]>>({});
324
+ const [boardCursors, setBoardCursors] = useState<Record<string, string | null>>({});
325
+ const [boardFailed, setBoardFailed] = useState(false);
326
+ const [boardLoaded, setBoardLoaded] = useState(false);
327
+ const boardSignature = useMemo(
328
+ () =>
329
+ JSON.stringify([
330
+ projectId ?? "",
331
+ boardGroupBy,
332
+ displayOptions.orderBy,
333
+ displayOptions.orderDir,
334
+ displayOptions.pageSize,
335
+ selection.kind,
336
+ selection.kind === "folder" ? selection.id : "",
337
+ searchText ?? "",
338
+ serializeFilters(filterConditions) ?? "",
339
+ ]),
340
+ [
341
+ projectId,
342
+ boardGroupBy,
343
+ displayOptions.orderBy,
344
+ displayOptions.orderDir,
345
+ displayOptions.pageSize,
346
+ selection,
347
+ searchText,
348
+ filterConditions,
349
+ ],
350
+ );
351
+ const boardSignatureRef = useRef(boardSignature);
352
+ boardSignatureRef.current = boardSignature;
353
+ // 在途守卫(仅列级):columnKey = 该列加载更多(防重复点击重放 cursor);
354
+ // 整板拉取不设 in-flight 早退(见 fetchBoard 注释)
355
+ const boardInflightRef = useRef<Set<string>>(new Set());
356
+ const boardCursorsRef = useRef(boardCursors);
357
+ boardCursorsRef.current = boardCursors;
358
+
359
+ const boardRequestBase = useCallback(
360
+ () => ({
361
+ projectId,
362
+ groupBy: boardGroupBy,
363
+ orderBy: displayOptions.orderBy,
364
+ orderDir: displayOptions.orderDir,
365
+ filters: serializeFilters(filterConditions) ?? undefined,
366
+ // 任务 27 补:看板搜索(任务 25 boardSchema 漏收 searchText,搜索不生效)
367
+ searchText,
368
+ }),
369
+ [projectId, boardGroupBy, displayOptions.orderBy, displayOptions.orderDir, filterConditions, searchText],
370
+ );
371
+
372
+ const fetchBoard = useCallback(async () => {
373
+ // 整板拉取不设 in-flight 早退(二期 finding C 归宿,fetchCases 同口径):
374
+ // 此前的 inflight.has("") 早退会在「整板在途 + 签名变化」窗口丢掉重拉请求
375
+ // ——看板滞留旧筛选数据直到下次外部触发。签名守卫已防错序(过期响应丢弃,
376
+ // 后发请求晚到覆盖),并发重入只多一次同参请求,落地值幂等。
377
+ const requestSignature = boardSignatureRef.current;
378
+ try {
379
+ const { columns } = await listTestCaseBoardColumnsFn({
380
+ data: {
381
+ ...boardRequestBase(),
382
+ columns: boardColumnKeys.map((key) => ({ key })),
383
+ take: Math.min(displayOptions.pageSize, 500),
384
+ },
385
+ });
386
+ if (boardSignatureRef.current !== requestSignature) return;
387
+ setBoardData(Object.fromEntries(columns.map((c) => [c.key, c.items])));
388
+ setBoardCursors(Object.fromEntries(columns.map((c) => [c.key, c.nextCursor])));
389
+ setBoardFailed(false);
390
+ setBoardLoaded(true);
391
+ } catch {
392
+ if (boardSignatureRef.current === requestSignature) setBoardFailed(true);
393
+ }
394
+ }, [boardRequestBase, boardColumnKeys, displayOptions.pageSize]);
395
+
396
+ const loadMoreColumn = useCallback(
397
+ async (columnKey: string) => {
398
+ const cursor = boardCursorsRef.current[columnKey];
399
+ const inflight = boardInflightRef.current;
400
+ if (!cursor || inflight.has(columnKey)) return;
401
+ inflight.add(columnKey);
402
+ // 签名过期守卫(fetchCases 追加加载同款,审查修复):列加载在途期间改
403
+ // 搜索/筛选/项目 → 整板已按新签名重置,旧筛选行不得追加进新板、
404
+ // 旧 nextCursor 不得覆写新 cursor
405
+ const requestSignature = boardSignatureRef.current;
406
+ try {
407
+ const { columns } = await listTestCaseBoardColumnsFn({
408
+ data: {
409
+ ...boardRequestBase(),
410
+ columns: [{ key: columnKey, cursor }],
411
+ take: Math.min(displayOptions.pageSize, 500),
412
+ },
413
+ });
414
+ if (boardSignatureRef.current !== requestSignature) return;
415
+ const col = columns[0];
416
+ if (!col) return;
417
+ setBoardData((prev) => {
418
+ const known = new Set((prev[columnKey] ?? []).map((c) => c.id));
419
+ return {
420
+ ...prev,
421
+ [columnKey]: [
422
+ ...(prev[columnKey] ?? []),
423
+ ...col.items.filter((c) => !known.has(c.id)),
424
+ ],
425
+ };
426
+ });
427
+ setBoardCursors((prev) => ({ ...prev, [columnKey]: col.nextCursor }));
428
+ } catch {
429
+ // 加载更多失败静默保留原列(cursor 未动,可再次点击重试)
430
+ } finally {
431
+ inflight.delete(columnKey);
432
+ }
433
+ },
434
+ [boardRequestBase, displayOptions.pageSize],
435
+ );
436
+
437
+ useEffect(() => {
438
+ if (view !== "board") return;
439
+ void fetchBoard();
440
+ }, [view, fetchBoard]);
441
+
442
+ // 手动排序(拖拽列内/跨列均写目标列 boardOrder):reorderTestCasesFn 批量落库
443
+ const handleBoardReorder = useCallback(async (orders: ReorderEntry[]) => {
444
+ if (orders.length === 0) return;
445
+ await reorderTestCasesFn({ data: { items: orders } });
446
+ }, []);
447
+
448
+ // 排序键不是 manual 时 boardOrder 不被读取(拖拽会被排回原位),落库后自动切换
449
+ const handleBoardAutoSwitchManual = useCallback(async () => {
450
+ const patch = manualOrderSwitchPatch(displayOptions.orderBy, true);
451
+ if (!patch) return;
452
+ updateDisplayOptions(patch);
453
+ toast.info(m.testcase_board_manualOrderEnabled());
454
+ }, [displayOptions.orderBy, updateDisplayOptions]);
455
+
456
+ // 看板行内编辑/拖拽落库的本地收敛:与列表缓存(cases.items)双写,
457
+ // peek 面板与切回列表视图时数据不回跳
458
+ const patchBoardCase = useCallback((id: string, patch: Partial<TestCaseSummary>) => {
459
+ setBoardData((prev) => {
460
+ let changed = false;
461
+ const next: Record<string, TestCaseSummary[]> = {};
462
+ for (const [key, items] of Object.entries(prev)) {
463
+ if (!items.some((c) => c.id === id)) {
464
+ next[key] = items;
465
+ continue;
466
+ }
467
+ changed = true;
468
+ next[key] = items.map((c) => (c.id === id ? { ...c, ...patch } : c));
469
+ }
470
+ return changed ? next : prev;
471
+ });
472
+ }, []);
473
+
474
+ const refreshBoard = useCallback(() => {
475
+ void fetchBoard();
476
+ }, [fetchBoard]);
477
+ const handleLoadMoreColumn = useCallback(
478
+ (columnKey: string) => {
479
+ void loadMoreColumn(columnKey);
480
+ },
481
+ [loadMoreColumn],
482
+ );
483
+
253
484
  // 乐观 patch 三档的本地档(A7):CaseRow 负责 ①乐观回调(本函数)②serverFn
254
485
  // ③收敛/回滚(同样回调本函数合并);原地合并不重排,扁平分页序稳定。
255
486
  const patchCase = useCallback((id: string, patch: Partial<TestCaseSummary>) => {
@@ -259,6 +490,25 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
259
490
  }));
260
491
  }, []);
261
492
 
493
+ // 看板行内编辑/拖拽落库双写入口(PR-7b 任务 26):列表缓存与看板列数据同步收敛
494
+ const patchCaseEverywhere = useCallback(
495
+ (id: string, patch: Partial<TestCaseSummary>) => {
496
+ patchCase(id, patch);
497
+ patchBoardCase(id, patch);
498
+ },
499
+ [patchCase, patchBoardCase],
500
+ );
501
+
502
+ // 列表实时刷新(§7.1):presence 信号(成员进出本模块实体页)+ 回前台 +
503
+ // 轮询兜底 → 走既有重拉入口(协同落库不 bump updatedAt,列表无推送通道);
504
+ // 看板视图在场时一并重拉列数据(任务 26)
505
+ const refreshCaseList = useCallback(() => {
506
+ void fetchCases();
507
+ void fetchGroups();
508
+ if (view === "board") void fetchBoard();
509
+ }, [fetchCases, fetchGroups, view, fetchBoard]);
510
+ usePresenceListRefresh({ module: "test-cases", refresh: refreshCaseList });
511
+
262
512
  const refreshFolders = useCallback(async () => {
263
513
  try {
264
514
  const { folders: rows } = await listFoldersFn({ data: { projectId } });
@@ -268,11 +518,55 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
268
518
  }
269
519
  }, [projectId]);
270
520
 
271
- const handleFiltersChange = useCallback((next: TestCaseFilterState) => {
272
- setFilters(next);
521
+ // ── 项目选择器接线(任务 17)──
522
+ // Popover「新建项目」后刷新花名册(story-list-view 同款)
523
+ const refreshProjects = useCallback(() => {
524
+ listProjectsBriefFn()
525
+ .then((rows) => setProjects(rows))
526
+ .catch(() => {});
527
+ }, []);
528
+
529
+ // 切作用域:树选中必须重置(folder/ungrouped 语义仅单项目成立);手动切项目
530
+ // 视为偏离当前保存视图 → 回落默认视图
531
+ const handleProjectChange = useCallback((next: string | undefined) => {
532
+ setProjectId(next);
533
+ setSelection({ kind: "all" });
534
+ setActiveViewId(DEFAULT_VIEW_ID);
535
+ }, []);
536
+
537
+ // 全部项目 scope 无模块树:groupBy=folder 自动回落 none(覆盖切 scope、应用
538
+ // 含 folder 分组的保存视图等一切进入路径;单项目 scope 切回后保持 none 不恢复;
539
+ // 下拉侧的「模块」项隐藏是第一道过滤,本 clamp 是第二道保险——任务 20)
540
+ useEffect(() => {
541
+ if (projectId || groupBy !== "folder") return;
542
+ updateDisplayOptions({ groupBy: "none" });
543
+ setActiveViewId(DEFAULT_VIEW_ID);
544
+ }, [projectId, groupBy, updateDisplayOptions]);
545
+
546
+ // 全部项目 scope 下 folder 筛选条件不合法(模块树属单项目):切入聚合 scope
547
+ // 时剔除 folder 条件(resolveFilterKeys 下拉隐藏是第一道过滤,本 clamp 同为
548
+ // 第二道保险——任务 21,与上方 groupBy clamp 同构)
549
+ useEffect(() => {
550
+ if (projectId) return;
551
+ setFilterConditions((prev) =>
552
+ prev.some((c) => c.property === "folder") ? prev.filter((c) => c.property !== "folder") : prev,
553
+ );
554
+ }, [projectId]);
555
+
556
+ // 模块树属单项目:切项目(含切入/切出全部项目 scope)后按新项目刷新树;
557
+ // 首屏不刷(路由 beforeLoad 已预载 initialFolders)
558
+ const prevProjectRef = useRef(projectId);
559
+ useEffect(() => {
560
+ if (prevProjectRef.current === projectId) return;
561
+ prevProjectRef.current = projectId;
562
+ void refreshFolders();
563
+ }, [projectId, refreshFolders]);
564
+
565
+ const handleConditionsChange = useCallback((next: TestcaseFilterCondition[]) => {
566
+ setFilterConditions(next);
273
567
  // 手动筛选偏离当前保存视图 → 回落内置默认视图(视图选择器取消高亮)
274
568
  setActiveViewId(DEFAULT_VIEW_ID);
275
- if (!hasActiveFilters(next)) setFilterBarVisible(false);
569
+ if (next.length === 0) setFilterBarVisible(false);
276
570
  }, []);
277
571
 
278
572
  const handleSearchChange = useCallback((next: string | undefined) => {
@@ -285,11 +579,6 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
285
579
  setActiveViewId(DEFAULT_VIEW_ID);
286
580
  }, []);
287
581
 
288
- const handleGroupByChange = useCallback((next: TestCaseGroupBy) => {
289
- setGroupBy(next);
290
- setActiveViewId(DEFAULT_VIEW_ID);
291
- }, []);
292
-
293
582
  // ── 圈选多选接线(任务 13):勾选翻转;加入成功后清空(用例已在计划内)──
294
583
  const toggleSelect = useCallback((id: string) => {
295
584
  setSelectedIds((prev) => {
@@ -313,26 +602,138 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
313
602
  setPeekId(id);
314
603
  }, []);
315
604
  const handleClosePeek = useCallback(() => setPeekId(null), []);
316
- // 面板 ↑↓/上下一条按钮:在已加载扁平序内移动(边界由 disabled + 越界守卫双兜底)
605
+ // 面板 ↑↓/上下一条按钮:在当前视图的可见序内移动(看板 = 列序 + 列内序;
606
+ // 边界由 disabled + 越界守卫双兜底)
607
+ const boardItems = useMemo(() => Object.values(boardData).flat(), [boardData]);
608
+
609
+ // ── 批量条接线(任务 28):三态全选分母取当前视图已加载行(story totalLoaded
610
+ // 同口径);批量更新成功后清空多选并重拉——folder 移动涉及重新分组,走
611
+ // refreshCaseList 全量兜底(列表 + 分组计数 + 看板在场时一并)──
612
+ const totalLoaded = view === "board" ? boardItems.length : cases.items.length;
613
+ const allSelected = totalLoaded > 0 && selectedIds.size >= totalLoaded;
614
+ const toggleAll = useCallback(() => {
615
+ if (allSelected) {
616
+ clearSelection();
617
+ return;
618
+ }
619
+ const pool = view === "board" ? boardItems : cases.items;
620
+ setSelectedIds(new Set(pool.map((c) => c.id)));
621
+ }, [allSelected, clearSelection, view, boardItems, cases.items]);
622
+ const handleBatchUpdated = useCallback(() => {
623
+ clearSelection();
624
+ refreshCaseList();
625
+ }, [clearSelection, refreshCaseList]);
626
+
627
+ // ── manual 档批量上移/下移(任务 29,story-list moveStories 同构)──
628
+ // 按分组桶装视觉顺序(grouped-list groupKeyOf 同一 key 口径;groupBy=none
629
+ // 整列表单组);折叠组行不渲染、无从勾选,桶内含其 id 也无选中可动。
630
+ const moveGroups = useMemo(() => {
631
+ const map = new Map<string, string[]>();
632
+ if (view !== "list") return map;
633
+ if (groupBy === "none") {
634
+ map.set("", cases.items.map((c) => c.id));
635
+ return map;
636
+ }
637
+ for (const g of groups) map.set(g.key, []);
638
+ for (const c of cases.items) {
639
+ const k = groupKeyOf(groupBy, c);
640
+ const arr = map.get(k);
641
+ if (arr) arr.push(c.id);
642
+ else map.set(k, [c.id]);
643
+ }
644
+ return map;
645
+ }, [view, groupBy, groups, cases.items]);
646
+
647
+ // 与 reorderTestCases 的组内语义一致:任一组的选中项可移动即可移动
648
+ const canMoveGrouped = useCallback(
649
+ (dir: "up" | "down") => {
650
+ if (selectedIds.size === 0) return false;
651
+ for (const ids of moveGroups.values()) {
652
+ const groupSelected = new Set(ids.filter((id) => selectedIds.has(id)));
653
+ if (canMove(ids, groupSelected, dir)) return true;
654
+ }
655
+ return false;
656
+ },
657
+ [moveGroups, selectedIds],
658
+ );
659
+
660
+ const moveCases = useCallback(
661
+ async (dir: "up" | "down") => {
662
+ const targets: ReorderEntry[] = [];
663
+ for (const ids of moveGroups.values()) {
664
+ const groupSelected = new Set(ids.filter((id) => selectedIds.has(id)));
665
+ const next = computeMove(ids, groupSelected, dir);
666
+ if (next.some((id, i) => id !== ids[i])) {
667
+ next.forEach((id, i) => targets.push({ id, boardOrder: i }));
668
+ }
669
+ }
670
+ if (targets.length === 0) return;
671
+ try {
672
+ await reorderTestCasesFn({ data: { items: targets } });
673
+ toast.success(dir === "up" ? m.testcase_batch_movedUp() : m.testcase_batch_movedDown());
674
+ refreshCaseList();
675
+ } catch (e) {
676
+ toastError(e, m.testcase_batch_moveFailed());
677
+ }
678
+ },
679
+ [moveGroups, selectedIds, refreshCaseList],
680
+ );
681
+
682
+ // 圈选加入计划的目标项目(任务 28 落任务 17 note):listPlansFn 按单项目解析
683
+ //(缺省静默回退内置独立项目),scope=all 聚合圈选时改传首条选中用例的
684
+ // projectId 取计划列表;混合项目圈选以首条为准(跨项目用例由 addCasesToPlanFn
685
+ // 服务端 skipped 明细兜底)
686
+ const selectionProjectId = useMemo(() => {
687
+ if (projectId) return projectId;
688
+ const pool = view === "board" ? boardItems : cases.items;
689
+ return pool.find((c) => selectedIds.has(c.id))?.projectId;
690
+ }, [projectId, view, boardItems, cases.items, selectedIds]);
691
+
692
+ const peekPool = view === "board" ? boardItems : cases.items;
317
693
  const handlePeekStep = useCallback(
318
694
  (dir: 1 | -1) => {
319
695
  if (!peekId) return;
320
- const idx = cases.items.findIndex((c) => c.id === peekId);
696
+ const idx = peekPool.findIndex((c) => c.id === peekId);
321
697
  const nextIdx = idx + dir;
322
- if (idx === -1 || nextIdx < 0 || nextIdx >= cases.items.length) return;
323
- const nextId = cases.items[nextIdx]!.id;
698
+ if (idx === -1 || nextIdx < 0 || nextIdx >= peekPool.length) return;
699
+ const nextId = peekPool[nextIdx]!.id;
324
700
  setSelectedId(nextId);
325
701
  setPeekId(nextId);
326
702
  },
327
- [peekId, cases.items],
703
+ [peekId, peekPool],
328
704
  );
329
- const peekedCase = peekId ? (cases.items.find((c) => c.id === peekId) ?? null) : null;
330
- const peekedIdx = peekId ? cases.items.findIndex((c) => c.id === peekId) : -1;
705
+ const peekedCase = peekId ? (peekPool.find((c) => c.id === peekId) ?? null) : null;
706
+ const peekedIdx = peekId ? peekPool.findIndex((c) => c.id === peekId) : -1;
707
+
708
+ // 创建/编辑(保存)视图提交(任务 22 起双模式):filters 键集收敛在前端
709
+ //(任务 6 审查指针②),displayOptions 快照当前 groupBy/view。P1 单项目作用域,
710
+ // projectId 不落(null)。带 id 走 update(仅 patch 元数据 name/description/icon,
711
+ // 筛选/显示选项不可在本对话框改);description 空串传 null 以清除(updateSchema
712
+ // nullable,undefined 语义是「不更新」会残留旧值)。提交错误由对话框内联展示。
713
+ const reloadViews = useCallback(async () => {
714
+ const rows = await listFilterViewsFn();
715
+ setViews(rows.map(toSavedView));
716
+ return rows.map(toSavedView);
717
+ }, []);
331
718
 
332
- // 创建(保存)视图提交:filters 键集收敛在前端(任务 6 审查指针②),
333
- // displayOptions 快照当前 groupBy/view。P1 单项目作用域,projectId 不落(null)。
334
- const handleCreateViewSubmit = useCallback(
719
+ const handleViewDialogSubmit = useCallback(
335
720
  async (payload: CreateViewDialogPayload) => {
721
+ if (payload.id) {
722
+ await updateFilterViewFn({
723
+ data: {
724
+ id: payload.id,
725
+ name: payload.name,
726
+ description: payload.description ?? null,
727
+ icon: payload.icon,
728
+ },
729
+ });
730
+ const nextViews = await reloadViews();
731
+ if (activeViewId === payload.id) {
732
+ const updated = nextViews.find((v) => v.id === payload.id);
733
+ if (updated) applyView(updated);
734
+ }
735
+ return;
736
+ }
336
737
  await createFilterViewFn({
337
738
  data: {
338
739
  name: payload.name,
@@ -341,63 +742,127 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
341
742
  projectId: null,
342
743
  // 展开为新鲜对象字面量:接口无隐式索引签名,validator 侧入参是
343
744
  // Record<string, unknown>(服务端只验「是对象」,键集收敛在本模块)
344
- filters: { ...toViewFilters({ selection, searchText, filters }) },
745
+ filters: { ...toViewFilters({ selection, searchText, conditions: filterConditions }) },
345
746
  displayOptions: { groupBy, view },
346
747
  },
347
748
  });
348
- const rows = await listFilterViewsFn();
349
- setViews(rows.map(toSavedView));
749
+ await reloadViews();
350
750
  },
351
- [selection, searchText, filters, groupBy, view],
751
+ [selection, searchText, filterConditions, groupBy, view, activeViewId, applyView, reloadViews],
752
+ );
753
+
754
+ // ── 视图管理入口(任务 22,story-list-view handleEditView/handleDeleteView 同构)──
755
+ const openCreateView = useCallback(() => {
756
+ setViewDialog({ mode: "create", view: null });
757
+ setViewDialogOpen(true);
758
+ }, []);
759
+
760
+ const handleEditView = useCallback((target: TestCaseSavedView) => {
761
+ setViewDialog({ mode: "edit", view: target });
762
+ setViewDialogOpen(true);
763
+ }, []);
764
+
765
+ const handleDeleteView = useCallback(
766
+ async (target: TestCaseSavedView) => {
767
+ try {
768
+ await deleteFilterViewFn({ data: { id: target.id } });
769
+ await reloadViews();
770
+ if (activeViewId === target.id) applyView(buildDefaultView());
771
+ } catch (e) {
772
+ toastError(e, m.testcase_view_deleteFailed());
773
+ }
774
+ },
775
+ [activeViewId, applyView, reloadViews],
352
776
  );
353
777
 
354
778
  // 保存对话框里的当前筛选摘要(值列表;空筛选给提示文案)
355
779
  const filterSummary = useMemo(() => {
356
780
  const parts: string[] = [];
357
- if (searchText) parts.push(`「${searchText}」`);
781
+ if (searchText) parts.push(m.testcase_filter_searchChip({ query: searchText }));
358
782
  if (selection.kind === "folder") {
359
783
  parts.push(folders.find((f) => f.id === selection.id)?.name ?? m.testcase_folderTree_ungrouped());
360
784
  } else if (selection.kind === "ungrouped") {
361
785
  parts.push(m.testcase_folderTree_ungrouped());
362
786
  }
363
- if (filters.state) parts.push(stateLabel(filters.state));
364
- if (filters.type) parts.push(typeLabel(filters.type));
365
- if (filters.priority) parts.push(priorityLabel(filters.priority));
787
+ for (const condition of filterConditions) {
788
+ parts.push(filterConditionSummary(condition, { users, folders }));
789
+ }
366
790
  return parts.length > 0 ? parts.join(" · ") : m.testcase_view_filterEmpty();
367
- }, [searchText, selection, filters, folders]);
791
+ }, [searchText, selection, filterConditions, users, folders]);
368
792
 
369
- const createViewInitialValues = useMemo(
370
- () => ({ name: "", description: "", icon: "list" }),
371
- [],
793
+ // 对话框初值(任务 22):编辑态回填被编辑视图元数据;引用随 viewDialog 变化
794
+ //(open 期间保持稳定,对话框 useEffect 以其复位表单)
795
+ const viewDialogInitialValues = useMemo(
796
+ () =>
797
+ viewDialog.view
798
+ ? {
799
+ id: viewDialog.view.id,
800
+ name: viewDialog.view.name,
801
+ description: viewDialog.view.description ?? "",
802
+ icon: viewDialog.view.icon,
803
+ }
804
+ : { name: "", description: "", icon: "list" },
805
+ [viewDialog],
372
806
  );
373
807
 
374
- const loadFailedBlock = loadFailed ? (
808
+ // 失败/空态/首载口径按视图分治(任务 29:空态 h-40 居中 + 首载 WavePhysicsLoader,
809
+ // 照 story-list 同款):看板有自己的失败态与已加载标记(任务 26)
810
+ const bodyFailed = view === "board" ? boardFailed : loadFailed;
811
+ const bodyEmpty =
812
+ view === "board"
813
+ ? boardLoaded && boardItems.length === 0
814
+ : initialLoaded && cases.items.length === 0;
815
+ const bodyFirstLoading =
816
+ view === "board" ? !boardLoaded && !boardFailed : !initialLoaded && !loadFailed;
817
+ // 首载动画延迟显示(follow-up A):真实等待超 400ms 才出现,快请求直接见数据/空态
818
+ const firstLoadingDeferred = useDeferredFlag(bodyFirstLoading);
819
+ const retryFetch = view === "board" ? fetchBoard : fetchCases;
820
+
821
+ const loadFailedBlock = bodyFailed ? (
375
822
  <div className="flex flex-1 flex-col items-center justify-center gap-3 text-sm text-muted-foreground">
376
823
  <span>{m.testcase_listView_loadFailed()}</span>
377
- <Button variant="outline" size="sm" onClick={() => void fetchCases()}>
824
+ <Button variant="outline" size="sm" onClick={() => void retryFetch()}>
378
825
  <RotateCcw className="size-3.5" />
379
826
  </Button>
380
827
  </div>
381
828
  ) : null;
382
829
 
383
- const emptyBlock = initialLoaded && cases.items.length === 0 ? (
384
- <div className="flex flex-1 flex-col items-center justify-center gap-1 text-center">
830
+ const firstLoadingBlock = (
831
+ <div className="flex flex-1 flex-col items-center justify-center gap-3 py-12" aria-busy="true">
832
+ <WavePhysicsLoader />
833
+ <p className="text-sm text-muted-foreground">{m.testcase_listView_loading()}</p>
834
+ </div>
835
+ );
836
+
837
+ const emptyBlock = (
838
+ <div className="flex h-40 flex-1 flex-col items-center justify-center gap-1 text-center">
385
839
  <span className="text-sm font-medium">{m.testcase_listView_empty()}</span>
386
840
  <span className="text-xs text-muted-foreground">{m.testcase_listView_emptyDesc()}</span>
387
841
  </div>
388
- ) : null;
842
+ );
389
843
 
390
- // 列表区:任务 8 分组虚拟列表(A8,groupBy 仅作用于列表视图);任务 9 表格视图
391
- // (A3,排序+列钉住,分组不适用——与需求页表格同口径)
844
+ // 列表区:任务 8 分组虚拟列表(A8,groupBy 仅作用于列表视图);PR-7b 任务 26
845
+ // 看板视图(全列集含空列,列级 cursor 分页 + 拖拽改分组字段 + 手动排序);
846
+ // 任务 27 定版 list|board,表格视图移除(存量 view:"table" 反序列化回落 list)
847
+ const boardColumns = useMemo(
848
+ () => Object.fromEntries(boardColumnKeys.map((key) => [key, boardData[key] ?? []])),
849
+ [boardColumnKeys, boardData],
850
+ );
392
851
  const listBody =
393
- view === "table" ? (
394
- <TableView
395
- cases={cases.items}
396
- hasMore={cases.nextCursor !== null}
397
- selectedId={selectedId}
398
- onSelect={handleSelect}
852
+ view === "board" ? (
853
+ <TestCaseBoardView
854
+ columns={boardColumns}
855
+ groupBy={boardGroupBy}
856
+ properties={displayOptions.properties}
857
+ folders={folders}
858
+ users={users}
399
859
  onOpenCase={handleOpenCase}
400
- onLoadMore={loadMore}
860
+ onLoadMoreColumn={handleLoadMoreColumn}
861
+ onPatchCase={patchCaseEverywhere}
862
+ onCaseGroupChanged={refreshBoard}
863
+ onReorder={handleBoardReorder}
864
+ onAutoSwitchManual={handleBoardAutoSwitchManual}
865
+ onRefresh={refreshBoard}
401
866
  />
402
867
  ) : (
403
868
  <GroupedList
@@ -420,68 +885,99 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
420
885
 
421
886
  return (
422
887
  <div className="relative flex h-full min-h-0 gap-3 overflow-hidden p-4">
423
- <div
424
- className={
425
- treeVisible
426
- ? "flex w-56 shrink-0 flex-col rounded-lg border border-border bg-background p-2"
427
- : "flex w-0 shrink-0 flex-col overflow-hidden p-0"
428
- }
429
- >
430
- {treeVisible && (
431
- <FolderTree
432
- folders={folders}
433
- selected={selection}
434
- onSelect={handleSelectionChange}
435
- onFoldersChanged={() => void refreshFolders()}
436
- />
437
- )}
438
- </div>
888
+ {/* 全部项目 scope(projectId=undefined):模块树整栏不渲染(任务 17),
889
+ 收起/展开按钮一并隐藏(无树可操作) */}
890
+ {projectId && (
891
+ <div
892
+ className={
893
+ treeVisible
894
+ ? "flex w-56 shrink-0 flex-col rounded-lg border border-border bg-background p-2"
895
+ : "flex w-0 shrink-0 flex-col overflow-hidden p-0"
896
+ }
897
+ >
898
+ {treeVisible && (
899
+ <FolderTree
900
+ folders={folders}
901
+ selected={selection}
902
+ onSelect={handleSelectionChange}
903
+ onFoldersChanged={() => void refreshFolders()}
904
+ />
905
+ )}
906
+ </div>
907
+ )}
439
908
  <div className="flex min-w-0 flex-1 flex-col gap-3 overflow-hidden">
440
909
  <div className="flex items-center gap-2">
441
- <Button
442
- variant="ghost"
443
- size="icon"
444
- aria-label={treeVisible ? m.testcase_folderTree_hideTree() : m.testcase_folderTree_showTree()}
445
- onClick={() => setTreeVisible((v) => !v)}
446
- className="size-8 shrink-0 text-muted-foreground"
447
- >
448
- {treeVisible ? <PanelLeftClose className="size-4" /> : <PanelLeftOpen className="size-4" />}
449
- </Button>
450
- <TestCaseToolbar
451
- total={cases.items.length}
452
- projectId={projectId}
453
- search={searchText}
454
- filters={filters}
455
- filterBarVisible={filterBarVisible}
456
- onToggleFilterBar={() => setFilterBarVisible((v) => !v)}
457
- onSearchChange={handleSearchChange}
458
- onFiltersChange={handleFiltersChange}
459
- view={view}
460
- onViewChange={setView}
461
- groupBy={groupBy}
462
- onGroupByChange={handleGroupByChange}
463
- views={allViews}
464
- activeViewId={activeViewId}
465
- onApplyView={applyView}
466
- onAddView={() => setCreateViewOpen(true)}
467
- onCreateClick={() => setCreateCaseOpen(true)}
468
- />
910
+ {projectId && (
911
+ <Button
912
+ variant="ghost"
913
+ size="icon"
914
+ aria-label={treeVisible ? m.testcase_folderTree_hideTree() : m.testcase_folderTree_showTree()}
915
+ onClick={() => setTreeVisible((v) => !v)}
916
+ className="size-8 shrink-0 text-muted-foreground"
917
+ >
918
+ {treeVisible ? <PanelLeftClose className="size-4" /> : <PanelLeftOpen className="size-4" />}
919
+ </Button>
920
+ )}
921
+ {/* ToolbarShell 的 @container 行是 inline-size containment:父级若按内容
922
+ 定宽(水平 flex item)行宽会塌缩,必须由本包装提供确定宽度 */}
923
+ <div className="min-w-0 flex-1">
924
+ <TestCaseToolbar
925
+ total={view === "board" && boardLoaded ? boardItems.length : cases.items.length}
926
+ projectId={projectId}
927
+ projects={projects}
928
+ onProjectChange={handleProjectChange}
929
+ onProjectCreated={refreshProjects}
930
+ search={searchText}
931
+ filterConditions={filterConditions}
932
+ filterBarVisible={filterBarVisible}
933
+ onToggleFilterBar={() => setFilterBarVisible((v) => !v)}
934
+ onSearchChange={handleSearchChange}
935
+ onFilterConditionsChange={handleConditionsChange}
936
+ users={users}
937
+ folders={folders}
938
+ displayFilters={displayOptions}
939
+ onDisplayOptionsChange={updateDisplayOptions}
940
+ view={view}
941
+ onViewChange={setView}
942
+ views={allViews}
943
+ activeViewId={activeViewId}
944
+ onApplyView={applyView}
945
+ onEditView={handleEditView}
946
+ onDeleteView={(view) => void handleDeleteView(view)}
947
+ onAddView={openCreateView}
948
+ onCreateClick={() => setCreateCaseOpen(true)}
949
+ />
950
+ </div>
469
951
  </div>
470
952
  <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
471
- {loadFailed ? loadFailedBlock : cases.items.length === 0 ? emptyBlock : listBody}
472
- <AddToPlanBar
953
+ {bodyFailed ? loadFailedBlock : bodyEmpty ? emptyBlock : bodyFirstLoading && firstLoadingDeferred ? firstLoadingBlock : listBody}
954
+ <TestCaseBatchToolbar
473
955
  selectedCount={selectedIds.size}
474
- onAddToPlan={() => setAddToPlanOpen(true)}
956
+ totalLoaded={totalLoaded}
957
+ allSelected={allSelected}
958
+ projectId={projectId}
959
+ users={users}
960
+ folders={folders}
961
+ caseIds={[...selectedIds]}
962
+ canMove={view === "list" && displayOptions.orderBy === "manual"}
963
+ canMoveUp={canMoveGrouped("up")}
964
+ canMoveDown={canMoveGrouped("down")}
965
+ onMoveUp={() => void moveCases("up")}
966
+ onMoveDown={() => void moveCases("down")}
967
+ onToggleAll={toggleAll}
475
968
  onClearSelection={clearSelection}
969
+ onAddToPlan={() => setAddToPlanOpen(true)}
970
+ onUpdated={handleBatchUpdated}
476
971
  />
477
972
  </div>
478
973
  </div>
479
974
  <CreateViewDialog
480
- open={createViewOpen}
481
- onOpenChange={setCreateViewOpen}
482
- initialValues={createViewInitialValues}
975
+ open={viewDialogOpen}
976
+ onOpenChange={setViewDialogOpen}
977
+ mode={viewDialog.mode}
978
+ initialValues={viewDialogInitialValues}
483
979
  filterSummary={filterSummary}
484
- onSubmit={handleCreateViewSubmit}
980
+ onSubmit={handleViewDialogSubmit}
485
981
  />
486
982
  {/* 新建成功:refetch 列表 + 分组计数(story create-dialog 先例为 refetch 非
487
983
  prepend——列表 orderBy sequenceId asc,新用例落在已加载区间之外也一致),
@@ -505,7 +1001,7 @@ export function TestCaseListView({ projects, initialFolders, initialUsers }: Tes
505
1001
  open={addToPlanOpen}
506
1002
  onOpenChange={setAddToPlanOpen}
507
1003
  caseIds={[...selectedIds]}
508
- projectId={projectId}
1004
+ projectId={selectionProjectId}
509
1005
  onAdded={clearSelection}
510
1006
  />
511
1007
  {peekedCase && (