@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
@@ -0,0 +1,166 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // 用例显示选项 per-user 持久化(UI 对齐二期任务 20,照 bug-display-options.ts
19
+ // 模式)。存储落点:schema 无 User.testcaseDisplayOptions 字段且本期禁止迁移,
20
+ // 借用 TestCaseFilterView 的 per-user 保留行(name = TESTCASE_DISPLAY_OPTIONS_
21
+ // VIEW_NAME,filters 存空对象),该行对 testcase-filter-views.ts 的保存视图
22
+ // CRUD 不可见(list where / create·update 名称守卫已在彼处收口)。
23
+ import { getPrisma } from "@allbluecn/database/get-prisma";
24
+ import { createServerFn } from "@tanstack/react-start";
25
+ import { getRequest } from "@tanstack/react-start/server";
26
+ import { z } from "zod";
27
+ import { requireAuth } from "@/server/auth-helpers";
28
+ import { requireFeature } from "@/server/entitlements";
29
+ import { isProEdition } from "@/server/pro-services";
30
+ import {
31
+ DEFAULT_TESTCASE_DISPLAY_OPTIONS,
32
+ TESTCASE_DISPLAY_OPTIONS_VIEW_NAME,
33
+ TESTCASE_DISPLAY_PROPERTIES,
34
+ TESTCASE_GROUP_KEYS,
35
+ TESTCASE_ORDER_DIRECTIONS,
36
+ TESTCASE_ORDER_KEYS,
37
+ TESTCASE_PAGE_SIZE_OPTIONS,
38
+ type TestCaseDisplayOptions,
39
+ type TestCaseDisplayProperty,
40
+ type TestCaseGroupKey,
41
+ type TestCaseOrderDirection,
42
+ type TestCaseOrderKey,
43
+ type TestCasePageSize,
44
+ } from "@/components/testcase/display/field-configs";
45
+ import type { Prisma } from "@allbluecn/database";
46
+
47
+ function toJson<T>(value: T): Prisma.InputJsonValue {
48
+ return value as unknown as Prisma.InputJsonValue;
49
+ }
50
+
51
+ /** 解析存储的 JSON 偏好,非法/缺失字段逐项回退默认值(照 bug parseStoredOptions) */
52
+ function parseStoredOptions(raw: unknown): TestCaseDisplayOptions {
53
+ if (typeof raw !== "object" || raw === null) return { ...DEFAULT_TESTCASE_DISPLAY_OPTIONS };
54
+ const o = raw as Record<string, unknown>;
55
+
56
+ const groupBy =
57
+ typeof o.groupBy === "string" && (TESTCASE_GROUP_KEYS as readonly string[]).includes(o.groupBy)
58
+ ? (o.groupBy as TestCaseGroupKey)
59
+ : DEFAULT_TESTCASE_DISPLAY_OPTIONS.groupBy;
60
+ const orderBy =
61
+ typeof o.orderBy === "string" && (TESTCASE_ORDER_KEYS as readonly string[]).includes(o.orderBy)
62
+ ? (o.orderBy as TestCaseOrderKey)
63
+ : DEFAULT_TESTCASE_DISPLAY_OPTIONS.orderBy;
64
+ const orderDir =
65
+ typeof o.orderDir === "string" &&
66
+ (TESTCASE_ORDER_DIRECTIONS as readonly string[]).includes(o.orderDir)
67
+ ? (o.orderDir as TestCaseOrderDirection)
68
+ : DEFAULT_TESTCASE_DISPLAY_OPTIONS.orderDir;
69
+ const properties = Array.isArray(o.properties)
70
+ ? (o.properties as unknown[]).filter(
71
+ (p): p is TestCaseDisplayProperty =>
72
+ typeof p === "string" && (TESTCASE_DISPLAY_PROPERTIES as readonly string[]).includes(p),
73
+ )
74
+ : [];
75
+ const pageSize =
76
+ typeof o.pageSize === "number" &&
77
+ (TESTCASE_PAGE_SIZE_OPTIONS as readonly number[]).includes(o.pageSize)
78
+ ? (o.pageSize as TestCasePageSize)
79
+ : DEFAULT_TESTCASE_DISPLAY_OPTIONS.pageSize;
80
+
81
+ return {
82
+ groupBy,
83
+ orderBy,
84
+ orderDir,
85
+ properties:
86
+ properties.length > 0 ? properties : [...DEFAULT_TESTCASE_DISPLAY_OPTIONS.properties],
87
+ pageSize,
88
+ };
89
+ }
90
+
91
+ const optionsSchema = z.object({
92
+ groupBy: z.enum(TESTCASE_GROUP_KEYS),
93
+ orderBy: z.enum(TESTCASE_ORDER_KEYS),
94
+ orderDir: z.enum(TESTCASE_ORDER_DIRECTIONS),
95
+ properties: z.array(z.enum(TESTCASE_DISPLAY_PROPERTIES)),
96
+ pageSize: z.union([z.literal(100), z.literal(200), z.literal(500), z.literal(1000)]),
97
+ });
98
+
99
+ /** 订阅过期/降级兜底:已持久化的 500/1000 无权限时降级为 200(照 bug 同一业务规则) */
100
+ async function capPageSize(request: Request, pageSize: TestCasePageSize): Promise<TestCasePageSize> {
101
+ if (pageSize <= 200) return pageSize;
102
+ if (!isProEdition()) return 200;
103
+ try {
104
+ await requireFeature(request, "largePageSize");
105
+ return pageSize;
106
+ } catch {
107
+ return 200;
108
+ }
109
+ }
110
+
111
+ /** 获取当前用户持久化的用例显示选项(非法/缺失字段已回退默认值) */
112
+ export const getTestCaseDisplayOptionsFn = createServerFn({ method: "GET" }).handler(async () => {
113
+ const prisma = await getPrisma();
114
+ const request = getRequest();
115
+ const session = await requireAuth(request);
116
+ const userId = session.user!.id!;
117
+
118
+ const row = await prisma.testCaseFilterView.findFirst({
119
+ where: { userId, name: TESTCASE_DISPLAY_OPTIONS_VIEW_NAME },
120
+ orderBy: { createdAt: "asc" },
121
+ select: { displayOptions: true },
122
+ });
123
+ const options = parseStoredOptions(row?.displayOptions);
124
+ options.pageSize = await capPageSize(request, options.pageSize);
125
+ return options;
126
+ });
127
+
128
+ /** 保存当前用户的用例显示选项,返回更新后的完整对象 */
129
+ export const updateTestCaseDisplayOptionsFn = createServerFn({ method: "POST" })
130
+ .validator(optionsSchema)
131
+ .handler(async ({ data }) => {
132
+ const prisma = await getPrisma();
133
+ const request = getRequest();
134
+ const session = await requireAuth(request);
135
+ const userId = session.user!.id!;
136
+
137
+ // 服务端强制校验:pageSize > 200(500/1000)为 Pro 专属,前端锁定只是 UX
138
+ const options: TestCaseDisplayOptions = {
139
+ ...data,
140
+ pageSize: await capPageSize(request, data.pageSize),
141
+ };
142
+
143
+ // 与 get 路径同一 orderBy:并发双保留行时读写对齐首行,避免保存后读不到
144
+ const existing = await prisma.testCaseFilterView.findFirst({
145
+ where: { userId, name: TESTCASE_DISPLAY_OPTIONS_VIEW_NAME },
146
+ orderBy: { createdAt: "asc" },
147
+ select: { id: true },
148
+ });
149
+ if (existing) {
150
+ await prisma.testCaseFilterView.update({
151
+ where: { id: existing.id },
152
+ data: { displayOptions: toJson(options) },
153
+ });
154
+ } else {
155
+ await prisma.testCaseFilterView.create({
156
+ data: {
157
+ userId,
158
+ name: TESTCASE_DISPLAY_OPTIONS_VIEW_NAME,
159
+ // schema 该列 Json 对象形状(默认 "{}"),与 bug 的数组形状不同
160
+ filters: toJson({}),
161
+ displayOptions: toJson(options),
162
+ },
163
+ });
164
+ }
165
+ return options;
166
+ });
@@ -0,0 +1,125 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // 用例列表 CSV 导出 serverFn(四期 Q8 任务 44,照 testcase-crud listTestCasesFn
19
+ // 口径同构)。权限:首行 requireAuth + resolveStoryScopeProjectIds(projectId
20
+ // 缺省 → 成员关系内全部项目聚合;传值 → 单项目经鉴权,防越权导出)。筛选:
21
+ // filters 为客户端 serializeFilters 产物(FilterCondition JSON 串),与列表共
22
+ // 用 server/testcase/list-query.ts buildListWhere 翻译——导出范围=当前筛选/
23
+ // 搜索/scope(设计 §8 审查断言④)。聚合:服务端游标分批(take 1000/批,
24
+ // sequenceId asc + id tiebreaker 与列表同序)取全量,CSV_EXPORT_MAX_ROWS 上限
25
+ // 截断置 truncated(R10 大数据量防炸)。编码:server/csv.ts toCsv(BOM+转义+
26
+ // 公式注入防护)。字段=列表显示属性(恒显编号/标题 + TESTCASE_DISPLAY_PROPERTIES
27
+ // 四项),DB 枚举原值输出保持机器可读。
28
+ import { getPrisma } from "@allbluecn/database/get-prisma";
29
+ import { createServerFn } from "@tanstack/react-start";
30
+ import { getRequest } from "@tanstack/react-start/server";
31
+ import { z } from "zod";
32
+ import type { Prisma } from "@allbluecn/database";
33
+ import { requireAuth } from "@/server/auth-helpers";
34
+ import { resolveStoryScopeProjectIds } from "@/server/builtin-project";
35
+ import { buildListWhere } from "@/server/testcase/list-query";
36
+ import { CSV_EXPORT_MAX_ROWS, toCsv, type CsvCell } from "@/server/csv";
37
+
38
+ const exportSchema = z.object({
39
+ // 「全部项目」聚合:缺省 → 成员关系内全部项目(同 listTestCasesFn scope 口径);
40
+ // 传值 → 单项目经鉴权
41
+ projectId: z.string().optional(),
42
+ // FilterCondition 三段式(serializeFilters 产物),buildListWhere 翻译
43
+ filters: z.string().optional(),
44
+ // 列表搜索框文本(title/identifier 双列 insensitive)
45
+ search: z.string().optional(),
46
+ // CSV 表头语言(前端随 paraglide getLocale 透传;缺省 zh 兼容旧调用)
47
+ locale: z.enum(["zh", "en"]).default("zh"),
48
+ });
49
+
50
+ /** CSV 表头按 locale 切换(数据行枚举保持 DB 原值,机器可读) */
51
+ const EXPORT_HEADERS: Record<"zh" | "en", string[]> = {
52
+ zh: ["编号", "标题", "类型", "优先级", "状态", "负责人"],
53
+ en: ["ID", "Title", "Type", "Priority", "State", "Assignee"],
54
+ };
55
+
56
+ /** 游标聚合批大小(每批一条查询,防一次性大 take 拖垮 DB) */
57
+ const BATCH_SIZE = 1000;
58
+
59
+ /** 导出列 = 列表显示属性(id 供游标翻页取值;user brief 仅取 username 展示列) */
60
+ const TEST_CASE_EXPORT_SELECT = {
61
+ id: true,
62
+ identifier: true,
63
+ title: true,
64
+ type: true,
65
+ priority: true,
66
+ state: true,
67
+ assignee: { select: { username: true } },
68
+ } satisfies Prisma.TestCaseDefaultArgs["select"];
69
+
70
+ type TestCaseExportRow = Prisma.TestCaseGetPayload<{ select: typeof TEST_CASE_EXPORT_SELECT }>;
71
+
72
+ export const exportTestCasesFn = createServerFn({ method: "GET" })
73
+ .validator(exportSchema)
74
+ .handler(async ({ data }) => {
75
+ const prisma = await getPrisma();
76
+ const request = getRequest();
77
+ const session = await requireAuth(request);
78
+ const userId = session.user!.id!;
79
+
80
+ const projectIds = await resolveStoryScopeProjectIds(data.projectId, userId);
81
+
82
+ // 导出范围=当前筛选:与 listTestCasesFn 完全同款 where(归档闸门固定未归档
83
+ // ——归档是「回收站」语义,CSV 导出面向在用数据)
84
+ const where = buildListWhere({
85
+ archived: false,
86
+ projectIds,
87
+ searchText: data.search,
88
+ filters: data.filters,
89
+ });
90
+
91
+ const rows: TestCaseExportRow[] = [];
92
+ let cursor: string | undefined;
93
+ let lastBatchFull = false;
94
+ while (rows.length < CSV_EXPORT_MAX_ROWS) {
95
+ const take = Math.min(BATCH_SIZE, CSV_EXPORT_MAX_ROWS - rows.length);
96
+ const batch = await prisma.testCase.findMany({
97
+ where,
98
+ orderBy: [{ sequenceId: "asc" }, { id: "asc" }],
99
+ take,
100
+ ...(cursor ? { skip: 1, cursor: { id: cursor } } : {}),
101
+ select: TEST_CASE_EXPORT_SELECT,
102
+ });
103
+ rows.push(...batch);
104
+ lastBatchFull = batch.length >= take;
105
+ if (!lastBatchFull) break;
106
+ cursor = batch[batch.length - 1]!.id;
107
+ }
108
+ // 恰好 20000 整除时末批可能全满:按「疑似还有更多」置 truncated(宁多提示
109
+ // 不漏提示,前端 toast 文案即按上限表述)
110
+ const truncated = rows.length >= CSV_EXPORT_MAX_ROWS && lastBatchFull;
111
+
112
+ const csvRows: CsvCell[][] = [
113
+ EXPORT_HEADERS[data.locale],
114
+ ...rows.map((r) => [
115
+ r.identifier,
116
+ r.title,
117
+ r.type,
118
+ r.priority,
119
+ r.state,
120
+ r.assignee?.username ?? null,
121
+ ]),
122
+ ];
123
+
124
+ return { csv: toCsv(csvRows), truncated };
125
+ });
@@ -28,8 +28,16 @@ import { getRequest } from "@tanstack/react-start/server";
28
28
  import { z } from "zod";
29
29
  import type { Prisma } from "@allbluecn/database";
30
30
  import { requireAuth } from "@/server/auth-helpers";
31
+ import { TESTCASE_DISPLAY_OPTIONS_VIEW_NAME } from "@/components/testcase/display/field-configs";
31
32
 
32
- /** 筛选对象(规格 §3.3 注释钉死键集;值全为 string | null,跨 JSON 边界可序列化) */
33
+ // 显示选项保留行(任务 20,testcase-display-options.ts 落点)对视图清单
34
+ // 不可见、不可建/改(bug-views.ts NOT_RESERVED 同构收口)。
35
+ const NOT_RESERVED = { name: { not: TESTCASE_DISPLAY_OPTIONS_VIEW_NAME } };
36
+
37
+ /** 筛选对象(规格 §3.3 注释钉死键集;值全为 string | null,跨 JSON 边界可序列化)。
38
+ * conditions(任务 21):serializeFilters 产物(FilterCondition JSON 串)——
39
+ * DB Json 对象形状不变仍为 string 值域;存量行的扁平 state/type/priority 键
40
+ * 保留读取兼容(view-types 的 viewFiltersToState 回落映射) */
33
41
  export interface TestCaseViewFilters {
34
42
  folderId?: string | null;
35
43
  type?: string | null;
@@ -40,12 +48,15 @@ export interface TestCaseViewFilters {
40
48
  label?: string | null;
41
49
  storyId?: string | null;
42
50
  searchText?: string | null;
51
+ conditions?: string | null;
43
52
  }
44
53
 
45
- /** 显示选项(groupBy/view 缺省由前端自补默认;density 预留) */
54
+ /** 显示选项(groupBy/view 缺省由前端自补默认;density 预留;view=board 为
55
+ * PR-7b 任务 26 新增,任务 27 移除 "table" 档——displayOptions 为 Json 透传
56
+ * 存储,类型仅客户端契约,存量 "table" 在 toView 读取面回落 "list") */
46
57
  export interface TestCaseViewDisplayOptions {
47
58
  groupBy?: "folder" | "state" | "priority" | "none";
48
- view?: "list" | "table";
59
+ view?: "list" | "board";
49
60
  density?: string;
50
61
  }
51
62
 
@@ -85,6 +96,16 @@ function asObject(value: unknown): Record<string, unknown> {
85
96
  : {};
86
97
  }
87
98
 
99
+ /** displayOptions.view 存量回退(任务 27):表格视图已移除,DB Json 透传行的
100
+ * "table" 残留在唯一 DTO 反序列化收口(toView,list/create/update 三路径共用)
101
+ * 回落 "list";board 保留,缺省不在此补(前端 applyView ?? "list" 口径不变)。
102
+ * 比较取原始 unknown 值——cast 后的联合类型与 "table" 无重叠(TS2367),
103
+ * 而 Json 透传行运行时取值本不受接口契约约束 */
104
+ function normalizeDisplayOptions(value: Record<string, unknown>): TestCaseViewDisplayOptions {
105
+ const options = value as TestCaseViewDisplayOptions;
106
+ return value.view === "table" ? { ...options, view: "list" } : options;
107
+ }
108
+
88
109
  function toView(row: ViewRow): TestCaseFilterViewDTO {
89
110
  return {
90
111
  id: row.id,
@@ -93,7 +114,7 @@ function toView(row: ViewRow): TestCaseFilterViewDTO {
93
114
  icon: row.icon,
94
115
  projectId: row.projectId,
95
116
  filters: asObject(row.filters) as TestCaseViewFilters,
96
- displayOptions: asObject(row.displayOptions) as TestCaseViewDisplayOptions,
117
+ displayOptions: normalizeDisplayOptions(asObject(row.displayOptions)),
97
118
  sortOrder: row.sortOrder,
98
119
  createdAt: row.createdAt.toISOString(),
99
120
  updatedAt: row.updatedAt.toISOString(),
@@ -110,7 +131,7 @@ export const listFilterViewsFn = createServerFn({ method: "GET" }).handler(async
110
131
  const session = await requireAuth(request);
111
132
  const userId = session.user!.id!;
112
133
  const rows = await prisma.testCaseFilterView.findMany({
113
- where: { userId },
134
+ where: { userId, ...NOT_RESERVED },
114
135
  orderBy: { sortOrder: "asc" },
115
136
  select: VIEW_SELECT,
116
137
  });
@@ -131,6 +152,9 @@ const createSchema = z.object({
131
152
  export const createFilterViewFn = createServerFn({ method: "POST" })
132
153
  .validator(createSchema)
133
154
  .handler(async ({ data }) => {
155
+ if (data.name === TESTCASE_DISPLAY_OPTIONS_VIEW_NAME) {
156
+ throw new Error("该视图名称为系统保留,请换一个名称");
157
+ }
134
158
  const prisma = await getPrisma();
135
159
  const request = getRequest();
136
160
  const session = await requireAuth(request);
@@ -163,6 +187,9 @@ const updateSchema = z.object({
163
187
  export const updateFilterViewFn = createServerFn({ method: "POST" })
164
188
  .validator(updateSchema)
165
189
  .handler(async ({ data }) => {
190
+ if (data.name === TESTCASE_DISPLAY_OPTIONS_VIEW_NAME) {
191
+ throw new Error("该视图名称为系统保留,请换一个名称");
192
+ }
166
193
  const prisma = await getPrisma();
167
194
  const request = getRequest();
168
195
  const session = await requireAuth(request);
@@ -176,8 +203,8 @@ export const updateFilterViewFn = createServerFn({ method: "POST" })
176
203
  if (patch.filters !== undefined) updates.filters = toJson(patch.filters);
177
204
  if (patch.displayOptions !== undefined) updates.displayOptions = toJson(patch.displayOptions);
178
205
  const row = await prisma.testCaseFilterView.update({
179
- // userId 限定:他人视图改不到(不存在/越权时 P2025 由调用方感知)
180
- where: { id, userId },
206
+ // userId + 非保留名限定:他人视图与显示选项保留行均改不到(P2025 由调用方感知)
207
+ where: { id, userId, ...NOT_RESERVED },
181
208
  data: updates,
182
209
  select: VIEW_SELECT,
183
210
  });
@@ -194,8 +221,8 @@ export const deleteFilterViewFn = createServerFn({ method: "POST" })
194
221
  const session = await requireAuth(request);
195
222
  const userId = session.user!.id!;
196
223
  await prisma.testCaseFilterView.delete({
197
- // userId 限定:他人视图删不到
198
- where: { id: data.id, userId },
224
+ // userId + 非保留名限定:他人视图与显示选项保留行均删不到(照 bug-views.ts)
225
+ where: { id: data.id, userId, ...NOT_RESERVED },
199
226
  });
200
227
  return { success: true };
201
228
  });
@@ -16,9 +16,9 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  // 分组统计计数 serverFn(照 story-groups.ts listStoryGroupsFn 单函数口径,供分组列表头)。
19
- // 计数与 listTestCasesFn(testcase-crud.ts)同口径——下方 where 构造逐字段复制自其 listSchema
20
- // 分支(默认排除归档 + 全部筛选参数);crud 侧未来抽共享 buildListWhere 时两处合一,
21
- // 本文件测试已锁定当前 where 形状防漂移。空组不返回(groupBy 天然只出非空键)。
19
+ // 计数与 listTestCasesFn(testcase-crud.ts)同口径——where 构造两处合一,共用
20
+ // server/testcase/list-query.ts 的 buildListWhere(任务 21 抽建;本文件测试已
21
+ // 锁定 where 形状防漂移)。空组不返回(groupBy 天然只出非空键)。
22
22
  // label 返回枚举 key 原文(中文 label 由前端 i18n 映射;folder 维度 key 为 folderId/
23
23
  // unassigned,前端用已有模块树数据解析名称)。
24
24
  import { getPrisma } from "@allbluecn/database/get-prisma";
@@ -27,7 +27,8 @@ import { getRequest } from "@tanstack/react-start/server";
27
27
  import { z } from "zod";
28
28
  import type { Prisma } from "@allbluecn/database";
29
29
  import { requireAuth } from "@/server/auth-helpers";
30
- import { resolveStoryScopeProjectId } from "@/server/builtin-project";
30
+ import { resolveStoryScopeProjectIds } from "@/server/builtin-project";
31
+ import { buildListWhere } from "@/server/testcase/list-query";
31
32
  import {
32
33
  TEST_CASE_AUTOMATION_VALUES,
33
34
  TEST_CASE_PRIORITY_VALUES,
@@ -71,7 +72,8 @@ function deriveGroupKey(groupBy: TestCaseGroupBy, row: Record<string, unknown>):
71
72
  }
72
73
 
73
74
  const listGroupsSchema = z.object({
74
- projectId: z.string().min(1),
75
+ // 「全部项目」聚合(二期 TC-A):缺省 → 成员关系内全部项目,与 listTestCasesFn 一致
76
+ projectId: z.string().optional(),
75
77
  groupBy: z.enum(GROUP_BY_VALUES),
76
78
  // 以下筛选参数与 listTestCasesFn 一致(cursor/take 分页参数不适用于计数)
77
79
  folderId: z.string().nullable().optional(),
@@ -81,6 +83,9 @@ const listGroupsSchema = z.object({
81
83
  priority: z.enum(TEST_CASE_PRIORITY_VALUES).optional(),
82
84
  automation: z.enum(TEST_CASE_AUTOMATION_VALUES).optional(),
83
85
  assigneeId: z.string().nullable().optional(),
86
+ // FilterCondition 三段式(任务 21):与列表一致经 buildListWhere 翻译,
87
+ // 分组计数与列表行同口径
88
+ filters: z.string().optional(),
84
89
  // 归档口径与列表一致:缺省/false 仅未归档,true 仅归档
85
90
  archived: z.boolean().optional(),
86
91
  searchText: z.string().optional(),
@@ -94,26 +99,25 @@ export const listGroupsFn = createServerFn({ method: "GET" })
94
99
  const session = await requireAuth(request);
95
100
  const userId = session.user!.id!;
96
101
 
97
- // scope 解析与列表一致:未传/无效/非成员 → 静默回退独立项目,防越权查询
98
- const projectId = await resolveStoryScopeProjectId(data.projectId, userId);
102
+ // scope 解析与列表一致(同 story/story-groups 与 bug 域):未传 → 成员关系
103
+ // 全部项目聚合;单项目经鉴权(无效/非成员 → 静默回退独立项目),防越权查询
104
+ const projectIds = await resolveStoryScopeProjectIds(data.projectId, userId);
99
105
 
100
- const where: Prisma.TestCaseWhereInput = {
101
- projectId,
106
+ // where 构造与列表同源(server/testcase/list-query.ts buildListWhere,
107
+ // 任务 21 起两处合一——此前为逐字段复制,文件头注钉的收口兑现)
108
+ const where = buildListWhere({
102
109
  archived: data.archived ?? false,
103
- ...(data.folderId !== undefined && { folderId: data.folderId }),
104
- ...(data.storyId !== undefined && { storyId: data.storyId }),
105
- ...(data.state && { state: data.state }),
106
- ...(data.type && { type: data.type }),
107
- ...(data.priority && { priority: data.priority }),
108
- ...(data.automation && { automation: data.automation }),
109
- ...(data.assigneeId !== undefined && { assigneeId: data.assigneeId }),
110
- ...(data.searchText && {
111
- OR: [
112
- { title: { contains: data.searchText, mode: "insensitive" } },
113
- { identifier: { contains: data.searchText, mode: "insensitive" } },
114
- ],
115
- }),
116
- };
110
+ projectIds,
111
+ folderId: data.folderId,
112
+ storyId: data.storyId,
113
+ state: data.state,
114
+ type: data.type,
115
+ priority: data.priority,
116
+ automation: data.automation,
117
+ assigneeId: data.assigneeId,
118
+ filters: data.filters,
119
+ searchText: data.searchText,
120
+ });
117
121
 
118
122
  const counts = await prisma.testCase.groupBy({
119
123
  by: [GROUP_BY_FIELD[data.groupBy]],
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ // 用例看板手动排序 serverFn(UI 对齐二期 PR-7a 任务 25,照 reorderStoriesFn
19
+ // 同构:$transaction 批量 updateMany + 可见项目集过滤)。本文件只导出
20
+ // createServerFn 产物。purity 口径④豁免说明:boardOrder 是看板列内手动排序
21
+ // 权重(纯元数据,不在 CASE_Y_OWNED_FIELDS/CASE_Y_UPDATE_KEYS 协同写面内),
22
+ // 与 story 的 boardOrder 直写先例(story-search.ts reorderStoriesFn)一致,
23
+ // 无 withCollabGuard/touchCollabSnapshot 接线。
24
+ import { getPrisma } from "@allbluecn/database/get-prisma";
25
+ import { createServerFn } from "@tanstack/react-start";
26
+ import { getRequest } from "@tanstack/react-start/server";
27
+ import { z } from "zod";
28
+ import { requireAuth } from "@/server/auth-helpers";
29
+
30
+ const reorderSchema = z.object({
31
+ // 批量上限 500(story-crud/bulk idListSchema 同款):防误用超大拖垮 $transaction
32
+ items: z
33
+ .array(
34
+ z.object({
35
+ id: z.string(),
36
+ boardOrder: z.number().int().min(0),
37
+ }),
38
+ )
39
+ .max(500),
40
+ });
41
+
42
+ export const reorderTestCasesFn = createServerFn({ method: "POST" })
43
+ .validator(reorderSchema)
44
+ .handler(async ({ data }) => {
45
+ const prisma = await getPrisma();
46
+ const request = getRequest();
47
+ const session = await requireAuth(request);
48
+ // 可见性过滤进 where(SEC-03:防跨项目改他人用例排序);updateMany 对
49
+ // 越权/不存在 id 静默跳过,不中断整批合法排序
50
+ const memberships = await prisma.projectMember.findMany({
51
+ where: { userId: session.user!.id!, status: { not: "suspended" } },
52
+ select: { projectId: true },
53
+ });
54
+ const visibleProjectIds = memberships.map((m) => m.projectId);
55
+
56
+ await prisma.$transaction(
57
+ data.items.map((item) =>
58
+ prisma.testCase.updateMany({
59
+ where: { id: item.id, projectId: { in: visibleProjectIds } },
60
+ data: { boardOrder: item.boardOrder },
61
+ }),
62
+ ),
63
+ );
64
+
65
+ return { success: true };
66
+ });
@@ -0,0 +1,57 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import { describe, expect, it } from "vitest";
19
+ import { toDateKeyUtc } from "../date-key";
20
+
21
+ /**
22
+ * task-8 根因 B 回归钉:日期变化检测的旧值日键必须取 UTC 日切片
23
+ * (与客户端 parseDate / 甘特回写 payload 同域),不得用服务器本地日键。
24
+ *
25
+ * 场景复现(Asia/Shanghai 服务器):种子行 startDate 存为 naive
26
+ * 「2026-10-01 19:47:18」(Prisma 回读 = 2026-10-01T19:47:18.713Z),
27
+ * 本地日键 =「2026-10-02」而客户端键 =「2026-10-01」——客户端把条形
28
+ * 拖后 1 天(payload「2026-10-02」)会被旧实现判为「无变化」→ 200 但
29
+ * 静默丢写,UI 乐观态与 DB 分叉。
30
+ */
31
+ describe("toDateKeyUtc", () => {
32
+ it("取 ISO(UTC) 日切片,与客户端 parseDate 切片同域", () => {
33
+ expect(toDateKeyUtc(new Date("2026-10-01T19:47:18.713Z"))).toBe("2026-10-01");
34
+ expect(toDateKeyUtc(new Date("2026-12-02T00:00:00.000Z"))).toBe("2026-12-02");
35
+ // 零点时刻(本代码路径写入的形态)两个日键恒一致
36
+ expect(toDateKeyUtc(new Date("2026-10-03T00:00:00.000Z"))).toBe("2026-10-03");
37
+ });
38
+
39
+ it("恒等于 toISOString 前 10 位(任意时区下语义不变)", () => {
40
+ for (const iso of [
41
+ "2026-01-31T23:59:59.999Z",
42
+ "2026-02-01T00:00:00.000Z",
43
+ "2026-06-15T12:34:56.789Z",
44
+ "2026-12-31T16:00:00.000Z",
45
+ ]) {
46
+ expect(toDateKeyUtc(new Date(iso))).toBe(iso.slice(0, 10));
47
+ }
48
+ });
49
+
50
+ it("gantt +1 天改期判定回归:payload 键 ≠ 旧值键 → 必须判为变化", () => {
51
+ // 789 号行实测数据:客户端显示 Oct 1,拖拽 +1 天 payload = "2026-10-02"
52
+ const oldKey = toDateKeyUtc(new Date("2026-10-01T19:47:18.713Z"));
53
+ const payload = "2026-10-02";
54
+ expect(oldKey).toBe("2026-10-01");
55
+ expect(payload !== oldKey).toBe(true);
56
+ });
57
+ });
@@ -0,0 +1,36 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ /**
19
+ * 日期变化检测的「旧值日键」——ISO(UTC) 日切片。
20
+ *
21
+ * 为什么必须是 UTC 日键、不能用服务器本地日键(task-8 根因 B):
22
+ * - 对外契约是 UTC:serverFn 序列化走 `toISOString()`,客户端 parseDate
23
+ * 切同一 UTC 日片,甘特拖拽回写 payload 的日键同源——「旧值键」必须
24
+ * 与客户端同域,否则比较错位。
25
+ * - 存储列为 naive timestamp:Prisma 按 UTC 墙钟回读;写入 `new
26
+ * Date("YYYY-MM-DD")` 却取服务器本地墙钟,两条口径只在零点时刻一致。
27
+ * 历史数据带非零时刻(如 2026-10-01T19:47:18Z)时,本地(+08)日键
28
+ * =「2026-10-02」而客户端键 =「2026-10-01」——客户端 +1 天改期会被
29
+ * 误判为「无变化」→ 200 但静默丢写(UI 乐观态与 DB 分叉)。
30
+ * - 新值键用 payload 自身(`data.startDate.slice(0, 10)`),对
31
+ * "YYYY-MM-DD" 输入与 UTC 日键恒等,写入经 Prisma 回读后收敛为
32
+ * payload 键(零点时刻),后续编辑不再错位。
33
+ */
34
+ export function toDateKeyUtc(d: Date): string {
35
+ return d.toISOString().slice(0, 10);
36
+ }