@tuturuuu/ui 0.8.0 → 0.10.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 (245) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/biome.json +1 -1
  3. package/package.json +74 -71
  4. package/src/components/ui/accordion.tsx +1 -1
  5. package/src/components/ui/breadcrumb.tsx +1 -1
  6. package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
  8. package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
  9. package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
  10. package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
  11. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
  12. package/src/components/ui/calendar.tsx +1 -1
  13. package/src/components/ui/carousel.tsx +1 -1
  14. package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
  15. package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
  16. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
  17. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
  18. package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
  19. package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
  20. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
  21. package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
  22. package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
  23. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
  24. package/src/components/ui/checkbox.tsx +1 -1
  25. package/src/components/ui/color-picker.tsx +1 -1
  26. package/src/components/ui/command.tsx +1 -1
  27. package/src/components/ui/context-menu.tsx +5 -1
  28. package/src/components/ui/custom/__tests__/settings-dialog-search.test.ts +78 -0
  29. package/src/components/ui/custom/__tests__/settings-dialog-shell-compile-graph.test.ts +76 -0
  30. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
  31. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +46 -1
  32. package/src/components/ui/custom/combobox.test.tsx +195 -0
  33. package/src/components/ui/custom/combobox.tsx +273 -156
  34. package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
  35. package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
  36. package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
  37. package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
  38. package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
  39. package/src/components/ui/custom/nav-link.test.tsx +165 -0
  40. package/src/components/ui/custom/nav-link.tsx +69 -11
  41. package/src/components/ui/custom/navigation.tsx +1 -0
  42. package/src/components/ui/custom/settings/task-settings.tsx +104 -0
  43. package/src/components/ui/custom/settings-dialog-search-loader.d.ts +5 -0
  44. package/src/components/ui/custom/settings-dialog-search-loader.js +3 -0
  45. package/src/components/ui/custom/settings-dialog-search.ts +75 -0
  46. package/src/components/ui/custom/settings-dialog-shell.tsx +65 -28
  47. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  48. package/src/components/ui/custom/workspace-select-helpers.ts +23 -0
  49. package/src/components/ui/custom/workspace-select.tsx +25 -19
  50. package/src/components/ui/dialog.test.tsx +52 -0
  51. package/src/components/ui/dialog.tsx +6 -2
  52. package/src/components/ui/dropdown-menu.tsx +5 -1
  53. package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
  54. package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
  55. package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
  56. package/src/components/ui/finance/debts/debts-page.tsx +15 -2
  57. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
  58. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
  59. package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
  60. package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
  61. package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
  62. package/src/components/ui/finance/invoices/utils.ts +3 -1
  63. package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
  64. package/src/components/ui/finance/transactions/form-types.ts +1 -0
  65. package/src/components/ui/finance/transactions/form.tsx +2 -0
  66. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
  67. package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
  68. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  69. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  70. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  71. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  72. package/src/components/ui/finance/wallets/form.tsx +15 -4
  73. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  74. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  75. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  76. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  77. package/src/components/ui/input-otp.tsx +1 -1
  78. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  79. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  80. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  81. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  82. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  83. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  84. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  85. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  86. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  87. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  88. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  89. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  90. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  91. package/src/components/ui/navigation-menu.tsx +1 -1
  92. package/src/components/ui/pagination.tsx +1 -1
  93. package/src/components/ui/radio-group.tsx +1 -1
  94. package/src/components/ui/select.tsx +5 -1
  95. package/src/components/ui/sheet.tsx +1 -1
  96. package/src/components/ui/sidebar.tsx +1 -1
  97. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  98. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  99. package/src/components/ui/storefront/cart-summary.tsx +93 -154
  100. package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
  101. package/src/components/ui/storefront/listing-card.tsx +1 -1
  102. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  103. package/src/components/ui/storefront/product-detail.tsx +1 -1
  104. package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
  105. package/src/components/ui/storefront/storefront-surface.tsx +101 -166
  106. package/src/components/ui/storefront/types.ts +4 -0
  107. package/src/components/ui/storefront/utils.ts +6 -0
  108. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  109. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  110. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  111. package/src/components/ui/text-editor/editor.tsx +69 -14
  112. package/src/components/ui/text-editor/extensions.ts +8 -2
  113. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  114. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  115. package/src/components/ui/toast.tsx +1 -1
  116. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +286 -0
  117. package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +12 -0
  118. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  119. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +15 -226
  120. package/src/components/ui/tu-do/boards/board-share-settings-panel.tsx +351 -0
  121. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +121 -39
  122. package/src/components/ui/tu-do/boards/boardId/enhanced-task-list.tsx +7 -0
  123. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  124. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  125. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  126. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-types.ts +3 -3
  127. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  128. package/src/components/ui/tu-do/boards/boardId/kanban/data/use-bulk-resources.ts +59 -5
  129. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  130. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  131. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +20 -1
  132. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
  133. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  134. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  135. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
  136. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  137. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  138. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  139. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  140. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  141. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  142. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  143. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  144. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  145. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  146. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  147. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +642 -5
  148. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +224 -15
  149. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +535 -53
  150. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +101 -33
  151. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +235 -113
  152. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +50 -5
  153. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +12 -2
  154. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +10 -1
  155. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  156. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +20 -0
  157. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +10 -0
  158. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +271 -36
  159. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  160. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  161. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +22 -0
  162. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +9 -0
  163. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +9 -0
  164. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-toolbar.tsx +9 -0
  165. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +35 -3
  166. package/src/components/ui/tu-do/boards/form.tsx +1 -1
  167. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  168. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  169. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  170. package/src/components/ui/tu-do/hooks/__tests__/useTaskLabelManagement.test.tsx +48 -0
  171. package/src/components/ui/tu-do/hooks/__tests__/useTaskProjectManagement.test.tsx +144 -0
  172. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +7 -0
  173. package/src/components/ui/tu-do/hooks/useTaskLabelManagement.ts +115 -106
  174. package/src/components/ui/tu-do/hooks/useTaskProjectManagement.ts +115 -122
  175. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  176. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  177. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  178. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  179. package/src/components/ui/tu-do/progress/task-progress-import-panel.tsx +60 -0
  180. package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +156 -0
  181. package/src/components/ui/tu-do/progress/task-progress-page.tsx +348 -0
  182. package/src/components/ui/tu-do/progress/task-progress-panels.tsx +301 -0
  183. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +26 -0
  184. package/src/components/ui/tu-do/shared/__tests__/assignee-select.test.tsx +81 -10
  185. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +141 -1
  186. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +377 -36
  187. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +374 -0
  188. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +419 -5
  189. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +38 -0
  190. package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +147 -0
  191. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  192. package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +3 -0
  193. package/src/components/ui/tu-do/shared/assignee-select.tsx +77 -26
  194. package/src/components/ui/tu-do/shared/board-client.tsx +15 -10
  195. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  196. package/src/components/ui/tu-do/shared/board-header.tsx +471 -975
  197. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  198. package/src/components/ui/tu-do/shared/board-switcher.tsx +244 -220
  199. package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +18 -12
  200. package/src/components/ui/tu-do/shared/board-views.tsx +577 -85
  201. package/src/components/ui/tu-do/shared/list-view.tsx +246 -2
  202. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  203. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  204. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  205. package/src/components/ui/tu-do/shared/task-cache-patches.ts +394 -0
  206. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +21 -1
  207. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +5 -1
  208. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +25 -2
  209. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +7 -1
  210. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  211. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-data.ts +79 -10
  212. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +76 -77
  213. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.test.tsx +63 -0
  214. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.ts +78 -69
  215. package/src/components/ui/tu-do/shared/task-edit-dialog/personal-overrides-section.tsx +28 -8
  216. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/dependencies-section.tsx +14 -3
  217. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/parent-section.tsx +6 -1
  218. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/related-section.tsx +6 -1
  219. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/subtasks-section.tsx +6 -1
  220. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/types/task-relationships.types.ts +8 -0
  221. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  222. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  223. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  224. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +8 -1
  225. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.test.tsx +150 -0
  226. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +61 -35
  227. package/src/components/ui/tu-do/shared/task-edit-dialog/task-relationships-properties.tsx +44 -2
  228. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  229. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +11 -1
  230. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  231. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +11 -0
  232. package/src/components/ui/tu-do/shared/use-progressive-board-loader.ts +2 -0
  233. package/src/declarations.d.ts +1 -0
  234. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  235. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  236. package/src/hooks/__tests__/useBoardPresence.test.tsx +191 -0
  237. package/src/hooks/__tests__/useBoardRealtime.test.tsx +24 -144
  238. package/src/hooks/use-calendar-sync.tsx +247 -243
  239. package/src/hooks/use-calendar.tsx +323 -138
  240. package/src/hooks/use-task-actions.ts +24 -0
  241. package/src/hooks/use-user-workspace-config.ts +75 -0
  242. package/src/hooks/use-workspace-currency.ts +8 -3
  243. package/src/hooks/useBoardPresence.ts +364 -0
  244. package/src/hooks/useBoardRealtimeEventHandler.ts +45 -90
  245. package/src/lib/workspace-actions.ts +2 -6
@@ -17,6 +17,8 @@ import { getBoardConfigKey } from '../board-config-storage';
17
17
  import { BoardViews } from '../board-views';
18
18
 
19
19
  const listWorkspaceTasksMock = vi.hoisted(() => vi.fn());
20
+ const getUserWorkspaceConfigMock = vi.hoisted(() => vi.fn());
21
+ const updateUserWorkspaceConfigMock = vi.hoisted(() => vi.fn());
20
22
  const createTaskMock = vi.fn();
21
23
  const loadListPageMock = vi.fn();
22
24
  let progressivePagination: Record<string, unknown> = {};
@@ -31,6 +33,11 @@ let kanbanBoardProps:
31
33
  let listViewProps:
32
34
  | React.ComponentProps<typeof import('../list-view')['ListView']>
33
35
  | undefined;
36
+ let timelineBoardProps:
37
+ | React.ComponentProps<
38
+ typeof import('../../boards/boardId/timeline-board')['TimelineBoard']
39
+ >
40
+ | undefined;
34
41
 
35
42
  vi.mock('next-intl', () => ({
36
43
  useTranslations: () => (key: string) => key,
@@ -54,6 +61,15 @@ vi.mock('@tuturuuu/internal-api/tasks', () => ({
54
61
  listWorkspaceTasks: listWorkspaceTasksMock,
55
62
  }));
56
63
 
64
+ vi.mock('@tuturuuu/internal-api/users', () => ({
65
+ TASK_BOARD_PINNED_SPECIAL_LISTS_CONFIG_ID: 'TASK_BOARD_PINNED_SPECIAL_LISTS',
66
+ TASK_LAST_BOARD_VIEW_CONFIG_ID: 'TASK_LAST_BOARD_VIEW',
67
+ getUserWorkspaceConfig: (...args: unknown[]) =>
68
+ getUserWorkspaceConfigMock(...args),
69
+ updateUserWorkspaceConfig: (...args: unknown[]) =>
70
+ updateUserWorkspaceConfigMock(...args),
71
+ }));
72
+
57
73
  vi.mock('../progressive-loader-context', () => ({
58
74
  useProgressiveLoader: () => ({
59
75
  loadListPage: loadListPageMock,
@@ -75,7 +91,32 @@ vi.mock('../board-header', () => ({
75
91
  }));
76
92
 
77
93
  vi.mock('../recycle-bin-panel', () => ({
78
- RecycleBinPanel: () => null,
94
+ RecycleBinContent: () => <div data-testid="recycle-bin-view">Recycle</div>,
95
+ }));
96
+
97
+ vi.mock('../../drafts/drafts-page', () => ({
98
+ DraftsPage: ({
99
+ boardId,
100
+ includeUnassignedForBoard,
101
+ wsId,
102
+ }: {
103
+ boardId?: string;
104
+ includeUnassignedForBoard?: boolean;
105
+ wsId: string;
106
+ }) => (
107
+ <div
108
+ data-board-id={boardId}
109
+ data-include-unassigned={String(includeUnassignedForBoard)}
110
+ data-testid="drafts-view"
111
+ data-ws-id={wsId}
112
+ >
113
+ Drafts
114
+ </div>
115
+ ),
116
+ }));
117
+
118
+ vi.mock('../../my-tasks/my-tasks-content', () => ({
119
+ default: () => <div data-testid="my-tasks-view">My Tasks</div>,
79
120
  }));
80
121
 
81
122
  vi.mock('../../boards/boardId/kanban', () => ({
@@ -93,7 +134,10 @@ vi.mock('../list-view', () => ({
93
134
  }));
94
135
 
95
136
  vi.mock('../../boards/boardId/timeline-board', () => ({
96
- TimelineBoard: () => <div data-testid="timeline-view">Timeline</div>,
137
+ TimelineBoard: (props: any) => {
138
+ timelineBoardProps = props;
139
+ return <div data-testid="timeline-view">Timeline</div>;
140
+ },
97
141
  }));
98
142
 
99
143
  const mockBoard = {
@@ -170,6 +214,7 @@ function renderBoardViews(overrides?: {
170
214
  board?: Record<string, unknown>;
171
215
  idleBottomIsland?: React.ReactNode;
172
216
  lists?: TaskList[];
217
+ props?: Partial<React.ComponentProps<typeof BoardViews>>;
173
218
  tasks?: Task[];
174
219
  workspace?: { id: string; personal: boolean };
175
220
  }) {
@@ -192,6 +237,7 @@ function renderBoardViews(overrides?: {
192
237
  workspace={(overrides?.workspace ?? mockWorkspace) as any}
193
238
  workspaceLabels={mockWorkspaceLabels}
194
239
  idleBottomIsland={overrides?.idleBottomIsland}
240
+ {...overrides?.props}
195
241
  />
196
242
  </HotkeysProvider>
197
243
  </QueryClientProvider>
@@ -208,12 +254,18 @@ describe('BoardViews', () => {
208
254
  boardHeaderProps = undefined;
209
255
  kanbanBoardProps = undefined;
210
256
  listViewProps = undefined;
257
+ timelineBoardProps = undefined;
211
258
  createTaskMock.mockReset();
212
259
  loadListPageMock.mockReset();
213
260
  progressivePagination = {};
214
261
  listWorkspaceTasksMock.mockReset();
215
262
  listWorkspaceTasksMock.mockResolvedValue({ tasks: mockTasks });
263
+ getUserWorkspaceConfigMock.mockReset();
264
+ getUserWorkspaceConfigMock.mockResolvedValue({ value: null });
265
+ updateUserWorkspaceConfigMock.mockReset();
266
+ updateUserWorkspaceConfigMock.mockResolvedValue({ message: 'ok' });
216
267
  window.localStorage.clear();
268
+ window.history.replaceState({}, '', '/');
217
269
  });
218
270
 
219
271
  it('registers visible hotkey labels for each board view', () => {
@@ -222,11 +274,126 @@ describe('BoardViews', () => {
222
274
  expect(boardHeaderProps?.viewHotkeyLabels).toEqual({
223
275
  kanban: formatHotkeySequence(['G', 'K']),
224
276
  list: formatHotkeySequence(['G', 'L']),
277
+ my_tasks: formatHotkeySequence(['G', 'M']),
225
278
  timeline: formatHotkeySequence(['G', 'T']),
279
+ drafts: formatHotkeySequence(['G', 'D']),
280
+ recycle_bin: formatHotkeySequence(['G', 'R']),
226
281
  });
227
282
  });
228
283
 
229
- it('switches between kanban, list, and timeline using TanStack hotkey sequences', async () => {
284
+ it('exposes drafts and recycle bin as editable board modes by default', () => {
285
+ renderBoardViews();
286
+
287
+ expect(boardHeaderProps?.availableViews).toEqual([
288
+ 'kanban',
289
+ 'list',
290
+ 'my_tasks',
291
+ 'timeline',
292
+ 'drafts',
293
+ 'recycle_bin',
294
+ ]);
295
+ });
296
+
297
+ it('passes explicit read-only public mode through shared board components', () => {
298
+ renderBoardViews({
299
+ props: {
300
+ availableViews: ['kanban', 'list'],
301
+ publicHeaderPrefix: <span data-testid="public-prefix" />,
302
+ publicView: true,
303
+ readOnly: true,
304
+ },
305
+ });
306
+
307
+ expect(boardHeaderProps).toMatchObject({
308
+ availableViews: ['kanban', 'list'],
309
+ publicView: true,
310
+ readOnly: true,
311
+ });
312
+ expect(boardHeaderProps?.titlePrefix).toBeDefined();
313
+ expect(kanbanBoardProps?.readOnly).toBe(true);
314
+ expect(listWorkspaceTasksMock).not.toHaveBeenCalled();
315
+ });
316
+
317
+ it('enables assignees for personal boards that have guest access', async () => {
318
+ renderBoardViews({
319
+ board: {
320
+ ...mockBoard,
321
+ has_guest_access: true,
322
+ },
323
+ workspace: { id: 'ws-1', personal: true },
324
+ });
325
+
326
+ expect(kanbanBoardProps?.canUseBoardAssignees).toBe(true);
327
+ expect(kanbanBoardProps?.assigneeMemberSource).toBe('board');
328
+
329
+ await act(async () => {
330
+ boardHeaderProps?.onViewChange('list');
331
+ });
332
+
333
+ await waitFor(() => {
334
+ expect(screen.getByTestId('list-view')).toBeInTheDocument();
335
+ });
336
+ expect(listViewProps?.canUseBoardAssignees).toBe(true);
337
+ expect(listViewProps?.assigneeMemberSource).toBe('board');
338
+
339
+ await act(async () => {
340
+ boardHeaderProps?.onViewChange('timeline');
341
+ });
342
+
343
+ await waitFor(() => {
344
+ expect(screen.getByTestId('timeline-view')).toBeInTheDocument();
345
+ });
346
+ expect(timelineBoardProps?.canUseBoardAssignees).toBe(true);
347
+ expect(timelineBoardProps?.assigneeMemberSource).toBe('board');
348
+ });
349
+
350
+ it('keeps assignees hidden for unshared personal boards', () => {
351
+ renderBoardViews({
352
+ workspace: { id: 'ws-1', personal: true },
353
+ });
354
+
355
+ expect(kanbanBoardProps?.canUseBoardAssignees).toBe(false);
356
+ expect(kanbanBoardProps?.assigneeMemberSource).toBe('workspace');
357
+ });
358
+
359
+ it('merges workspace and board assignee sources for team boards that have guest access', () => {
360
+ renderBoardViews({
361
+ board: {
362
+ ...mockBoard,
363
+ has_guest_access: true,
364
+ },
365
+ workspace: { id: 'ws-1', personal: false },
366
+ });
367
+
368
+ expect(kanbanBoardProps?.canUseBoardAssignees).toBe(true);
369
+ expect(kanbanBoardProps?.assigneeMemberSource).toBe('workspace-and-board');
370
+ });
371
+
372
+ it('renders board-scoped drafts and recycle bin views from the header mode switcher', async () => {
373
+ renderBoardViews();
374
+
375
+ await act(async () => {
376
+ boardHeaderProps?.onViewChange('drafts');
377
+ });
378
+
379
+ expect(screen.getByTestId('drafts-view')).toHaveAttribute(
380
+ 'data-board-id',
381
+ 'board-1'
382
+ );
383
+ expect(screen.getByTestId('drafts-view')).toHaveAttribute(
384
+ 'data-include-unassigned',
385
+ 'true'
386
+ );
387
+
388
+ await act(async () => {
389
+ boardHeaderProps?.onViewChange('recycle_bin');
390
+ });
391
+
392
+ expect(screen.getByTestId('recycle-bin-view')).toBeInTheDocument();
393
+ expect(createTaskMock).not.toHaveBeenCalled();
394
+ });
395
+
396
+ it('switches between all board views using TanStack hotkey sequences', async () => {
230
397
  renderBoardViews();
231
398
 
232
399
  expect(screen.getByTestId('kanban-view')).toBeInTheDocument();
@@ -251,6 +418,43 @@ describe('BoardViews', () => {
251
418
  await waitFor(() => {
252
419
  expect(screen.getByTestId('kanban-view')).toBeInTheDocument();
253
420
  });
421
+
422
+ fireEvent.keyDown(document, { key: 'g' });
423
+ fireEvent.keyDown(document, { key: 'm' });
424
+
425
+ await waitFor(() => {
426
+ expect(screen.getByTestId('my-tasks-view')).toBeInTheDocument();
427
+ });
428
+
429
+ fireEvent.keyDown(document, { key: 'g' });
430
+ fireEvent.keyDown(document, { key: 'd' });
431
+
432
+ await waitFor(() => {
433
+ expect(screen.getByTestId('drafts-view')).toBeInTheDocument();
434
+ });
435
+
436
+ fireEvent.keyDown(document, { key: 'g' });
437
+ fireEvent.keyDown(document, { key: 'r' });
438
+
439
+ await waitFor(() => {
440
+ expect(screen.getByTestId('recycle-bin-view')).toBeInTheDocument();
441
+ });
442
+ });
443
+
444
+ it('saves the selected workspace task view when the header changes view', async () => {
445
+ renderBoardViews();
446
+
447
+ await act(async () => {
448
+ boardHeaderProps?.onViewChange('list');
449
+ });
450
+
451
+ await waitFor(() => {
452
+ expect(updateUserWorkspaceConfigMock).toHaveBeenCalledWith(
453
+ 'ws-1',
454
+ 'TASK_LAST_BOARD_VIEW',
455
+ 'list'
456
+ );
457
+ });
254
458
  });
255
459
 
256
460
  it('toggles the idle bottom island around active kanban bulk selection', async () => {
@@ -386,7 +590,7 @@ describe('BoardViews', () => {
386
590
  );
387
591
  });
388
592
 
389
- it('expands the virtual external task list by default when assigned external tasks exist', () => {
593
+ it('collapses the virtual external task list by default even when assigned external tasks exist', () => {
390
594
  renderBoardViews({
391
595
  workspace: {
392
596
  ...mockWorkspace,
@@ -409,7 +613,7 @@ describe('BoardViews', () => {
409
613
  expect(kanbanBoardProps?.lists[0]).toEqual(
410
614
  expect.objectContaining({
411
615
  id: 'personal-external-staging:board-1',
412
- is_external_collapsed: false,
616
+ is_external_collapsed: true,
413
617
  is_external_staging: true,
414
618
  })
415
619
  );
@@ -508,6 +712,125 @@ describe('BoardViews', () => {
508
712
  });
509
713
  });
510
714
 
715
+ it('collapses deadline sections by default and persists per board and section', async () => {
716
+ window.localStorage.setItem(
717
+ 'task-board-deadline-section-collapsed:board-1:overdue',
718
+ 'false'
719
+ );
720
+
721
+ renderBoardViews();
722
+
723
+ await waitFor(() => {
724
+ expect(kanbanBoardProps?.deadlineSectionsCollapsed).toEqual(
725
+ expect.objectContaining({
726
+ overdue: false,
727
+ upcoming: true,
728
+ })
729
+ );
730
+ });
731
+
732
+ act(() => {
733
+ kanbanBoardProps?.onDeadlineSectionCollapsedChange?.('upcoming', false);
734
+ });
735
+
736
+ await waitFor(() => {
737
+ expect(
738
+ window.localStorage.getItem(
739
+ 'task-board-deadline-section-collapsed:board-1:upcoming'
740
+ )
741
+ ).toBe('false');
742
+ expect(kanbanBoardProps?.deadlineSectionsCollapsed).toEqual(
743
+ expect.objectContaining({
744
+ overdue: false,
745
+ upcoming: false,
746
+ })
747
+ );
748
+ });
749
+ });
750
+
751
+ it('does not force pinned special task lists to stay expanded', async () => {
752
+ getUserWorkspaceConfigMock.mockImplementation(
753
+ (_workspaceId: string, configId: string) =>
754
+ Promise.resolve({
755
+ value:
756
+ configId === 'TASK_BOARD_PINNED_SPECIAL_LISTS'
757
+ ? JSON.stringify([
758
+ 'external_tasks',
759
+ 'closed_tasks',
760
+ 'overdue',
761
+ 'upcoming',
762
+ ])
763
+ : null,
764
+ })
765
+ );
766
+
767
+ renderBoardViews({
768
+ lists: [...mockLists, closedList],
769
+ workspace: {
770
+ ...mockWorkspace,
771
+ personal: true,
772
+ },
773
+ });
774
+
775
+ await waitFor(() => {
776
+ expect(kanbanBoardProps?.specialTaskListPins).toEqual(
777
+ expect.objectContaining({
778
+ closed_tasks: true,
779
+ external_tasks: true,
780
+ overdue: true,
781
+ upcoming: true,
782
+ })
783
+ );
784
+ expect(kanbanBoardProps?.lists[0]).toEqual(
785
+ expect.objectContaining({
786
+ is_external_collapsed: true,
787
+ is_external_staging: true,
788
+ })
789
+ );
790
+ expect(
791
+ kanbanBoardProps?.lists.find((list) => list.id === 'list-closed')
792
+ ).toEqual(
793
+ expect.objectContaining({
794
+ is_collapsed: true,
795
+ status: 'closed',
796
+ })
797
+ );
798
+ expect(kanbanBoardProps?.deadlineSectionsCollapsed).toEqual(
799
+ expect.objectContaining({
800
+ overdue: true,
801
+ upcoming: true,
802
+ })
803
+ );
804
+ });
805
+
806
+ act(() => {
807
+ kanbanBoardProps?.onExternalTasksCollapsedChange?.(false);
808
+ kanbanBoardProps?.onTaskListCollapsedChange?.('list-closed', false);
809
+ kanbanBoardProps?.onDeadlineSectionCollapsedChange?.('overdue', false);
810
+ });
811
+
812
+ await waitFor(() => {
813
+ expect(kanbanBoardProps?.lists[0]).toEqual(
814
+ expect.objectContaining({
815
+ is_external_collapsed: false,
816
+ })
817
+ );
818
+ expect(
819
+ kanbanBoardProps?.lists.find((list) => list.id === 'list-closed')
820
+ ).toEqual(
821
+ expect.objectContaining({
822
+ is_collapsed: false,
823
+ })
824
+ );
825
+ expect(kanbanBoardProps?.deadlineSectionsCollapsed).toEqual(
826
+ expect.objectContaining({
827
+ overdue: false,
828
+ upcoming: true,
829
+ })
830
+ );
831
+ });
832
+ });
833
+
511
834
  it('excludes deleted lists from active board views and create shortcuts', () => {
512
835
  const listsWithDeletedFirst: TaskList[] = [
513
836
  {
@@ -650,6 +973,31 @@ describe('BoardViews', () => {
650
973
  });
651
974
  });
652
975
 
976
+ it('passes board filter criteria into deadline task query options without global sort', async () => {
977
+ renderBoardViews();
978
+
979
+ act(() => {
980
+ boardHeaderProps?.onListStatusFilterChange('active');
981
+ boardHeaderProps?.onFiltersChange({
982
+ ...boardHeaderProps.filters,
983
+ labels: [{ color: 'BLUE', id: 'label-1', name: 'Urgent' } as any],
984
+ searchQuery: 'launch',
985
+ sortBy: 'name-asc',
986
+ });
987
+ });
988
+
989
+ await waitFor(() => {
990
+ expect(kanbanBoardProps?.deadlineTaskQueryOptions).toEqual(
991
+ expect.objectContaining({
992
+ labelIds: ['label-1'],
993
+ listStatuses: ['active'],
994
+ q: 'launch',
995
+ })
996
+ );
997
+ });
998
+ expect(kanbanBoardProps?.deadlineTaskQueryOptions?.sortBy).toBeUndefined();
999
+ });
1000
+
653
1001
  it('uses server-side search counts to hide task lists without matching tasks', async () => {
654
1002
  listWorkspaceTasksMock.mockImplementation(async (_workspaceId, options) => {
655
1003
  if (options?.includeListCounts) {
@@ -786,6 +1134,72 @@ describe('BoardViews', () => {
786
1134
  });
787
1135
  });
788
1136
 
1137
+ it('uses the route default view instead of stale local My Tasks config', async () => {
1138
+ window.localStorage.setItem(
1139
+ getBoardConfigKey(mockBoard.id),
1140
+ JSON.stringify({
1141
+ currentView: 'my_tasks',
1142
+ filters: {
1143
+ assignees: [],
1144
+ dueDateRange: null,
1145
+ estimationRange: null,
1146
+ includeMyTasks: false,
1147
+ includeUnassigned: false,
1148
+ labels: [],
1149
+ priorities: [],
1150
+ projects: [],
1151
+ sourceBoardIds: [],
1152
+ sourceScope: 'all_visible',
1153
+ sourceWorkspaceIds: [],
1154
+ },
1155
+ listStatusFilter: 'all',
1156
+ })
1157
+ );
1158
+ window.history.replaceState({}, '', '/ws-1/tasks/boards/board-1');
1159
+
1160
+ renderBoardViews({ props: { defaultView: 'kanban' } });
1161
+
1162
+ await waitFor(() => {
1163
+ expect(screen.getByTestId('kanban-view')).toBeInTheDocument();
1164
+ });
1165
+ expect(screen.queryByTestId('my-tasks-view')).not.toBeInTheDocument();
1166
+ });
1167
+
1168
+ it('lets an explicit URL view override the route default view', async () => {
1169
+ window.localStorage.setItem(
1170
+ getBoardConfigKey(mockBoard.id),
1171
+ JSON.stringify({
1172
+ currentView: 'timeline',
1173
+ filters: {
1174
+ assignees: [],
1175
+ dueDateRange: null,
1176
+ estimationRange: null,
1177
+ includeMyTasks: false,
1178
+ includeUnassigned: false,
1179
+ labels: [],
1180
+ priorities: [],
1181
+ projects: [],
1182
+ sourceBoardIds: [],
1183
+ sourceScope: 'all_visible',
1184
+ sourceWorkspaceIds: [],
1185
+ },
1186
+ listStatusFilter: 'all',
1187
+ })
1188
+ );
1189
+ window.history.replaceState(
1190
+ {},
1191
+ '',
1192
+ '/ws-1/tasks/boards/board-1?view=my_tasks'
1193
+ );
1194
+
1195
+ renderBoardViews({ props: { defaultView: 'kanban' } });
1196
+
1197
+ await waitFor(() => {
1198
+ expect(screen.getByTestId('my-tasks-view')).toBeInTheDocument();
1199
+ });
1200
+ expect(screen.queryByTestId('kanban-view')).not.toBeInTheDocument();
1201
+ });
1202
+
789
1203
  it('ignores board hotkeys while typing in an input', async () => {
790
1204
  renderBoardViews();
791
1205
  const input = screen.getByTestId('board-header-input');
@@ -0,0 +1,38 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { TaskBoardLoadingState } from '../task-board-loading-state';
5
+
6
+ describe('TaskBoardLoadingState', () => {
7
+ it('fills the padded board route with a transparent kanban skeleton', () => {
8
+ render(<TaskBoardLoadingState root />);
9
+
10
+ expect(screen.getByTestId('task-board-loading-state')).toHaveClass(
11
+ '-m-4',
12
+ 'h-[calc(100dvh+2rem)]',
13
+ 'w-[calc(100%+2rem)]',
14
+ 'bg-transparent'
15
+ );
16
+ expect(screen.getByTestId('kanban-skeleton')).toHaveClass('bg-transparent');
17
+ expect(screen.getByTestId('kanban-skeleton-frame')).toHaveClass(
18
+ 'py-2',
19
+ 'pl-2',
20
+ 'pr-0'
21
+ );
22
+ expect(screen.getByTestId('kanban-skeleton-frame')).not.toHaveClass('p-2');
23
+ });
24
+
25
+ it('keeps embedded loading skeletons constrained to the parent width', () => {
26
+ render(<TaskBoardLoadingState />);
27
+
28
+ expect(screen.getByTestId('task-board-loading-state')).toHaveClass(
29
+ 'w-full',
30
+ 'h-[calc(100dvh-1rem)]'
31
+ );
32
+ expect(screen.getByTestId('task-board-loading-state')).not.toHaveClass(
33
+ '-m-4',
34
+ 'w-[calc(100%+2rem)]'
35
+ );
36
+ expect(screen.getByTestId('kanban-skeleton-frame')).toHaveClass('p-2');
37
+ });
38
+ });