@tuturuuu/ui 0.7.0 → 0.9.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 (226) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/biome.json +1 -1
  3. package/package.json +75 -73
  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/currency-input.test.tsx +43 -0
  29. package/src/components/ui/currency-input.tsx +1 -1
  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 +19 -0
  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/settings-dialog-shell.tsx +2 -1
  40. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  41. package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +60 -35
  42. package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
  43. package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
  44. package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
  45. package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
  46. package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
  47. package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
  48. package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
  49. package/src/components/ui/custom/workspace-select.tsx +8 -3
  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 +3 -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-card.tsx +21 -9
  69. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  70. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  71. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  72. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  73. package/src/components/ui/finance/wallets/form.tsx +15 -4
  74. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  75. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  76. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  77. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  78. package/src/components/ui/input-otp.tsx +1 -1
  79. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  80. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  81. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  82. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  83. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  84. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  85. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  86. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  87. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  88. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  89. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  90. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  91. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  92. package/src/components/ui/money-input.test.tsx +64 -0
  93. package/src/components/ui/money-input.tsx +63 -0
  94. package/src/components/ui/navigation-menu.tsx +1 -1
  95. package/src/components/ui/pagination.tsx +1 -1
  96. package/src/components/ui/radio-group.tsx +1 -1
  97. package/src/components/ui/select.tsx +5 -1
  98. package/src/components/ui/sheet.tsx +1 -1
  99. package/src/components/ui/sidebar.tsx +1 -1
  100. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  101. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  102. package/src/components/ui/storefront/cart-summary.tsx +104 -80
  103. package/src/components/ui/storefront/checkout-overlay.tsx +26 -0
  104. package/src/components/ui/storefront/hero-panel.tsx +2 -8
  105. package/src/components/ui/storefront/image-panel.tsx +6 -0
  106. package/src/components/ui/storefront/index.ts +11 -0
  107. package/src/components/ui/storefront/listing-card.tsx +84 -22
  108. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  109. package/src/components/ui/storefront/product-detail.tsx +289 -0
  110. package/src/components/ui/storefront/product-dialog.tsx +72 -0
  111. package/src/components/ui/storefront/storefront-surface.test.tsx +221 -3
  112. package/src/components/ui/storefront/storefront-surface.tsx +288 -153
  113. package/src/components/ui/storefront/types.ts +27 -1
  114. package/src/components/ui/storefront/utils.ts +117 -27
  115. package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
  116. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  117. package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
  118. package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -0
  119. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  120. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  121. package/src/components/ui/text-editor/content-migration.ts +41 -18
  122. package/src/components/ui/text-editor/editor.tsx +69 -14
  123. package/src/components/ui/text-editor/extensions.ts +9 -3
  124. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  125. package/src/components/ui/text-editor/image-extension.ts +40 -18
  126. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  127. package/src/components/ui/text-editor/video-extension.ts +11 -2
  128. package/src/components/ui/toast.tsx +1 -1
  129. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
  130. package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +70 -1
  131. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  132. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
  133. package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +127 -0
  134. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +113 -46
  135. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  136. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  137. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  138. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  139. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  140. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  141. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +51 -9
  142. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  143. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  144. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -0
  145. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +127 -38
  146. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  147. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  148. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  149. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  150. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  151. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  152. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  153. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  154. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  155. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  156. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  157. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +410 -4
  158. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +106 -14
  159. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
  160. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
  161. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
  162. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +186 -0
  163. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +59 -2
  164. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
  165. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  166. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
  167. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  168. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  169. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
  170. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +9 -0
  171. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
  172. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
  173. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
  174. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
  175. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  176. package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
  177. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  178. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  179. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  180. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  181. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  182. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  183. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
  184. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
  185. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
  186. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +237 -3
  187. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
  188. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  189. package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
  190. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  191. package/src/components/ui/tu-do/shared/board-header.tsx +465 -937
  192. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  193. package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
  194. package/src/components/ui/tu-do/shared/board-views.tsx +596 -82
  195. package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
  196. package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
  197. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  198. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  199. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  200. package/src/components/ui/tu-do/shared/task-dialog-presentation.test.ts +53 -0
  201. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
  202. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
  203. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
  204. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -1
  205. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  206. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  207. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  208. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  209. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  210. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +44 -15
  211. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  212. package/src/declarations.d.ts +1 -0
  213. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  214. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  215. package/src/hooks/__tests__/useBoardRealtime.test.tsx +2 -2
  216. package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -0
  217. package/src/hooks/use-calendar-sync.tsx +247 -243
  218. package/src/hooks/use-calendar.tsx +323 -138
  219. package/src/hooks/use-task-actions.ts +24 -0
  220. package/src/hooks/use-user-workspace-config.ts +75 -0
  221. package/src/hooks/use-workspace-currency.ts +8 -3
  222. package/src/hooks/useBoardRealtime.ts +6 -3
  223. package/src/hooks/useBoardRealtime.types.ts +11 -0
  224. package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
  225. package/src/hooks/useCursorTracking.ts +91 -27
  226. package/src/hooks/useTaskUserRealtime.ts +5 -3
@@ -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> = {};
@@ -54,6 +56,15 @@ vi.mock('@tuturuuu/internal-api/tasks', () => ({
54
56
  listWorkspaceTasks: listWorkspaceTasksMock,
55
57
  }));
56
58
 
59
+ vi.mock('@tuturuuu/internal-api/users', () => ({
60
+ TASK_BOARD_PINNED_SPECIAL_LISTS_CONFIG_ID: 'TASK_BOARD_PINNED_SPECIAL_LISTS',
61
+ TASK_LAST_BOARD_VIEW_CONFIG_ID: 'TASK_LAST_BOARD_VIEW',
62
+ getUserWorkspaceConfig: (...args: unknown[]) =>
63
+ getUserWorkspaceConfigMock(...args),
64
+ updateUserWorkspaceConfig: (...args: unknown[]) =>
65
+ updateUserWorkspaceConfigMock(...args),
66
+ }));
67
+
57
68
  vi.mock('../progressive-loader-context', () => ({
58
69
  useProgressiveLoader: () => ({
59
70
  loadListPage: loadListPageMock,
@@ -75,7 +86,32 @@ vi.mock('../board-header', () => ({
75
86
  }));
76
87
 
77
88
  vi.mock('../recycle-bin-panel', () => ({
78
- RecycleBinPanel: () => null,
89
+ RecycleBinContent: () => <div data-testid="recycle-bin-view">Recycle</div>,
90
+ }));
91
+
92
+ vi.mock('../../drafts/drafts-page', () => ({
93
+ DraftsPage: ({
94
+ boardId,
95
+ includeUnassignedForBoard,
96
+ wsId,
97
+ }: {
98
+ boardId?: string;
99
+ includeUnassignedForBoard?: boolean;
100
+ wsId: string;
101
+ }) => (
102
+ <div
103
+ data-board-id={boardId}
104
+ data-include-unassigned={String(includeUnassignedForBoard)}
105
+ data-testid="drafts-view"
106
+ data-ws-id={wsId}
107
+ >
108
+ Drafts
109
+ </div>
110
+ ),
111
+ }));
112
+
113
+ vi.mock('../../my-tasks/my-tasks-content', () => ({
114
+ default: () => <div data-testid="my-tasks-view">My Tasks</div>,
79
115
  }));
80
116
 
81
117
  vi.mock('../../boards/boardId/kanban', () => ({
@@ -167,8 +203,10 @@ const mockTasks: Task[] = [
167
203
  const mockWorkspaceLabels: WorkspaceLabel[] = [];
168
204
 
169
205
  function renderBoardViews(overrides?: {
206
+ board?: Record<string, unknown>;
170
207
  idleBottomIsland?: React.ReactNode;
171
208
  lists?: TaskList[];
209
+ props?: Partial<React.ComponentProps<typeof BoardViews>>;
172
210
  tasks?: Task[];
173
211
  workspace?: { id: string; personal: boolean };
174
212
  }) {
@@ -184,13 +222,14 @@ function renderBoardViews(overrides?: {
184
222
  <QueryClientProvider client={queryClient}>
185
223
  <HotkeysProvider>
186
224
  <BoardViews
187
- board={mockBoard as any}
225
+ board={(overrides?.board ?? mockBoard) as any}
188
226
  currentUserId="user-1"
189
227
  lists={overrides?.lists ?? mockLists}
190
228
  tasks={overrides?.tasks ?? mockTasks}
191
229
  workspace={(overrides?.workspace ?? mockWorkspace) as any}
192
230
  workspaceLabels={mockWorkspaceLabels}
193
231
  idleBottomIsland={overrides?.idleBottomIsland}
232
+ {...overrides?.props}
194
233
  />
195
234
  </HotkeysProvider>
196
235
  </QueryClientProvider>
@@ -212,7 +251,12 @@ describe('BoardViews', () => {
212
251
  progressivePagination = {};
213
252
  listWorkspaceTasksMock.mockReset();
214
253
  listWorkspaceTasksMock.mockResolvedValue({ tasks: mockTasks });
254
+ getUserWorkspaceConfigMock.mockReset();
255
+ getUserWorkspaceConfigMock.mockResolvedValue({ value: null });
256
+ updateUserWorkspaceConfigMock.mockReset();
257
+ updateUserWorkspaceConfigMock.mockResolvedValue({ message: 'ok' });
215
258
  window.localStorage.clear();
259
+ window.history.replaceState({}, '', '/');
216
260
  });
217
261
 
218
262
  it('registers visible hotkey labels for each board view', () => {
@@ -221,11 +265,71 @@ describe('BoardViews', () => {
221
265
  expect(boardHeaderProps?.viewHotkeyLabels).toEqual({
222
266
  kanban: formatHotkeySequence(['G', 'K']),
223
267
  list: formatHotkeySequence(['G', 'L']),
268
+ my_tasks: formatHotkeySequence(['G', 'M']),
224
269
  timeline: formatHotkeySequence(['G', 'T']),
270
+ drafts: formatHotkeySequence(['G', 'D']),
271
+ recycle_bin: formatHotkeySequence(['G', 'R']),
272
+ });
273
+ });
274
+
275
+ it('exposes drafts and recycle bin as editable board modes by default', () => {
276
+ renderBoardViews();
277
+
278
+ expect(boardHeaderProps?.availableViews).toEqual([
279
+ 'kanban',
280
+ 'list',
281
+ 'my_tasks',
282
+ 'timeline',
283
+ 'drafts',
284
+ 'recycle_bin',
285
+ ]);
286
+ });
287
+
288
+ it('passes explicit read-only public mode through shared board components', () => {
289
+ renderBoardViews({
290
+ props: {
291
+ availableViews: ['kanban', 'list'],
292
+ publicHeaderPrefix: <span data-testid="public-prefix" />,
293
+ publicView: true,
294
+ readOnly: true,
295
+ },
296
+ });
297
+
298
+ expect(boardHeaderProps).toMatchObject({
299
+ availableViews: ['kanban', 'list'],
300
+ publicView: true,
301
+ readOnly: true,
302
+ });
303
+ expect(boardHeaderProps?.titlePrefix).toBeDefined();
304
+ expect(kanbanBoardProps?.readOnly).toBe(true);
305
+ expect(listWorkspaceTasksMock).not.toHaveBeenCalled();
306
+ });
307
+
308
+ it('renders board-scoped drafts and recycle bin views from the header mode switcher', async () => {
309
+ renderBoardViews();
310
+
311
+ await act(async () => {
312
+ boardHeaderProps?.onViewChange('drafts');
313
+ });
314
+
315
+ expect(screen.getByTestId('drafts-view')).toHaveAttribute(
316
+ 'data-board-id',
317
+ 'board-1'
318
+ );
319
+ expect(screen.getByTestId('drafts-view')).toHaveAttribute(
320
+ 'data-include-unassigned',
321
+ 'true'
322
+ );
323
+
324
+ await act(async () => {
325
+ boardHeaderProps?.onViewChange('recycle_bin');
225
326
  });
327
+
328
+ expect(screen.getByTestId('recycle-bin-view')).toBeInTheDocument();
329
+ expect(createTaskMock).not.toHaveBeenCalled();
226
330
  });
227
331
 
228
- it('switches between kanban, list, and timeline using TanStack hotkey sequences', async () => {
332
+ it('switches between all board views using TanStack hotkey sequences', async () => {
229
333
  renderBoardViews();
230
334
 
231
335
  expect(screen.getByTestId('kanban-view')).toBeInTheDocument();
@@ -250,6 +354,43 @@ describe('BoardViews', () => {
250
354
  await waitFor(() => {
251
355
  expect(screen.getByTestId('kanban-view')).toBeInTheDocument();
252
356
  });
357
+
358
+ fireEvent.keyDown(document, { key: 'g' });
359
+ fireEvent.keyDown(document, { key: 'm' });
360
+
361
+ await waitFor(() => {
362
+ expect(screen.getByTestId('my-tasks-view')).toBeInTheDocument();
363
+ });
364
+
365
+ fireEvent.keyDown(document, { key: 'g' });
366
+ fireEvent.keyDown(document, { key: 'd' });
367
+
368
+ await waitFor(() => {
369
+ expect(screen.getByTestId('drafts-view')).toBeInTheDocument();
370
+ });
371
+
372
+ fireEvent.keyDown(document, { key: 'g' });
373
+ fireEvent.keyDown(document, { key: 'r' });
374
+
375
+ await waitFor(() => {
376
+ expect(screen.getByTestId('recycle-bin-view')).toBeInTheDocument();
377
+ });
378
+ });
379
+
380
+ it('saves the selected workspace task view when the header changes view', async () => {
381
+ renderBoardViews();
382
+
383
+ await act(async () => {
384
+ boardHeaderProps?.onViewChange('list');
385
+ });
386
+
387
+ await waitFor(() => {
388
+ expect(updateUserWorkspaceConfigMock).toHaveBeenCalledWith(
389
+ 'ws-1',
390
+ 'TASK_LAST_BOARD_VIEW',
391
+ 'list'
392
+ );
393
+ });
253
394
  });
254
395
 
255
396
  it('toggles the idle bottom island around active kanban bulk selection', async () => {
@@ -323,6 +464,38 @@ describe('BoardViews', () => {
323
464
  );
324
465
  });
325
466
 
467
+ it('creates a task in the board default list when configured', () => {
468
+ renderBoardViews({
469
+ board: { ...mockBoard, default_list_id: 'list-2' },
470
+ });
471
+
472
+ fireEvent.keyDown(document, { key: 'c' });
473
+
474
+ expect(createTaskMock).toHaveBeenCalledTimes(1);
475
+ expect(createTaskMock).toHaveBeenCalledWith(
476
+ 'board-1',
477
+ 'list-2',
478
+ mockLists,
479
+ expect.objectContaining({ labels: [] })
480
+ );
481
+ });
482
+
483
+ it('falls back to the first list when the default list is unavailable', () => {
484
+ renderBoardViews({
485
+ board: { ...mockBoard, default_list_id: 'list-does-not-exist' },
486
+ });
487
+
488
+ fireEvent.keyDown(document, { key: 'c' });
489
+
490
+ expect(createTaskMock).toHaveBeenCalledTimes(1);
491
+ expect(createTaskMock).toHaveBeenCalledWith(
492
+ 'board-1',
493
+ 'list-1',
494
+ mockLists,
495
+ expect.objectContaining({ labels: [] })
496
+ );
497
+ });
498
+
326
499
  it('pins a collapsed virtual external task list on personal boards without assigned external tasks', () => {
327
500
  renderBoardViews({
328
501
  workspace: {
@@ -475,6 +648,42 @@ describe('BoardViews', () => {
475
648
  });
476
649
  });
477
650
 
651
+ it('persists deadline section collapse state per board and section', async () => {
652
+ window.localStorage.setItem(
653
+ 'task-board-deadline-section-collapsed:board-1:overdue',
654
+ 'true'
655
+ );
656
+
657
+ renderBoardViews();
658
+
659
+ await waitFor(() => {
660
+ expect(kanbanBoardProps?.deadlineSectionsCollapsed).toEqual(
661
+ expect.objectContaining({
662
+ overdue: true,
663
+ upcoming: false,
664
+ })
665
+ );
666
+ });
667
+
668
+ act(() => {
669
+ kanbanBoardProps?.onDeadlineSectionCollapsedChange?.('upcoming', true);
670
+ });
671
+
672
+ await waitFor(() => {
673
+ expect(
674
+ window.localStorage.getItem(
675
+ 'task-board-deadline-section-collapsed:board-1:upcoming'
676
+ )
677
+ ).toBe('true');
678
+ expect(kanbanBoardProps?.deadlineSectionsCollapsed).toEqual(
679
+ expect.objectContaining({
680
+ overdue: true,
681
+ upcoming: true,
682
+ })
683
+ );
684
+ });
685
+ });
686
+
478
687
  it('excludes deleted lists from active board views and create shortcuts', () => {
479
688
  const listsWithDeletedFirst: TaskList[] = [
480
689
  {
@@ -617,6 +826,31 @@ describe('BoardViews', () => {
617
826
  });
618
827
  });
619
828
 
829
+ it('passes board filter criteria into deadline task query options without global sort', async () => {
830
+ renderBoardViews();
831
+
832
+ act(() => {
833
+ boardHeaderProps?.onListStatusFilterChange('active');
834
+ boardHeaderProps?.onFiltersChange({
835
+ ...boardHeaderProps.filters,
836
+ labels: [{ color: 'BLUE', id: 'label-1', name: 'Urgent' } as any],
837
+ searchQuery: 'launch',
838
+ sortBy: 'name-asc',
839
+ });
840
+ });
841
+
842
+ await waitFor(() => {
843
+ expect(kanbanBoardProps?.deadlineTaskQueryOptions).toEqual(
844
+ expect.objectContaining({
845
+ labelIds: ['label-1'],
846
+ listStatuses: ['active'],
847
+ q: 'launch',
848
+ })
849
+ );
850
+ });
851
+ expect(kanbanBoardProps?.deadlineTaskQueryOptions?.sortBy).toBeUndefined();
852
+ });
853
+
620
854
  it('uses server-side search counts to hide task lists without matching tasks', async () => {
621
855
  listWorkspaceTasksMock.mockImplementation(async (_workspaceId, options) => {
622
856
  if (options?.includeListCounts) {
@@ -0,0 +1,17 @@
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
+ 'bg-transparent'
14
+ );
15
+ expect(screen.getByTestId('kanban-skeleton')).toHaveClass('bg-transparent');
16
+ });
17
+ });
@@ -65,6 +65,22 @@ describe('TaskLegacyRouteRecovery', () => {
65
65
  });
66
66
  });
67
67
 
68
+ it('uses the board skeleton while resolving the canonical task route', () => {
69
+ mockGetWorkspaceTask.mockReturnValue(new Promise(() => {}));
70
+
71
+ renderWithQueryClient(
72
+ <TaskLegacyRouteRecovery
73
+ routePrefix="/tasks"
74
+ taskId="task-1"
75
+ workspaceId="personal"
76
+ />
77
+ );
78
+
79
+ expect(screen.getByTestId('task-board-loading-state')).toBeInTheDocument();
80
+ expect(screen.getByTestId('kanban-skeleton')).toBeInTheDocument();
81
+ expect(screen.queryByText('loading')).not.toBeInTheDocument();
82
+ });
83
+
68
84
  it('shows a recoverable error state instead of a 404 when resolution fails', async () => {
69
85
  mockGetWorkspaceTask.mockRejectedValue(new Error('boom'));
70
86
 
@@ -24,6 +24,7 @@ import {
24
24
  import { BoardViews } from './board-views';
25
25
  import { ProgressiveLoaderProvider } from './progressive-loader-context';
26
26
  import { dispatchRecentSidebarVisit } from './recent-sidebar-events';
27
+ import { TaskBoardLoadingState } from './task-board-loading-state';
27
28
  import { useProgressiveBoardLoader } from './use-progressive-board-loader';
28
29
 
29
30
  const BOARD_REVALIDATE_COOLDOWN_MS = 30_000;
@@ -237,13 +238,7 @@ export function BoardClient({
237
238
  ]);
238
239
 
239
240
  if (boardLoading && !board) {
240
- return (
241
- <div className="flex flex-col">
242
- <div className="p-4 text-center text-muted-foreground">
243
- Loading board...
244
- </div>
245
- </div>
246
- );
241
+ return <TaskBoardLoadingState />;
247
242
  }
248
243
 
249
244
  if (!board?.id) {
@@ -2,7 +2,13 @@
2
2
 
3
3
  import type { TaskFilters } from '../boards/boardId/task-filter';
4
4
 
5
- export type StoredBoardView = 'kanban' | 'list' | 'timeline';
5
+ export type StoredBoardView =
6
+ | 'kanban'
7
+ | 'list'
8
+ | 'my_tasks'
9
+ | 'timeline'
10
+ | 'drafts'
11
+ | 'recycle_bin';
6
12
  export type StoredListStatusFilter = 'all' | 'active' | 'not_started';
7
13
 
8
14
  export interface BoardViewConfig {