@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
@@ -15,7 +15,7 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
- import React, { lazy, Suspense, useCallback, useEffect, useRef, useState, type LazyExoticComponent, type ComponentType } from "react";
18
+ import React, { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState, type LazyExoticComponent, type ComponentType, type ReactElement, type ReactNode } from "react";
19
19
  import {
20
20
  ChevronsDownUp,
21
21
  ChevronsUpDown,
@@ -27,7 +27,6 @@ import {
27
27
  ClockArrowUp,
28
28
  } from "lucide-react";
29
29
  import { UserAvatar } from "@/components/shared-kit";
30
- import { Input } from "@/components/ui/input";
31
30
 
32
31
  import { Skeleton } from "@/components/ui/skeleton";
33
32
  import { Button } from "@/components/ui/button";
@@ -39,8 +38,9 @@ import { toast } from "@/components/ui/toast";
39
38
  import type { StoryDetail, UserBrief, CommentWithReplies, ActivityItem, ReactionGroup, RelationsData } from "../types";
40
39
  import { parseStoryDescription, serializeStoryDescription } from "../types";
41
40
  import type { StoryState } from "../constants";
41
+ import type { StoryPatch } from "../display/group-fields";
42
42
  import { ActivityTabs } from "./activity-tabs";
43
- import { EmojiPicker } from "./emoji-picker";
43
+ import { EmojiPicker } from "@/components/shared-kit/comments/emoji-picker";
44
44
  import { StoryAiToolbar } from "../ai/story-ai-toolbar";
45
45
  import { useStoryAiModel } from "../ai/use-story-ai-model";
46
46
  import { StateEditor, PriorityEditor, AssigneeEditor, DateEditor } from "../inline-editors";
@@ -53,6 +53,15 @@ import { useDebouncer } from "@/lib/pacer";
53
53
 
54
54
  import type { NotionDocEditorProps } from "@/components/tiptap/notion-like";
55
55
  import { errorMessage } from "@/lib/toast-error";
56
+ import { buildRoomName } from "@allbluecn/collab-core/rooms";
57
+ import { getUserColor } from "@/lib/collab/user-color";
58
+ import { useCollabDoc } from "@/lib/collab/use-collab-doc";
59
+ import type { CollabProviderHandle } from "@/lib/collab/provider";
60
+ import { useCollabFields } from "@/lib/collab/bindings/fields";
61
+ import { useSelfProfileBroadcast } from "@/lib/collab/use-self-profile-broadcast";
62
+ import type { NotionEditorCollabExtensions } from "@/components/tiptap/notion-like/editor-extensions";
63
+ import { FieldEditingPill } from "@/components/collab/field-editing-pill";
64
+ import { useSession } from "@/components/shared-kit/auth/auth-client";
56
65
  const NotionDocEditor = lazy(() => import("@/components/tiptap/notion-like").then(m => ({ default: m.NotionDocEditor }))) as LazyExoticComponent<ComponentType<NotionDocEditorProps>>;
57
66
 
58
67
  /**
@@ -66,29 +75,136 @@ const NotionDocEditor = lazy(() => import("@/components/tiptap/notion-like").the
66
75
  */
67
76
  const DESCRIPTION_FLUSH_DEBOUNCE_MS = 8000;
68
77
 
69
- export function StoryDetailBody({
70
- story,
71
- users,
72
- comments,
73
- activities,
74
- currentUserId,
75
- layout = "split",
76
- identifier,
77
- project,
78
- noScroll,
79
- labels,
80
- stories,
81
- projects,
82
- iterations,
83
- relationsData,
84
- onRelationsUpdate,
85
- onAppendActivities,
86
- onAppendComment,
87
- onRemoveComment,
88
- onStoryUpdate,
89
- onNavigateInPanel,
90
- breadcrumbItems,
91
- }: {
78
+ /**
79
+ * 协同态绑定 fields Y.Map 的字段清单(entity room Y 结构契约,§7.1 Story 行)。
80
+ * 覆盖本组件全部 DB 写通道对应的字段:标题/状态/优先级/负责人/起止日期/标签/
81
+ * 父需求——协同写手活跃期这些字段一律写 Y(exporter 落库),DB 通道短路。
82
+ * iteration 在本组件只做本地 UI 回显(无 serverFn 写通道),不入 Y。
83
+ */
84
+ const STORY_COLLAB_FIELDS = [
85
+ "name",
86
+ "state",
87
+ "priority",
88
+ "assigneeId",
89
+ "startDate",
90
+ "targetDate",
91
+ "labelIds",
92
+ "parentId",
93
+ ] as const;
94
+
95
+ /** 协同运行时状态(StoryCollabGate render-prop 产物;结构对齐任务 15 口径) */
96
+ interface StoryCollabRuntimeState {
97
+ /** 协同配置(null = 会话未就绪/协同不可用,编辑器暂不挂载协同扩展) */
98
+ config: NotionEditorCollabExtensions | null;
99
+ /** 会话是否就绪(协同配置稳定后再挂编辑器,防 caret user 半截装配) */
100
+ ready: boolean;
101
+ /** provider offline(降级单机模式,提示条 + 旧保存链路) */
102
+ offline: boolean;
103
+ /** provider connecting(建连窗口,轻提示条;窗口内 Y 是唯一写手) */
104
+ connecting: boolean;
105
+ /** 协同写手活跃(connecting/synced 均算——Y 是唯一内容源,DB 链路短路) */
106
+ collabActive: boolean;
107
+ /** room 连接句柄(字段 binding 数据源;null = 非协同/noop) */
108
+ handle: CollabProviderHandle | null;
109
+ /** 字段 editing 标记署名(session brief;null 走 binding 内 profile/clientID 降级) */
110
+ identity: { userId: string; name: string } | null;
111
+ }
112
+
113
+ /** 非协同分支的协同状态常量(稳定引用,行为与既有单机链路逐字一致) */
114
+ const NON_COLLAB_STATE: StoryCollabRuntimeState = {
115
+ config: null,
116
+ ready: true,
117
+ offline: false,
118
+ connecting: false,
119
+ collabActive: false,
120
+ handle: null,
121
+ identity: null,
122
+ };
123
+
124
+ interface StoryCollabGateProps {
125
+ storyId: string;
126
+ snapshot: string | null;
127
+ children: (state: StoryCollabRuntimeState) => ReactNode;
128
+ }
129
+
130
+ /**
131
+ * 协同运行时装配(条件挂载于 collab 分支,非协同路径不产生任何协同 hook
132
+ * 观察/连接——结构对齐任务 15 CollabDocGate):会话 brief(caret 用户 + 字段
133
+ * editing 署名)+ entity room 建连/快照预水合/状态订阅。
134
+ */
135
+ function StoryCollabGate({ storyId, snapshot, children }: StoryCollabGateProps) {
136
+ const { data: session, isPending } = useSession();
137
+ const roomName = useMemo(
138
+ () => buildRoomName({ kind: "entity-story", storyId }),
139
+ [storyId],
140
+ );
141
+ const { handle, status } = useCollabDoc({ roomName, snapshot });
142
+ // 自身 profile 广播:字段 binding 的 identity 缺省路径(会话 pending 期间
143
+ // 创建 binding)读 awareness profile 兜底署名——无广播则 editing 标记降级
144
+ // 为 client:{clientID}(远端 pill 无名)。Story 详情无 presence 头像组,
145
+ // 广播在本 gate 自持(hook 自 collab-presence-header 提取复用)。
146
+ useSelfProfileBroadcast(handle);
147
+
148
+ const user = session?.user?.id
149
+ ? {
150
+ name: session.user.name || session.user.id,
151
+ color: getUserColor(session.user.id),
152
+ }
153
+ : null;
154
+
155
+ const config: NotionEditorCollabExtensions | null =
156
+ handle && user
157
+ ? {
158
+ ydoc: handle.doc,
159
+ // noop/awareness 缺失时不挂 Caret(扩展工厂按 provider 判定)
160
+ provider: handle.awareness ? handle : null,
161
+ user,
162
+ }
163
+ : null;
164
+
165
+ // I1 钉住(同任务 15):扩展集在编辑器创建时冻结,config 一旦非空就沿用
166
+ // 首个非空值——防中途回落 null 时 stale 内容经仍在位的 Collaboration 插件
167
+ // 写进 Y 并同步给协作者(协同内容静默回滚)。
168
+ const pinnedConfigRef = useRef<NotionEditorCollabExtensions | null>(null);
169
+ if (pinnedConfigRef.current === null && config !== null) {
170
+ pinnedConfigRef.current = config;
171
+ }
172
+ const effectiveConfig = pinnedConfigRef.current;
173
+
174
+ // 会话就绪但拿不到 user(_nav beforeLoad 守卫下不应发生)时不进协同态:
175
+ // collabActive=false 让 DB 保存链路兜底,防「编辑器非 Y 绑定 + DB 短路」双失能
176
+ const sessionUsable = isPending || user != null;
177
+
178
+ return (
179
+ <>
180
+ {children({
181
+ config: effectiveConfig,
182
+ ready: !isPending,
183
+ offline: status === "offline",
184
+ connecting: status === "connecting",
185
+ collabActive: sessionUsable && status !== null && status !== "offline",
186
+ handle,
187
+ identity:
188
+ session?.user?.id
189
+ ? { userId: session.user.id, name: session.user.name || session.user.id }
190
+ : null,
191
+ })}
192
+ </>
193
+ );
194
+ }
195
+
196
+ /** 详情面板可回写视图(列表/看板乐观更新)的字段集合——与 StoryPatch 对齐。
197
+ * projectId 不在其中(项目归属变更走专门的转移流程,不在此通道)。 */
198
+ const PATCHABLE_FIELDS = new Set<keyof StoryPatch>([
199
+ "name",
200
+ "state",
201
+ "priority",
202
+ "assigneeId",
203
+ "startDate",
204
+ "targetDate",
205
+ ]);
206
+
207
+ export interface StoryDetailBodyProps {
92
208
  story: StoryDetail;
93
209
  users: UserBrief[];
94
210
  comments: CommentWithReplies[];
@@ -114,14 +230,152 @@ export function StoryDetailBody({
114
230
  onAppendComment?: (comment: CommentWithReplies) => void;
115
231
  onRemoveComment?: (commentId: string) => void;
116
232
  onStoryUpdate?: (s: StoryDetail) => void;
233
+ /** 字段落库成功后的补丁回调(PeekPanel 回写列表/看板做乐观更新) */
234
+ onStoryPatch?: (storyId: string, patch: StoryPatch) => void;
117
235
  onNavigateInPanel?: (targetId: string, targetIdentifier?: string, relationKind?: "child" | "dependency") => void;
236
+ onOpenEntity?: (entity: import("../types").StoryEntityOverlay) => void;
237
+ /** 用例/BUG 行的面板内叠加打开(peek 传入;缺省整页跳转) */
118
238
  /** 内嵌面板导航面包屑(在面板内跳转时替代单层 identifier 显示) */
119
239
  breadcrumbItems?: Array<{ identifier?: string; projectId?: string; label: string; onClick?: () => void }>;
120
- }) {
240
+ /** 编辑器实例就绪回调(透传 NotionDocEditor.onEditorReady;测试观察点) */
241
+ onEditorReady?: (editor: Editor) => void;
242
+ /**
243
+ * 协同模式(Yjs 多人协作任务 20,规格 §7.1 Story 行):true 时 description
244
+ * 接 Collaboration(Y fragment 'default')+ 字段接 useCollabFields,全部
245
+ * 保存通道(8s 防抖 / 失焦 / 卸载 / meta 字段 / 标签 / 父需求)短路——
246
+ * Y 是唯一写手;offline 降级恢复 DB 链路。缺省 false = 现有单机链路逐字保留。
247
+ */
248
+ collab?: boolean;
249
+ /** SSR 协同快照(loader 并行取 getCollabSnapshotFn;collab=true 时必传,null = 空文档) */
250
+ collabSnapshot?: string | null;
251
+ }
252
+
253
+ /** memo:顶栏滚动标题等轻量 state 翻转时,详情巨树(面板/行列表)必须整体跳过
254
+ * 重渲染,否则越阈一次卡几百毫秒、标题延迟出现。调用方回调需 useCallback 稳定。 */
255
+ export const StoryDetailBody = React.memo(function StoryDetailBody(props: StoryDetailBodyProps) {
256
+ const { collab, collabSnapshot, ...bodyProps } = props;
257
+ // 协同双模式:collab=true 时 gate 组件持有 useSession/useCollabDoc(条件
258
+ // 挂载——非协同路径不产生任何协同 hook 观察/连接);其余完全同分支。
259
+ if (collab) {
260
+ return (
261
+ <StoryCollabGate storyId={bodyProps.story.id} snapshot={collabSnapshot ?? null}>
262
+ {(collabState) => (
263
+ <StoryDetailBodyInner
264
+ {...bodyProps}
265
+ collabEnabled
266
+ collabState={collabState}
267
+ />
268
+ )}
269
+ </StoryCollabGate>
270
+ );
271
+ }
272
+ return (
273
+ <StoryDetailBodyInner
274
+ {...bodyProps}
275
+ collabEnabled={false}
276
+ collabState={NON_COLLAB_STATE}
277
+ />
278
+ );
279
+ });
280
+
281
+ function StoryDetailBodyInner({
282
+ story,
283
+ users,
284
+ comments,
285
+ activities,
286
+ currentUserId,
287
+ layout = "split",
288
+ identifier,
289
+ project,
290
+ noScroll,
291
+ labels,
292
+ stories,
293
+ projects,
294
+ iterations,
295
+ relationsData,
296
+ onRelationsUpdate,
297
+ onAppendActivities,
298
+ onAppendComment,
299
+ onRemoveComment,
300
+ onStoryUpdate,
301
+ onStoryPatch,
302
+ onNavigateInPanel,
303
+ onOpenEntity,
304
+ breadcrumbItems,
305
+ onEditorReady: onEditorReadyProp,
306
+ collabEnabled,
307
+ collabState,
308
+ }: StoryDetailBodyProps & { collabEnabled: boolean; collabState: StoryCollabRuntimeState }) {
121
309
  const [name, setName] = useState(story.name);
310
+ // 本详情页会话内新建的标签(labels prop/loader 清单不含;并入后 chip 才能渲染名字)
311
+ const [createdLabels, setCreatedLabels] = useState<{ id: string; name: string; color: string }[]>([]);
312
+ const mergedLabels = useMemo(() => {
313
+ const base = [...(labels ?? []), ...createdLabels];
314
+ const seen = new Set<string>();
315
+ return base.filter((l) => (seen.has(l.id) ? false : (seen.add(l.id), true)));
316
+ }, [labels, createdLabels]);
317
+ const handleLabelCreated = (label: { id: string; name: string; color: string }) => {
318
+ setCreatedLabels((prev) => (prev.some((l) => l.id === label.id) ? prev : [...prev, label]));
319
+ };
122
320
  useEffect(() => {
123
321
  setName(story.name);
124
322
  }, [story.name]);
323
+
324
+ // ── 协同态运行时(任务 20)──────────────────────────────────────────
325
+ // 核心不变式(对齐任务 15):任一时刻只有一条保存链路活跃。
326
+ // collabActive(connecting/synced)→ Y 唯一写手,DB 全通道短路;
327
+ // offline(降级单机)→ DB 通道恢复,Y 仅作编辑器内部内容源。
328
+ const fields = useCollabFields(
329
+ collabState.handle,
330
+ STORY_COLLAB_FIELDS,
331
+ collabState.identity ? { identity: collabState.identity } : undefined,
332
+ );
333
+ const collabActiveRef = useRef(collabState.collabActive);
334
+ collabActiveRef.current = collabState.collabActive;
335
+
336
+ // 协同态展示覆盖:Y 已写的字段(非 undefined)覆盖 loader story 值——
337
+ // 远端字段变化实时回写本地 UI(§7.1「不再手动 F5」)。非协同恒等 story。
338
+ const fieldsValues = fields.values;
339
+ const effectiveStory = useMemo(() => {
340
+ if (!collabEnabled) return story;
341
+ const next: StoryDetail = { ...story };
342
+ if (typeof fieldsValues.name === "string") next.name = fieldsValues.name;
343
+ if (typeof fieldsValues.state === "string") next.state = fieldsValues.state as StoryDetail["state"];
344
+ if (typeof fieldsValues.priority === "string") next.priority = fieldsValues.priority as StoryDetail["priority"];
345
+ if (fieldsValues.assigneeId !== undefined) {
346
+ next.assignee =
347
+ fieldsValues.assigneeId == null
348
+ ? null
349
+ : users.find((u) => u.id === fieldsValues.assigneeId) ?? null;
350
+ }
351
+ if (fieldsValues.startDate !== undefined) next.startDate = fieldsValues.startDate as string | null;
352
+ if (fieldsValues.targetDate !== undefined) next.targetDate = fieldsValues.targetDate as string | null;
353
+ if (Array.isArray(fieldsValues.labelIds)) {
354
+ const ids = fieldsValues.labelIds as string[];
355
+ next.labels = mergedLabels.filter((l) => ids.includes(l.id));
356
+ }
357
+ if (fieldsValues.parentId !== undefined) {
358
+ const parentId = fieldsValues.parentId as string | null;
359
+ next.parentId = parentId;
360
+ if (!parentId) {
361
+ next.parent = null;
362
+ } else {
363
+ const found = stories?.find((s) => s.id === parentId);
364
+ next.parent = found
365
+ ? { id: found.id, title: found.name, status: found.state, identifier: found.identifier }
366
+ : null;
367
+ }
368
+ }
369
+ return next;
370
+ }, [collabEnabled, story, fieldsValues, users, mergedLabels, stories]);
371
+
372
+ // 协同:远端 name 改动实时回写本地输入框(与既有 story.name 回显同语义;
373
+ // 本地 blur 写 Y 后 yName 变化与本地值一致,setName 幂等)
374
+ const yName = collabEnabled ? fieldsValues.name : undefined;
375
+ useEffect(() => {
376
+ if (typeof yName === "string") setName(yName);
377
+ }, [yName]);
378
+
125
379
  const parsedDesc = parseStoryDescription(story.description);
126
380
  const [descContent, setDescContent] = useState(parsedDesc);
127
381
  const [saving, setSaving] = useState(false);
@@ -194,6 +448,7 @@ export function StoryDetailBody({
194
448
 
195
449
  const onEditorReady = useCallback((editor: Editor) => {
196
450
  editorRef.current = editor;
451
+ onEditorReadyProp?.(editor);
197
452
  requestAnimationFrame(checkOverflow);
198
453
  editor.on("focus", () => {
199
454
  setExpanded(true);
@@ -201,7 +456,7 @@ export function StoryDetailBody({
201
456
  editor.on("update", () => {
202
457
  requestAnimationFrame(checkOverflow);
203
458
  });
204
- }, [checkOverflow]);
459
+ }, [checkOverflow, onEditorReadyProp]);
205
460
 
206
461
  // Click outside the editor area → unfocus
207
462
  useEffect(() => {
@@ -219,12 +474,19 @@ export function StoryDetailBody({
219
474
  setIsEditorFocused(true);
220
475
  }, []);
221
476
 
477
+ // 保存通道守卫(审查重点 #3):协同写手活跃期 DB 通道全部短路——Y 是唯一
478
+ // 写手(exporter 落库,任务 21);降级(offline)自动恢复。防御性收敛在
479
+ // 各通道入口,未来新增调用点不需要记得检查协同态。
222
480
  const updateField = async (field: string, value: string | null) => {
481
+ if (collabEnabled && collabActiveRef.current) return;
223
482
  setSaving(true);
224
483
  try {
225
484
  const res = await updateStoryFn({ data: { storyId: story.id, [field]: value } });
226
485
  const updated = { ...story, ...res.story } as unknown as StoryDetail;
227
486
  onStoryUpdate?.(updated);
487
+ if (onStoryPatch && PATCHABLE_FIELDS.has(field as keyof StoryPatch)) {
488
+ onStoryPatch(story.id, { [field]: value } as StoryPatch);
489
+ }
228
490
  if (res.activities.length > 0) onAppendActivities?.(res.activities);
229
491
  } catch (err) {
230
492
  toast.error(m.story_detail_saveFailed(), {
@@ -236,11 +498,13 @@ export function StoryDetailBody({
236
498
  };
237
499
 
238
500
  const updateLabels = async (labelIds: string[]) => {
501
+ if (collabEnabled && collabActiveRef.current) return;
239
502
  setSaving(true);
240
503
  try {
241
504
  const res = await updateStoryFn({ data: { storyId: story.id, labelIds } });
242
505
  const updated = { ...story, ...res.story } as unknown as StoryDetail;
243
506
  onStoryUpdate?.(updated);
507
+ onStoryPatch?.(story.id, { labelIds });
244
508
  if (res.activities.length > 0) onAppendActivities?.(res.activities);
245
509
  } catch (err) {
246
510
  toast.error(m.story_detail_saveFailed(), {
@@ -252,6 +516,7 @@ export function StoryDetailBody({
252
516
  };
253
517
 
254
518
  const updateParent = async (parentId: string | null) => {
519
+ if (collabEnabled && collabActiveRef.current) return;
255
520
  setSaving(true);
256
521
  try {
257
522
  const res = await updateStoryFn({ data: { storyId: story.id, parentId } });
@@ -277,8 +542,52 @@ export function StoryDetailBody({
277
542
  }
278
543
  };
279
544
 
545
+ // 协同 name 通道:blur 写 Y(远端 pill 由 setEditing 标记)
546
+ const handleCollabNameBlur = () => {
547
+ fields.setEditing("name", false);
548
+ const trimmed = name.trim();
549
+ if (!trimmed || trimmed === effectiveStory.name) return;
550
+ fields.setValue("name", trimmed);
551
+ };
552
+
553
+ // 协同字段写手分派:collabActive → Y(本地立即生效 + 增量广播);降级 → DB
554
+ const fieldWrite = (field: string, value: string | null) => {
555
+ if (collabEnabled && collabActiveRef.current) {
556
+ fields.setValue(field, value);
557
+ return;
558
+ }
559
+ updateField(field, value);
560
+ };
561
+
562
+ const collabWriteParent = (parentId: string | null) => {
563
+ if (collabEnabled && collabActiveRef.current) {
564
+ fields.setValue("parentId", parentId);
565
+ return;
566
+ }
567
+ updateParent(parentId);
568
+ };
569
+
570
+ const collabWriteLabels = (labelIds: string[]) => {
571
+ if (collabEnabled && collabActiveRef.current) {
572
+ fields.setValue("labelIds", labelIds);
573
+ return;
574
+ }
575
+ updateLabels(labelIds);
576
+ };
577
+
578
+ // 卸载守卫:卸载后 flush 的成功回调不得再触发(peek 面板内导航时旧 body
579
+ // 的落盘成功若仍回写 onStoryUpdate,会把已切换到的新需求数据污染成旧的)
580
+ const unmountedRef = useRef(false);
581
+ useEffect(() => {
582
+ return () => {
583
+ unmountedRef.current = true;
584
+ };
585
+ }, []);
280
586
  const flushDescription = useCallback(() => {
281
587
  flushDebouncer.cancel();
588
+ // 协同写手活跃(connecting/synced):描述的 DB 通道短路(Y 唯一写手);
589
+ // 失焦 / 卸载 / 兜底防抖三条触发路径都汇聚到这里,一处守卫全覆盖
590
+ if (collabEnabled && collabActiveRef.current) return;
282
591
  const content = pendingDescRef.current;
283
592
  if (!content) return;
284
593
  pendingDescRef.current = null;
@@ -288,13 +597,18 @@ export function StoryDetailBody({
288
597
  updateStoryFn({ data: { storyId: story.id, description: json } })
289
598
  .then((res) => {
290
599
  lastPersistedRef.current = json;
600
+ // 落库成功即数据安全;卸载后(面板内已切到其他需求)不再回写视图
601
+ if (unmountedRef.current) return;
291
602
  const updated = { ...story, ...res.story } as unknown as StoryDetail;
292
603
  onStoryUpdate?.(updated);
293
604
  if (res.activities.length > 0) onAppendActivities?.(res.activities);
294
605
  })
295
- .catch((err) => toast.error(m.story_detail_saveFailed(), { description: errorMessage(err, m.story_comment_retry()) }))
606
+ .catch((err) => {
607
+ if (unmountedRef.current) return;
608
+ toast.error(m.story_detail_saveFailed(), { description: errorMessage(err, m.story_comment_retry()) });
609
+ })
296
610
  .finally(() => setSaving(false));
297
- }, [story.id, story, onAppendActivities, onStoryUpdate, flushDebouncer]);
611
+ }, [story.id, story, onAppendActivities, onStoryUpdate, flushDebouncer, collabEnabled]);
298
612
 
299
613
  // 用 ref 持有最新 flush 实现,避免 handleDescriptionChange / 失焦 / 卸载 effect
300
614
  // 依赖 flushDescription 的 deps 变化而反复触发。
@@ -305,11 +619,52 @@ export function StoryDetailBody({
305
619
 
306
620
  // 编辑器内容变化时只累积最新内容到 pendingDescRef,不立即保存。
307
621
  // 兜底防抖避免用户长时间不操作也最终落盘(防崩溃丢数据)。
622
+ // 协同态(含远端 update 触发的 onChange):Y 已吸收全部事务(Collaboration
623
+ // 扩展挂载于编辑器),不进 pendingDescRef、不武装 8s 兜底防抖——8s 防抖让位。
308
624
  const handleDescriptionChange = useCallback((content: { html: string; markdown: string }) => {
309
- pendingDescRef.current = content;
310
625
  setIsDescEmpty(!content.markdown.trim());
626
+ if (collabEnabled && collabActiveRef.current) {
627
+ // descContent 仅供 AI 工具栏取新鲜正文(content prop 已被协同配置忽略)
628
+ setDescContent(content);
629
+ return;
630
+ }
631
+ pendingDescRef.current = content;
311
632
  flushDebouncer.maybeExecute();
312
- }, [flushDebouncer]);
633
+ }, [flushDebouncer, collabEnabled]);
634
+
635
+ // 降级/恢复切换语义(核心不变式:任一时刻只有一条保存链路活跃):
636
+ // 切 offline 瞬间——把编辑器当前内容(含协同期 Y 独占、未落库的编辑)搬进
637
+ // pendingDescRef 并立即 flush,旧 DB 链路由此接管;切回 collabActive 瞬间
638
+ // ——清除 DB 保存意图(未到期的兜底防抖到期时被 flushDescription 守卫
639
+ // 短路),Y 继续增量同步,无内容丢失、无双写。
640
+ const prevCollabActiveRef = useRef(collabState.collabActive);
641
+ useEffect(() => {
642
+ if (!collabEnabled) return;
643
+ if (prevCollabActiveRef.current === collabState.collabActive) return;
644
+ prevCollabActiveRef.current = collabState.collabActive;
645
+ if (!collabState.collabActive) {
646
+ const ed = editorRef.current;
647
+ if (ed && !ed.isDestroyed) {
648
+ pendingDescRef.current = {
649
+ html: ed.getHTML(),
650
+ markdown:
651
+ (ed.storage.markdown as { manager?: { serialize?: (json: unknown) => string } } | undefined)
652
+ ?.manager?.serialize?.(ed.getJSON()) ?? "",
653
+ };
654
+ flushDescriptionRef.current();
655
+ }
656
+ } else {
657
+ pendingDescRef.current = null;
658
+ flushDebouncer.cancel();
659
+ }
660
+ }, [collabState.collabActive, collabEnabled, flushDebouncer]);
661
+
662
+ // 协同:描述编辑标记随编辑器焦点写/摘 awareness(远端见 description pill)
663
+ const setDescEditing = fields.setEditing;
664
+ useEffect(() => {
665
+ if (!collabEnabled) return;
666
+ setDescEditing("description", isEditorFocused);
667
+ }, [isEditorFocused, collabEnabled, setDescEditing]);
313
668
 
314
669
  // 编辑器失焦 → 立即提交本次编辑会话累积的内容(一次连续编辑 = 一条活动记录)
315
670
  useEffect(() => {
@@ -326,37 +681,76 @@ export function StoryDetailBody({
326
681
 
327
682
  const handleAiApply = useCallback((newMarkdown: string) => {
328
683
  if (editorRef.current) {
684
+ // 协同态 setContent 经 Collaboration 插件写进 Y(增量广播给协作者)
329
685
  editorRef.current.commands.setContent(newMarkdown);
330
686
  }
331
687
  const newContent = { html: newMarkdown, markdown: newMarkdown };
332
688
  setDescContent(newContent);
333
689
  setIsDescEmpty(!newMarkdown.trim());
690
+ // 协同写手活跃:DB 通道短路(Y 已含 AI 改写结果,exporter 落库)
691
+ if (collabEnabled && collabActiveRef.current) return;
334
692
  const json = serializeStoryDescription(newMarkdown, newMarkdown);
335
693
  lastPersistedRef.current = json;
336
694
  updateField("description", json);
337
- }, [updateField]);
695
+ }, [updateField, collabEnabled]);
338
696
 
339
697
  // 截止日期已过期(未完成/未取消)→ 红色,与子需求列表样式一致
698
+ // (协同态读 effectiveStory——远端改 state/targetDate 实时生效)
340
699
  const dueOverdue = !!(
341
- story.targetDate &&
342
- story.state !== "done" &&
343
- story.state !== "cancelled" &&
344
- new Date(story.targetDate.slice(0, 10)) < new Date(new Date().toISOString().slice(0, 10))
700
+ effectiveStory.targetDate &&
701
+ effectiveStory.state !== "done" &&
702
+ effectiveStory.state !== "cancelled" &&
703
+ new Date(effectiveStory.targetDate.slice(0, 10)) < new Date(new Date().toISOString().slice(0, 10))
345
704
  );
346
705
 
706
+ // 协同:字段旁 pill 包装(仅协同态挂载;非协同保持原 JSX 结构逐字一致)
707
+ const withPill = (fieldName: string, editor: ReactElement) =>
708
+ collabEnabled ? (
709
+ <div className="flex items-center gap-1">
710
+ {editor}
711
+ <FieldEditingPill fieldName={fieldName} editors={fields.editingBy[fieldName]} users={users} />
712
+ </div>
713
+ ) : (
714
+ editor
715
+ );
716
+
717
+ // 协同:popover 编辑器的 open 状态外泄为 editing 标记(open=写、close=摘)
718
+ const editMarkProps = (fieldName: string) =>
719
+ collabEnabled
720
+ ? { onOpenChange: (open: boolean) => fields.setEditing(fieldName, open) }
721
+ : {};
722
+
347
723
  const metaBar = (
348
- <div className="flex flex-wrap items-center gap-2 px-6">
349
- <StateEditor value={story.state} onChange={(v) => updateField("state", v)} variant="button" />
350
- <PriorityEditor value={story.priority} onChange={(v) => updateField("priority", v)} variant="button" />
351
- <AssigneeEditor assignee={story.assignee} users={users} onChange={(v) => updateField("assigneeId", v)} variant="button" />
352
- <DateEditor value={story.startDate} onChange={(v) => updateField("startDate", v)} label={m.story_detail_startDate()} icon={CalendarPlus2} />
353
- <DateEditor value={story.targetDate} onChange={(v) => updateField("targetDate", v)} label={m.story_detail_dueDate()} icon={CalendarCheck} overdue={dueOverdue} />
724
+ <div className="flex flex-wrap items-center gap-2 px-3">
725
+ {withPill("state", <StateEditor value={effectiveStory.state} onChange={(v) => fieldWrite("state", v)} variant="button" {...editMarkProps("state")} />)}
726
+ {withPill("priority", <PriorityEditor value={effectiveStory.priority} onChange={(v) => fieldWrite("priority", v)} variant="button" {...editMarkProps("priority")} />)}
727
+ {withPill("assigneeId", <AssigneeEditor assignee={effectiveStory.assignee} users={users} onChange={(v) => fieldWrite("assigneeId", v)} variant="button" {...editMarkProps("assigneeId")} />)}
728
+ {withPill("startDate", <DateEditor value={effectiveStory.startDate} onChange={(v) => fieldWrite("startDate", v)} label={m.story_detail_startDate()} icon={CalendarPlus2} {...editMarkProps("startDate")} />)}
729
+ {withPill("targetDate", <DateEditor value={effectiveStory.targetDate} onChange={(v) => fieldWrite("targetDate", v)} label={m.story_detail_dueDate()} icon={CalendarCheck} overdue={dueOverdue} {...editMarkProps("targetDate")} />)}
354
730
  </div>
355
731
  );
356
732
 
357
733
  const main = (
358
734
  <>
359
- <div className="flex items-start justify-between gap-4 px-6">
735
+ {collabEnabled && collabState.connecting && (
736
+ <div
737
+ data-testid="collab-connecting-banner"
738
+ role="status"
739
+ className="bg-sky-500/10 text-sky-700 dark:text-sky-400 border-b border-sky-500/20 text-xs px-4 py-1.5 text-center"
740
+ >
741
+ {m.collab_connectingBanner()}
742
+ </div>
743
+ )}
744
+ {collabEnabled && collabState.offline && (
745
+ <div
746
+ data-testid="collab-offline-banner"
747
+ role="status"
748
+ className="bg-amber-500/10 text-amber-700 dark:text-amber-400 border-b border-amber-500/20 text-xs px-4 py-1.5 text-center"
749
+ >
750
+ {m.collab_offlineBanner()}
751
+ </div>
752
+ )}
753
+ <div className="flex items-start justify-between gap-4 px-3">
360
754
  <div className="flex-1 space-y-4">
361
755
  <div className="flex items-center gap-2">
362
756
  {breadcrumbItems && breadcrumbItems.length > 0 ? (
@@ -412,16 +806,31 @@ export function StoryDetailBody({
412
806
  )}
413
807
  </div>
414
808
  {/* {saving && <span className="text-xs text-muted-foreground">保存中...</span>} */}
415
- <Input
809
+ <textarea
416
810
  value={name}
811
+ rows={1}
812
+ aria-label={m.story_inline_titlePlaceholder()}
417
813
  onChange={(e) => setName(e.target.value)}
418
- onBlur={saveName}
419
- className="border-0 px-0 text-2xl md:text-2xl font-semibold shadow-none focus-visible:ring-0"
814
+ onFocus={collabEnabled ? () => fields.setEditing("name", true) : undefined}
815
+ onBlur={collabEnabled ? handleCollabNameBlur : saveName}
816
+ onKeyDown={(e) => {
817
+ if (e.key === "Enter" && !e.shiftKey) {
818
+ e.preventDefault();
819
+ e.currentTarget.blur();
820
+ }
821
+ }}
822
+ className="block w-full resize-none border-0 px-0 text-2xl leading-8 md:text-2xl font-semibold shadow-none outline-none field-sizing-content bg-transparent dark:bg-transparent focus:bg-input/30 dark:focus:bg-input/30"
420
823
  />
421
824
  </div>
422
825
  </div>
423
826
  {metaBar}
424
827
  <div ref={editorContainerRef} className={`relative notion-full-width${!isEditorFocused && isDescEmpty ? " story-editor-no-placeholder" : ""}`} style={{ "--notion-content-width": "1fr", "--notion-margin-width": "0px" } as React.CSSProperties} onPointerDown={onEditorPointerDown}>
828
+ {/* 协同:description 编辑 pill(编辑器右上角;§10.1 字段型行) */}
829
+ {collabEnabled && (
830
+ <div className="absolute top-2 right-4 z-10">
831
+ <FieldEditingPill fieldName="description" editors={fields.editingBy.description} users={users} />
832
+ </div>
833
+ )}
425
834
  <div className={!expanded && needsTruncation ? "max-h-[90px] overflow-hidden" : ""}>
426
835
  <Suspense fallback={
427
836
  <div className="flex flex-col space-y-2 p-3 min-h-24">
@@ -430,17 +839,20 @@ export function StoryDetailBody({
430
839
  <Skeleton className="h-4 w-2/3" />
431
840
  </div>
432
841
  }>
433
- <NotionDocEditor
434
- key={story.id}
435
- content={descContent}
436
- onChange={handleDescriptionChange}
437
- showHeader={false}
438
- hideInsertButton
439
- hideAi
440
- editorClassName="story-editor !ml-0"
441
- onEditorReady={onEditorReady}
442
- disableMobileFullscreen
443
- />
842
+ {collabState.ready && (
843
+ <NotionDocEditor
844
+ key={story.id}
845
+ content={collabState.config ? undefined : descContent}
846
+ onChange={handleDescriptionChange}
847
+ showHeader={false}
848
+ hideInsertButton
849
+ hideAi
850
+ editorClassName="story-editor !ml-0"
851
+ onEditorReady={onEditorReady}
852
+ disableMobileFullscreen
853
+ collab={collabState.config ?? undefined}
854
+ />
855
+ )}
444
856
  </Suspense>
445
857
  </div>
446
858
  {!isEditorFocused && isDescEmpty && (
@@ -452,14 +864,14 @@ export function StoryDetailBody({
452
864
  <div className="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-background to-transparent pointer-events-none" />
453
865
  )}
454
866
  </div>
455
- <div className="px-6">
867
+ <div className="px-3">
456
868
  <div className="flex items-center gap-2">
457
869
  <div className="flex min-w-0 flex-1 items-center gap-1.5">
458
870
  {needsTruncation && (
459
871
  <Button
460
872
  variant="ghost"
461
873
  size="sm"
462
- className="h-6 shrink-0 pl-0 pr-2 text-xs text-muted-foreground"
874
+ className="h-6 shrink-0 -ml-2 px-2 text-xs text-muted-foreground"
463
875
  onClick={() => setExpanded(!expanded)}
464
876
  >
465
877
  {expanded ? (
@@ -535,7 +947,7 @@ export function StoryDetailBody({
535
947
  <Button
536
948
  variant="ghost"
537
949
  size="sm"
538
- className="h-7 gap-1 pl-0 pr-2 text-xs text-muted-foreground"
950
+ className="h-7 gap-1 px-2 text-xs text-muted-foreground"
539
951
  >
540
952
  <SmilePlus className="size-3.5" />
541
953
  {m.story_detail_addReaction()}
@@ -571,33 +983,36 @@ export function StoryDetailBody({
571
983
  storyId={story.id}
572
984
  storyIdentifier={identifier}
573
985
  projectId={story.projectId}
986
+ storyUpdatedAt={effectiveStory.updatedAt}
574
987
  data={relationsData}
575
988
  onRefresh={onRelationsUpdate ?? (() => { })}
576
989
  onAppendActivities={onAppendActivities}
577
990
  users={users}
578
991
  projects={projects?.map((p) => ({ ...p, displayId: p.displayId ?? null }))}
579
- labels={labels}
992
+ labels={mergedLabels}
580
993
  onNavigateInPanel={onNavigateInPanel}
994
+ onOpenEntity={onOpenEntity}
581
995
  />
582
996
  </div>
583
997
  )}
584
998
 
585
999
  <StoryProperties
586
1000
  story={{
587
- id: story.id,
588
- name: story.name,
589
- description: story.description,
590
- parentId: story.parentId,
591
- parent: story.parent,
592
- labels: story.labels,
593
- projectId: story.projectId,
594
- iteration: story.iteration ?? null,
1001
+ id: effectiveStory.id,
1002
+ name: effectiveStory.name,
1003
+ description: effectiveStory.description,
1004
+ parentId: effectiveStory.parentId,
1005
+ parent: effectiveStory.parent,
1006
+ labels: effectiveStory.labels,
1007
+ projectId: effectiveStory.projectId,
1008
+ iteration: effectiveStory.iteration ?? null,
595
1009
  }}
596
- labels={labels}
1010
+ labels={mergedLabels}
597
1011
  projects={projects}
598
1012
  iterations={iterations}
599
- onParentChange={updateParent}
600
- onLabelsChange={updateLabels}
1013
+ onParentChange={collabWriteParent}
1014
+ onLabelsChange={collabWriteLabels}
1015
+ onLabelCreated={handleLabelCreated}
601
1016
  onIterationChange={(it) => {
602
1017
  if (onStoryUpdate) {
603
1018
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -607,7 +1022,7 @@ export function StoryDetailBody({
607
1022
  onActivities={onAppendActivities}
608
1023
  />
609
1024
 
610
- <div className="px-6 py-3 space-y-3.5">
1025
+ <div className="px-3 py-3 space-y-3.5">
611
1026
  <div className="flex items-center gap-2 text-xs">
612
1027
  <CircleUser className="size-4 shrink-0 text-muted-foreground" />
613
1028
  <span className="text-muted-foreground shrink-0">{m.story_detail_createdBy()}</span>
@@ -632,7 +1047,10 @@ export function StoryDetailBody({
632
1047
 
633
1048
  if (layout === "stack") {
634
1049
  return (
635
- <div className={`flex min-h-0 flex-1 flex-col ${noScroll ? "" : "overflow-y-auto"}`}>
1050
+ <div
1051
+ data-slot="story-detail-scroll"
1052
+ className={`flex min-h-0 flex-1 flex-col overflow-x-hidden ${noScroll ? "" : "overflow-y-auto"}`}
1053
+ >
636
1054
  <div className="flex-1 space-y-4 pt-6 pb-6">{main}</div>
637
1055
  </div>
638
1056
  );
@@ -640,7 +1058,14 @@ export function StoryDetailBody({
640
1058
 
641
1059
  return (
642
1060
  <div className={`flex min-h-0 flex-1 ${noScroll ? "" : "overflow-hidden"}`}>
643
- <div className={`flex-1 space-y-4 pt-6 pb-6 ${noScroll ? "" : "overflow-y-auto"}`}>{main}</div>
1061
+ {/* padding 放内层:sticky(tabs/面板头)的约束矩形是滚动容器减去 padding,
1062
+ padding 挂在滚动层会让吸顶元素永远留一条 padding 高的缝 */}
1063
+ <div
1064
+ data-slot="story-detail-scroll"
1065
+ className={`flex-1 overflow-x-hidden ${noScroll ? "" : "overflow-y-auto"}`}
1066
+ >
1067
+ <div className="space-y-4 pt-6 pb-6">{main}</div>
1068
+ </div>
644
1069
  </div>
645
1070
  );
646
1071
  }