@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,378 @@
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
+ // 协同客户端连接单例管理(§6)。
19
+ //
20
+ // ── 多路复用决策(2026-10-07 实测,HocuspocusProvider@4.7.0)──────────────
21
+ // 结论:采用 HocuspocusProviderWebsocket 共享单连接(多 room 复用一条
22
+ // WebSocket),回退方案(每 room 独立连接)不需要。
23
+ // 实测证据(apps/collab/src/__tests__/multiplex.test.ts,真 server 随机端口):
24
+ // ① 双 room 双活内容隔离 ✓ ② 并发建连/断开 20 轮无卡死、无串 room ✓
25
+ // ③ 即建即断竞态 10 轮不抛错 ✓ ④ 全套连续 10 次运行全绿 ✓
26
+ // 已证 4.7 约束:共享连接按 effectiveName 键控,同一 room 同时只允许一个
27
+ // provider(重复 attach 抛错)——本模块 entries 单例表天然保证;服务端
28
+ // (@hocuspocus/server@4.7)onAuthenticate 按文档执行,room 级 ticket 在
29
+ // 共享连接下成立,单 room 鉴权失败不断开整条连接。
30
+ // ──────────────────────────────────────────────────────────────────────
31
+ //
32
+ // 连接流程:serverFn 取 ticket(token 为 async 函数,每次 onOpen 重新取,
33
+ // 60s exp 的 ticket 过期即由重连自动重取)→ HocuspocusProvider 建连 →
34
+ // 鉴权失败(PermissionDenied/token 获取失败)按指数退避 detach/attach 重试
35
+ // (1s 起步 ×2,30s 封顶,对齐 use-notifications 的 sse-transport 参数),
36
+ // 连续 6 次失败进入 offline;socket 级连接失败由 provider 内置 retry 承接
37
+ // (同参数 + maxAttempts 6),耗尽同样进入 offline。
38
+ //
39
+ // 纪律:本目录禁止 import @/server/** 非 serverFns 子路径与
40
+ // @allbluecn/database(ESLint no-restricted-imports 守护);严禁引入
41
+ // @hocuspocus/server(P0 级客户端 bundle 污染)。
42
+
43
+ import {
44
+ HocuspocusProvider,
45
+ HocuspocusProviderWebsocket,
46
+ WebSocketStatus,
47
+ } from '@hocuspocus/provider'
48
+ import * as Y from 'yjs'
49
+ import { createCollabTicketFn } from '@/server/serverFns/collab'
50
+ import { getCollabWsUrl, isCollabEnabled } from './env'
51
+
52
+ /**
53
+ * awareness 实例类型(从 provider getter 派生,避免 apps/web 直依赖
54
+ * y-protocols——pnpm 严格隔离下非直接依赖不可 import)。
55
+ * noop 模式无 provider 实例,handle.awareness 为 null(presence 降级不渲染)。
56
+ */
57
+ export type CollabAwareness = NonNullable<HocuspocusProvider['awareness']>
58
+
59
+ /** 连接状态机(任务 15 降级模式消费):connecting 建连/重试中、synced 可协同、offline 不可用 */
60
+ export type CollabConnectionStatus = 'connecting' | 'synced' | 'offline'
61
+
62
+ type StatusListener = (status: CollabConnectionStatus) => void
63
+
64
+ /** 退避参数:对齐 sse-transport(use-notifications 重连先例)1s ×2 30s 封顶 */
65
+ const RECONNECT_INITIAL_DELAY = 1000
66
+ const RECONNECT_FACTOR = 2
67
+ const RECONNECT_MAX_DELAY = 30000
68
+ /** 连续失败上限:达到即 offline(auth 与 socket 两条路径同一口径) */
69
+ const MAX_ATTEMPTS = 6
70
+
71
+ /** 指数退避:第 n 次失败的等待时长(n 从 1 计) */
72
+ function backoffDelay(failures: number): number {
73
+ return Math.min(
74
+ RECONNECT_INITIAL_DELAY * RECONNECT_FACTOR ** (failures - 1),
75
+ RECONNECT_MAX_DELAY,
76
+ )
77
+ }
78
+
79
+ interface LiveEntry {
80
+ kind: 'live'
81
+ roomName: string
82
+ socketUrl: string
83
+ doc: Y.Doc
84
+ provider: HocuspocusProvider
85
+ status: CollabConnectionStatus
86
+ listeners: Set<StatusListener>
87
+ authFailures: number
88
+ retryTimer: ReturnType<typeof setTimeout> | null
89
+ /** 自定义 ticket 获取(presence/share 等非实体 room);缺省走实体 ticket serverFn */
90
+ getTicket?: TicketFetcher
91
+ handle: CollabProviderHandle
92
+ }
93
+
94
+ interface NoopEntry {
95
+ kind: 'noop'
96
+ roomName: string
97
+ doc: Y.Doc
98
+ status: 'offline'
99
+ listeners: Set<StatusListener>
100
+ handle: CollabProviderHandle
101
+ }
102
+
103
+ type Entry = LiveEntry | NoopEntry
104
+
105
+ /** 自定义 ticket 获取器(getOrCreateProvider opts.getTicket) */
106
+ type TicketFetcher = (roomName: string) => Promise<string>
107
+
108
+ /** room → 连接条目(单例表;同一 room 重复调用返回同一实例) */
109
+ const entries = new Map<string, Entry>()
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // 共享 socket 池(多路复用决策落地:每服务地址一条 WebSocket,引用计数管理)
113
+ // ---------------------------------------------------------------------------
114
+
115
+ interface SharedSocket {
116
+ socket: HocuspocusProviderWebsocket
117
+ refCount: number
118
+ }
119
+
120
+ const sharedSockets = new Map<string, SharedSocket>()
121
+
122
+ /** 测试注入口:替换底层 WebSocket 实现(生产走浏览器原生 WebSocket) */
123
+ type WebSocketPolyfillConstructor = abstract new (
124
+ url: string,
125
+ ) => EventTarget & {
126
+ send(data: ArrayBuffer | Uint8Array): void
127
+ close(): void
128
+ readyState: number
129
+ }
130
+ let webSocketPolyfillOverride: WebSocketPolyfillConstructor | undefined
131
+
132
+ function liveEntries(): LiveEntry[] {
133
+ return [...entries.values()].filter(
134
+ (entry): entry is LiveEntry => entry.kind === 'live',
135
+ )
136
+ }
137
+
138
+ function acquireSharedSocket(url: string): SharedSocket {
139
+ const existing = sharedSockets.get(url)
140
+ if (existing) {
141
+ existing.refCount += 1
142
+ return existing
143
+ }
144
+ const socket = new HocuspocusProviderWebsocket({
145
+ url,
146
+ delay: RECONNECT_INITIAL_DELAY,
147
+ initialDelay: 0,
148
+ factor: RECONNECT_FACTOR,
149
+ maxDelay: RECONNECT_MAX_DELAY,
150
+ maxAttempts: MAX_ATTEMPTS,
151
+ jitter: false,
152
+ WebSocketPolyfill: webSocketPolyfillOverride,
153
+ onStatus: ({ status }) => {
154
+ if (status !== WebSocketStatus.Disconnected) return
155
+ // 网络断开:未 offline 的活连接回到 connecting(重连后 onOpen 自动重鉴权)
156
+ for (const entry of liveEntries()) {
157
+ if (entry.status === 'synced') setEntryStatus(entry, 'connecting')
158
+ }
159
+ },
160
+ onMaxAttemptsFailed: () => {
161
+ // socket 级重试耗尽:所有活连接进入 offline
162
+ for (const entry of liveEntries()) setEntryStatus(entry, 'offline')
163
+ },
164
+ })
165
+ const shared: SharedSocket = { socket, refCount: 1 }
166
+ sharedSockets.set(url, shared)
167
+ return shared
168
+ }
169
+
170
+ function releaseSharedSocket(url: string): void {
171
+ const shared = sharedSockets.get(url)
172
+ if (!shared) return
173
+ shared.refCount -= 1
174
+ if (shared.refCount <= 0) {
175
+ sharedSockets.delete(url)
176
+ shared.socket.destroy()
177
+ }
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // 状态机与鉴权失败重试
182
+ // ---------------------------------------------------------------------------
183
+
184
+ function setEntryStatus(entry: Entry, status: CollabConnectionStatus): void {
185
+ if (entry.status === status) return
186
+ entry.status = status
187
+ for (const listener of entry.listeners) listener(status)
188
+ }
189
+
190
+ /**
191
+ * 鉴权失败(PermissionDenied / ticket 获取失败):detach 后按指数退避
192
+ * re-attach。attach 到已连接 socket 会重新走 onOpen → sendToken(新 ticket)。
193
+ * 共享 socket 上其余 room 不受影响(服务端按文档拒绝,连接存活)。
194
+ */
195
+ function handleAuthFailure(entry: LiveEntry): void {
196
+ if (entry.retryTimer) return
197
+ entry.authFailures += 1
198
+ if (entry.authFailures >= MAX_ATTEMPTS) {
199
+ // 上限:进入 offline,停止重试(重建 provider 才能恢复)
200
+ entry.provider.detach()
201
+ setEntryStatus(entry, 'offline')
202
+ return
203
+ }
204
+ setEntryStatus(entry, 'connecting')
205
+ const delay = backoffDelay(entry.authFailures)
206
+ entry.provider.detach()
207
+ entry.retryTimer = setTimeout(() => {
208
+ entry.retryTimer = null
209
+ if (entries.get(entry.roomName) !== entry) return // 已销毁
210
+ entry.provider.attach()
211
+ }, delay)
212
+ }
213
+
214
+ function createLiveEntry(roomName: string, getTicket?: TicketFetcher): LiveEntry {
215
+ const url = getCollabWsUrl()
216
+ const shared = acquireSharedSocket(url)
217
+ const doc = new Y.Doc()
218
+ // token 为 async 函数:每次 onOpen(含重连)都重新取 ticket,60s exp 的
219
+ // 过期 ticket 由重连自然重取,无需独立刷新逻辑;getTicket 覆盖仅在建连
220
+ // 时生效(单例表按 roomName 键控,同 room 的后续调用共享首个 fetcher)
221
+ const fetchTicket: TicketFetcher = getTicket ?? (async (name) => {
222
+ const { ticket } = await createCollabTicketFn({ data: { roomName: name } })
223
+ return ticket
224
+ })
225
+ const provider = new HocuspocusProvider({
226
+ websocketProvider: shared.socket,
227
+ name: roomName,
228
+ document: doc,
229
+ token: async () => fetchTicket(roomName),
230
+ onSynced: ({ state }) => {
231
+ if (!state) return
232
+ entry.authFailures = 0 // 完整恢复:清零退避计数
233
+ setEntryStatus(entry, 'synced')
234
+ },
235
+ onAuthenticationFailed: () => handleAuthFailure(entry),
236
+ })
237
+ const entry: LiveEntry = {
238
+ kind: 'live',
239
+ roomName,
240
+ socketUrl: url,
241
+ doc,
242
+ provider,
243
+ status: 'connecting',
244
+ listeners: new Set(),
245
+ authFailures: 0,
246
+ retryTimer: null,
247
+ getTicket,
248
+ handle: null as unknown as CollabProviderHandle,
249
+ }
250
+ entry.handle = buildHandle(entry)
251
+ entries.set(roomName, entry)
252
+ provider.attach()
253
+ return entry
254
+ }
255
+
256
+ function createNoopEntry(roomName: string): NoopEntry {
257
+ const entry: NoopEntry = {
258
+ kind: 'noop',
259
+ roomName,
260
+ doc: new Y.Doc(),
261
+ status: 'offline',
262
+ listeners: new Set(),
263
+ handle: null as unknown as CollabProviderHandle,
264
+ }
265
+ entry.handle = buildHandle(entry)
266
+ entries.set(roomName, entry)
267
+ return entry
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // 公开 API
272
+ // ---------------------------------------------------------------------------
273
+
274
+ export interface CollabProviderHandle {
275
+ readonly roomName: string
276
+ /** 协同 Y.Doc(noop 模式为本地空文档,编辑器可单机使用) */
277
+ readonly doc: Y.Doc
278
+ /**
279
+ * awareness 实例(presence 读写 local/remote state 用)。live 模式由
280
+ * provider 构造器同步创建(恒非 null);noop 模式(协同不可用)为
281
+ * null——消费方按「降级不渲染」处理,不抛错。
282
+ */
283
+ readonly awareness: CollabAwareness | null
284
+ getStatus(): CollabConnectionStatus
285
+ /** 订阅状态变化;立即以当前状态回调一次,返回退订函数 */
286
+ onStatusChange(listener: StatusListener): () => void
287
+ /**
288
+ * 透传底层 provider 事件(UniqueID 协同态监听 'synced' 再补 ID——
289
+ * 编辑器扩展工厂的契约面,缺成员即编辑器创建崩溃)。noop 安全空操作。
290
+ * on 返回退订函数;off 幂等移除。
291
+ */
292
+ on(event: string, listener: (...args: unknown[]) => unknown): () => void
293
+ off(event: string, listener: (...args: unknown[]) => unknown): void
294
+ destroy(): void
295
+ }
296
+
297
+ function buildHandle(entry: Entry): CollabProviderHandle {
298
+ return {
299
+ roomName: entry.roomName,
300
+ doc: entry.doc,
301
+ awareness: entry.kind === 'live' ? entry.provider.awareness : null,
302
+ getStatus: () => entry.status,
303
+ onStatusChange: (listener) => {
304
+ entry.listeners.add(listener)
305
+ listener(entry.status) // 立即回放当前状态
306
+ return () => entry.listeners.delete(listener)
307
+ },
308
+ on: (event, listener) => {
309
+ if (entry.kind !== 'live') return () => {}
310
+ entry.provider.on(event, listener)
311
+ return () => entry.provider.off(event, listener)
312
+ },
313
+ off: (event, listener) => {
314
+ if (entry.kind !== 'live') return
315
+ entry.provider.off(event, listener)
316
+ },
317
+ destroy: () => destroyProvider(entry.roomName),
318
+ }
319
+ }
320
+
321
+ /** getOrCreateProvider 建连选项(仅在首次创建该 room 时生效) */
322
+ export interface CollabProviderOptions {
323
+ /**
324
+ * 自定义 ticket 获取(presence:team:* 等非实体 room 必传,缺省走
325
+ * createCollabTicketFn 实体 ticket——presence room 会被其入口拒绝)。
326
+ * 同 room 已存在单例时本参数被忽略(复用首个连接与 fetcher)。
327
+ */
328
+ getTicket?: (roomName: string) => Promise<string>
329
+ }
330
+
331
+ /**
332
+ * 取 room 协同连接(单例):已存在直接复用;协同不可用(desktop 版 /
333
+ * 未配置 VITE_COLLAB_WS_URL)返回 noop(全方法安全空操作,状态恒 offline、
334
+ * awareness 为 null)。
335
+ */
336
+ export function getOrCreateProvider(
337
+ roomName: string,
338
+ options?: CollabProviderOptions,
339
+ ): CollabProviderHandle {
340
+ const existing = entries.get(roomName)
341
+ if (existing) return existing.handle
342
+ const entry = isCollabEnabled()
343
+ ? createLiveEntry(roomName, options?.getTicket)
344
+ : createNoopEntry(roomName)
345
+ return entry.handle
346
+ }
347
+
348
+ /** 销毁 room 连接:清理重试定时器、从共享 socket 摘除(最后一个销毁时关连接) */
349
+ export function destroyProvider(roomName: string): void {
350
+ const entry = entries.get(roomName)
351
+ if (!entry) return
352
+ entries.delete(roomName)
353
+ if (entry.kind === 'live') {
354
+ if (entry.retryTimer) clearTimeout(entry.retryTimer)
355
+ entry.provider.destroy()
356
+ releaseSharedSocket(entry.socketUrl)
357
+ }
358
+ entry.listeners.clear()
359
+ entry.doc.destroy()
360
+ }
361
+
362
+ // ---------------------------------------------------------------------------
363
+ // 测试钩子(不构成公开 API 契约)
364
+ // ---------------------------------------------------------------------------
365
+
366
+ /** 注入 mock WebSocket(单元测试用;传 undefined 还原原生实现) */
367
+ export function __setWebSocketPolyfillForTests(
368
+ polyfill: WebSocketPolyfillConstructor | undefined,
369
+ ): void {
370
+ webSocketPolyfillOverride = polyfill
371
+ }
372
+
373
+ /** 清空全部连接与共享 socket 池(单元测试隔离用) */
374
+ export function __resetCollabForTests(): void {
375
+ for (const roomName of [...entries.keys()]) destroyProvider(roomName)
376
+ for (const [, shared] of sharedSockets) shared.socket.destroy()
377
+ sharedSockets.clear()
378
+ }
@@ -0,0 +1,100 @@
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
+ // 实体 room 的协同文档 hook(规格 §6 文件树 use-collab-doc.ts)。
11
+ //
12
+ // 职责(任务 15 消费口径):
13
+ // ① getOrCreateProvider 单例建连(noop 环境 desktop/未配置 WS 返回 noop)
14
+ // ② SSR 快照预水合:loader 传来的 base64 update 在建连前 applyUpdate 进
15
+ // provider doc——水合目标就是连接 doc 本身(非旁路副本),provider
16
+ // 建连后的增量同步直接续在同一份 Y.Doc 上(§6 水合链路)
17
+ // ③ 状态订阅:connecting/synced/offline 状态机暴露给编辑器降级消费
18
+ // ④ 卸载销毁(room 单例引用随页面离开释放;lazy 路由双挂载是完整
19
+ // unmount/remount——useState 重新初始化重建连接,路径安全;应用主树
20
+ // 未启用 React StrictMode,若未来启用需复查「state 保留 + effect
21
+ // cleanup 销毁」的组合,届时改为 effect 内创建)
22
+ //
23
+ // 返回 null 的情形:SSR(服务端不建连——HocuspocusProvider/WebSocket 是
24
+ // 纯浏览器资源,模块级单例表也不能在服务端累积)。消费方按「协同不可用」
25
+ // 处理:NotionDocEditor 以 immediatelyRender:false 仅在客户端创建编辑器,
26
+ // 服务端返回 null 不产生 DOM 差异。
27
+
28
+ import { useEffect, useState } from 'react'
29
+ import * as Y from 'yjs'
30
+ import {
31
+ getOrCreateProvider,
32
+ type CollabConnectionStatus,
33
+ type CollabProviderHandle,
34
+ } from './provider'
35
+
36
+ /** base64 → bytes(客户端安全,无 node:crypto;同 use-share-presence 口径) */
37
+ function base64ToBytes(base64: string): Uint8Array {
38
+ const binary = atob(base64)
39
+ const bytes = new Uint8Array(binary.length)
40
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
41
+ return bytes
42
+ }
43
+
44
+ export interface UseCollabDocOptions {
45
+ roomName: string
46
+ /** loader 并行取回的 SSR 快照(base64;null = 空文档/首次协同打开) */
47
+ snapshot: string | null
48
+ }
49
+
50
+ export interface UseCollabDocResult {
51
+ /** room 连接句柄(SSR 为 null;doc 已完成快照预水合) */
52
+ handle: CollabProviderHandle | null
53
+ /** 连接状态机(handle 为 null 时恒 null) */
54
+ status: CollabConnectionStatus | null
55
+ }
56
+
57
+ export function useCollabDoc(options: UseCollabDocOptions): UseCollabDocResult {
58
+ const { roomName, snapshot } = options
59
+
60
+ // 建连 + 预水合(useState 惰性初始化:每实例恰好一次;快照幂等,Y update
61
+ // 重复应用无副作用)。roomName 按实体 key 变化时组件整树重建(route
62
+ // key={id}),无需响应 roomName 变化。
63
+ const [handle] = useState<CollabProviderHandle | null>(() => {
64
+ if (import.meta.env.SSR) return null
65
+ const created = getOrCreateProvider(roomName)
66
+ if (snapshot) {
67
+ try {
68
+ // yjs 13.6.x:applyUpdate 是模块级导出(Doc 实例方法已移除)
69
+ Y.applyUpdate(created.doc, base64ToBytes(snapshot))
70
+ } catch (error) {
71
+ // 快照损坏不阻塞编辑:降级为空文档增量同步
72
+ console.error('collab snapshot decode failed:', error)
73
+ }
74
+ }
75
+ return created
76
+ })
77
+
78
+ const status = useCollabStatus(handle)
79
+
80
+ // 卸载销毁:页面离开释放 room 连接(presence hook 同口径)
81
+ useEffect(() => {
82
+ return () => {
83
+ handle?.destroy()
84
+ }
85
+ }, [handle])
86
+
87
+ return { handle, status }
88
+ }
89
+
90
+ /** 订阅 provider 状态机(useCollabDoc 内部用;单独导出便于测试) */
91
+ export function useCollabStatus(handle: CollabProviderHandle | null): CollabConnectionStatus | null {
92
+ const [status, setStatus] = useState<CollabConnectionStatus | null>(() =>
93
+ handle ? handle.getStatus() : null,
94
+ )
95
+ useEffect(() => {
96
+ if (!handle) return
97
+ return handle.onStatusChange(setStatus)
98
+ }, [handle])
99
+ return status
100
+ }
@@ -0,0 +1,142 @@
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
+ // 列表实时刷新 hook(任务 24,§7.1「列表查询缓存 observe 失效刷新」)。
19
+ //
20
+ // 控制者裁决落地(task-24 派发上下文):列表页不建连实体 room(每行建连
21
+ // 不可取,性能红线),远端字段变更感知走 presence 弱信号组合——
22
+ // ① presence 信号:本模块内「正在看实体」的成员集合(id+route 签名)
23
+ // 变化(成员进出实体页)→ 防抖刷新对应列表。协同编辑者保存后离开
24
+ // 实体页(presence 变化)时,停留在列表页的其他成员随即看到新数据。
25
+ // ② 窗口回前台刷新(refetchOnWindowFocus 惯例)。
26
+ // ③ 适度轮询(refetchInterval 惯例,默认 60s):覆盖「编辑者保存后停留
27
+ // 在详情页、列表读者无任何操作」的无信号窗口。
28
+ // 已知边界(报告备案):协同态编辑不 bump DB updatedAt、落库走 collab
29
+ // debounce,编辑者停留详情页期间列表只能靠 ②③ 兜底;实时性由
30
+ // refetchInterval 上限决定,属裁决接受的取舍。
31
+ //
32
+ // 数据源约束:story/bug/testcase 列表页均为「serverFn 直调 + 本地 state」
33
+ // (禁 react-query),故本 hook 不做 invalidateQueries,而是接收调用方的
34
+ // refresh 回调(列表页既有重拉入口,内部自带 signature 防过期)。
35
+ //
36
+ // 自身 presence 不过滤(不传 selfId):本机其他 tab 打开实体同样构成
37
+ // 「实体可能已变更」信号,刷新无害且有益。
38
+ //
39
+ // section 口径:仅统计 section === 'detail' 的成员(buildPresenceState
40
+ // 启发式:模块根路径=list、更深路径=detail);成员在列表页进出不改变
41
+ // 列表数据,不作信号。旧端 presence 无 section(缺省 undefined)但 route
42
+ // 指向实体深路径时按在看处理(宽松兼容,多刷一次无害)——见
43
+ // isEntityViewer。
44
+
45
+ import { useEffect, useRef } from 'react'
46
+ import { useStore } from '@tanstack/react-store'
47
+ import { useDebouncedCallback } from '@/lib/pacer'
48
+ import { presenceStore } from './presence-store'
49
+ import { MODULE_ROUTES } from './module-map'
50
+ import type { PresenceState } from './presence-store'
51
+
52
+ /** presence 变化 → 刷新的防抖窗口(合并 awareness 快照重建的连发) */
53
+ export const PRESENCE_LIST_REFRESH_DEBOUNCE_MS = 1000
54
+
55
+ /** 轮询兜底间隔(refetchInterval 惯例;0 关闭) */
56
+ export const PRESENCE_LIST_REFRESH_POLL_MS = 60_000
57
+
58
+ /** 实体在看判定:detail section,或旧端无 section 但路由深于模块根 */
59
+ function isEntityViewer(state: PresenceState): boolean {
60
+ if (state.section === 'detail') return true
61
+ if (state.section) return false
62
+ // 无 section(旧端兼容):以模块根路径深度判定(深于根 = 实体页)
63
+ const def = MODULE_ROUTES.find((r) => r.module === state.module)
64
+ return def != null && state.route.startsWith(`${def.link}/`)
65
+ }
66
+
67
+ /** 在看者集合签名(id+route 排序拼接):ts 心跳不改签名,进出实体才改 */
68
+ export function entityViewersSignature(list: readonly PresenceState[]): string {
69
+ return list
70
+ .filter(isEntityViewer)
71
+ .map((s) => `${s.user.id}@${s.route}`)
72
+ .sort()
73
+ .join('|')
74
+ }
75
+
76
+ export interface UsePresenceListRefreshArgs {
77
+ /** 一级模块 key(§8.1,如 stories/bugs/test-cases) */
78
+ module: string
79
+ /** 列表重拉回调(列表页既有 refresh 入口,闭包始终取最新) */
80
+ refresh: () => void
81
+ /** 轮询间隔 ms(默认 60s;传 0 关闭轮询) */
82
+ pollIntervalMs?: number
83
+ /** presence 信号防抖窗口 ms(默认 1s;测试可调小) */
84
+ debounceMs?: number
85
+ }
86
+
87
+ /**
88
+ * presence 驱动的列表刷新。在列表页组件挂载处调用一次;refresh 回调用
89
+ * 列表页既有重拉函数(无需 memo 稳定,hook 内部经 ref 取最新闭包)。
90
+ */
91
+ export function usePresenceListRefresh({
92
+ module,
93
+ refresh,
94
+ pollIntervalMs = PRESENCE_LIST_REFRESH_POLL_MS,
95
+ debounceMs = PRESENCE_LIST_REFRESH_DEBOUNCE_MS,
96
+ }: UsePresenceListRefreshArgs): void {
97
+ // 审查重要①:签名计算下沉 store selector——快照是签名字符串(默认
98
+ // Object.is 值相等),心跳式全量重建(id+route 不变的新对象)不再触发
99
+ // 挂载组件(StoryListView 等重型列表体)的空转重渲染。模块过滤收进
100
+ // selector(原 byModule 前置口径不变),跨模块 presence 噪声不进签名。
101
+ const signature = useStore(
102
+ presenceStore,
103
+ (state) =>
104
+ entityViewersSignature(
105
+ [...state.teammates.values()].filter((s) => s.module === module),
106
+ ),
107
+ )
108
+
109
+ // refresh 恒取最新闭包(列表页筛选状态变化后重拉取当次筛选)
110
+ const refreshRef = useRef(refresh)
111
+ refreshRef.current = refresh
112
+ const debouncedRefresh = useDebouncedCallback(
113
+ () => refreshRef.current(),
114
+ { wait: debounceMs },
115
+ )
116
+ // useDebouncedCallback 每次渲染可能返回新函数(number-drag-input 同款
117
+ // 稳定化口径):effect 经 ref 间接调用,避免订阅/定时器反复重建
118
+ const debouncedRef = useRef(debouncedRefresh)
119
+ debouncedRef.current = debouncedRefresh
120
+
121
+ // ① presence 信号:签名变化(挂载初值除外)→ 防抖刷新
122
+ const prevSignature = useRef(signature)
123
+ useEffect(() => {
124
+ if (prevSignature.current === signature) return
125
+ prevSignature.current = signature
126
+ debouncedRef.current()
127
+ }, [signature])
128
+
129
+ // ② 窗口回前台刷新 + ③ 轮询兜底(presence 不可用时列表仍有新鲜度下限)
130
+ useEffect(() => {
131
+ const onVisibilityChange = () => {
132
+ if (document.visibilityState === 'visible') debouncedRef.current()
133
+ }
134
+ document.addEventListener('visibilitychange', onVisibilityChange)
135
+ const pollTimer =
136
+ pollIntervalMs > 0 ? setInterval(() => debouncedRef.current(), pollIntervalMs) : null
137
+ return () => {
138
+ document.removeEventListener('visibilitychange', onVisibilityChange)
139
+ if (pollTimer) clearInterval(pollTimer)
140
+ }
141
+ }, [pollIntervalMs])
142
+ }