@tuturuuu/ui 0.5.0 → 0.6.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 (88) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/package.json +41 -34
  3. package/src/components/ui/currency-input.tsx +65 -23
  4. package/src/components/ui/custom/__tests__/sidebar-context.test.tsx +64 -0
  5. package/src/components/ui/custom/__tests__/sidebar-remote-behavior-bridge.test.tsx +109 -0
  6. package/src/components/ui/custom/combobox.test.tsx +141 -0
  7. package/src/components/ui/custom/combobox.tsx +105 -36
  8. package/src/components/ui/custom/settings/task-settings.tsx +50 -0
  9. package/src/components/ui/custom/settings/task-sound-settings.test.tsx +21 -1
  10. package/src/components/ui/custom/sidebar-context.tsx +68 -6
  11. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +21 -2
  12. package/src/components/ui/finance/finance-layout.tsx +2 -4
  13. package/src/components/ui/finance/shared/balance-mode-toggle.tsx +35 -0
  14. package/src/components/ui/finance/shared/finance-layout-controls.tsx +43 -0
  15. package/src/components/ui/finance/shared/quick-actions.tsx +14 -6
  16. package/src/components/ui/finance/shared/use-finance-balance-mode.ts +72 -0
  17. package/src/components/ui/finance/shared/wallet-balance-mode.test.ts +66 -0
  18. package/src/components/ui/finance/shared/wallet-balance-mode.ts +42 -0
  19. package/src/components/ui/finance/transactions/form-types.ts +23 -0
  20. package/src/components/ui/finance/transactions/form.tsx +81 -22
  21. package/src/components/ui/finance/transactions/wallet-filter.tsx +21 -2
  22. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +73 -26
  23. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-history-dialog.tsx +617 -0
  24. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-panel.tsx +2 -1
  25. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-sections.tsx +4 -4
  26. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +298 -34
  27. package/src/components/ui/finance/wallets/checkpoints/wallet-total-check-dialog.tsx +219 -46
  28. package/src/components/ui/finance/wallets/columns-rendering.test.tsx +125 -0
  29. package/src/components/ui/finance/wallets/columns.test.ts +56 -0
  30. package/src/components/ui/finance/wallets/columns.tsx +196 -43
  31. package/src/components/ui/finance/wallets/form.test.tsx +79 -14
  32. package/src/components/ui/finance/wallets/form.tsx +41 -197
  33. package/src/components/ui/finance/wallets/query-invalidation.ts +1 -0
  34. package/src/components/ui/finance/wallets/wallet-basics-fields.tsx +141 -0
  35. package/src/components/ui/finance/wallets/wallet-credit-fields.tsx +136 -0
  36. package/src/components/ui/finance/wallets/walletId/credit-wallet-summary.tsx +143 -68
  37. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.test.tsx +105 -0
  38. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +120 -16
  39. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.test.tsx +64 -0
  40. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.tsx +226 -6
  41. package/src/components/ui/finance/wallets/walletId/wallet-details-page.test.tsx +64 -2
  42. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +42 -35
  43. package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +171 -0
  44. package/src/components/ui/finance/wallets/wallets-data-table.tsx +132 -29
  45. package/src/components/ui/finance/wallets/wallets-page.test.tsx +111 -37
  46. package/src/components/ui/finance/wallets/wallets-page.tsx +38 -78
  47. package/src/components/ui/storefront/accent-button.tsx +33 -0
  48. package/src/components/ui/storefront/cart-summary.tsx +140 -0
  49. package/src/components/ui/storefront/empty-listings.tsx +32 -0
  50. package/src/components/ui/storefront/hero-panel.tsx +70 -0
  51. package/src/components/ui/storefront/image-panel.tsx +40 -0
  52. package/src/components/ui/storefront/index.ts +12 -0
  53. package/src/components/ui/storefront/listing-card.tsx +129 -0
  54. package/src/components/ui/storefront/storefront-surface.test.tsx +85 -0
  55. package/src/components/ui/storefront/storefront-surface.tsx +235 -0
  56. package/src/components/ui/storefront/types.ts +99 -0
  57. package/src/components/ui/storefront/utils.ts +90 -0
  58. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +134 -0
  59. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +127 -0
  60. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +17 -42
  61. package/src/components/ui/tu-do/boards/boardId/timeline-board-open-task.test.tsx +164 -0
  62. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +25 -16
  63. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +15 -1
  64. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +2 -0
  65. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +114 -7
  66. package/src/components/ui/tu-do/providers/__tests__/task-dialog-provider.test.tsx +217 -5
  67. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +180 -35
  68. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +222 -26
  69. package/src/components/ui/tu-do/shared/board-client.tsx +1 -3
  70. package/src/components/ui/tu-do/shared/list-view-context-menu.test.tsx +55 -2
  71. package/src/components/ui/tu-do/shared/list-view.tsx +23 -16
  72. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +93 -76
  73. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +11 -0
  74. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +128 -1
  75. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +104 -69
  76. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.test.tsx +129 -0
  77. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.tsx +358 -0
  78. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +1 -1
  79. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +6 -2
  80. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.test.tsx +17 -1
  81. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.tsx +151 -111
  82. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-reset.ts +18 -2
  83. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +1 -2
  84. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +5 -3
  85. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +584 -53
  86. package/src/hooks/useBoardRealtime.ts +54 -1
  87. package/src/hooks/useBoardRealtimeEventHandler.ts +169 -4
  88. package/src/hooks/useTaskUserRealtime.ts +338 -0
@@ -1,18 +1,23 @@
1
1
  'use client';
2
2
 
3
- import { useQuery, useQueryClient } from '@tanstack/react-query';
3
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
4
  import type { Editor, JSONContent } from '@tiptap/react';
5
+ import { Archive, CheckCircle2, Trash2 } from '@tuturuuu/icons';
5
6
  import {
6
7
  checkWorkspacePermission,
7
8
  createWorkspaceTask,
9
+ createWorkspaceTaskSuggestions,
8
10
  updateWorkspaceCalendarEvent,
9
11
  uploadWorkspaceTaskFile,
10
12
  } from '@tuturuuu/internal-api';
13
+ import type { WorkspaceTaskSuggestionTask } from '@tuturuuu/internal-api/tasks';
11
14
  import type { Task } from '@tuturuuu/types/primitives/Task';
12
15
  import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
16
+ import { Button } from '@tuturuuu/ui/button';
13
17
  import { Dialog, DialogContent, DialogTitle } from '@tuturuuu/ui/dialog';
14
18
  import { useYjsCollaboration } from '@tuturuuu/ui/hooks/use-yjs-collaboration';
15
19
  import { toast } from '@tuturuuu/ui/sonner';
20
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip';
16
21
  import { MAX_TASK_DESCRIPTION_LENGTH } from '@tuturuuu/utils/constants';
17
22
  import { convertListItemToTask } from '@tuturuuu/utils/editor';
18
23
  import {
@@ -40,8 +45,16 @@ import {
40
45
  import { DescriptionOverflowWarningDialog } from './description-overflow-warning-dialog';
41
46
  import { createInitialSuggestionState } from './mention-system/types';
42
47
  import { SyncWarningDialog } from './sync-warning-dialog';
43
- import { CompactTaskCreatePopover } from './task-edit-dialog/components/compact-task-create-popover';
48
+ import {
49
+ normalizeTaskDialogPresentation,
50
+ type TaskDialogPresentation,
51
+ } from './task-dialog-presentation';
52
+ import { CompactTaskDialogPanel } from './task-edit-dialog/components/compact-task-create-popover';
44
53
  import { MobileFloatingSaveButton } from './task-edit-dialog/components/mobile-floating-save-button';
54
+ import {
55
+ SmartTaskSuggestionsButton,
56
+ SmartTaskSuggestionsPanel,
57
+ } from './task-edit-dialog/components/smart-task-suggestions-panel';
45
58
  import { TaskDescriptionEditor } from './task-edit-dialog/components/task-description-editor';
46
59
  import {
47
60
  getTaskDialogHeaderInfo,
@@ -91,6 +104,7 @@ import {
91
104
  import {
92
105
  broadcastTaskDescriptionUpsert,
93
106
  clearDraft,
107
+ getDescriptionContent,
94
108
  getDraftStorageKey,
95
109
  getTaskDescriptionPercentLeft,
96
110
  getTaskDescriptionStorageLength,
@@ -129,6 +143,9 @@ export interface TaskEditDialogProps {
129
143
  collaborationMode?: boolean;
130
144
  /** Whether realtime features (Yjs sync, presence avatars) are enabled - true for all tiers */
131
145
  realtimeEnabled?: boolean;
146
+ isHydratingTask?: boolean;
147
+ taskLoadError?: boolean;
148
+ taskHydrationVersion?: number;
132
149
  isPersonalWorkspace?: boolean;
133
150
  parentTaskId?: string;
134
151
  parentTaskName?: string;
@@ -138,6 +155,8 @@ export interface TaskEditDialogProps {
138
155
  sharedContext?: SharedTaskContext;
139
156
  /** Whether draft mode is enabled from user settings */
140
157
  draftModeEnabled?: boolean;
158
+ /** Preferred opening presentation for normal task dialogs */
159
+ defaultPresentation?: TaskDialogPresentation;
141
160
  /** When editing an existing draft, this is the draft ID */
142
161
  draftId?: string;
143
162
  onClose: () => void;
@@ -148,6 +167,7 @@ export interface TaskEditDialogProps {
148
167
  onAddBlockingTask?: () => void;
149
168
  onAddBlockedByTask?: () => void;
150
169
  onAddRelatedTask?: () => void;
170
+ onRetryTaskLoad?: () => void;
151
171
  }
152
172
 
153
173
  export function TaskEditDialog({
@@ -163,6 +183,9 @@ export function TaskEditDialog({
163
183
  mode = 'edit',
164
184
  collaborationMode = false,
165
185
  realtimeEnabled = false,
186
+ isHydratingTask = false,
187
+ taskLoadError = false,
188
+ taskHydrationVersion = 0,
166
189
  isPersonalWorkspace = false,
167
190
  parentTaskId,
168
191
  parentTaskName,
@@ -170,6 +193,7 @@ export function TaskEditDialog({
170
193
  currentUser: propsCurrentUser,
171
194
  sharedContext,
172
195
  draftModeEnabled = false,
196
+ defaultPresentation = 'compact',
173
197
  draftId,
174
198
  onClose,
175
199
  onUpdate,
@@ -179,6 +203,7 @@ export function TaskEditDialog({
179
203
  onAddBlockingTask,
180
204
  onAddBlockedByTask,
181
205
  onAddRelatedTask,
206
+ onRetryTaskLoad,
182
207
  }: TaskEditDialogProps) {
183
208
  const isCreateMode = mode === 'create';
184
209
  const effectiveTaskWsId = !isCreateMode ? (taskWsId ?? wsId) : wsId;
@@ -196,6 +221,12 @@ export function TaskEditDialog({
196
221
  // Disable editing if we are viewing via a shared link
197
222
  // User requested: always disable editing for shared tasks, regardless of permission
198
223
  const disabled = !!shareCode;
224
+ const taskControlsDisabled = disabled || isHydratingTask || taskLoadError;
225
+ const taskTitleDisabled = disabled || taskLoadError;
226
+ const effectiveRealtimeEnabled =
227
+ realtimeEnabled && !isHydratingTask && !taskLoadError;
228
+ const effectiveCollaborationMode =
229
+ collaborationMode && !isHydratingTask && !taskLoadError;
199
230
 
200
231
  // Keep this permission query disabled when `disabled` shared-link mode is active
201
232
  // (`enabled` becomes false) so `canManageTaskMedia` stays undefined while
@@ -217,7 +248,7 @@ export function TaskEditDialog({
217
248
  );
218
249
  return result.hasPermission;
219
250
  },
220
- enabled: Boolean(effectiveTaskWsId) && !disabled,
251
+ enabled: Boolean(effectiveTaskWsId) && !taskControlsDisabled,
221
252
  staleTime: 5 * 60 * 1000,
222
253
  });
223
254
 
@@ -235,7 +266,7 @@ export function TaskEditDialog({
235
266
  });
236
267
 
237
268
  // Refs
238
- const titleInputRef = useRef<HTMLInputElement>(null);
269
+ const titleInputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
239
270
  const editorRef = useRef<HTMLDivElement>(null);
240
271
  const richTextEditorRef = useRef<HTMLDivElement>(null);
241
272
  const lastCursorPositionRef = useRef<number | null>(null);
@@ -245,6 +276,7 @@ export function TaskEditDialog({
245
276
  );
246
277
  const nameUpdateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
247
278
  const pendingNameRef = useRef<string | null>(null);
279
+ const nameEditedDuringHydrationRef = useRef(false);
248
280
  const quickDueRef = useRef<(days: number | null) => void>(() => {});
249
281
  const updateEstimationRef = useRef<(points: number | null) => void>(() => {});
250
282
  const handleConvertToTaskRef = useRef<(() => Promise<void>) | null>(null);
@@ -256,6 +288,17 @@ export function TaskEditDialog({
256
288
  descriptionRef.current = formState.description;
257
289
  }, [formState.description]);
258
290
 
291
+ const setTaskName = useCallback(
292
+ (value: string) => {
293
+ if (isHydratingTask) {
294
+ nameEditedDuringHydrationRef.current = true;
295
+ }
296
+
297
+ formState.setName(value);
298
+ },
299
+ [formState.setName, isHydratingTask]
300
+ );
301
+
259
302
  // User state
260
303
  const [user, setUser] = useState<TaskDialogUserIdentity | null>(
261
304
  propsCurrentUser ? normalizeTaskDialogCurrentUser(propsCurrentUser) : null
@@ -386,7 +429,7 @@ export function TaskEditDialog({
386
429
  columnName: 'description_yjs_state',
387
430
  id: task?.id || '',
388
431
  user: yjsUser,
389
- enabled: isOpen && !isCreateMode && realtimeEnabled && !!task?.id,
432
+ enabled: isOpen && !isCreateMode && effectiveRealtimeEnabled && !!task?.id,
390
433
  broadcastDebounceMs: workspaceTier && workspaceTier !== 'FREE' ? 0 : 200,
391
434
  saveDebounceMs: 5000,
392
435
  loadDocumentState: loadTaskDescriptionState,
@@ -396,7 +439,7 @@ export function TaskEditDialog({
396
439
  const [hasHydratedYjsState, setHasHydratedYjsState] = useState(false);
397
440
 
398
441
  useEffect(() => {
399
- if (!isOpen || isCreateMode || !realtimeEnabled || !task?.id) {
442
+ if (!isOpen || isCreateMode || !effectiveRealtimeEnabled || !task?.id) {
400
443
  setHasHydratedYjsState(false);
401
444
  return;
402
445
  }
@@ -404,13 +447,13 @@ export function TaskEditDialog({
404
447
  if (synced) {
405
448
  setHasHydratedYjsState(true);
406
449
  }
407
- }, [isOpen, isCreateMode, realtimeEnabled, task?.id, synced]);
450
+ }, [isOpen, isCreateMode, effectiveRealtimeEnabled, task?.id, synced]);
408
451
 
409
452
  const isYjsSyncing = useMemo(() => {
410
453
  return (
411
454
  isOpen &&
412
455
  !isCreateMode &&
413
- realtimeEnabled &&
456
+ effectiveRealtimeEnabled &&
414
457
  !!task?.id &&
415
458
  !hasHydratedYjsState &&
416
459
  !synced
@@ -419,7 +462,7 @@ export function TaskEditDialog({
419
462
  hasHydratedYjsState,
420
463
  isOpen,
421
464
  isCreateMode,
422
- realtimeEnabled,
465
+ effectiveRealtimeEnabled,
423
466
  task?.id,
424
467
  synced,
425
468
  ]);
@@ -509,7 +552,30 @@ export function TaskEditDialog({
509
552
  useState(false);
510
553
  const [showShareDialog, setShowShareDialog] = useState(false);
511
554
  const [saveAsDraft, setSaveAsDraft] = useState(draftModeEnabled);
512
- const [isCreateFullscreen, setIsCreateFullscreen] = useState(false);
555
+ const normalizedDefaultPresentation = useMemo(
556
+ () => normalizeTaskDialogPresentation(defaultPresentation),
557
+ [defaultPresentation]
558
+ );
559
+ const [presentation, setPresentation] = useState<TaskDialogPresentation>(
560
+ normalizedDefaultPresentation
561
+ );
562
+ const [smartSuggestions, setSmartSuggestions] = useState<
563
+ WorkspaceTaskSuggestionTask[]
564
+ >([]);
565
+ const [selectedSmartSuggestionIds, setSelectedSmartSuggestionIds] = useState<
566
+ string[]
567
+ >([]);
568
+ const [smartSuggestionError, setSmartSuggestionError] = useState<
569
+ string | null
570
+ >(null);
571
+ const [smartCreateErrors, setSmartCreateErrors] = useState<
572
+ Record<string, string>
573
+ >({});
574
+ const [creatingSmartSuggestionIds, setCreatingSmartSuggestionIds] = useState<
575
+ string[]
576
+ >([]);
577
+ const [isCreatingSmartSuggestions, setIsCreatingSmartSuggestions] =
578
+ useState(false);
513
579
  const previousOpenRef = useRef(false);
514
580
  const [isTitleVisible, setIsTitleVisible] = useState(true);
515
581
  const [scrollContainer, setScrollContainer] = useState<HTMLDivElement | null>(
@@ -709,8 +775,8 @@ export function TaskEditDialog({
709
775
  selectedLabels: formState.selectedLabels,
710
776
  selectedAssignees: formState.selectedAssignees,
711
777
  isCreateMode,
712
- isLoading,
713
- collaborationMode,
778
+ isLoading: isLoading || taskControlsDisabled,
779
+ collaborationMode: effectiveCollaborationMode,
714
780
  draftId,
715
781
  });
716
782
 
@@ -733,7 +799,7 @@ export function TaskEditDialog({
733
799
  boardId,
734
800
  isCreateMode,
735
801
  isOpen,
736
- realtimeEnabled,
802
+ realtimeEnabled: effectiveRealtimeEnabled,
737
803
  isPersonalWorkspace,
738
804
  name: formState.name,
739
805
  priority: formState.priority,
@@ -783,6 +849,41 @@ export function TaskEditDialog({
783
849
  onUpdate,
784
850
  });
785
851
 
852
+ const currentList = availableLists?.find(
853
+ (list) => list.id === formState.selectedListId
854
+ );
855
+ const doneList = availableLists?.find(
856
+ (list) => list.status === 'done' && !list.deleted
857
+ );
858
+ const closedList = availableLists?.find(
859
+ (list) => list.status === 'closed' && !list.deleted
860
+ );
861
+ const isDeletedTask = Boolean(
862
+ task?.deleted_at ||
863
+ (task as (Task & { deleted?: boolean }) | undefined)?.deleted
864
+ );
865
+ const canShowCompactEditActions =
866
+ !isCreateMode &&
867
+ !!task?.id &&
868
+ task.id !== 'new' &&
869
+ !isDeletedTask &&
870
+ !disabled &&
871
+ !taskLoadError;
872
+ const compactEditActionsDisabled = isLoading || isHydratingTask;
873
+ const canShowCompactStatusActions =
874
+ canShowCompactEditActions && currentList?.status !== 'documents';
875
+ const showCompactDoneAction =
876
+ canShowCompactStatusActions &&
877
+ !!doneList &&
878
+ doneList.id !== formState.selectedListId &&
879
+ currentList?.status !== 'done';
880
+ const showCompactClosedAction =
881
+ canShowCompactStatusActions &&
882
+ !!closedList &&
883
+ closedList.id !== formState.selectedListId &&
884
+ closedList.id !== doneList?.id &&
885
+ currentList?.status !== 'closed';
886
+
786
887
  // Task relationships
787
888
  const {
788
889
  toggleLabel,
@@ -861,6 +962,8 @@ export function TaskEditDialog({
861
962
  isCreateMode,
862
963
  task,
863
964
  filters,
965
+ taskHydrationVersion,
966
+ preserveNameOnHydration: nameEditedDuringHydrationRef.current,
864
967
  setName: formState.setName,
865
968
  setDescription: formState.setDescription,
866
969
  setPriority: formState.setPriority,
@@ -873,6 +976,12 @@ export function TaskEditDialog({
873
976
  setSelectedProjects: formState.setSelectedProjects,
874
977
  });
875
978
 
979
+ useEffect(() => {
980
+ if (!isHydratingTask) {
981
+ nameEditedDuringHydrationRef.current = false;
982
+ }
983
+ }, [isHydratingTask]);
984
+
876
985
  // Yjs sync
877
986
  useTaskYjsSync({
878
987
  taskId: task?.id,
@@ -880,7 +989,7 @@ export function TaskEditDialog({
880
989
  boardId,
881
990
  isOpen,
882
991
  isCreateMode,
883
- realtimeEnabled,
992
+ realtimeEnabled: effectiveRealtimeEnabled,
884
993
  editorInstance,
885
994
  doc,
886
995
  yjsProvider: provider,
@@ -1203,7 +1312,7 @@ export function TaskEditDialog({
1203
1312
  saveAsDraft: isCreateMode && (!!draftId || saveAsDraft),
1204
1313
  draftId,
1205
1314
  isCreateMode,
1206
- collaborationMode,
1315
+ collaborationMode: effectiveCollaborationMode,
1207
1316
  isPersonalWorkspace,
1208
1317
  shareCode,
1209
1318
  sharedPermission,
@@ -1252,6 +1361,309 @@ export function TaskEditDialog({
1252
1361
  setSelectedProjects: formState.setSelectedProjects,
1253
1362
  });
1254
1363
 
1364
+ const smartSuggestionsMutation = useMutation({
1365
+ mutationFn: async () => {
1366
+ const prompt = formState.name.trim();
1367
+ if (!prompt) {
1368
+ throw new Error(dialogT('smart_prompt_required'));
1369
+ }
1370
+
1371
+ const currentDescription =
1372
+ flushEditorPendingRef.current?.() ?? formState.description;
1373
+
1374
+ return createWorkspaceTaskSuggestions(effectiveTaskWsId, {
1375
+ boardId,
1376
+ prompt,
1377
+ description: serializeTaskDescriptionContent(currentDescription),
1378
+ currentListId: formState.selectedListId || undefined,
1379
+ clientTimezone:
1380
+ Intl.DateTimeFormat().resolvedOptions().timeZone || undefined,
1381
+ clientTimestamp: new Date().toISOString(),
1382
+ });
1383
+ },
1384
+ onMutate: () => {
1385
+ setSmartSuggestionError(null);
1386
+ setSmartCreateErrors({});
1387
+ },
1388
+ onSuccess: (response) => {
1389
+ const suggestions = response.tasks ?? [];
1390
+ setSmartSuggestions(suggestions);
1391
+ setSelectedSmartSuggestionIds(
1392
+ suggestions.map((suggestion) => suggestion.id)
1393
+ );
1394
+ if (!suggestions.length) {
1395
+ setSmartSuggestionError(dialogT('smart_no_suggestions'));
1396
+ }
1397
+ },
1398
+ onError: (error) => {
1399
+ setSmartSuggestionError(
1400
+ error instanceof Error
1401
+ ? error.message
1402
+ : dialogT('smart_suggestions_failed_description')
1403
+ );
1404
+ },
1405
+ });
1406
+
1407
+ const canUseSmartSuggestions =
1408
+ isCreateMode && !draftId && !disabled && Boolean(boardId);
1409
+
1410
+ const handleGenerateSmartSuggestions = useCallback(() => {
1411
+ if (!canUseSmartSuggestions) return;
1412
+
1413
+ if (!formState.name.trim()) {
1414
+ toast.error(dialogT('smart_prompt_required'));
1415
+ return;
1416
+ }
1417
+
1418
+ smartSuggestionsMutation.mutate();
1419
+ }, [
1420
+ canUseSmartSuggestions,
1421
+ dialogT,
1422
+ formState.name,
1423
+ smartSuggestionsMutation,
1424
+ ]);
1425
+
1426
+ const applySmartSuggestion = useCallback(
1427
+ (suggestion: WorkspaceTaskSuggestionTask) => {
1428
+ formState.setName(suggestion.title);
1429
+ formState.setDescription(getDescriptionContent(suggestion.description));
1430
+ formState.setPriority(suggestion.priority);
1431
+ formState.setEndDate(
1432
+ suggestion.endDate ? new Date(suggestion.endDate) : undefined
1433
+ );
1434
+ formState.setSelectedListId(suggestion.listId);
1435
+ formState.setEstimationPoints(suggestion.estimationPoints);
1436
+ formState.setSelectedLabels(
1437
+ suggestion.labels.map((label) => ({
1438
+ id: label.id,
1439
+ name: label.name,
1440
+ color: label.color ?? '',
1441
+ created_at: label.created_at ?? '',
1442
+ }))
1443
+ );
1444
+ formState.setSelectedProjects(
1445
+ suggestion.projects.map((project) => ({
1446
+ id: project.id,
1447
+ name: project.name,
1448
+ status: project.status ?? 'active',
1449
+ }))
1450
+ );
1451
+ formState.setTotalDuration(
1452
+ suggestion.durationMinutes ? suggestion.durationMinutes / 60 : null
1453
+ );
1454
+ formState.setIsSplittable(suggestion.isSplittable);
1455
+ formState.setMinSplitDurationMinutes(suggestion.minSplitDurationMinutes);
1456
+ formState.setMaxSplitDurationMinutes(suggestion.maxSplitDurationMinutes);
1457
+ formState.setCalendarHours(suggestion.calendarHours ?? null);
1458
+ formState.setAutoSchedule(suggestion.autoSchedule);
1459
+ toast.success(dialogT('smart_suggestion_applied'));
1460
+ },
1461
+ [
1462
+ dialogT,
1463
+ formState.setAutoSchedule,
1464
+ formState.setCalendarHours,
1465
+ formState.setDescription,
1466
+ formState.setEndDate,
1467
+ formState.setEstimationPoints,
1468
+ formState.setIsSplittable,
1469
+ formState.setMaxSplitDurationMinutes,
1470
+ formState.setMinSplitDurationMinutes,
1471
+ formState.setName,
1472
+ formState.setPriority,
1473
+ formState.setSelectedLabels,
1474
+ formState.setSelectedListId,
1475
+ formState.setSelectedProjects,
1476
+ formState.setTotalDuration,
1477
+ ]
1478
+ );
1479
+
1480
+ const handleToggleSmartSuggestion = useCallback((suggestionId: string) => {
1481
+ setSelectedSmartSuggestionIds((current) =>
1482
+ current.includes(suggestionId)
1483
+ ? current.filter((id) => id !== suggestionId)
1484
+ : [...current, suggestionId]
1485
+ );
1486
+ }, []);
1487
+
1488
+ const handleCreateSelectedSmartSuggestions = useCallback(async () => {
1489
+ if (!selectedSmartSuggestionIds.length) return;
1490
+
1491
+ const selectedSuggestions = smartSuggestions.filter((suggestion) =>
1492
+ selectedSmartSuggestionIds.includes(suggestion.id)
1493
+ );
1494
+ if (!selectedSuggestions.length) return;
1495
+
1496
+ setIsCreatingSmartSuggestions(true);
1497
+ setSmartCreateErrors({});
1498
+ setCreatingSmartSuggestionIds(selectedSuggestions.map((s) => s.id));
1499
+
1500
+ let resolvedUserId = user?.id;
1501
+ if (!resolvedUserId && isPersonalWorkspace) {
1502
+ resolvedUserId = userForSave?.id;
1503
+ }
1504
+
1505
+ const currentAssigneeIds = formState.selectedAssignees
1506
+ .map((assignee) => assignee.user_id || assignee.id)
1507
+ .filter((assigneeId): assigneeId is string => !!assigneeId);
1508
+ const desiredAssigneeIds =
1509
+ currentAssigneeIds.length > 0
1510
+ ? currentAssigneeIds
1511
+ : userTaskSettings?.task_auto_assign_to_self &&
1512
+ resolvedUserId &&
1513
+ !isPersonalWorkspace
1514
+ ? [resolvedUserId]
1515
+ : [];
1516
+ const nextErrors: Record<string, string> = {};
1517
+ const broadcast = getActiveBroadcast() ?? taskRealtimeBroadcastRef.current;
1518
+
1519
+ for (const suggestion of selectedSuggestions) {
1520
+ setCreatingSmartSuggestionIds((current) =>
1521
+ current.includes(suggestion.id) ? current : [...current, suggestion.id]
1522
+ );
1523
+
1524
+ try {
1525
+ const response = await createWorkspaceTask(effectiveTaskWsId, {
1526
+ name: suggestion.title,
1527
+ description: suggestion.description,
1528
+ listId: suggestion.listId,
1529
+ priority: suggestion.priority,
1530
+ end_date: suggestion.endDate,
1531
+ estimation_points: suggestion.estimationPoints,
1532
+ label_ids: suggestion.labelIds,
1533
+ project_ids: suggestion.projectIds,
1534
+ assignee_ids: desiredAssigneeIds,
1535
+ total_duration: suggestion.durationMinutes
1536
+ ? suggestion.durationMinutes / 60
1537
+ : null,
1538
+ is_splittable: suggestion.isSplittable,
1539
+ min_split_duration_minutes: suggestion.minSplitDurationMinutes,
1540
+ max_split_duration_minutes: suggestion.maxSplitDurationMinutes,
1541
+ calendar_hours: suggestion.calendarHours,
1542
+ auto_schedule: suggestion.autoSchedule,
1543
+ });
1544
+
1545
+ const createdTask: Task = {
1546
+ ...(response.task as Task),
1547
+ labels: suggestion.labels.map((label) => ({
1548
+ id: label.id,
1549
+ name: label.name,
1550
+ color: label.color ?? '',
1551
+ created_at: label.created_at ?? '',
1552
+ })),
1553
+ assignees: desiredAssigneeIds.map((assigneeId) => ({
1554
+ id: assigneeId,
1555
+ })),
1556
+ projects: suggestion.projects.map((project) => ({
1557
+ id: project.id,
1558
+ name: project.name,
1559
+ status: project.status ?? 'active',
1560
+ })),
1561
+ };
1562
+
1563
+ queryClient.setQueryData(
1564
+ ['tasks', boardId],
1565
+ (old: Task[] | undefined) => {
1566
+ if (!old) return [createdTask];
1567
+ if (old.some((task) => task.id === createdTask.id)) return old;
1568
+ return [...old, createdTask];
1569
+ }
1570
+ );
1571
+ broadcast?.('task:upsert', { task: createdTask });
1572
+ if (
1573
+ suggestion.labelIds.length ||
1574
+ suggestion.projectIds.length ||
1575
+ desiredAssigneeIds.length
1576
+ ) {
1577
+ broadcast?.('task:relations-changed', { taskId: createdTask.id });
1578
+ }
1579
+ } catch (error) {
1580
+ nextErrors[suggestion.id] =
1581
+ error instanceof Error
1582
+ ? error.message
1583
+ : dialogT('smart_create_failed');
1584
+ } finally {
1585
+ setCreatingSmartSuggestionIds((current) =>
1586
+ current.filter((id) => id !== suggestion.id)
1587
+ );
1588
+ }
1589
+ }
1590
+
1591
+ await invalidateTaskCaches(queryClient, boardId);
1592
+ onUpdate();
1593
+
1594
+ setSmartCreateErrors(nextErrors);
1595
+ const failedIds = Object.keys(nextErrors);
1596
+ setSelectedSmartSuggestionIds(failedIds);
1597
+
1598
+ if (failedIds.length) {
1599
+ toast.error(dialogT('smart_create_partial_failed'));
1600
+ } else {
1601
+ toast.success(
1602
+ dialogT('smart_create_selected_success', {
1603
+ count: selectedSuggestions.length,
1604
+ })
1605
+ );
1606
+ setSmartSuggestions([]);
1607
+ setSelectedSmartSuggestionIds([]);
1608
+ onClose();
1609
+ }
1610
+
1611
+ setIsCreatingSmartSuggestions(false);
1612
+ setCreatingSmartSuggestionIds([]);
1613
+ }, [
1614
+ boardId,
1615
+ dialogT,
1616
+ effectiveTaskWsId,
1617
+ formState.selectedAssignees,
1618
+ isPersonalWorkspace,
1619
+ onClose,
1620
+ onUpdate,
1621
+ queryClient,
1622
+ selectedSmartSuggestionIds,
1623
+ smartSuggestions,
1624
+ user?.id,
1625
+ userForSave?.id,
1626
+ userTaskSettings?.task_auto_assign_to_self,
1627
+ ]);
1628
+
1629
+ const smartSuggestionsPanel =
1630
+ canUseSmartSuggestions &&
1631
+ (smartSuggestionsMutation.isPending ||
1632
+ smartSuggestionError ||
1633
+ smartSuggestions.length > 0) ? (
1634
+ <SmartTaskSuggestionsPanel
1635
+ suggestions={smartSuggestions}
1636
+ selectedSuggestionIds={selectedSmartSuggestionIds}
1637
+ createErrors={smartCreateErrors}
1638
+ creatingSuggestionIds={creatingSmartSuggestionIds}
1639
+ errorMessage={smartSuggestionError}
1640
+ isCreatingSelected={isCreatingSmartSuggestions}
1641
+ isLoading={smartSuggestionsMutation.isPending}
1642
+ onApplyFirst={() => {
1643
+ const firstSuggestion = smartSuggestions[0];
1644
+ if (firstSuggestion) applySmartSuggestion(firstSuggestion);
1645
+ }}
1646
+ onApplySuggestion={applySmartSuggestion}
1647
+ onClose={() => {
1648
+ setSmartSuggestions([]);
1649
+ setSelectedSmartSuggestionIds([]);
1650
+ setSmartSuggestionError(null);
1651
+ setSmartCreateErrors({});
1652
+ }}
1653
+ onCreateSelected={handleCreateSelectedSmartSuggestions}
1654
+ onRetry={handleGenerateSmartSuggestions}
1655
+ onToggleSuggestion={handleToggleSmartSuggestion}
1656
+ />
1657
+ ) : null;
1658
+
1659
+ const smartSuggestionsButton = canUseSmartSuggestions ? (
1660
+ <SmartTaskSuggestionsButton
1661
+ disabled={smartSuggestionsMutation.isPending || isLoading}
1662
+ isLoading={smartSuggestionsMutation.isPending}
1663
+ onClick={handleGenerateSmartSuggestions}
1664
+ />
1665
+ ) : null;
1666
+
1255
1667
  const persistTaskDescriptionOnClose = useCallback(async () => {
1256
1668
  if (isCreateMode || !task?.id || !flushEditorPendingRef.current) {
1257
1669
  return true;
@@ -1335,7 +1747,7 @@ export function TaskEditDialog({
1335
1747
  useTaskDialogClose({
1336
1748
  taskId: task?.id,
1337
1749
  isCreateMode,
1338
- collaborationMode,
1750
+ collaborationMode: effectiveCollaborationMode,
1339
1751
  synced,
1340
1752
  connected,
1341
1753
  draftStorageKey,
@@ -1483,8 +1895,8 @@ export function TaskEditDialog({
1483
1895
  isOpen,
1484
1896
  canSave,
1485
1897
  isCreateMode,
1486
- collaborationMode,
1487
- disabled,
1898
+ collaborationMode: effectiveCollaborationMode,
1899
+ disabled: taskControlsDisabled,
1488
1900
  editorInstance,
1489
1901
  boardConfig,
1490
1902
  slashState: suggestionMenus.slashState,
@@ -1534,14 +1946,20 @@ export function TaskEditDialog({
1534
1946
  previousOpenRef.current = isOpen;
1535
1947
 
1536
1948
  if (!isOpen) {
1537
- setIsCreateFullscreen(false);
1949
+ setPresentation(normalizedDefaultPresentation);
1950
+ setSmartSuggestions([]);
1951
+ setSelectedSmartSuggestionIds([]);
1952
+ setSmartSuggestionError(null);
1953
+ setSmartCreateErrors({});
1954
+ setCreatingSmartSuggestionIds([]);
1955
+ setIsCreatingSmartSuggestions(false);
1538
1956
  return;
1539
1957
  }
1540
1958
 
1541
- if (justOpened && isCreateMode && !draftId) {
1542
- setIsCreateFullscreen(false);
1959
+ if (justOpened) {
1960
+ setPresentation(draftId ? 'fullscreen' : normalizedDefaultPresentation);
1543
1961
  }
1544
- }, [isOpen, isCreateMode, draftId]);
1962
+ }, [isOpen, draftId, normalizedDefaultPresentation]);
1545
1963
 
1546
1964
  // Track whether the title input is scrolled out of view
1547
1965
  useEffect(() => {
@@ -1604,8 +2022,25 @@ export function TaskEditDialog({
1604
2022
  taskSearchQuery,
1605
2023
  ]);
1606
2024
 
1607
- const showCompactCreate =
1608
- isCreateMode && !draftId && !isCreateFullscreen && !disabled;
2025
+ const showCompactDialog = presentation === 'compact' && !draftId;
2026
+ const taskHydrationNotice = taskLoadError ? (
2027
+ <div
2028
+ className="mx-4 mb-2 flex items-center justify-between gap-3 rounded-md border border-dynamic-red/30 bg-dynamic-red/10 px-3 py-2 text-dynamic-red text-sm md:mx-8"
2029
+ role="alert"
2030
+ >
2031
+ <span>{t('please_try_again_later')}</span>
2032
+ {onRetryTaskLoad && (
2033
+ <Button
2034
+ type="button"
2035
+ variant="secondary"
2036
+ size="xs"
2037
+ onClick={onRetryTaskLoad}
2038
+ >
2039
+ {t('retry')}
2040
+ </Button>
2041
+ )}
2042
+ </div>
2043
+ ) : null;
1609
2044
  const compactHeaderInfo = useMemo(
1610
2045
  () =>
1611
2046
  getTaskDialogHeaderInfo(
@@ -1627,6 +2062,62 @@ export function TaskEditDialog({
1627
2062
  rootT,
1628
2063
  ]
1629
2064
  );
2065
+ const compactEditActions = canShowCompactEditActions ? (
2066
+ <>
2067
+ {showCompactDoneAction && doneList && (
2068
+ <Tooltip>
2069
+ <TooltipTrigger asChild>
2070
+ <Button
2071
+ type="button"
2072
+ variant="ghost"
2073
+ size="icon"
2074
+ aria-label={t('mark_as_done')}
2075
+ disabled={compactEditActionsDisabled}
2076
+ className="h-8 w-8 text-muted-foreground hover:text-foreground"
2077
+ onClick={() => void updateList(doneList.id)}
2078
+ >
2079
+ <CheckCircle2 className="h-4 w-4" />
2080
+ </Button>
2081
+ </TooltipTrigger>
2082
+ <TooltipContent side="top">{t('mark_as_done')}</TooltipContent>
2083
+ </Tooltip>
2084
+ )}
2085
+ {showCompactClosedAction && closedList && (
2086
+ <Tooltip>
2087
+ <TooltipTrigger asChild>
2088
+ <Button
2089
+ type="button"
2090
+ variant="ghost"
2091
+ size="icon"
2092
+ aria-label={t('archive')}
2093
+ disabled={compactEditActionsDisabled}
2094
+ className="h-8 w-8 text-muted-foreground hover:text-foreground"
2095
+ onClick={() => void updateList(closedList.id)}
2096
+ >
2097
+ <Archive className="h-4 w-4" />
2098
+ </Button>
2099
+ </TooltipTrigger>
2100
+ <TooltipContent side="top">{t('archive')}</TooltipContent>
2101
+ </Tooltip>
2102
+ )}
2103
+ <Tooltip>
2104
+ <TooltipTrigger asChild>
2105
+ <Button
2106
+ type="button"
2107
+ variant="ghost"
2108
+ size="icon"
2109
+ aria-label={t('delete_task')}
2110
+ disabled={compactEditActionsDisabled}
2111
+ className="h-8 w-8 text-muted-foreground hover:text-foreground"
2112
+ onClick={() => setShowDeleteConfirm(true)}
2113
+ >
2114
+ <Trash2 className="h-4 w-4" />
2115
+ </Button>
2116
+ </TooltipTrigger>
2117
+ <TooltipContent side="top">{t('delete_task')}</TooltipContent>
2118
+ </Tooltip>
2119
+ </>
2120
+ ) : undefined;
1630
2121
 
1631
2122
  // Update refs
1632
2123
  quickDueRef.current = handleQuickDueDate;
@@ -1702,7 +2193,7 @@ export function TaskEditDialog({
1702
2193
  onSaveSchedulingSettings={saveSchedulingSettings}
1703
2194
  schedulingSaving={schedulingSaving}
1704
2195
  scheduledEvents={localCalendarEvents}
1705
- disabled={disabled}
2196
+ disabled={taskControlsDisabled}
1706
2197
  isDraftMode={!!draftId || (isCreateMode && saveAsDraft)}
1707
2198
  variant={variant}
1708
2199
  />
@@ -1743,7 +2234,7 @@ export function TaskEditDialog({
1743
2234
  <DialogContent
1744
2235
  showCloseButton={false}
1745
2236
  className={
1746
- showCompactCreate
2237
+ showCompactDialog
1747
2238
  ? 'w-[min(calc(100vw-2rem),30rem)] max-w-[30rem] gap-0 overflow-hidden rounded-lg border p-0 shadow-xl'
1748
2239
  : 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-bottom-2 data-[state=open]:slide-in-from-bottom-2 inset-0! top-0! left-0! flex h-screen max-h-screen w-screen max-w-none! translate-x-0! translate-y-0! gap-0 rounded-none! border-0 p-0'
1749
2240
  }
@@ -1772,13 +2263,14 @@ export function TaskEditDialog({
1772
2263
  e.preventDefault();
1773
2264
  }}
1774
2265
  >
1775
- {showCompactCreate ? (
1776
- <CompactTaskCreatePopover
2266
+ {showCompactDialog ? (
2267
+ <CompactTaskDialogPanel
1777
2268
  title={compactHeaderInfo.title}
1778
2269
  description={compactHeaderInfo.description}
1779
2270
  icon={compactHeaderInfo.icon}
1780
2271
  iconBgClass={compactHeaderInfo.iconBgClass}
1781
2272
  iconRingClass={compactHeaderInfo.iconRingClass}
2273
+ showHeaderTitle={isCreateMode}
1782
2274
  titleInput={
1783
2275
  <TaskNameInput
1784
2276
  name={formState.name}
@@ -1787,25 +2279,41 @@ export function TaskEditDialog({
1787
2279
  editorRef={editorRef}
1788
2280
  lastCursorPositionRef={lastCursorPositionRef}
1789
2281
  targetEditorCursorRef={targetEditorCursorRef}
1790
- setName={formState.setName}
2282
+ setName={setTaskName}
1791
2283
  updateName={updateName}
1792
2284
  flushNameUpdate={flushNameUpdate}
1793
- disabled={disabled}
2285
+ disabled={taskTitleDisabled}
1794
2286
  variant="compact"
1795
- onSubmit={handleSave}
2287
+ onSubmit={
2288
+ isCreateMode && !taskControlsDisabled
2289
+ ? handleSave
2290
+ : undefined
2291
+ }
1796
2292
  />
1797
2293
  }
1798
2294
  propertyControls={renderTaskPropertiesSection('compact')}
1799
- saveAsDraft={saveAsDraft}
1800
- createMultiple={createMultiple}
1801
- canSave={canSave && !isDescriptionOverLimit}
2295
+ taskStatus={taskHydrationNotice}
2296
+ smartAction={smartSuggestionsButton}
2297
+ smartPanel={smartSuggestionsPanel}
2298
+ saveAsDraft={isCreateMode ? saveAsDraft : undefined}
2299
+ createMultiple={isCreateMode ? createMultiple : undefined}
2300
+ canSave={
2301
+ isCreateMode
2302
+ ? canSave && !isDescriptionOverLimit && !taskControlsDisabled
2303
+ : undefined
2304
+ }
1802
2305
  isLoading={isLoading}
1803
2306
  isPersonalWorkspace={isPersonalWorkspace}
1804
- onSaveAsDraftChange={setSaveAsDraft}
1805
- onCreateMultipleChange={setCreateMultiple}
2307
+ editActions={compactEditActions}
2308
+ onSaveAsDraftChange={isCreateMode ? setSaveAsDraft : undefined}
2309
+ onCreateMultipleChange={
2310
+ isCreateMode ? setCreateMultiple : undefined
2311
+ }
1806
2312
  onClose={handleAttemptClose}
1807
- onFullscreen={() => setIsCreateFullscreen(true)}
1808
- onSave={handleSave}
2313
+ onFullscreen={() => setPresentation('fullscreen')}
2314
+ onSave={
2315
+ isCreateMode && !taskControlsDisabled ? handleSave : undefined
2316
+ }
1809
2317
  />
1810
2318
  ) : (
1811
2319
  <>
@@ -1816,8 +2324,8 @@ export function TaskEditDialog({
1816
2324
  {!disabled && (
1817
2325
  <TaskDialogHeader
1818
2326
  isCreateMode={isCreateMode}
1819
- collaborationMode={collaborationMode}
1820
- realtimeEnabled={realtimeEnabled}
2327
+ collaborationMode={effectiveCollaborationMode}
2328
+ realtimeEnabled={effectiveRealtimeEnabled}
1821
2329
  isOpen={isOpen}
1822
2330
  synced={synced}
1823
2331
  connected={connected}
@@ -1847,7 +2355,11 @@ export function TaskEditDialog({
1847
2355
  wsId={effectiveTaskWsId}
1848
2356
  boardId={boardId}
1849
2357
  pathname={pathname}
1850
- canSave={canSave && !isDescriptionOverLimit}
2358
+ canSave={
2359
+ canSave &&
2360
+ !isDescriptionOverLimit &&
2361
+ !taskControlsDisabled
2362
+ }
1851
2363
  isLoading={isLoading}
1852
2364
  setCreateMultiple={setCreateMultiple}
1853
2365
  handleClose={handleAttemptClose}
@@ -1866,8 +2378,11 @@ export function TaskEditDialog({
1866
2378
  : undefined
1867
2379
  }
1868
2380
  disabled={disabled}
2381
+ controlsDisabled={taskControlsDisabled}
1869
2382
  onScrollToUserCursor={
1870
- collaborationMode ? scrollToUserCursor : undefined
2383
+ effectiveCollaborationMode
2384
+ ? scrollToUserCursor
2385
+ : undefined
1871
2386
  }
1872
2387
  />
1873
2388
  )}
@@ -1884,15 +2399,29 @@ export function TaskEditDialog({
1884
2399
  editorRef={editorRef}
1885
2400
  lastCursorPositionRef={lastCursorPositionRef}
1886
2401
  targetEditorCursorRef={targetEditorCursorRef}
1887
- setName={formState.setName}
2402
+ setName={setTaskName}
1888
2403
  updateName={updateName}
1889
2404
  flushNameUpdate={flushNameUpdate}
1890
- disabled={disabled}
2405
+ disabled={taskTitleDisabled}
1891
2406
  />
1892
2407
 
2408
+ {smartSuggestionsButton && (
2409
+ <div className="flex justify-end px-4 pb-2 md:px-8">
2410
+ {smartSuggestionsButton}
2411
+ </div>
2412
+ )}
2413
+
1893
2414
  {!disabled && renderTaskPropertiesSection()}
1894
2415
 
1895
- {!disabled && !isCreateMode && (
2416
+ {taskHydrationNotice}
2417
+
2418
+ {smartSuggestionsPanel && (
2419
+ <div className="px-4 pb-3 md:px-8">
2420
+ {smartSuggestionsPanel}
2421
+ </div>
2422
+ )}
2423
+
2424
+ {!taskControlsDisabled && !isCreateMode && (
1896
2425
  <PersonalOverridesSection
1897
2426
  taskId={task?.id}
1898
2427
  isCreateMode={isCreateMode}
@@ -1901,7 +2430,7 @@ export function TaskEditDialog({
1901
2430
  />
1902
2431
  )}
1903
2432
 
1904
- {!disabled &&
2433
+ {!taskControlsDisabled &&
1905
2434
  !draftId &&
1906
2435
  !(isCreateMode && saveAsDraft) && (
1907
2436
  <TaskRelationshipsProperties
@@ -1950,7 +2479,7 @@ export function TaskEditDialog({
1950
2479
  onAddExistingAsSubtask={addChildTask}
1951
2480
  isSaving={!!savingRelationship}
1952
2481
  savingTaskId={savingRelationship}
1953
- disabled={disabled}
2482
+ disabled={taskControlsDisabled}
1954
2483
  />
1955
2484
  )}
1956
2485
 
@@ -1959,8 +2488,8 @@ export function TaskEditDialog({
1959
2488
  setDescription={formState.setDescription}
1960
2489
  isOpen={isOpen}
1961
2490
  isCreateMode={isCreateMode}
1962
- collaborationMode={collaborationMode}
1963
- realtimeEnabled={realtimeEnabled}
2491
+ collaborationMode={effectiveCollaborationMode}
2492
+ realtimeEnabled={effectiveRealtimeEnabled}
1964
2493
  isYjsSyncing={isYjsSyncing}
1965
2494
  wsId={effectiveTaskWsId}
1966
2495
  boardId={boardId}
@@ -1994,7 +2523,7 @@ export function TaskEditDialog({
1994
2523
  descriptionPercentLeft={descriptionPercentLeft}
1995
2524
  descriptionLimit={MAX_TASK_DESCRIPTION_LENGTH}
1996
2525
  isDescriptionOverLimit={isDescriptionOverLimit}
1997
- disabled={disabled}
2526
+ disabled={taskControlsDisabled}
1998
2527
  mentionTranslations={{
1999
2528
  delete_task: t('delete_task'),
2000
2529
  delete_task_confirmation: (name: string) =>
@@ -2075,11 +2604,13 @@ export function TaskEditDialog({
2075
2604
 
2076
2605
  <MobileFloatingSaveButton
2077
2606
  isCreateMode={isCreateMode}
2078
- collaborationMode={collaborationMode}
2607
+ collaborationMode={effectiveCollaborationMode}
2079
2608
  isLoading={isLoading}
2080
- canSave={canSave && !isDescriptionOverLimit}
2609
+ canSave={
2610
+ canSave && !isDescriptionOverLimit && !taskControlsDisabled
2611
+ }
2081
2612
  handleSave={handleSave}
2082
- disabled={disabled}
2613
+ disabled={taskControlsDisabled}
2083
2614
  />
2084
2615
  </>
2085
2616
  )}