@tuturuuu/ui 0.14.0 → 0.17.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 (101) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/biome.json +1 -1
  3. package/package.json +63 -62
  4. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +12 -0
  5. package/src/components/ui/custom/__tests__/sidebar-context.test.tsx +93 -6
  6. package/src/components/ui/custom/loading-statistic-card.tsx +18 -0
  7. package/src/components/ui/custom/notification-popover-client.tsx +5 -1
  8. package/src/components/ui/custom/settings/keyboard-shortcuts-settings.test.tsx +34 -0
  9. package/src/components/ui/custom/settings/keyboard-shortcuts-settings.tsx +106 -0
  10. package/src/components/ui/custom/settings/sidebar-settings.tsx +18 -6
  11. package/src/components/ui/custom/settings-dialog-shell.tsx +18 -12
  12. package/src/components/ui/custom/sidebar-context.tsx +58 -3
  13. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +5 -3
  14. package/src/components/ui/custom/statistic-card.tsx +104 -0
  15. package/src/components/ui/custom/structure.test.tsx +29 -0
  16. package/src/components/ui/custom/structure.tsx +103 -88
  17. package/src/components/ui/finance/invoices/civil-month.ts +224 -0
  18. package/src/components/ui/finance/invoices/components/subscription-invoice-proposal.test.tsx +49 -0
  19. package/src/components/ui/finance/invoices/components/subscription-invoice-proposal.tsx +81 -0
  20. package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +8 -2
  21. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +90 -2
  22. package/src/components/ui/finance/invoices/new-invoice-page.tsx +40 -4
  23. package/src/components/ui/finance/invoices/pending-columns.test.ts +26 -0
  24. package/src/components/ui/finance/invoices/pending-columns.tsx +45 -14
  25. package/src/components/ui/finance/invoices/standard-invoice.test.ts +31 -0
  26. package/src/components/ui/finance/invoices/standard-invoice.tsx +33 -16
  27. package/src/components/ui/finance/invoices/subscription-invoice.test.tsx +331 -0
  28. package/src/components/ui/finance/invoices/subscription-invoice.tsx +53 -28
  29. package/src/components/ui/finance/invoices/utils.test.ts +84 -0
  30. package/src/components/ui/finance/invoices/utils.ts +56 -134
  31. package/src/components/ui/resizable.test.ts +15 -0
  32. package/src/components/ui/resizable.tsx +28 -6
  33. package/src/components/ui/storefront/browse-panel.tsx +161 -0
  34. package/src/components/ui/storefront/bundle-selection-dialog.tsx +1 -1
  35. package/src/components/ui/storefront/cart-popover.tsx +1 -1
  36. package/src/components/ui/storefront/hero-panel.tsx +35 -30
  37. package/src/components/ui/storefront/listing-card.tsx +10 -5
  38. package/src/components/ui/storefront/merch-sections.tsx +7 -5
  39. package/src/components/ui/storefront/product-detail.tsx +79 -6
  40. package/src/components/ui/storefront/product-dialog.tsx +5 -1
  41. package/src/components/ui/storefront/storefront-surface.test.tsx +72 -0
  42. package/src/components/ui/storefront/storefront-surface.tsx +67 -63
  43. package/src/components/ui/storefront/types.ts +28 -0
  44. package/src/components/ui/storefront/utils.ts +4 -5
  45. package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +59 -1
  46. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +37 -8
  47. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-move.test.tsx +62 -0
  48. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +48 -0
  49. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +32 -2
  50. package/src/components/ui/tu-do/boards/boardId/menus/__tests__/task-menus.test.tsx +41 -3
  51. package/src/components/ui/tu-do/boards/boardId/menus/task-due-date-menu.tsx +31 -10
  52. package/src/components/ui/tu-do/boards/boardId/menus/task-priority-menu.tsx +7 -5
  53. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-menu.tsx +7 -5
  54. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-archive-visibility.test.ts +44 -0
  55. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-archive-visibility.ts +17 -0
  56. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +2 -0
  57. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +7 -0
  58. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +44 -30
  59. package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +54 -0
  60. package/src/components/ui/tu-do/my-tasks/task-list-with-completion.tsx +43 -1
  61. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +33 -15
  62. package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +3 -3
  63. package/src/components/ui/tu-do/progress/task-progress-page.tsx +60 -26
  64. package/src/components/ui/tu-do/progress/task-progress-panels.tsx +12 -10
  65. package/src/components/ui/tu-do/projects/components/project-grid-card.tsx +22 -6
  66. package/src/components/ui/tu-do/projects/components/project-list-item.tsx +24 -8
  67. package/src/components/ui/tu-do/projects/projectId/components/__tests__/tasks-tab-layout.test.tsx +6 -0
  68. package/src/components/ui/tu-do/projects/projectId/components/tasks-tab.tsx +3 -1
  69. package/src/components/ui/tu-do/projects/projectId/task-project-detail-page-client.tsx +11 -3
  70. package/src/components/ui/tu-do/projects/projectId/task-project-detail.tsx +2 -0
  71. package/src/components/ui/tu-do/projects/projectId/types.ts +1 -0
  72. package/src/components/ui/tu-do/projects/task-projects-client.tsx +45 -1
  73. package/src/components/ui/tu-do/projects/types.ts +4 -1
  74. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +10 -0
  75. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +7 -1
  76. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +87 -2
  77. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +27 -3
  78. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +41 -3
  79. package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +54 -0
  80. package/src/components/ui/tu-do/shared/board-header.tsx +20 -0
  81. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +4 -4
  82. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +2 -0
  83. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +7 -0
  84. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-mutations.test.tsx +231 -0
  85. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +139 -68
  86. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.test.tsx +68 -0
  87. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +17 -0
  88. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +45 -23
  89. package/src/components/ui/tu-do/shared/task-row-actions-menu.test.tsx +138 -0
  90. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +15 -9
  91. package/src/components/ui/tu-do/templates/client.tsx +97 -78
  92. package/src/components/ui/tu-do/templates/marketplace/client.tsx +171 -135
  93. package/src/components/ui/tu-do/templates/task-templates-hub.tsx +3 -0
  94. package/src/components/ui/tu-do/templates/templateId/client.tsx +24 -9
  95. package/src/components/ui/tu-do/templates/templateId/task-template-detail-page-client.tsx +8 -2
  96. package/src/globals.css +1 -0
  97. package/src/hooks/__tests__/use-notifications-subscription.test.tsx +34 -0
  98. package/src/hooks/__tests__/use-task-actions.test.tsx +14 -39
  99. package/src/hooks/task-actions-personal-external.ts +12 -6
  100. package/src/hooks/use-notifications.ts +14 -2
  101. package/vitest.config.ts +4 -0
@@ -286,12 +286,9 @@ describe('useTaskActions', () => {
286
286
  });
287
287
  });
288
288
 
289
- it('moves an external task to the personal and source done lists when checked', async () => {
289
+ it('moves an external task to done through terminal personal placement when checked', async () => {
290
290
  const completedAt = '2026-05-07T03:00:00.000Z';
291
291
  queryClient.setQueryData(['tasks', 'board-1'], [mockExternalTask]);
292
- mockListWorkspaceTaskLists.mockResolvedValueOnce({
293
- lists: mockSourceLists,
294
- });
295
292
  mockUpsertCurrentUserTaskPersonalPlacement.mockResolvedValueOnce({
296
293
  task: {
297
294
  ...mockExternalTask,
@@ -299,14 +296,10 @@ describe('useTaskActions', () => {
299
296
  personal_list_id: 'completion-list',
300
297
  personal_sort_key: 1_500_000,
301
298
  sort_key: 1_500_000,
302
- },
303
- });
304
- mockUpdateWorkspaceTask.mockResolvedValueOnce({
305
- task: {
306
- ...mockExternalTask,
307
- list_id: 'source-done-list',
299
+ source_list_id: 'source-done-list',
300
+ source_list_status: 'done',
308
301
  completed_at: completedAt,
309
- closed_at: null,
302
+ closed_at: completedAt,
310
303
  },
311
304
  });
312
305
 
@@ -329,10 +322,7 @@ describe('useTaskActions', () => {
329
322
  await result.current.handleArchiveToggle();
330
323
  });
331
324
 
332
- expect(mockListWorkspaceTaskLists).toHaveBeenCalledWith(
333
- 'source-ws',
334
- 'source-board'
335
- );
325
+ expect(mockListWorkspaceTaskLists).not.toHaveBeenCalled();
336
326
  expect(mockUpsertCurrentUserTaskPersonalPlacement).toHaveBeenCalledWith(
337
327
  'task-1',
338
328
  expect.objectContaining({
@@ -340,15 +330,10 @@ describe('useTaskActions', () => {
340
330
  personal_list_id: 'completion-list',
341
331
  previous_task_id: null,
342
332
  next_task_id: null,
333
+ terminal_status: 'done',
343
334
  })
344
335
  );
345
- expect(mockUpdateWorkspaceTask).toHaveBeenCalledWith(
346
- 'source-ws',
347
- 'task-1',
348
- {
349
- list_id: 'source-done-list',
350
- }
351
- );
336
+ expect(mockUpdateWorkspaceTask).not.toHaveBeenCalled();
352
337
  expect(queryClient.getQueryData<Task[]>(['tasks', 'board-1'])).toEqual([
353
338
  expect.objectContaining({
354
339
  id: 'task-1',
@@ -356,6 +341,7 @@ describe('useTaskActions', () => {
356
341
  personal_list_id: 'completion-list',
357
342
  source_list_id: 'source-done-list',
358
343
  completed_at: completedAt,
344
+ closed_at: completedAt,
359
345
  _localMutationAt: expect.any(Number),
360
346
  }),
361
347
  ]);
@@ -733,12 +719,9 @@ describe('useTaskActions', () => {
733
719
  expect(setMenuOpen).toHaveBeenCalledWith(false);
734
720
  });
735
721
 
736
- it('moves an external task to the personal and source closed lists', async () => {
722
+ it('moves an external task to closed through terminal personal placement', async () => {
737
723
  const closedAt = '2026-05-07T03:30:00.000Z';
738
724
  queryClient.setQueryData(['tasks', 'board-1'], [mockExternalTask]);
739
- mockListWorkspaceTaskLists.mockResolvedValueOnce({
740
- lists: mockSourceLists,
741
- });
742
725
  mockUpsertCurrentUserTaskPersonalPlacement.mockResolvedValueOnce({
743
726
  task: {
744
727
  ...mockExternalTask,
@@ -746,12 +729,8 @@ describe('useTaskActions', () => {
746
729
  personal_list_id: 'closed-list',
747
730
  personal_sort_key: 1_700_000,
748
731
  sort_key: 1_700_000,
749
- },
750
- });
751
- mockUpdateWorkspaceTask.mockResolvedValueOnce({
752
- task: {
753
- ...mockExternalTask,
754
- list_id: 'source-closed-list',
732
+ source_list_id: 'source-closed-list',
733
+ source_list_status: 'closed',
755
734
  completed_at: null,
756
735
  closed_at: closedAt,
757
736
  },
@@ -781,15 +760,11 @@ describe('useTaskActions', () => {
781
760
  expect.objectContaining({
782
761
  personal_board_id: 'board-1',
783
762
  personal_list_id: 'closed-list',
763
+ terminal_status: 'closed',
784
764
  })
785
765
  );
786
- expect(mockUpdateWorkspaceTask).toHaveBeenCalledWith(
787
- 'source-ws',
788
- 'task-1',
789
- {
790
- list_id: 'source-closed-list',
791
- }
792
- );
766
+ expect(mockListWorkspaceTaskLists).not.toHaveBeenCalled();
767
+ expect(mockUpdateWorkspaceTask).not.toHaveBeenCalled();
793
768
  expect(queryClient.getQueryData<Task[]>(['tasks', 'board-1'])).toEqual([
794
769
  expect.objectContaining({
795
770
  id: 'task-1',
@@ -152,16 +152,21 @@ export async function moveExternalTaskToPersonalList({
152
152
  const now = new Date().toISOString();
153
153
  let sourceTargetList: TaskList | null = null;
154
154
  const desiredSourceStatus = sourceStatus ?? targetList.status;
155
+ const terminalStatus =
156
+ targetList.status === 'done' || targetList.status === 'closed'
157
+ ? targetList.status
158
+ : undefined;
155
159
  const hasKnownSourceStatusChange = Boolean(
156
160
  task.source_list_status && task.source_list_status !== desiredSourceStatus
157
161
  );
158
162
  const shouldInspectSourceList = Boolean(
159
- sourceStatus ||
160
- hasKnownSourceStatusChange ||
161
- (!task.source_list_status &&
162
- task.source_list_id &&
163
- sourceWorkspaceId &&
164
- sourceBoardId)
163
+ !terminalStatus &&
164
+ (sourceStatus ||
165
+ hasKnownSourceStatusChange ||
166
+ (!task.source_list_status &&
167
+ task.source_list_id &&
168
+ sourceWorkspaceId &&
169
+ sourceBoardId))
165
170
  );
166
171
 
167
172
  try {
@@ -235,6 +240,7 @@ export async function moveExternalTaskToPersonalList({
235
240
  personal_list_id: targetList.id,
236
241
  previous_task_id: order.previous_task_id,
237
242
  next_task_id: order.next_task_id,
243
+ terminal_status: terminalStatus,
238
244
  }
239
245
  );
240
246
 
@@ -90,6 +90,8 @@ interface NotificationSubscriptionEntry {
90
90
  supabase: SupabaseClient;
91
91
  }
92
92
 
93
+ export const UNREAD_COUNT_FALLBACK_INTERVAL_MS = 5 * 60 * 1000;
94
+
93
95
  const notificationSubscriptionRegistry = new Map<
94
96
  string,
95
97
  NotificationSubscriptionEntry
@@ -265,11 +267,13 @@ export function useInfiniteNotifications({
265
267
  unreadOnly = false,
266
268
  readOnly = false,
267
269
  pageSize = 20,
270
+ enabled = true,
268
271
  }: {
269
272
  wsId?: string;
270
273
  unreadOnly?: boolean;
271
274
  readOnly?: boolean;
272
275
  pageSize?: number;
276
+ enabled?: boolean;
273
277
  }) {
274
278
  return useInfiniteQuery({
275
279
  queryKey: [
@@ -299,6 +303,7 @@ export function useInfiniteNotifications({
299
303
  } satisfies NotificationsPage;
300
304
  },
301
305
  initialPageParam: 0,
306
+ enabled,
302
307
  getNextPageParam: (lastPage) => {
303
308
  const nextOffset = lastPage.offset + lastPage.limit;
304
309
  return nextOffset < lastPage.count ? nextOffset : undefined;
@@ -312,7 +317,7 @@ export function useInfiniteNotifications({
312
317
  * Hook to get unread notification count.
313
318
  * If wsId is provided, scopes to that workspace. Otherwise returns total unread count.
314
319
  */
315
- export function useUnreadCount(wsId?: string) {
320
+ export function useUnreadCount(wsId?: string, options?: { enabled?: boolean }) {
316
321
  return useQuery({
317
322
  queryKey: ['notifications', 'unread-count', wsId || 'all'],
318
323
  queryFn: async () => {
@@ -328,7 +333,14 @@ export function useUnreadCount(wsId?: string) {
328
333
  const data = await response.json();
329
334
  return data.count as number;
330
335
  },
331
- refetchInterval: 30000,
336
+ enabled: options?.enabled ?? true,
337
+ staleTime: 60_000,
338
+ // Realtime invalidation is the primary update path. Keep a low-frequency
339
+ // refresh as a safety net for disconnected or suspended browser sessions.
340
+ refetchInterval: UNREAD_COUNT_FALLBACK_INTERVAL_MS,
341
+ refetchIntervalInBackground: false,
342
+ refetchOnReconnect: true,
343
+ refetchOnWindowFocus: true,
332
344
  });
333
345
  }
334
346
 
package/vitest.config.ts CHANGED
@@ -29,6 +29,10 @@ export default defineConfig({
29
29
  find: '@tuturuuu/internal-api',
30
30
  replacement: resolve(__dirname, '../internal-api/src/index.ts'),
31
31
  },
32
+ {
33
+ find: /^@tuturuuu\/types\/primitives\/(.+)$/,
34
+ replacement: `${resolve(__dirname, '../types/src/primitives')}/$1.ts`,
35
+ },
32
36
  {
33
37
  find: '@tuturuuu/supabase/next/client',
34
38
  replacement: resolve(__dirname, '../supabase/src/next/client.ts'),