@tuturuuu/ui 0.9.0 → 0.11.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 (123) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/package.json +6 -5
  3. package/src/components/ui/custom/__tests__/settings-dialog-search.test.ts +78 -0
  4. package/src/components/ui/custom/__tests__/settings-dialog-shell-compile-graph.test.ts +76 -0
  5. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +27 -1
  6. package/src/components/ui/custom/nav-link.test.tsx +165 -0
  7. package/src/components/ui/custom/nav-link.tsx +69 -11
  8. package/src/components/ui/custom/navigation.tsx +1 -0
  9. package/src/components/ui/custom/settings/task-settings.tsx +104 -0
  10. package/src/components/ui/custom/settings-dialog-search-loader.d.ts +5 -0
  11. package/src/components/ui/custom/settings-dialog-search-loader.js +3 -0
  12. package/src/components/ui/custom/settings-dialog-search.ts +75 -0
  13. package/src/components/ui/custom/settings-dialog-shell.tsx +63 -27
  14. package/src/components/ui/custom/workspace-select-helpers.ts +23 -0
  15. package/src/components/ui/custom/workspace-select.tsx +17 -16
  16. package/src/components/ui/finance/invoices/components/subscription-attendance-summary.tsx +19 -5
  17. package/src/components/ui/finance/invoices/components/subscription-prepaid-controls.test.tsx +41 -0
  18. package/src/components/ui/finance/invoices/components/subscription-prepaid-controls.tsx +93 -0
  19. package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +82 -70
  20. package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-content.ts +50 -25
  21. package/src/components/ui/finance/invoices/hooks.ts +11 -2
  22. package/src/components/ui/finance/invoices/internal-api.ts +5 -0
  23. package/src/components/ui/finance/invoices/subscription-invoice.tsx +92 -31
  24. package/src/components/ui/finance/invoices/utils.test.ts +82 -0
  25. package/src/components/ui/finance/invoices/utils.ts +240 -7
  26. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +16 -0
  27. package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +12 -0
  28. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +4 -328
  29. package/src/components/ui/tu-do/boards/board-share-settings-panel.tsx +351 -0
  30. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +50 -37
  31. package/src/components/ui/tu-do/boards/boardId/enhanced-task-list.tsx +7 -0
  32. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-types.ts +3 -3
  33. package/src/components/ui/tu-do/boards/boardId/kanban/data/use-bulk-resources.ts +59 -5
  34. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +20 -1
  35. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +263 -21
  36. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +133 -14
  37. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +112 -54
  38. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +8 -2
  39. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +29 -14
  40. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +24 -1
  41. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +7 -0
  42. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +7 -1
  43. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +20 -0
  44. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +10 -0
  45. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +80 -8
  46. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +15 -0
  47. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +9 -0
  48. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +9 -0
  49. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-toolbar.tsx +9 -0
  50. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +35 -3
  51. package/src/components/ui/tu-do/boards/form.tsx +1 -1
  52. package/src/components/ui/tu-do/hooks/__tests__/useTaskLabelManagement.test.tsx +48 -0
  53. package/src/components/ui/tu-do/hooks/__tests__/useTaskProjectManagement.test.tsx +144 -0
  54. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +7 -0
  55. package/src/components/ui/tu-do/hooks/useTaskLabelManagement.ts +115 -106
  56. package/src/components/ui/tu-do/hooks/useTaskProjectManagement.ts +115 -122
  57. package/src/components/ui/tu-do/progress/task-progress-import-panel.tsx +60 -0
  58. package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +156 -0
  59. package/src/components/ui/tu-do/progress/task-progress-page.tsx +348 -0
  60. package/src/components/ui/tu-do/progress/task-progress-panels.tsx +301 -0
  61. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +26 -0
  62. package/src/components/ui/tu-do/shared/__tests__/assignee-select.test.tsx +81 -10
  63. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +123 -1
  64. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +38 -0
  65. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +128 -7
  66. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +222 -9
  67. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +58 -0
  68. package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +147 -0
  69. package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +3 -0
  70. package/src/components/ui/tu-do/shared/assignee-select.tsx +77 -26
  71. package/src/components/ui/tu-do/shared/board-client.tsx +16 -4
  72. package/src/components/ui/tu-do/shared/board-header.tsx +8 -1
  73. package/src/components/ui/tu-do/shared/board-switcher.tsx +70 -38
  74. package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +18 -12
  75. package/src/components/ui/tu-do/shared/board-views.tsx +49 -69
  76. package/src/components/ui/tu-do/shared/list-view.tsx +21 -3
  77. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +58 -4
  78. package/src/components/ui/tu-do/shared/task-cache-patches.ts +394 -0
  79. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +21 -1
  80. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +5 -1
  81. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -0
  82. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +25 -2
  83. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +7 -1
  84. package/src/components/ui/tu-do/shared/task-edit-dialog/description-versions.test.ts +97 -0
  85. package/src/components/ui/tu-do/shared/task-edit-dialog/description-versions.ts +210 -0
  86. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-data.ts +79 -10
  87. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +76 -77
  88. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.test.tsx +63 -0
  89. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.ts +78 -69
  90. package/src/components/ui/tu-do/shared/task-edit-dialog/personal-overrides-section.tsx +28 -8
  91. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/dependencies-section.tsx +14 -3
  92. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/parent-section.tsx +6 -1
  93. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/related-section.tsx +6 -1
  94. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/subtasks-section.tsx +6 -1
  95. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/types/task-relationships.types.ts +8 -0
  96. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +56 -8
  97. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-change-dialog.test.tsx +63 -0
  98. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-change-dialog.tsx +218 -0
  99. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-restore-banner.tsx +83 -0
  100. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-version-restore-dialog.test.tsx +120 -0
  101. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-version-restore-dialog.tsx +180 -0
  102. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +8 -1
  103. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.test.tsx +150 -0
  104. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +61 -35
  105. package/src/components/ui/tu-do/shared/task-edit-dialog/task-relationships-properties.tsx +44 -2
  106. package/src/components/ui/tu-do/shared/task-edit-dialog/utils.test.ts +100 -0
  107. package/src/components/ui/tu-do/shared/task-edit-dialog/utils.ts +109 -0
  108. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +390 -34
  109. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +11 -0
  110. package/src/components/ui/tu-do/shared/use-progressive-board-loader.ts +2 -0
  111. package/src/components/ui/tu-do/templates/task-template-api.ts +142 -0
  112. package/src/components/ui/tu-do/templates/task-template-card.tsx +118 -0
  113. package/src/components/ui/tu-do/templates/task-template-client.tsx +258 -0
  114. package/src/components/ui/tu-do/templates/task-template-dialogs.test.tsx +167 -0
  115. package/src/components/ui/tu-do/templates/task-template-dialogs.tsx +376 -0
  116. package/src/components/ui/tu-do/templates/task-templates-hub.test.tsx +114 -0
  117. package/src/components/ui/tu-do/templates/task-templates-hub.tsx +50 -0
  118. package/src/components/ui/tu-do/templates/task-templates-page.tsx +6 -11
  119. package/src/hooks/__tests__/useBoardPresence.test.tsx +191 -0
  120. package/src/hooks/__tests__/useBoardRealtime.test.tsx +24 -144
  121. package/src/hooks/useBoardPresence.ts +364 -0
  122. package/src/hooks/useBoardRealtimeEventHandler.ts +34 -90
  123. package/src/lib/workspace-actions.ts +2 -6
@@ -69,6 +69,8 @@ export const getLinkedFinanceCategorySelection = (
69
69
 
70
70
  const MONTH_VALUE_PATTERN = /^(\d{4})-(\d{2})$/;
71
71
  const DATE_VALUE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
72
+ export const MAX_PREPAID_MONTH_COUNT = 12;
73
+ export const PREPAID_MONTH_OPTION_HORIZON = 12;
72
74
 
73
75
  export const parseLocalCalendarDate = (
74
76
  value: string | Date | null | undefined
@@ -122,6 +124,79 @@ export const formatMonthLabel = (month: string, locale: string): string => {
122
124
  });
123
125
  };
124
126
 
127
+ export const addMonthsToMonthValue = (
128
+ month: string,
129
+ monthOffset: number
130
+ ): string => {
131
+ const date = getMonthStartDate(month);
132
+ if (Number.isNaN(date.getTime())) return month;
133
+
134
+ date.setMonth(date.getMonth() + monthOffset);
135
+ return formatMonthValue(date);
136
+ };
137
+
138
+ export const normalizePrepaidMonthCount = (
139
+ value: number | null | undefined
140
+ ): number => {
141
+ if (!Number.isFinite(value) || !value) return 1;
142
+
143
+ return Math.min(MAX_PREPAID_MONTH_COUNT, Math.max(1, Math.trunc(value)));
144
+ };
145
+
146
+ export const getCoverageMonths = (
147
+ selectedMonth: string,
148
+ prepaidMonthCount = 1
149
+ ): string[] => {
150
+ const monthCount = normalizePrepaidMonthCount(prepaidMonthCount);
151
+ const startMonth = getMonthStartDate(selectedMonth);
152
+ if (Number.isNaN(startMonth.getTime())) return [];
153
+
154
+ return Array.from({ length: monthCount }, (_, index) => {
155
+ const date = new Date(startMonth);
156
+ date.setMonth(date.getMonth() + index);
157
+ return formatMonthValue(date);
158
+ });
159
+ };
160
+
161
+ export const getCoverageEndMonthValue = (
162
+ selectedMonth: string,
163
+ prepaidMonthCount = 1
164
+ ): string => {
165
+ const coverageMonths = getCoverageMonths(selectedMonth, prepaidMonthCount);
166
+ return coverageMonths[coverageMonths.length - 1] ?? selectedMonth;
167
+ };
168
+
169
+ export const getCoverageValidUntilMonthValue = (
170
+ selectedMonth: string,
171
+ prepaidMonthCount = 1
172
+ ): string => addMonthsToMonthValue(selectedMonth, prepaidMonthCount);
173
+
174
+ export const formatCoverageRangeLabel = ({
175
+ locale,
176
+ prepaidMonthCount,
177
+ selectedMonth,
178
+ }: {
179
+ locale: string;
180
+ prepaidMonthCount?: number | null;
181
+ selectedMonth: string;
182
+ }): string => {
183
+ const coverageMonths = getCoverageMonths(
184
+ selectedMonth,
185
+ prepaidMonthCount ?? 1
186
+ );
187
+ const firstMonth = coverageMonths[0];
188
+ const lastMonth = coverageMonths[coverageMonths.length - 1];
189
+
190
+ if (!firstMonth || !lastMonth || firstMonth === lastMonth) {
191
+ return formatMonthLabel(selectedMonth, locale);
192
+ }
193
+
194
+ return `${formatMonthLabel(firstMonth, locale)} - ${formatMonthLabel(
195
+ lastMonth,
196
+ locale
197
+ )}`;
198
+ };
199
+
125
200
  const getComparableTimestamp = (value: string | null | undefined): number => {
126
201
  if (!value) return 0;
127
202
  const timestamp = new Date(value).getTime();
@@ -183,6 +258,38 @@ export const isSubscriptionMonthPaidForGroup = (
183
258
  getSubscriptionCoverageInvoiceForGroup(latestInvoices, groupId)
184
259
  );
185
260
 
261
+ export const isSubscriptionRangePaidForGroup = (
262
+ groupId: string,
263
+ selectedMonth: string,
264
+ prepaidMonthCount: number,
265
+ latestInvoices: SubscriptionCoverageInvoice[]
266
+ ): boolean => {
267
+ const coverageMonths = getCoverageMonths(selectedMonth, prepaidMonthCount);
268
+ if (coverageMonths.length === 0) return false;
269
+
270
+ return coverageMonths.every((month) =>
271
+ isSubscriptionMonthPaidForGroup(groupId, month, latestInvoices)
272
+ );
273
+ };
274
+
275
+ export const isSubscriptionRangeFullyPaidForGroups = (
276
+ groupIds: string[],
277
+ selectedMonth: string,
278
+ prepaidMonthCount: number,
279
+ latestInvoices: SubscriptionCoverageInvoice[]
280
+ ): boolean => {
281
+ if (groupIds.length === 0) return false;
282
+
283
+ return groupIds.every((groupId) =>
284
+ isSubscriptionRangePaidForGroup(
285
+ groupId,
286
+ selectedMonth,
287
+ prepaidMonthCount,
288
+ latestInvoices
289
+ )
290
+ );
291
+ };
292
+
186
293
  export const getAttendanceStats = (
187
294
  attendance: AttendanceRecord[]
188
295
  ): AttendanceStats => {
@@ -283,6 +390,17 @@ export const getBillableSessionsForGroups = (
283
390
  });
284
391
  };
285
392
 
393
+ export const getBillableSessionsForGroupsInRange = (
394
+ userGroups: UserGroup[],
395
+ groupIds: string[],
396
+ selectedMonth: string,
397
+ prepaidMonthCount = 1,
398
+ latestInvoices: SubscriptionCoverageInvoice[] = []
399
+ ): BillableSession[] =>
400
+ getCoverageMonths(selectedMonth, prepaidMonthCount).flatMap((month) =>
401
+ getBillableSessionsForGroups(userGroups, groupIds, month, latestInvoices)
402
+ );
403
+
286
404
  export const getBillableAttendanceRecords = (
287
405
  attendance: AttendanceRecord[],
288
406
  groupIds: string[],
@@ -305,6 +423,17 @@ export const getBillableAttendanceRecords = (
305
423
  });
306
424
  };
307
425
 
426
+ export const getBillableAttendanceRecordsInRange = (
427
+ attendance: AttendanceRecord[],
428
+ groupIds: string[],
429
+ selectedMonth: string,
430
+ prepaidMonthCount = 1,
431
+ latestInvoices: SubscriptionCoverageInvoice[] = []
432
+ ): AttendanceRecord[] =>
433
+ getCoverageMonths(selectedMonth, prepaidMonthCount).flatMap((month) =>
434
+ getBillableAttendanceRecords(attendance, groupIds, month, latestInvoices)
435
+ );
436
+
308
437
  export const getSessionsForMonth = (
309
438
  sessionsArray: string[] | null,
310
439
  month: string
@@ -453,26 +582,35 @@ export const getAvailableMonths = (
453
582
  groupIds: string[],
454
583
  latestInvoices: SubscriptionCoverageInvoice[],
455
584
  locale: string,
456
- selectedMonthFallback: string | null = null
585
+ selectedMonthFallback: string | null = null,
586
+ futureMonthHorizon = 0
457
587
  ): AvailableMonthOption[] => {
458
588
  if (groupIds.length === 0) return [];
459
589
  const { earliestStart, latestEnd } = getGroupsDateRange(userGroups, groupIds);
460
590
  if (!earliestStart) return [];
461
591
 
592
+ const currentMonthStart = (() => {
593
+ const date = new Date();
594
+ date.setDate(1);
595
+ return date;
596
+ })();
597
+ const futureHorizonEnd = new Date(currentMonthStart);
598
+ futureHorizonEnd.setMonth(
599
+ futureHorizonEnd.getMonth() + Math.max(0, futureMonthHorizon)
600
+ );
601
+
462
602
  const resolvedLatestEnd = latestEnd
463
603
  ? latestEnd
464
604
  : selectedMonthFallback
465
605
  ? getMonthStartDate(selectedMonthFallback)
466
- : (() => {
467
- const d = new Date();
468
- d.setDate(1);
469
- return d;
470
- })();
606
+ : currentMonthStart;
607
+ const resolvedRangeEnd =
608
+ futureMonthHorizon > 0 && !latestEnd ? futureHorizonEnd : resolvedLatestEnd;
471
609
 
472
610
  const months: AvailableMonthOption[] = [];
473
611
  const currentDate = new Date(earliestStart);
474
612
  currentDate.setDate(1);
475
- const normalizedLatestEnd = new Date(resolvedLatestEnd);
613
+ const normalizedLatestEnd = new Date(resolvedRangeEnd);
476
614
  normalizedLatestEnd.setDate(1);
477
615
 
478
616
  while (currentDate <= normalizedLatestEnd) {
@@ -504,6 +642,101 @@ export const getTotalSessionsForGroups = (
504
642
  ).length;
505
643
  };
506
644
 
645
+ export const getBillableQuantityForGroupRange = ({
646
+ groupId,
647
+ latestInvoices,
648
+ now = new Date(),
649
+ prepaidMonthCount = 1,
650
+ selectedMonth,
651
+ useAttendanceBased,
652
+ userAttendance,
653
+ userGroups,
654
+ }: {
655
+ groupId: string;
656
+ latestInvoices?: SubscriptionCoverageInvoice[];
657
+ now?: Date;
658
+ prepaidMonthCount?: number;
659
+ selectedMonth: string;
660
+ useAttendanceBased: boolean;
661
+ userAttendance: AttendanceRecord[];
662
+ userGroups: UserGroup[];
663
+ }): number => {
664
+ const currentMonthStart = getMonthStartDate(now);
665
+ const coverageMonths = getCoverageMonths(selectedMonth, prepaidMonthCount);
666
+
667
+ return coverageMonths.reduce((total, month) => {
668
+ if (isSubscriptionMonthPaidForGroup(groupId, month, latestInvoices ?? [])) {
669
+ return total;
670
+ }
671
+
672
+ const monthStart = getMonthStartDate(month);
673
+ const useScheduledSessions =
674
+ !useAttendanceBased ||
675
+ (!Number.isNaN(monthStart.getTime()) &&
676
+ !Number.isNaN(currentMonthStart.getTime()) &&
677
+ monthStart > currentMonthStart);
678
+
679
+ if (useScheduledSessions) {
680
+ return (
681
+ total +
682
+ getBillableSessionsForGroups(
683
+ userGroups,
684
+ [groupId],
685
+ month,
686
+ latestInvoices
687
+ ).length
688
+ );
689
+ }
690
+
691
+ return (
692
+ total +
693
+ getEffectiveAttendanceDays(
694
+ getBillableAttendanceRecords(
695
+ userAttendance,
696
+ [groupId],
697
+ month,
698
+ latestInvoices
699
+ )
700
+ )
701
+ );
702
+ }, 0);
703
+ };
704
+
705
+ export const getBillableQuantityMapForGroupsRange = ({
706
+ groupIds,
707
+ latestInvoices,
708
+ now,
709
+ prepaidMonthCount = 1,
710
+ selectedMonth,
711
+ useAttendanceBased,
712
+ userAttendance,
713
+ userGroups,
714
+ }: {
715
+ groupIds: string[];
716
+ latestInvoices?: SubscriptionCoverageInvoice[];
717
+ now?: Date;
718
+ prepaidMonthCount?: number;
719
+ selectedMonth: string;
720
+ useAttendanceBased: boolean;
721
+ userAttendance: AttendanceRecord[];
722
+ userGroups: UserGroup[];
723
+ }): Record<string, number> =>
724
+ Object.fromEntries(
725
+ groupIds.map((groupId) => [
726
+ groupId,
727
+ getBillableQuantityForGroupRange({
728
+ groupId,
729
+ latestInvoices,
730
+ now,
731
+ prepaidMonthCount,
732
+ selectedMonth,
733
+ useAttendanceBased,
734
+ userAttendance,
735
+ userGroups,
736
+ }),
737
+ ])
738
+ );
739
+
507
740
  /** Days before valid_until to consider "expiring soon" */
508
741
  const EXPIRING_SOON_DAYS = 14;
509
742
 
@@ -195,6 +195,22 @@ describe('BoardShareDialog', () => {
195
195
  expect(screen.getByText('pm@example.com')).toBeInTheDocument();
196
196
  });
197
197
 
198
+ it('does not crash when viewable members payload is missing members', async () => {
199
+ listWorkspaceTaskBoardViewableMembersMock.mockResolvedValue({});
200
+
201
+ renderBoardShareDialog();
202
+
203
+ fireEvent.click(
204
+ screen.getByRole('button', {
205
+ name: /ws-task-boards.share.workspace_members.title/,
206
+ })
207
+ );
208
+
209
+ expect(
210
+ await screen.findByText('ws-task-boards.share.workspace_members.empty')
211
+ ).toBeInTheDocument();
212
+ });
213
+
198
214
  it('keeps direct board guests first-class for invite, update, and remove', async () => {
199
215
  listWorkspaceTaskBoardSharesMock.mockResolvedValue({
200
216
  shares: [
@@ -104,6 +104,18 @@ describe('TaskBoardForm', () => {
104
104
  });
105
105
  });
106
106
 
107
+ it('uses the surface background for the board name input', () => {
108
+ render(
109
+ <QueryClientProvider client={queryClient}>
110
+ <TaskBoardForm wsId="ws-1" onFinish={vi.fn()} />
111
+ </QueryClientProvider>
112
+ );
113
+
114
+ expect(screen.getByLabelText('ws-task-boards.name')).toHaveClass(
115
+ 'bg-background'
116
+ );
117
+ });
118
+
107
119
  it('shows the localized duplicate-name message when the API rejects a duplicate board name', async () => {
108
120
  mutateAsync.mockRejectedValueOnce({
109
121
  code: 'TASK_BOARD_NAME_EXISTS',