@taskforcehq/taskforce 0.3.301 → 0.3.303

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 (171) hide show
  1. package/dist/Taskforce.module.css +37 -2
  2. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  3. package/dist/TaskforceCore.js +72 -33
  4. package/dist/TaskforceCore.test.js +848 -130
  5. package/dist/components/features/TaskSettings.js +14 -82
  6. package/dist/components/features/TaskSettings.test.js +33 -70
  7. package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
  8. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
  9. package/dist/components/task/TaskActionHeader.js +6 -3
  10. package/dist/components/task/TaskCard.js +6 -3
  11. package/dist/components/task/TaskForm.js +2 -2
  12. package/dist/components/task/TaskForm.test.js +41 -2
  13. package/dist/components/task/TaskKanban.test.js +17 -0
  14. package/dist/components/ui/ReferenceBadgeButton.js +1 -1
  15. package/dist/components/views/PlansPage.d.ts +3 -1
  16. package/dist/components/views/PlansPage.js +109 -49
  17. package/dist/components/views/PlansPage.test.d.ts +1 -0
  18. package/dist/components/views/PlansPage.test.js +100 -0
  19. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  20. package/dist/components/views/StandaloneLayout.js +473 -256
  21. package/dist/components/views/panels/PlanningDrawer.js +18 -1
  22. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
  23. package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
  24. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  25. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  26. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  27. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  28. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  29. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  30. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  31. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  32. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  33. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  34. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  35. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  36. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  37. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  38. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  39. package/dist/components/views/standalone/modals/types.js +1 -0
  40. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  41. package/dist/components/views/teamManagementAccess.js +2 -2
  42. package/dist/components/views/teamManagementAccess.test.js +3 -3
  43. package/dist/config/deployment.js +2 -2
  44. package/dist/config/deployment.test.js +5 -3
  45. package/dist/core/AiProfileService.js +7 -2
  46. package/dist/core/AiProfiles.test.js +19 -0
  47. package/dist/core/EntitlementsPolicy.test.js +2 -2
  48. package/dist/core/GlobalSettingsService.js +78 -18
  49. package/dist/core/PlanEntitlementService.d.ts +29 -5
  50. package/dist/core/PlanEntitlementService.js +297 -219
  51. package/dist/core/SignupMonetizationSettings.test.js +173 -49
  52. package/dist/core/SyncReconciliationService.d.ts +2 -0
  53. package/dist/core/SyncReconciliationService.js +3 -0
  54. package/dist/core/SystemAdmin.test.js +186 -96
  55. package/dist/core/TaskAuditTimeline.test.js +40 -0
  56. package/dist/core/Taskforce.d.ts +100 -10
  57. package/dist/core/Taskforce.ids.test.js +133 -0
  58. package/dist/core/Taskforce.js +459 -127
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  61. package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
  62. package/dist/core/taskReferencePolicy.d.ts +28 -0
  63. package/dist/core/taskReferencePolicy.js +189 -0
  64. package/dist/core/types.d.ts +13 -12
  65. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  66. package/dist/hooks/useSyncOrchestrator.js +17 -3
  67. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
  68. package/dist/hooks/useTaskData.js +3 -2
  69. package/dist/hooks/useTaskData.test.js +66 -0
  70. package/dist/hooks/useTaskforce.d.ts +8 -0
  71. package/dist/hooks/useTaskforce.js +72 -19
  72. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  73. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  74. package/dist/mcp/aiProfileBootstrap.js +80 -0
  75. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  76. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  77. package/dist/mcp/clientIntegrations.d.ts +2 -8
  78. package/dist/mcp/clientIntegrations.js +21 -40
  79. package/dist/mcp/clientIntegrations.test.js +28 -2
  80. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  81. package/dist/mcp/clientProfileDefaults.js +37 -0
  82. package/dist/mcp/runtime.d.ts +10 -7
  83. package/dist/mcp/runtime.js +131 -48
  84. package/dist/mcp/runtime.test.js +321 -83
  85. package/dist/migrations/billingSchemaParity.test.js +3 -0
  86. package/dist/migrations/taskSchemaMigrations.js +68 -1
  87. package/dist/qaOpen.test.d.ts +1 -0
  88. package/dist/qaOpen.test.js +56 -0
  89. package/dist/server/index.cookieProxy.test.js +2 -0
  90. package/dist/server/index.d.ts +1 -0
  91. package/dist/server/index.js +17 -1
  92. package/dist/server/index.test.js +15 -1
  93. package/dist/server/mockStripe.d.ts +1 -0
  94. package/dist/server/mockStripe.js +2 -0
  95. package/dist/server/routes/admin.js +105 -45
  96. package/dist/server/routes/auth.d.ts +6 -0
  97. package/dist/server/routes/auth.js +505 -30
  98. package/dist/server/routes/billing.js +517 -24
  99. package/dist/server/routes/billing.test.js +967 -45
  100. package/dist/server/routes/documents.js +6 -1
  101. package/dist/server/routes/shared.d.ts +1 -1
  102. package/dist/server/routes/shared.js +16 -6
  103. package/dist/server/routes/sync.integration.test.js +290 -0
  104. package/dist/server/routes/sync.js +234 -1922
  105. package/dist/server/routes/tasks.js +18 -4
  106. package/dist/server/routes.js +29 -22
  107. package/dist/server/routes.test.js +639 -82
  108. package/dist/shared/runtimeContract.d.ts +1 -1
  109. package/dist/sync/cloudSyncApi.d.ts +1 -0
  110. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  111. package/dist/sync/collaborationSyncPayload.js +131 -0
  112. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  113. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  114. package/dist/sync/collaborationSyncState.d.ts +15 -0
  115. package/dist/sync/collaborationSyncState.js +60 -0
  116. package/dist/sync/contentSyncPayload.d.ts +45 -0
  117. package/dist/sync/contentSyncPayload.js +104 -0
  118. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  119. package/dist/sync/contentSyncPayload.test.js +104 -0
  120. package/dist/sync/contentSyncState.d.ts +98 -0
  121. package/dist/sync/contentSyncState.js +1004 -0
  122. package/dist/sync/planningSyncPayload.d.ts +9 -0
  123. package/dist/sync/planningSyncPayload.js +82 -0
  124. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  125. package/dist/sync/planningSyncPayload.test.js +68 -0
  126. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  127. package/dist/sync/syncApplyHandlers.js +445 -0
  128. package/dist/sync/syncService.d.ts +1 -0
  129. package/dist/sync/syncService.js +21 -8
  130. package/dist/sync/taskSyncPayload.d.ts +33 -0
  131. package/dist/sync/taskSyncPayload.js +126 -0
  132. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  133. package/dist/sync/taskSyncPayload.test.js +54 -0
  134. package/dist/sync/workspacePullFeed.d.ts +38 -0
  135. package/dist/sync/workspacePullFeed.js +349 -0
  136. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  137. package/dist/sync/workspacePullFeed.test.js +278 -0
  138. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  139. package/dist/sync/workspaceSyncModel.js +9 -2
  140. package/dist/sync/workspaceSyncModel.test.js +29 -0
  141. package/dist/test/setup.js +1 -1
  142. package/dist/types.d.ts +1 -0
  143. package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
  144. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
  145. package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
  146. package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
  147. package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
  148. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
  149. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  150. package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
  151. package/dist/ui/assets/index-CLIMgR6g.js +6 -0
  152. package/dist/ui/assets/index-DneETxcu.css +1 -0
  153. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
  154. package/dist/ui/index.html +3 -3
  155. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  156. package/dist/utils/accountProfileSummaryCache.js +97 -0
  157. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  158. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  159. package/dist/utils/taskActivity.js +17 -4
  160. package/dist/utils/taskActivity.test.js +27 -0
  161. package/dist/utils/taskNormalization.js +10 -2
  162. package/dist/utils/taskReferences.d.ts +11 -1
  163. package/dist/utils/taskReferences.js +33 -1
  164. package/package.json +4 -3
  165. package/scripts/qa-open-lib.mjs +175 -0
  166. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  167. package/scripts/qa-open.mjs +352 -0
  168. package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
  169. package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
  170. package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
  171. package/dist/ui/assets/index-xBWLPOEb.js +0 -6
@@ -2,16 +2,21 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import React, { useMemo, useCallback, useState, useEffect, useRef, useLayoutEffect, Suspense } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { useLocation, useNavigate } from 'react-router-dom';
5
- import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, RefreshCw, Users, Copy, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
5
+ import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, Users, ClockAlert, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
6
6
  import { STATUS_OPTIONS, getStatusDefinition } from '../../utils/constants';
7
7
  import styles from '../../Taskforce.module.css';
8
8
  import shellStyles from './WorkspaceShell.module.css';
9
9
  import accountStyles from './StandaloneAccount.module.css';
10
10
  import authStyles from './AuthShell.module.css';
11
11
  import folderBrowserStyles from './FolderBrowser.module.css';
12
- import managementStyles from './StandaloneManagement.module.css';
13
- import syncStatusStyles from './SyncStatusModal.module.css';
14
12
  import modalStyles from '../ui/Modal.module.css';
13
+ import { CreateWorkspaceConfirmModal } from './standalone/modals/CreateWorkspaceConfirmModal';
14
+ import { AccountHubModal } from './standalone/modals/AccountHubModal';
15
+ import { EditProfileModal } from './standalone/modals/EditProfileModal';
16
+ import { HelpModal } from './standalone/modals/HelpModal';
17
+ import { ScheduleWarningModal } from './standalone/modals/ScheduleWarningModal';
18
+ import { SyncStatusModal } from './standalone/modals/SyncStatusModal';
19
+ import { TeamManagementModal } from './standalone/modals/TeamManagementModal';
15
20
  import { Modal } from '../ui/Modal';
16
21
  import { TaskForm } from '../task/TaskForm';
17
22
  import { TaskKanban } from '../task/TaskKanban';
@@ -28,8 +33,11 @@ import { getImageReferenceLabel } from '../../utils/imageReferences';
28
33
  import { buildCustomTaxonomySortOptions } from '../../utils/taxonomySorting.js';
29
34
  import { getLocale, t } from '../../localization';
30
35
  import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
36
+ import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
37
+ import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
31
38
  import { AppShellHeader } from './AppShellHeader';
32
39
  import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
40
+ import { logBootstrapDebug } from '../../hooks/workspace/workspaceLocalState';
33
41
  import { FilterToolbar } from './panels/FilterToolbar';
34
42
  import { DocumentFilterToolbar } from './panels/DocumentFilterToolbar';
35
43
  import { PlanningDrawer, getPlanningDrawerWidthPx, } from './panels/PlanningDrawer';
@@ -37,7 +45,7 @@ import { getWorkspaceChromeDefinition, getWorkspaceModuleDefinitions, resolveWor
37
45
  import { EmptyWorkspaceFilterBar, WorkspaceHeaderActions, WorkspaceHeaderDivider, WorkspaceHeaderSearch, WorkspaceHeaderSelect, WorkspacePrimaryAction, } from './chrome/WorkspaceChromeSections';
38
46
  import { ScheduleSidebar, SCHEDULE_SIDEBAR_WIDTH_PX } from './panels/ScheduleSidebar';
39
47
  import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from '../features/documentWorkspaceFilters';
40
- import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement, resolveTeamPlanModeFromEntitlements } from './teamManagementAccess';
48
+ import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement } from './teamManagementAccess';
41
49
  import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
42
50
  import { AGENTS_WORKSPACE_FEATURE_KEY, ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, DOCUMENT_WORKSPACE_FEATURE_KEY, INITIATIVES_WORKSPACE_FEATURE_KEY, WORKFLOWS_WORKSPACE_FEATURE_KEY, resolveFeatureAccess, } from '../../runtime/appGates';
43
51
  // Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
@@ -48,6 +56,9 @@ const WorkflowsModule = React.lazy(() => import('../features/WorkflowsModule').t
48
56
  const AgentsModule = React.lazy(() => import('../features/AgentsModule').then(m => ({ default: m.AgentsModule })));
49
57
  const InitiativesModule = React.lazy(() => import('../features/InitiativesModule').then(m => ({ default: m.InitiativesModule })));
50
58
  const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m.PlansPage })));
59
+ const PROFILE_AVATAR_ACCEPT = 'image/png,image/jpeg,image/webp,image/gif';
60
+ const PROFILE_AVATAR_MAX_BYTES = 5 * 1024 * 1024;
61
+ const resolveSameOriginTaskforcePath = (path) => path;
51
62
  function getSyncEventStableKey(event, index = 0) {
52
63
  const explicitId = String(event.id ?? '').trim();
53
64
  if (explicitId)
@@ -147,6 +158,9 @@ function isDebugModeEnabled() {
147
158
  return runtimeOverride;
148
159
  return typeof __DEBUG_MODE__ !== 'undefined' && __DEBUG_MODE__;
149
160
  }
161
+ function getTimingNow() {
162
+ return typeof performance !== 'undefined' ? performance.now() : Date.now();
163
+ }
150
164
  export function StandaloneLayout(props) {
151
165
  const WORKSPACE_TOOL_RAIL_WIDTH = 56;
152
166
  const WORKSPACE_CONTENT_GUTTER = 6;
@@ -202,7 +216,7 @@ export function StandaloneLayout(props) {
202
216
  // clearFilters, // This was duplicated, removed one instance
203
217
  // activeTypes, // This was duplicated, removed one instance
204
218
  // Settings State & Props
205
- settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authWorkspaceId, authUserEmail, authUserDisplayName, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
219
+ settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserId, authWorkspaceId, authUserEmail, authUserDisplayName, authUserAvatarUrl, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, resolveCloudAuthUrl = resolveSameOriginTaskforcePath, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
206
220
  // Browser Props
207
221
  showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
208
222
  // Settings Managers
@@ -430,6 +444,15 @@ export function StandaloneLayout(props) {
430
444
  if (typeof state.planningDrawerOpen === 'boolean') {
431
445
  setPlanningDrawerOpen(state.planningDrawerOpen);
432
446
  }
447
+ if (typeof state.planningTreeCollapsed === 'boolean') {
448
+ setPlanningTreeCollapsed(state.planningTreeCollapsed);
449
+ }
450
+ if (typeof state.planningPrimaryCollapsed === 'boolean') {
451
+ setPlanningPrimaryCollapsed(state.planningPrimaryCollapsed);
452
+ }
453
+ if (typeof state.planningSecondaryCollapsed === 'boolean') {
454
+ setPlanningSecondaryCollapsed(state.planningSecondaryCollapsed);
455
+ }
433
456
  if (Array.isArray(state.planningExpandedInitiativeIds)) {
434
457
  setExpandedPlanningInitiativeIds(new Set(state.planningExpandedInitiativeIds
435
458
  .map((value) => String(value || '').trim())
@@ -499,6 +522,9 @@ export function StandaloneLayout(props) {
499
522
  documentTypeFilters,
500
523
  documentAttachmentFilters,
501
524
  planningDrawerOpen,
525
+ planningTreeCollapsed,
526
+ planningPrimaryCollapsed,
527
+ planningSecondaryCollapsed,
502
528
  planningExpandedInitiativeIds: Array.from(expandedPlanningInitiativeIds),
503
529
  planningDrawerDetail,
504
530
  planningNestedWorkstreamDetailId,
@@ -526,6 +552,9 @@ export function StandaloneLayout(props) {
526
552
  documentTypeFilters,
527
553
  documentAttachmentFilters,
528
554
  planningDrawerOpen,
555
+ planningTreeCollapsed,
556
+ planningPrimaryCollapsed,
557
+ planningSecondaryCollapsed,
529
558
  expandedPlanningInitiativeIds,
530
559
  planningDrawerDetail,
531
560
  planningNestedWorkstreamDetailId,
@@ -776,9 +805,9 @@ export function StandaloneLayout(props) {
776
805
  if (taskScope === 'deleted')
777
806
  return deletedScopeAllTasks;
778
807
  if (!showArchive)
779
- return props.searchAgnosticTasks;
780
- return [...props.searchAgnosticTasks, ...archivedScopeAllTasks];
781
- }, [archivedScopeAllTasks, deletedScopeAllTasks, props.searchAgnosticTasks, showArchive, taskScope]);
808
+ return props.tasks;
809
+ return [...props.tasks, ...archivedScopeAllTasks];
810
+ }, [archivedScopeAllTasks, deletedScopeAllTasks, props.tasks, showArchive, taskScope]);
782
811
  const hierarchyTasks = useMemo(() => {
783
812
  const byId = new Map();
784
813
  for (const task of archivedScopeAllTasks)
@@ -802,16 +831,11 @@ export function StandaloneLayout(props) {
802
831
  return map;
803
832
  }, [assigneeOptions]);
804
833
  const planningStructure = useMemo(() => {
805
- const planningInitiatives = taskScope === 'deleted'
806
- ? []
807
- : props.initiatives.filter((initiative) => taskScope === 'archived'
808
- ? Boolean(initiative.isArchived)
809
- : !initiative.isArchived);
810
- const planningWorkstreams = taskScope === 'deleted'
811
- ? []
812
- : props.workstreams.filter((workstream) => taskScope === 'archived'
813
- ? Boolean(workstream.isArchived)
814
- : !workstream.isArchived);
834
+ const planningScopedTasks = taskScope === 'archived'
835
+ ? archivedScopeAllTasks
836
+ : taskScope === 'deleted'
837
+ ? deletedScopeAllTasks
838
+ : props.tasks.filter((task) => !task.isArchived);
815
839
  const summarizeTasks = (scopeTasks) => {
816
840
  const taskCount = scopeTasks.length;
817
841
  const completedTaskCount = scopeTasks.filter((task) => task.isArchived || task.status === 'done' || task.status === 'cancelled').length;
@@ -827,7 +851,7 @@ export function StandaloneLayout(props) {
827
851
  const workstreamById = new Map();
828
852
  const initiativeById = new Map();
829
853
  const buildWorkstreamSummary = (workstream) => {
830
- const scopedTasks = hierarchyTasks.filter((task) => (task.workstreamId || null) === workstream.id);
854
+ const scopedTasks = planningScopedTasks.filter((task) => (task.workstreamId || null) === workstream.id);
831
855
  const taskPreviews = scopedTasks.map((task) => ({
832
856
  id: task.id,
833
857
  referenceNumber: task.referenceNumber ?? null,
@@ -853,10 +877,10 @@ export function StandaloneLayout(props) {
853
877
  workstreamById.set(summary.id, summary);
854
878
  return summary;
855
879
  };
856
- const initiatives = planningInitiatives.map((initiative) => {
857
- const workstreams = planningWorkstreams
858
- .filter((workstream) => (workstream.initiativeId || null) === initiative.id)
859
- .map(buildWorkstreamSummary);
880
+ const allWorkstreamSummaries = props.workstreams.map(buildWorkstreamSummary);
881
+ const allInitiatives = props.initiatives.map((initiative) => {
882
+ const workstreams = allWorkstreamSummaries
883
+ .filter((workstream) => (workstream.initiativeId || null) === initiative.id);
860
884
  const initiativeScopedTasks = workstreams
861
885
  .flatMap((workstream) => workstreamTaskIds.get(workstream.id) || [])
862
886
  .map((taskId) => hierarchyTaskById.get(taskId))
@@ -878,9 +902,18 @@ export function StandaloneLayout(props) {
878
902
  initiativeById.set(initiativeSummary.id, initiativeSummary);
879
903
  return initiativeSummary;
880
904
  });
881
- const standaloneWorkstreams = planningWorkstreams
905
+ const initiatives = taskScope === 'deleted'
906
+ ? []
907
+ : allInitiatives.filter((initiative) => taskScope === 'archived'
908
+ ? Boolean(initiative.isArchived)
909
+ : !initiative.isArchived);
910
+ const standaloneWorkstreams = allWorkstreamSummaries
882
911
  .filter((workstream) => !workstream.initiativeId)
883
- .map(buildWorkstreamSummary);
912
+ .filter((workstream) => taskScope === 'deleted'
913
+ ? false
914
+ : taskScope === 'archived'
915
+ ? Boolean(workstream.isArchived)
916
+ : !workstream.isArchived);
884
917
  return {
885
918
  initiatives,
886
919
  standaloneWorkstreams,
@@ -890,7 +923,7 @@ export function StandaloneLayout(props) {
890
923
  workstreamById,
891
924
  initiativeById,
892
925
  };
893
- }, [assigneeLabelByValue, hierarchyTaskById, hierarchyTasks, props.initiatives, props.workstreams, taskScope]);
926
+ }, [archivedScopeAllTasks, assigneeLabelByValue, deletedScopeAllTasks, hierarchyTaskById, props.initiatives, props.tasks, props.workstreams, taskScope]);
894
927
  const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => {
895
928
  const referenceLabel = formatInitiativeReference(initiative);
896
929
  return {
@@ -962,13 +995,6 @@ export function StandaloneLayout(props) {
962
995
  return exactInitiative;
963
996
  return resolveInitiativeReference(planningStructure.initiatives, planningDraftInitiativeId);
964
997
  }, [planningDraftInitiativeId, planningStructure.initiativeById, planningStructure.initiatives]);
965
- useEffect(() => {
966
- if (!planningDrawerOpen)
967
- return;
968
- setPlanningTreeCollapsed(false);
969
- setPlanningPrimaryCollapsed(false);
970
- setPlanningSecondaryCollapsed(false);
971
- }, [planningDrawerOpen]);
972
998
  useEffect(() => {
973
999
  if (selectedPlanningInitiativeId && !planningStructure.initiativeById.has(selectedPlanningInitiativeId)) {
974
1000
  setSelectedPlanningInitiativeId('');
@@ -976,23 +1002,7 @@ export function StandaloneLayout(props) {
976
1002
  if (selectedPlanningWorkstreamId && !planningStructure.workstreamById.has(selectedPlanningWorkstreamId)) {
977
1003
  setSelectedPlanningWorkstreamId('');
978
1004
  }
979
- if (planningDrawerDetail?.type === 'initiative' && !planningStructure.initiativeById.has(planningDrawerDetail.id)) {
980
- setPlanningDrawerDetail(null);
981
- }
982
- if (planningDrawerDetail?.type === 'workstream' && !planningStructure.workstreamById.has(planningDrawerDetail.id)) {
983
- setPlanningDrawerDetail(null);
984
- }
985
- if (planningNestedWorkstreamDetailId && !planningStructure.workstreamById.has(planningNestedWorkstreamDetailId)) {
986
- setPlanningNestedWorkstreamDetailId(null);
987
- }
988
- if (planningSecondaryEditor?.mode === 'edit'
989
- && !planningStructure.workstreamById.has(planningSecondaryEditor.targetId)) {
990
- setPlanningSecondaryEditor(null);
991
- }
992
1005
  }, [
993
- planningDrawerDetail,
994
- planningNestedWorkstreamDetailId,
995
- planningSecondaryEditor,
996
1006
  planningStructure.initiativeById,
997
1007
  planningStructure.workstreamById,
998
1008
  selectedPlanningInitiativeId,
@@ -1027,6 +1037,11 @@ export function StandaloneLayout(props) {
1027
1037
  return boardVisibleTasks;
1028
1038
  return boardVisibleTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
1029
1039
  }, [boardVisibleTasks, selectedPlanningTaskIds]);
1040
+ const scopedKanbanAllTasks = useMemo(() => {
1041
+ if (!selectedPlanningTaskIds || selectedPlanningTaskIds.size === 0)
1042
+ return kanbanAllTasks;
1043
+ return kanbanAllTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
1044
+ }, [kanbanAllTasks, selectedPlanningTaskIds]);
1030
1045
  const expiredRecoveryCandidates = useMemo(() => {
1031
1046
  if (groupBy !== 'schedule')
1032
1047
  return [];
@@ -1059,6 +1074,29 @@ export function StandaloneLayout(props) {
1059
1074
  return !!task.dueDate && task.dueDate < todayDateOnly;
1060
1075
  }).length;
1061
1076
  }, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
1077
+ const normalizedAuthUserId = String(authUserId || '').trim();
1078
+ const assignedTaskCount = useMemo(() => {
1079
+ if (!normalizedAuthUserId || normalizedAuthUserId === 'anonymous')
1080
+ return 0;
1081
+ return tasks.filter((task) => {
1082
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
1083
+ if (isTerminal)
1084
+ return false;
1085
+ return String(task.assignee || '').trim() === normalizedAuthUserId;
1086
+ }).length;
1087
+ }, [normalizedAuthUserId, tasks]);
1088
+ const assignedOverdueTaskCount = useMemo(() => {
1089
+ if (!normalizedAuthUserId || normalizedAuthUserId === 'anonymous')
1090
+ return 0;
1091
+ return tasks.filter((task) => {
1092
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
1093
+ if (isTerminal)
1094
+ return false;
1095
+ if (String(task.assignee || '').trim() !== normalizedAuthUserId)
1096
+ return false;
1097
+ return !!task.dueDate && task.dueDate < todayDateOnly;
1098
+ }).length;
1099
+ }, [normalizedAuthUserId, tasks, todayDateOnly]);
1062
1100
  const expiredColumnCandidates = useMemo(() => {
1063
1101
  if (groupBy !== 'schedule')
1064
1102
  return [];
@@ -1168,7 +1206,10 @@ export function StandaloneLayout(props) {
1168
1206
  const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
1169
1207
  const [showTeamManagementModal, setShowTeamManagementModal] = useState(false);
1170
1208
  const [teamManagementTab, setTeamManagementTab] = useState('members');
1209
+ const [teamWorkspaceRole, setTeamWorkspaceRole] = useState(null);
1171
1210
  const [teamPlanMode, setTeamPlanMode] = useState('unknown');
1211
+ const [teamManagementAccessLoading, setTeamManagementAccessLoading] = useState(false);
1212
+ const [teamManagementAccessError, setTeamManagementAccessError] = useState(null);
1172
1213
  const [teamMgmtError, setTeamMgmtError] = useState(null);
1173
1214
  const [teamUsersLoading, setTeamUsersLoading] = useState(false);
1174
1215
  const [teamUsers, setTeamUsers] = useState([]);
@@ -1183,7 +1224,7 @@ export function StandaloneLayout(props) {
1183
1224
  const [teamAuditEvents, setTeamAuditEvents] = useState([]);
1184
1225
  const [teamAuditHasMore, setTeamAuditHasMore] = useState(false);
1185
1226
  const [teamAuditPages, setTeamAuditPages] = useState(1);
1186
- const [billingStatus, setBillingStatus] = useState(null);
1227
+ const [accountProfileSummary, setAccountProfileSummary] = useState(null);
1187
1228
  const [billingLoading, setBillingLoading] = useState(false);
1188
1229
  const [billingError, setBillingError] = useState(null);
1189
1230
  const [billingActionBusy, setBillingActionBusy] = useState(false);
@@ -1191,12 +1232,25 @@ export function StandaloneLayout(props) {
1191
1232
  const [billingNotice, setBillingNotice] = useState(null);
1192
1233
  const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
1193
1234
  const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
1235
+ const [profileAvatarPreviewUrl, setProfileAvatarPreviewUrl] = useState('');
1236
+ const [profileAvatarDraftId, setProfileAvatarDraftId] = useState(null);
1237
+ const [profileAvatarBusy, setProfileAvatarBusy] = useState(false);
1194
1238
  const [profileSaveBusy, setProfileSaveBusy] = useState(false);
1195
1239
  const [profileSaveError, setProfileSaveError] = useState(null);
1196
1240
  const [profileSaveNotice, setProfileSaveNotice] = useState(null);
1197
1241
  const TEAM_AUDIT_PAGE_SIZE = 25;
1198
1242
  const accountMenuRef = useRef(null);
1243
+ const profileAvatarInputRef = useRef(null);
1244
+ const accountSurfaceOpenStartedAtRef = useRef(null);
1245
+ const accountSurfacePendingRef = useRef({
1246
+ billing: false,
1247
+ teamManagement: false
1248
+ });
1249
+ const accountProfileSummaryRef = useRef(null);
1199
1250
  // showSearch state removed - search always visible in actions area
1251
+ useEffect(() => {
1252
+ accountProfileSummaryRef.current = accountProfileSummary;
1253
+ }, [accountProfileSummary]);
1200
1254
  useEffect(() => {
1201
1255
  if (!showAccountMenu)
1202
1256
  return;
@@ -1239,32 +1293,25 @@ export function StandaloneLayout(props) {
1239
1293
  const fallbackId = String(currentWorkspaceId || '').trim();
1240
1294
  return fallbackId || 'Workspace';
1241
1295
  }, [availableWorkspaces, currentWorkspaceId, projectName, runtimeMode]);
1242
- const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, currentWorkspaceRole);
1243
- const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, currentWorkspaceRole, teamPlanMode);
1296
+ const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, teamWorkspaceRole);
1297
+ const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, teamWorkspaceRole, teamPlanMode);
1244
1298
  const authDisplayName = String(authUserDisplayName || '').trim();
1299
+ const authAvatarUrl = String(authUserAvatarUrl || '').trim();
1245
1300
  const authEmailLabel = String(authUserEmail || '').trim();
1246
1301
  const authIdentityLabel = authDisplayName || authEmailLabel;
1247
1302
  const hasAuthIdentity = authIdentityLabel.length > 0;
1248
1303
  const hasAuthEmail = authEmailLabel.length > 0;
1249
- const accountBadgeLabel = (authIdentityLabel || authEmailLabel || 'U').trim();
1250
- const accountBadgeInitial = accountBadgeLabel.charAt(0).toUpperCase() || 'U';
1251
- const subscriptionLabel = String(billingStatus?.planName || billingStatus?.planId || '').trim();
1304
+ const accountBadgeLabel = (authIdentityLabel || authEmailLabel || '').trim();
1305
+ const shouldShowResolvedAccountInitial = isAuthenticated && authSessionResolved && accountBadgeLabel.length > 0;
1306
+ const accountBadgeInitial = shouldShowResolvedAccountInitial ? accountBadgeLabel.charAt(0).toUpperCase() : '';
1307
+ const accountAvatarUrl = authAvatarUrl;
1308
+ const shouldShowResolvedAccountAvatar = isAuthenticated && authSessionResolved && accountAvatarUrl.length > 0;
1309
+ const profileAvatarDisplayUrl = String(profileAvatarPreviewUrl || authAvatarUrl).trim();
1310
+ const subscriptionLabel = String(accountProfileSummary?.planName || accountProfileSummary?.planId || '').trim();
1252
1311
  const hasSubscription = subscriptionLabel.length > 0;
1253
- const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
1312
+ const teamManagementWorkspaceId = String(accountProfileSummary?.workspaceId || authWorkspaceId || currentWorkspaceId || '').trim();
1254
1313
  const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
1255
1314
  const apiBaseUrl = props.config?.apiBaseUrl || '';
1256
- const billingApiBaseUrl = useMemo(() => {
1257
- const preferredBase = runtimeMode === 'local'
1258
- ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1259
- : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1260
- return String(preferredBase || '').trim().replace(/\/+$/, '');
1261
- }, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1262
- const buildBillingApiUrl = useCallback((path) => {
1263
- const normalizedPath = String(path || '').trim();
1264
- if (!billingApiBaseUrl)
1265
- return normalizedPath;
1266
- return `${billingApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
1267
- }, [billingApiBaseUrl]);
1268
1315
  const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
1269
1316
  String(props.config?.cloudBaseUrl || ''),
1270
1317
  String(props.config?.cloudAuthBaseUrl || ''),
@@ -1282,9 +1329,141 @@ export function StandaloneLayout(props) {
1282
1329
  if (!showEditProfileModal)
1283
1330
  return;
1284
1331
  setProfileDisplayNameDraft(authDisplayName);
1332
+ setProfileAvatarPreviewUrl(authAvatarUrl);
1333
+ setProfileAvatarDraftId(null);
1334
+ setProfileSaveError(null);
1335
+ setProfileSaveNotice(null);
1336
+ }, [authAvatarUrl, authDisplayName, showEditProfileModal]);
1337
+ const discardProfileAvatarDraft = useCallback(async (draftId) => {
1338
+ const normalizedDraftId = String(draftId || '').trim();
1339
+ if (!normalizedDraftId || !cloudAuthConfigured)
1340
+ return;
1341
+ try {
1342
+ const baseUrl = runtimeMode === 'local'
1343
+ ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1344
+ : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1345
+ await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/discard', {
1346
+ method: 'POST',
1347
+ headers: { 'Content-Type': 'application/json' },
1348
+ credentials: 'include',
1349
+ body: JSON.stringify({ draftId: normalizedDraftId })
1350
+ }, baseUrl);
1351
+ }
1352
+ catch {
1353
+ // Best-effort cleanup only.
1354
+ }
1355
+ }, [cloudAuthConfigured, props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1356
+ const closeEditProfileModal = useCallback(() => {
1357
+ const pendingDraftId = profileAvatarDraftId;
1358
+ setShowEditProfileModal(false);
1285
1359
  setProfileSaveError(null);
1286
1360
  setProfileSaveNotice(null);
1287
- }, [authDisplayName, showEditProfileModal]);
1361
+ setProfileAvatarDraftId(null);
1362
+ setProfileAvatarPreviewUrl(authAvatarUrl);
1363
+ if (pendingDraftId) {
1364
+ void discardProfileAvatarDraft(pendingDraftId);
1365
+ }
1366
+ }, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
1367
+ const handleProfileAvatarSelected = useCallback(async (file) => {
1368
+ if (!file || !cloudAuthConfigured)
1369
+ return;
1370
+ if (!file.type.startsWith('image/')) {
1371
+ setProfileSaveError('Profile photo must be an image file.');
1372
+ setProfileSaveNotice(null);
1373
+ return;
1374
+ }
1375
+ if (file.size > PROFILE_AVATAR_MAX_BYTES) {
1376
+ setProfileSaveError('Profile photo must be 5 MB or smaller.');
1377
+ setProfileSaveNotice(null);
1378
+ return;
1379
+ }
1380
+ setProfileAvatarBusy(true);
1381
+ setProfileSaveError(null);
1382
+ setProfileSaveNotice(null);
1383
+ try {
1384
+ const baseUrl = runtimeMode === 'local'
1385
+ ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1386
+ : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1387
+ const initRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/init', {
1388
+ method: 'POST',
1389
+ headers: { 'Content-Type': 'application/json' },
1390
+ credentials: 'include',
1391
+ body: JSON.stringify({
1392
+ originalName: file.name,
1393
+ mimeType: file.type,
1394
+ size: file.size
1395
+ })
1396
+ }, baseUrl);
1397
+ const initData = await initRes.json().catch(() => ({}));
1398
+ if (!initRes.ok || !initData?.success || typeof initData?.uploadUrl !== 'string' || typeof initData?.relativePath !== 'string') {
1399
+ throw new Error(initData?.error || 'Failed to start avatar upload.');
1400
+ }
1401
+ const uploadRes = await fetch(initData.uploadUrl, {
1402
+ method: String(initData.method || 'PUT'),
1403
+ headers: initData.headers || { 'Content-Type': file.type || 'application/octet-stream' },
1404
+ body: file
1405
+ });
1406
+ if (!uploadRes.ok) {
1407
+ throw new Error('Failed to upload avatar.');
1408
+ }
1409
+ const finalizeRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/finalize', {
1410
+ method: 'POST',
1411
+ headers: { 'Content-Type': 'application/json' },
1412
+ credentials: 'include',
1413
+ body: JSON.stringify({
1414
+ draftId: initData.draftId,
1415
+ relativePath: initData.relativePath
1416
+ })
1417
+ }, baseUrl);
1418
+ const finalizeData = await finalizeRes.json().catch(() => ({}));
1419
+ if (!finalizeRes.ok || !finalizeData?.success || typeof finalizeData?.draftId !== 'string') {
1420
+ throw new Error(finalizeData?.error || 'Failed to finalize avatar upload.');
1421
+ }
1422
+ const previousDraftId = profileAvatarDraftId;
1423
+ setProfileAvatarDraftId(finalizeData.draftId);
1424
+ setProfileAvatarPreviewUrl(typeof finalizeData?.avatarUrl === 'string' ? finalizeData.avatarUrl : '');
1425
+ setProfileSaveNotice('Profile photo ready to save.');
1426
+ if (previousDraftId && previousDraftId !== finalizeData.draftId) {
1427
+ void discardProfileAvatarDraft(previousDraftId);
1428
+ }
1429
+ }
1430
+ catch (error) {
1431
+ setProfileSaveError(error instanceof Error ? error.message : 'Failed to upload profile photo.');
1432
+ }
1433
+ finally {
1434
+ setProfileAvatarBusy(false);
1435
+ if (profileAvatarInputRef.current) {
1436
+ profileAvatarInputRef.current.value = '';
1437
+ }
1438
+ }
1439
+ }, [
1440
+ cloudAuthConfigured,
1441
+ discardProfileAvatarDraft,
1442
+ profileAvatarDraftId,
1443
+ props.config?.apiBaseUrl,
1444
+ props.config?.cloudAuthBaseUrl,
1445
+ runtimeMode
1446
+ ]);
1447
+ const handleDiscardPendingProfileAvatar = useCallback(() => {
1448
+ if (!profileAvatarDraftId)
1449
+ return;
1450
+ const draftId = profileAvatarDraftId;
1451
+ setProfileAvatarDraftId(null);
1452
+ setProfileAvatarPreviewUrl(authAvatarUrl);
1453
+ setProfileSaveNotice(null);
1454
+ setProfileSaveError(null);
1455
+ void discardProfileAvatarDraft(draftId);
1456
+ }, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
1457
+ const handleRemoveProfileAvatar = useCallback(() => {
1458
+ const pendingDraftId = profileAvatarDraftId;
1459
+ setProfileAvatarDraftId(null);
1460
+ setProfileAvatarPreviewUrl('');
1461
+ setProfileSaveNotice(authAvatarUrl ? 'Profile photo will be removed when you save.' : null);
1462
+ setProfileSaveError(null);
1463
+ if (pendingDraftId) {
1464
+ void discardProfileAvatarDraft(pendingDraftId);
1465
+ }
1466
+ }, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
1288
1467
  const handleSaveProfile = useCallback(async () => {
1289
1468
  const trimmedDisplayName = profileDisplayNameDraft.trim();
1290
1469
  if (!trimmedDisplayName) {
@@ -1295,65 +1474,161 @@ export function StandaloneLayout(props) {
1295
1474
  setProfileSaveBusy(true);
1296
1475
  setProfileSaveError(null);
1297
1476
  setProfileSaveNotice(null);
1298
- const result = await updateCurrentUserProfile({ displayName: trimmedDisplayName });
1477
+ const result = await updateCurrentUserProfile({
1478
+ displayName: trimmedDisplayName,
1479
+ avatarDraftId: profileAvatarDraftId,
1480
+ clearAvatar: !profileAvatarDraftId && !profileAvatarPreviewUrl && Boolean(authAvatarUrl)
1481
+ });
1299
1482
  if (!result.success) {
1300
1483
  setProfileSaveError(result.error || 'Failed to update profile.');
1301
1484
  setProfileSaveBusy(false);
1302
1485
  return;
1303
1486
  }
1304
1487
  setProfileSaveNotice('Profile updated.');
1488
+ setProfileAvatarDraftId(null);
1489
+ setProfileAvatarPreviewUrl('');
1305
1490
  setProfileSaveBusy(false);
1306
1491
  setShowEditProfileModal(false);
1307
- }, [profileDisplayNameDraft, updateCurrentUserProfile]);
1492
+ }, [authAvatarUrl, profileAvatarDraftId, profileAvatarPreviewUrl, profileDisplayNameDraft, updateCurrentUserProfile]);
1308
1493
  const teamAdminHeaders = useCallback(() => {
1309
1494
  const headers = {};
1310
1495
  const workspaceId = String(teamManagementWorkspaceId || '').trim();
1311
1496
  if (workspaceId)
1312
1497
  headers['x-taskforce-workspace-id'] = workspaceId;
1498
+ const workspaceRole = teamWorkspaceRole || currentWorkspaceRole;
1499
+ if (workspaceRole === 'owner'
1500
+ || workspaceRole === 'admin'
1501
+ || workspaceRole === 'member'
1502
+ || workspaceRole === 'read-only') {
1503
+ headers['x-taskforce-workspace-role'] = workspaceRole;
1504
+ }
1313
1505
  return headers;
1314
- }, [teamManagementWorkspaceId]);
1315
- const loadBillingStatus = useCallback(async () => {
1506
+ }, [currentWorkspaceRole, teamManagementWorkspaceId, teamWorkspaceRole]);
1507
+ const startAccountSurfaceTiming = useCallback((surface) => {
1508
+ const expectsBilling = isAuthenticated;
1509
+ const expectsTeamManagement = canAccessCloudTeamManagement;
1510
+ accountSurfaceOpenStartedAtRef.current = getTimingNow();
1511
+ accountSurfacePendingRef.current = {
1512
+ billing: expectsBilling,
1513
+ teamManagement: expectsTeamManagement
1514
+ };
1515
+ logBootstrapDebug('account_surface_opened', {
1516
+ surface,
1517
+ expectsBilling,
1518
+ expectsTeamManagement
1519
+ });
1520
+ if (!expectsBilling && !expectsTeamManagement) {
1521
+ logBootstrapDebug('account_surface_ready', {
1522
+ surface,
1523
+ durationMs: 0,
1524
+ teamManagementVisible: false,
1525
+ billingPlanId: null
1526
+ });
1527
+ accountSurfaceOpenStartedAtRef.current = null;
1528
+ }
1529
+ }, [canAccessCloudTeamManagement, isAuthenticated]);
1530
+ const markAccountSurfaceLoadSettled = useCallback((kind) => {
1531
+ const pending = accountSurfacePendingRef.current;
1532
+ pending[kind] = false;
1533
+ if (pending.billing || pending.teamManagement)
1534
+ return;
1535
+ const startedAt = accountSurfaceOpenStartedAtRef.current;
1536
+ if (startedAt === null)
1537
+ return;
1538
+ logBootstrapDebug('account_surface_ready', {
1539
+ surface: showAccountHub ? 'account_hub' : (showAccountMenu ? 'account_menu' : 'closed'),
1540
+ durationMs: Math.round(getTimingNow() - startedAt),
1541
+ teamManagementVisible: canOpenTeamManagement,
1542
+ billingPlanId: String(accountProfileSummary?.planId || '').trim() || null
1543
+ });
1544
+ accountSurfaceOpenStartedAtRef.current = null;
1545
+ }, [accountProfileSummary?.planId, canOpenTeamManagement, showAccountHub, showAccountMenu]);
1546
+ const loadAccountProfileSummary = useCallback(async () => {
1547
+ const accountProfileSummaryUrl = resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH);
1316
1548
  if (!isAuthenticated) {
1317
- setBillingStatus(null);
1549
+ setAccountProfileSummary(null);
1550
+ setBillingError(null);
1551
+ setTeamWorkspaceRole(null);
1552
+ setTeamPlanMode('unknown');
1553
+ setTeamManagementAccessLoading(false);
1554
+ setTeamManagementAccessError(null);
1555
+ invalidateAccountProfileSummaryCache(accountProfileSummaryUrl, { identityKey: authUserId });
1556
+ markAccountSurfaceLoadSettled('billing');
1557
+ markAccountSurfaceLoadSettled('teamManagement');
1318
1558
  return null;
1319
1559
  }
1560
+ const startedAt = getTimingNow();
1320
1561
  setBillingLoading(true);
1321
1562
  setBillingError(null);
1563
+ setTeamManagementAccessLoading(canAccessCloudTeamManagement);
1564
+ setTeamManagementAccessError(null);
1322
1565
  try {
1323
- const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/status'), {
1324
- method: 'GET',
1325
- credentials: 'include'
1566
+ const payload = await loadAccountProfileSummaryCached(accountProfileSummaryUrl, { identityKey: authUserId });
1567
+ const nextRole = payload?.workspaceRole === 'owner'
1568
+ || payload?.workspaceRole === 'admin'
1569
+ || payload?.workspaceRole === 'member'
1570
+ || payload?.workspaceRole === 'read-only'
1571
+ ? payload.workspaceRole
1572
+ : null;
1573
+ const nextMode = payload?.teamPlanMode === 'team' || payload?.teamPlanMode === 'personal'
1574
+ ? payload.teamPlanMode
1575
+ : 'unknown';
1576
+ setAccountProfileSummary(payload);
1577
+ setTeamWorkspaceRole(nextRole);
1578
+ setTeamPlanMode(nextMode);
1579
+ setTeamManagementAccessError(null);
1580
+ logBootstrapDebug('account_profile_summary_resolved', {
1581
+ durationMs: Math.round(getTimingNow() - startedAt),
1582
+ planId: String(payload?.planId || '').trim() || null,
1583
+ gate: String(payload?.gate || '').trim() || null,
1584
+ teamManagementAllowed: payload?.teamManagementAllowed === true
1326
1585
  });
1327
- const payload = await res.json().catch(() => ({}));
1328
- if (!res.ok) {
1329
- setBillingStatus(null);
1330
- setBillingError(String(payload?.error || 'Failed to load billing status.'));
1331
- return null;
1332
- }
1333
- setBillingStatus(payload);
1334
1586
  return payload;
1335
1587
  }
1336
- catch {
1337
- setBillingStatus(null);
1338
- setBillingError('Failed to load billing status.');
1339
- return null;
1588
+ catch (error) {
1589
+ const message = error instanceof Error ? error.message : 'Failed to load account summary.';
1590
+ const hasPreviousSummary = Boolean(accountProfileSummaryRef.current);
1591
+ setBillingError(message);
1592
+ if (!hasPreviousSummary) {
1593
+ setTeamWorkspaceRole(null);
1594
+ setTeamPlanMode('unknown');
1595
+ setTeamManagementAccessError(message);
1596
+ }
1597
+ else {
1598
+ setTeamManagementAccessError(null);
1599
+ }
1600
+ logBootstrapDebug('account_profile_summary_failed', {
1601
+ durationMs: Math.round(getTimingNow() - startedAt),
1602
+ error: message,
1603
+ preservedSummary: hasPreviousSummary
1604
+ });
1605
+ return accountProfileSummaryRef.current;
1340
1606
  }
1341
1607
  finally {
1342
1608
  setBillingLoading(false);
1609
+ setTeamManagementAccessLoading(false);
1610
+ markAccountSurfaceLoadSettled('billing');
1611
+ markAccountSurfaceLoadSettled('teamManagement');
1343
1612
  }
1344
- }, [buildBillingApiUrl, isAuthenticated]);
1613
+ }, [authUserId, canAccessCloudTeamManagement, isAuthenticated, markAccountSurfaceLoadSettled, resolveCloudAuthUrl]);
1345
1614
  const handleStartCheckout = useCallback(async () => {
1346
1615
  const params = new URLSearchParams();
1347
1616
  params.set('screen', 'plans');
1348
1617
  params.set('interval', billingIntervalChoice);
1618
+ const planId = String(accountProfileSummary?.planId || '').trim();
1619
+ const planVersionId = String(accountProfileSummary?.planVersionId || '').trim();
1620
+ if (planId)
1621
+ params.set('planId', planId);
1622
+ if (planVersionId)
1623
+ params.set('planVersionId', planVersionId);
1349
1624
  navigate(`/?${params.toString()}`);
1350
- }, [billingIntervalChoice, navigate]);
1625
+ }, [accountProfileSummary?.planId, accountProfileSummary?.planVersionId, billingIntervalChoice, navigate]);
1351
1626
  const handleOpenBillingPortal = useCallback(async () => {
1352
1627
  setBillingActionError(null);
1353
1628
  setBillingNotice(null);
1354
1629
  setBillingActionBusy(true);
1355
1630
  try {
1356
- const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/portal-session'), {
1631
+ const res = await fetch(resolveCloudAuthUrl('/api/taskforce/billing/portal-session'), {
1357
1632
  method: 'POST',
1358
1633
  credentials: 'include'
1359
1634
  });
@@ -1375,18 +1650,18 @@ export function StandaloneLayout(props) {
1375
1650
  finally {
1376
1651
  setBillingActionBusy(false);
1377
1652
  }
1378
- }, [buildBillingApiUrl]);
1653
+ }, [resolveCloudAuthUrl]);
1379
1654
  const handleSwitchSubscriptionInterval = useCallback(async () => {
1380
1655
  setBillingActionError(null);
1381
1656
  setBillingNotice(null);
1382
1657
  setBillingActionBusy(true);
1383
1658
  try {
1384
- const currentPlanVersionId = String(billingStatus?.planVersionId || '').trim();
1659
+ const currentPlanVersionId = String(accountProfileSummary?.planVersionId || '').trim();
1385
1660
  if (!currentPlanVersionId) {
1386
1661
  setBillingActionError('No active plan version is linked to this account.');
1387
1662
  return;
1388
1663
  }
1389
- const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/subscription'), {
1664
+ const res = await fetch(resolveCloudAuthUrl('/api/taskforce/billing/subscription'), {
1390
1665
  method: 'POST',
1391
1666
  credentials: 'include',
1392
1667
  headers: { 'Content-Type': 'application/json' },
@@ -1401,7 +1676,8 @@ export function StandaloneLayout(props) {
1401
1676
  return;
1402
1677
  }
1403
1678
  setBillingNotice('Subscription updated.');
1404
- await loadBillingStatus();
1679
+ invalidateAccountProfileSummaryCache(resolveCloudAuthUrl(ACCOUNT_PROFILE_SUMMARY_PATH), { identityKey: authUserId });
1680
+ await loadAccountProfileSummary();
1405
1681
  }
1406
1682
  catch {
1407
1683
  setBillingActionError('Failed to update subscription interval.');
@@ -1409,51 +1685,17 @@ export function StandaloneLayout(props) {
1409
1685
  finally {
1410
1686
  setBillingActionBusy(false);
1411
1687
  }
1412
- }, [billingIntervalChoice, billingStatus?.planVersionId, buildBillingApiUrl, loadBillingStatus]);
1413
- const loadTeamPlanMode = useCallback(async () => {
1414
- if (!canManageTeamWorkspace) {
1415
- setTeamPlanMode('unknown');
1416
- return 'unknown';
1417
- }
1418
- try {
1419
- const res = await fetch('/api/taskforce/account/entitlements', {
1420
- method: 'GET',
1421
- credentials: 'include',
1422
- headers: teamAdminHeaders()
1423
- });
1424
- const payload = await res.json().catch(() => ({}));
1425
- if (!res.ok) {
1426
- setTeamPlanMode('unknown');
1427
- return 'unknown';
1428
- }
1429
- const nextMode = resolveTeamPlanModeFromEntitlements(payload);
1430
- setTeamPlanMode(nextMode);
1431
- return nextMode;
1432
- }
1433
- catch {
1434
- setTeamPlanMode('unknown');
1435
- return 'unknown';
1436
- }
1437
- }, [canManageTeamWorkspace, teamAdminHeaders]);
1438
- useEffect(() => {
1439
- if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
1440
- return;
1441
- void loadTeamPlanMode();
1442
- }, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamPlanMode]);
1688
+ }, [accountProfileSummary?.planVersionId, authUserId, billingIntervalChoice, loadAccountProfileSummary, resolveCloudAuthUrl]);
1443
1689
  useEffect(() => {
1444
1690
  if (!isAuthenticated || !authSessionResolved)
1445
1691
  return;
1446
- if (!canManageTeamWorkspace) {
1447
- setTeamPlanMode('unknown');
1448
- return;
1449
- }
1450
- void loadTeamPlanMode();
1451
- }, [authSessionResolved, canManageTeamWorkspace, isAuthenticated, loadTeamPlanMode]);
1692
+ void loadAccountProfileSummary();
1693
+ }, [authSessionResolved, isAuthenticated, loadAccountProfileSummary]);
1452
1694
  useEffect(() => {
1453
1695
  if (!showAccountHub && !showAccountMenu)
1454
1696
  return;
1455
- void loadBillingStatus();
1456
- }, [loadBillingStatus, showAccountHub, showAccountMenu]);
1697
+ void loadAccountProfileSummary();
1698
+ }, [loadAccountProfileSummary, showAccountHub, showAccountMenu]);
1457
1699
  const loadTeamUsers = useCallback(async () => {
1458
1700
  if (!canManageTeamWorkspace)
1459
1701
  return;
@@ -1582,12 +1824,12 @@ export function StandaloneLayout(props) {
1582
1824
  setShowAccountMenu(false);
1583
1825
  setShowTeamManagementModal(true);
1584
1826
  setTeamManagementTab('members');
1585
- const resolvedMode = await loadTeamPlanMode();
1586
- if (resolvedMode !== 'team')
1827
+ const summary = await loadAccountProfileSummary();
1828
+ if (!(summary?.teamManagementAllowed ?? canOpenTeamManagement))
1587
1829
  return;
1588
1830
  await loadTeamUsers();
1589
1831
  await loadTeamAuditPage(0);
1590
- }, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
1832
+ }, [canOpenTeamManagement, loadAccountProfileSummary, loadTeamAuditPage, loadTeamUsers]);
1591
1833
  const executeTeamUserAction = useCallback(async (userId, fn) => {
1592
1834
  setTeamMgmtError(null);
1593
1835
  setTeamActionBusyUserId(userId);
@@ -1891,12 +2133,17 @@ export function StandaloneLayout(props) {
1891
2133
  const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
1892
2134
  const details = event.details && typeof event.details === 'object' ? event.details : null;
1893
2135
  const pathValue = String(details?.path || '').trim();
2136
+ const referenceLabelValue = String(details?.referenceLabel || '').trim();
2137
+ const taskTitleValue = String(details?.taskTitle || details?.existingTaskTitle || '').trim();
1894
2138
  const existingRefValue = Number(details?.existingReferenceNumber);
1895
2139
  const incomingRefValue = Number(details?.incomingReferenceNumber);
1896
- const pathLabel = pathValue || `Mismatch ${index + 1}`;
1897
- const refsLabel = (Number.isFinite(existingRefValue) || Number.isFinite(incomingRefValue))
2140
+ const pathLabel = pathValue || taskTitleValue || referenceLabelValue || `Mismatch ${index + 1}`;
2141
+ const refsLabel = (Number.isFinite(existingRefValue)
2142
+ || Number.isFinite(incomingRefValue))
1898
2143
  ? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
1899
- : null;
2144
+ : referenceLabelValue
2145
+ ? `Both claimed ${referenceLabelValue}`
2146
+ : null;
1900
2147
  return {
1901
2148
  key: getSyncEventStableKey(event, index),
1902
2149
  pathLabel,
@@ -2252,23 +2499,35 @@ export function StandaloneLayout(props) {
2252
2499
  }, [groupBy, activeCategories, activeTypes, priorities, approaches, assigneeOptions, scheduleColumns]);
2253
2500
  // === SECTION: AccountMenuControl ===
2254
2501
  // Not extracted: ~25 distinct props from local state would be needed.
2255
- // References: isAuthenticated, authIdentityLabel, billingStatus, runtimeLabel,
2502
+ // References: isAuthenticated, authIdentityLabel, accountProfileSummary, runtimeLabel,
2256
2503
  // connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
2257
2504
  // setWorkspacePanelOpen, canOpenTeamManagement, openTeamManagementModal, and more.
2258
- const accountMenuControl = (_jsxs("div", { className: accountStyles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `tf-control-icon ${accountStyles.avatarBtn}`, onClick: () => setShowAccountMenu(prev => !prev), title: isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-label": isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-haspopup": "menu", "aria-expanded": showAccountMenu, children: _jsx("span", { className: accountStyles.avatarBadge, "aria-hidden": "true", children: isAuthenticated ? accountBadgeInitial : '?' }) }), showAccountMenu && (_jsxs("div", { className: accountStyles.accountMenu, role: "menu", "aria-label": "Account menu", children: [_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Account" }), _jsx("div", { className: accountStyles.accountMenuHint, children: isAuthBootstrapPending
2505
+ const accountMenuControl = (_jsxs("div", { className: accountStyles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `tf-control-icon ${accountStyles.avatarBtn}`, onClick: () => {
2506
+ setShowAccountMenu((prev) => {
2507
+ const next = !prev;
2508
+ if (next) {
2509
+ startAccountSurfaceTiming('account_menu');
2510
+ }
2511
+ else {
2512
+ accountSurfaceOpenStartedAtRef.current = null;
2513
+ accountSurfacePendingRef.current = { billing: false, teamManagement: false };
2514
+ }
2515
+ return next;
2516
+ });
2517
+ }, title: isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-label": isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-haspopup": "menu", "aria-expanded": showAccountMenu, children: _jsx("span", { className: accountStyles.avatarBadge, "aria-hidden": "true", children: shouldShowResolvedAccountAvatar ? (_jsx("img", { src: accountAvatarUrl, alt: "", className: accountStyles.avatarImage })) : accountBadgeInitial ? (accountBadgeInitial) : (_jsx(User, { size: 14, className: accountStyles.avatarIcon })) }) }), showAccountMenu && (_jsxs("div", { className: accountStyles.accountMenu, role: "menu", "aria-label": "Account menu", children: [_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Account" }), _jsx("div", { className: accountStyles.accountMenuHint, children: isAuthBootstrapPending
2259
2518
  ? 'Checking sign-in status...'
2260
2519
  : isAuthenticated
2261
2520
  ? (_jsx(_Fragment, { children: hasAuthIdentity
2262
- ? (_jsx(_Fragment, { children: _jsxs("span", { className: accountStyles.accountIdentityBlock, children: [_jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel }), hasAuthEmail && authDisplayName && (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Email" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: authEmailLabel })] })), hasSubscription && (_jsxs("button", { "aria-label": `Subscription ${subscriptionLabel}`, className: `${accountStyles.accountIdentityMetaLine} ${accountStyles.accountIdentityMetaAction}`, onClick: () => {
2521
+ ? (_jsx(_Fragment, { children: _jsxs("span", { className: accountStyles.accountIdentityBlock, children: [_jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel }), hasAuthEmail && authDisplayName && (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Email" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: authEmailLabel })] })), billingLoading ? (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: "Loading\u2026" })] })) : hasSubscription ? (_jsxs("button", { "aria-label": `Subscription ${subscriptionLabel}`, className: `${accountStyles.accountIdentityMetaLine} ${accountStyles.accountIdentityMetaAction}`, onClick: () => {
2263
2522
  setShowAccountMenu(false);
2264
2523
  openPlansScreen();
2265
- }, type: "button", children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: subscriptionLabel })] })), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Runtime" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: runtimeLabel })] }), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Environment" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: connectedEnvironmentLabel })] })] }) }))
2524
+ }, type: "button", children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: subscriptionLabel })] })) : billingError ? (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: "Unavailable" })] })) : null, _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Runtime" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: runtimeLabel })] }), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Environment" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: connectedEnvironmentLabel })] })] }) }))
2266
2525
  : 'Signed in' }))
2267
2526
  : 'Not signed in' })] }), canManageCloudWorkspaces && (_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Workspaces (Owned + Invited)" }), availableWorkspaces.length === 0 && (_jsx("div", { className: accountStyles.accountMenuHint, children: "No workspaces found for this account yet." })), availableWorkspaces.map((workspace) => (_jsxs("button", { className: `${accountStyles.accountMenuItem} ${workspace.id === currentWorkspaceId ? accountStyles.accountMenuItemActive : ''}`, onClick: () => handleSwitchWorkspace(workspace.id), role: "menuitem", disabled: workspaceActionBusy || workspace.id === currentWorkspaceId, title: workspace.description || workspace.name, children: [_jsx(Folder, { size: 15 }), _jsxs("span", { style: { display: 'flex', flexDirection: 'column', gap: '2px' }, children: [_jsx("span", { children: workspace.name }), _jsx("span", { className: accountStyles.accountMenuMeta, children: workspace.role })] })] }, workspace.id))), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2268
2527
  setWorkspaceActionError('');
2269
2528
  setShowCreateWorkspaceConfirm(true);
2270
2529
  setShowAccountMenu(false);
2271
- }, role: "menuitem", disabled: workspaceActionBusy, children: [_jsx(Plus, { size: 15 }), "Create Workspace"] }), workspaceActionError && (_jsx("div", { className: accountStyles.accountMenuError, children: workspaceActionError }))] })), canOpenTeamManagement && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => { void openTeamManagementModal(); }, role: "menuitem", children: [_jsx(Users, { size: 15 }), "Team Management"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2530
+ }, role: "menuitem", disabled: workspaceActionBusy, children: [_jsx(Plus, { size: 15 }), "Create Workspace"] }), workspaceActionError && (_jsx("div", { className: accountStyles.accountMenuError, children: workspaceActionError }))] })), teamManagementAccessLoading ? (_jsx("div", { className: accountStyles.accountMenuHint, children: "Resolving team management access..." })) : canOpenTeamManagement ? (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => { void openTeamManagementModal(); }, role: "menuitem", children: [_jsx(Users, { size: 15 }), "Team Management"] })) : teamManagementAccessError ? (_jsx("div", { className: accountStyles.accountMenuHint, children: "Team Management unavailable right now." })) : null, isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2272
2531
  setShowAccountMenu(false);
2273
2532
  setShowEditProfileModal(true);
2274
2533
  }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Edit Profile"] })), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
@@ -2276,6 +2535,7 @@ export function StandaloneLayout(props) {
2276
2535
  openPlansScreen();
2277
2536
  }, role: "menuitem", children: [_jsx(Layers, { size: 15 }), "Plans"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2278
2537
  setShowAccountMenu(false);
2538
+ startAccountSurfaceTiming('account_hub');
2279
2539
  setShowAccountHub(true);
2280
2540
  }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2281
2541
  setShowAccountMenu(false);
@@ -2908,7 +3168,7 @@ export function StandaloneLayout(props) {
2908
3168
  const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
2909
3169
  ? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
2910
3170
  : null;
2911
- return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: isPlansScreen ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
3171
+ return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: isPlansScreen ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.taskCountBadge, title: "Tasks assigned to me", children: [_jsx(User, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedTaskCount] }), _jsxs("span", { className: `${styles.taskCountBadge} ${assignedOverdueTaskCount > 0 ? styles.taskCountBadgeAlert : ''}`.trim(), title: "Tasks overdue", children: [_jsx(ClockAlert, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
2912
3172
  marginLeft: '6px',
2913
3173
  height: '24px',
2914
3174
  width: '24px',
@@ -2920,7 +3180,7 @@ export function StandaloneLayout(props) {
2920
3180
  display: 'inline-flex',
2921
3181
  alignItems: 'center',
2922
3182
  justifyContent: 'center'
2923
- }, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: handlePlansBack, title: "Back", children: "Back" })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: billingApiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', embedded: true, shellOwnsScroll: true }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
3183
+ }, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: handlePlansBack, title: "Back", children: "Back" })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, authUserId: authUserId, apiBaseUrl: apiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', resolveCloudAuthUrl: resolveCloudAuthUrl, embedded: true, shellOwnsScroll: true }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
2924
3184
  setRequestedDocPath(null);
2925
3185
  setRequestedDocAssetId(null);
2926
3186
  }, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
@@ -2939,7 +3199,7 @@ export function StandaloneLayout(props) {
2939
3199
  inset: 0,
2940
3200
  zIndex: 35,
2941
3201
  pointerEvents: 'none',
2942
- } }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: [], columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
3202
+ } }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
2943
3203
  setScheduleSelectedDate(dateOnly);
2944
3204
  const parsed = parseDateOnlyLocal(dateOnly);
2945
3205
  if (parsed) {
@@ -3041,114 +3301,71 @@ export function StandaloneLayout(props) {
3041
3301
  return;
3042
3302
  }
3043
3303
  void handleUnarchive(taskId);
3044
- }, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel: settingsModel, onSectionChange: setSettingsSection }) }) }), _jsx(Modal, { isOpen: !!scheduleWarningPrompt, onClose: cancelScheduleWarning, title: "Date Warning", size: "sm", theme: currentTheme, draggable: false, closeOnOverlayClick: false, children: _jsxs("div", { className: `${styles.form} tf-inline-stack-sm`, style: { padding: '16px', gap: '12px' }, children: [_jsx("p", { className: "tf-text-body", children: "Due date is before scheduled date." }), scheduleWarningPrompt && (_jsxs("p", { className: "tf-text-helper", children: ["Due: ", scheduleWarningPrompt.dueDate, " \u00B7 Scheduled: ", scheduleWarningPrompt.scheduledDate] })), _jsxs("div", { className: styles.formActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: cancelScheduleWarning, children: "Go Back" }), _jsx("button", { className: styles.submitBtn, onClick: confirmScheduleWarning, children: "Save Anyway" })] })] }) }), _jsx(Modal, { isOpen: showHelpModal, onClose: () => setShowHelpModal(false), title: "Quick Start", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Use this quick guide to get productive in Taskforce dashboard mode." }), _jsxs("div", { children: [_jsx("strong", { children: "1. Capture work" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Click ", _jsx("code", { children: "Add Task" }), ", write a short title, and save."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "2. Plan the board" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Use ", _jsx("code", { children: "Sort" }), ", ", _jsx("code", { children: "Group" }), ", and ", _jsx("code", { children: "Filters" }), " in the header."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "3. Execute and stage" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Move tasks through ", _jsx("code", { children: "Task" }), ", ", _jsx("code", { children: "In Progress" }), ", ", _jsx("code", { children: "Review" }), ", and ", _jsx("code", { children: "Done" }), "."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "4. Archive finished work" }), _jsx("p", { className: managementStyles.sectionText, children: "Archive completed/cancelled tasks to keep active board signal high." })] }), _jsxs("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
3045
- setShowHelpModal(false);
3046
- setActiveTab('settings');
3047
- setSettingsSection('general');
3048
- }, children: "Open Settings" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowHelpModal(false), children: "Close Tutorial" })] })] }) }), _jsx(Modal, { isOpen: showEditProfileModal, onClose: () => setShowEditProfileModal(false), title: "Edit Profile", size: "sm", theme: currentTheme, draggable: true, footer: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: styles.secondaryButton, onClick: () => setShowEditProfileModal(false), disabled: profileSaveBusy, children: "Cancel" }), _jsx("button", { type: "button", className: styles.primaryUpdateBtn, onClick: () => { void handleSaveProfile(); }, disabled: profileSaveBusy || !profileDisplayNameDraft.trim(), children: profileSaveBusy ? 'Saving...' : 'Save Profile' })] })), children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.sectionText, children: "Update the name shown around your account and shared workspace surfaces." }), _jsxs("label", { className: managementStyles.inlineFieldLabel, children: ["Display name", _jsx("input", { className: styles.input, type: "text", value: profileDisplayNameDraft, onChange: (event) => setProfileDisplayNameDraft(event.target.value), placeholder: "Display name", disabled: profileSaveBusy })] }), _jsxs("label", { className: managementStyles.inlineFieldLabel, children: ["Email", _jsx("input", { className: styles.input, type: "email", value: authEmailLabel, readOnly: true, disabled: true })] }), profileSaveError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: profileSaveError })), profileSaveNotice && !profileSaveError && (_jsx("p", { className: managementStyles.errorText, children: profileSaveNotice }))] }) }), _jsx(Modal, { isOpen: showAccountHub, onClose: () => setShowAccountHub(false), title: "Account Hub", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Manage sign in, profile, and workspace controls from one place." }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Authentication" }), _jsx("p", { className: managementStyles.sectionText, children: runtimeMode === 'cloud'
3049
- ? (authRequiredForApi
3050
- ? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
3051
- : 'Cloud runtime with optional authentication.')
3052
- : 'Local runtime allows guest usage without sign in.' }), _jsx("p", { className: managementStyles.sectionTextSpaced, children: isAuthenticated
3053
- ? (hasAuthIdentity
3054
- ? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
3055
- : 'Signed in')
3056
- : 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children: String(billingStatus?.planId || 'n/a') }), ' · ', "Entitlement: ", _jsx("code", { children: String(billingStatus?.entitlementState || 'n/a') })] }), _jsxs("p", { className: managementStyles.sectionText, children: ["Stripe status: ", _jsx("code", { children: String(billingStatus?.stripeStatus || 'n/a') }), billingStatus?.effectiveUntil ? ` · Effective until ${new Date(billingStatus.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { className: managementStyles.wrapRow, children: _jsxs("label", { className: `${accountStyles.accountMenuMeta} ${managementStyles.inlineFieldLabel}`, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => setBillingIntervalChoice(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { className: managementStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => { void loadBillingStatus(); }, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: () => { void handleSwitchSubscriptionInterval(); }, disabled: billingActionBusy || !billingStatus?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: () => { void handleOpenBillingPortal(); }, disabled: billingActionBusy || !billingStatus?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick: () => { void handleStartCheckout(); }, disabled: billingActionBusy, children: billingActionBusy ? 'Working...' : 'Start Checkout' })] })] }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Audit Log" }), _jsx("p", { className: managementStyles.sectionText, children: "Audit entries are workspace-scoped for the active workspace, not global account history." }), _jsxs("p", { className: managementStyles.sectionText, children: ["Active workspace: ", _jsx("code", { children: currentWorkspaceId })] }), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, onClick: () => {
3057
- setShowAccountHub(false);
3058
- void openTeamManagementModal();
3059
- setTeamManagementTab('audit');
3060
- }, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Cloud Sync" }), _jsx("p", { className: managementStyles.sectionText, children: canManageWorkspaceSync
3061
- ? (workspaceCloudSyncEnabled
3062
- ? `Enabled · ${syncStatusMeta.label}`
3063
- : 'Disabled')
3064
- : 'Sign in to cloud account to enable workspace sync.' }), workspaceSyncError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: workspaceSyncError })), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: !canManageWorkspaceSync || syncControlBusy, onClick: () => { void handleWorkspaceSyncToggle(!workspaceCloudSyncEnabled); }, children: workspaceCloudSyncEnabled ? 'Disable Sync' : 'Enable Sync' }) })] }), _jsxs("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
3065
- setShowAccountHub(false);
3066
- setShowHelpModal(true);
3067
- }, children: "Help & Tutorial" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowAccountHub(false), children: "Close" })] })] }) }), _jsx(Modal, { isOpen: showTeamManagementModal, onClose: () => setShowTeamManagementModal(false), title: "Team Management", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [teamPlanMode === 'personal' && (_jsx("p", { className: authStyles.loginError, children: "Team management is only available in TEAM accounts." })), teamMgmtError && (_jsx("p", { className: authStyles.loginError, children: teamMgmtError })), _jsxs("div", { className: `${styles.settingsTabs} tf-scrollbar tf-scrollbar--track-transparent tf-scrollbar--compact`, children: [_jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'members' ? styles.settingsTabBtnActive : ''}`, onClick: () => {
3068
- setTeamManagementTab('members');
3069
- void loadTeamUsers();
3070
- }, children: "Members" }), _jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'invites' ? styles.settingsTabBtnActive : ''}`, onClick: () => setTeamManagementTab('invites'), children: "Invites" }), _jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'audit' ? styles.settingsTabBtnActive : ''}`, onClick: () => {
3071
- setTeamManagementTab('audit');
3072
- void loadTeamAuditPage(teamAuditPage);
3073
- }, children: "Audit Log" })] }), teamManagementTab === 'members' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamUsersLoading && _jsx("p", { className: managementStyles.mutedText, children: "Loading members..." }), !teamUsersLoading && teamUsers.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No users found." })), !teamUsersLoading && teamUsers.map((member) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: managementStyles.memberTitle, children: member.displayName || member.email }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [member.email, " \u00B7 ", member.status, member.disabled ? ' · deactivated' : ''] }), _jsxs("div", { className: managementStyles.memberActions, children: [_jsxs("select", { className: `${styles.input} ${managementStyles.selectRole}`, value: member.role, onChange: (event) => {
3074
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/role`, {
3075
- method: 'PATCH',
3076
- headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3077
- credentials: 'include',
3078
- body: JSON.stringify({
3079
- workspaceId: teamManagementWorkspaceId,
3080
- role: event.target.value
3081
- })
3082
- }));
3083
- }, disabled: teamActionBusyUserId === member.userId, children: [_jsx("option", { value: "owner", children: "Owner" }), _jsx("option", { value: "admin", children: "Admin" }), _jsx("option", { value: "member", children: "Member" })] }), member.role === 'member' && (_jsxs("select", { className: `${styles.input} ${managementStyles.selectPermission}`, value: member.permissionMode, onChange: (event) => {
3084
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/permission-mode`, {
3085
- method: 'PATCH',
3086
- headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3087
- credentials: 'include',
3088
- body: JSON.stringify({
3089
- workspaceId: teamManagementWorkspaceId,
3090
- mode: event.target.value
3091
- })
3092
- }));
3093
- }, disabled: teamActionBusyUserId === member.userId, children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
3094
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/disable`, {
3095
- method: 'PATCH',
3096
- headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3097
- credentials: 'include',
3098
- body: JSON.stringify({
3099
- workspaceId: teamManagementWorkspaceId,
3100
- disabled: !member.disabled
3101
- })
3102
- }));
3103
- }, children: member.disabled ? 'Reactivate' : 'Deactivate' }), member.status === 'invited' && (_jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
3104
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/invite/revoke`, {
3105
- method: 'POST',
3106
- headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3107
- credentials: 'include',
3108
- body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
3109
- }));
3110
- }, children: "Revoke Invite" })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
3111
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
3112
- method: 'DELETE',
3113
- headers: teamAdminHeaders(),
3114
- credentials: 'include'
3115
- }));
3116
- }, children: "Remove" })] })] }, member.userId)))] })), teamManagementTab === 'invites' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamInviteFeedback && (_jsx("p", { className: managementStyles.sectionText, children: teamInviteFeedback })), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: `${styles.label} ${managementStyles.labelFixed}`, children: "Email" }), _jsx("input", { className: styles.input, value: teamInviteEmail, onChange: (event) => setTeamInviteEmail(event.target.value), placeholder: "name@company.com" })] }), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: `${styles.label} ${managementStyles.labelFixed}`, children: "Role" }), _jsxs("select", { className: `${styles.input} ${managementStyles.selectRole}`, value: teamInviteRole, onChange: (event) => setTeamInviteRole(event.target.value === 'admin' ? 'admin' : 'member'), children: [_jsx("option", { value: "member", children: "Member" }), _jsx("option", { value: "admin", children: "Admin" })] }), teamInviteRole === 'member' && (_jsxs("select", { className: `${styles.input} ${managementStyles.selectPermission}`, value: teamInvitePermissionMode, onChange: (event) => setTeamInvitePermissionMode(event.target.value === 'read-only' ? 'read-only' : 'read-write'), children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.submitBtn, disabled: teamInviteBusy || !teamInviteEmail.trim() || teamPlanMode !== 'team', onClick: () => { void submitTeamInvite(); }, children: teamInviteBusy ? 'Sending...' : 'Send Invite' })] }), _jsxs("div", { className: managementStyles.inviteRow, children: [_jsx("div", { className: managementStyles.listHeading, children: "Pending Invites" }), pendingInvites.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No pending invites." })), pendingInvites.map((invite) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: managementStyles.memberTitle, children: invite.displayName || invite.email }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [invite.email, " \u00B7 ", invite.role] })] }, invite.userId)))] })] })), teamManagementTab === 'audit' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamAuditLoading && _jsx("p", { className: managementStyles.mutedText, children: "Loading audit entries..." }), !teamAuditLoading && teamAuditEvents.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No audit events for this workspace." })), !teamAuditLoading && (_jsx("div", { className: managementStyles.auditList, children: teamAuditEvents.map((event) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: accountStyles.accountMenuMeta, children: new Date(event.createdAt).toLocaleString() }), _jsx("div", { className: managementStyles.memberTitle, children: event.action }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [event.actorUserId, " (", event.actorRole, ")"] })] }, event.id))) })), _jsxs("div", { className: managementStyles.auditPager, children: [_jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || teamAuditPage === 0, onClick: () => { void loadTeamAuditPage(teamAuditPage - 1); }, children: "Previous" }), _jsxs("span", { className: accountStyles.accountMenuMeta, children: ["Page ", teamAuditPage + 1, " of ", teamAuditPages] }), _jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || !teamAuditHasMore || teamAuditPage + 1 >= teamAuditPages, onClick: () => { void loadTeamAuditPage(teamAuditPage + 1); }, children: "Next" })] })] })), _jsx("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: _jsx("button", { className: styles.cancelBtn, onClick: () => setShowTeamManagementModal(false), children: "Close" }) })] }) }), _jsx(Modal, { isOpen: showCreateWorkspaceConfirm, onClose: () => setShowCreateWorkspaceConfirm(false), title: "Create Workspace", size: "sm", theme: currentTheme, draggable: true, footer: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: styles.secondaryButton, onClick: () => setShowCreateWorkspaceConfirm(false), children: "Cancel" }), _jsx("button", { type: "button", className: styles.primaryUpdateBtn, onClick: () => openWorkspaceSetup({ intent: 'create-workspace' }), children: "Continue to Setup" })] })), children: _jsx("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: _jsx("p", { className: managementStyles.sectionText, children: "Start a new workspace with the guided setup flow. You will choose the workspace name, description, and starter taxonomy library before anything is created." }) }) }), _jsx(Modal, { isOpen: showSyncStatusModal, onClose: () => {
3117
- setShowSyncStatusModal(false);
3118
- }, title: "Sync Manager", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${syncStatusStyles.syncStatusModal}`, style: { gap: '10px' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusTop, children: [_jsx("div", { className: syncStatusStyles.syncStatusHeaderInfo, children: _jsxs("div", { className: syncStatusStyles.syncStatusMetaHeader, children: [_jsx("div", { className: syncStatusStyles.syncStatusBadge, style: {
3119
- borderColor: syncStatusMeta.border,
3120
- background: syncStatusMeta.background,
3121
- color: syncStatusMeta.color
3122
- }, children: syncStatusMeta.label }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, children: ["Workspace: ", _jsx("code", { children: currentWorkspaceLabel })] })] }) }), _jsx("label", { className: syncStatusStyles.syncToggle, children: _jsxs("span", { className: [
3123
- syncStatusStyles.syncToggleControl,
3124
- workspaceCloudSyncEnabled ? syncStatusStyles.syncToggleControlEnabled : syncStatusStyles.syncToggleControlDisabled,
3125
- syncControlBusy ? syncStatusStyles.syncToggleControlBusy : '',
3126
- !canManageWorkspaceSync ? syncStatusStyles.syncToggleControlBlocked : ''
3127
- ].filter(Boolean).join(' '), children: [_jsx("input", { className: syncStatusStyles.syncToggleInput, type: "checkbox", role: "switch", "aria-label": "Enable Sync", checked: workspaceCloudSyncEnabled, disabled: !canManageWorkspaceSync || syncControlBusy, onChange: (event) => {
3128
- void handleWorkspaceSyncToggle(event.target.checked);
3129
- } }), _jsx("span", { className: `${syncStatusStyles.syncToggleState} ${syncStatusStyles.syncToggleStateOn}`, children: "On" }), _jsx("span", { className: `${syncStatusStyles.syncToggleState} ${syncStatusStyles.syncToggleStateOff}`, children: "Off" }), _jsx("span", { className: syncStatusStyles.syncToggleThumb })] }) })] }), _jsxs("div", { className: syncStatusStyles.syncStatusSummaryCompact, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Health" }), _jsx("span", { className: syncStatusStyles.syncStatusSummaryValue, children: workspaceSyncSummary })] }), workspaceSyncRepairBusy && (_jsxs("div", { className: syncStatusStyles.syncStatusRepairBanner, children: [_jsxs("div", { className: syncStatusStyles.syncStatusRepairHeader, children: [_jsxs("div", { className: syncStatusStyles.syncStatusRepairBadge, children: [_jsx(Loader2, { size: 13, className: styles.spinner }), _jsx("span", { children: "Repair In Progress" })] }), _jsx("span", { className: syncStatusStyles.syncStatusRepairMeta, children: "Advanced recovery mode" })] }), _jsx("span", { className: syncStatusStyles.syncStatusRepairText, children: referenceMismatchCount > 0
3130
- ? `Taskforce is clearing the saved sync cursor and reconciling workspace state from the cloud again, including ${referenceMismatchCount} detected reference mismatch${referenceMismatchCount === 1 ? '' : 'es'}. The panel may stay busy for a while during large repairs.`
3131
- : 'Taskforce is clearing the saved sync cursor and reconciling workspace state from the cloud again. The panel may stay busy for a while during large repairs.' })] })), _jsx("div", { className: syncStatusStyles.syncStatusSectionHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Current Session" }) }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactGrid, children: [_jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Stage" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: syncStageLabel })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Local Changes" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: workspaceSyncPendingChanges })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Successful" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastSyncTime })] })] }), _jsx("div", { className: syncStatusStyles.syncStatusSectionHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Activity" }) }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactGrid, children: [_jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Pull" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastPullTime })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Push" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastPushTime })] })] }), syncLastError !== 'None' && (_jsxs("div", { className: syncStatusStyles.syncStatusPanel, style: { background: 'rgba(239, 68, 68, 0.05)', borderColor: 'rgba(239, 68, 68, 0.2)' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusSectionHeader, style: { marginTop: 0 }, children: [_jsx(AlertTriangle, { size: 12, color: "#fda4af" }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, style: { color: '#fda4af' }, children: ["Last Error (", workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleTimeString() : 'Recent', ")"] })] }), _jsx("span", { className: `${syncStatusStyles.syncStatusValue} ${syncStatusStyles.syncStatusValueError}`, children: syncLastError })] })), referenceMismatchCount > 0 && (_jsxs("div", { className: syncStatusStyles.syncStatusPanel, style: { background: 'rgba(250, 204, 21, 0.08)', borderColor: 'rgba(250, 204, 21, 0.22)' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusPanelHeader, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, style: { color: '#fde68a' }, children: "Identifier Integrity" }), _jsxs("span", { className: syncStatusStyles.syncStatusPanelMeta, children: [referenceMismatchCount, " mismatch", referenceMismatchCount === 1 ? '' : 'es', " detected"] })] }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: "Document or image reference numbers disagreed during normal sync. Standard sync now preserves existing identifiers; use Repair to reconcile mismatched references deliberately." }), activeReferenceMismatchSummaries.length > 0 && (_jsx("div", { style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }, children: activeReferenceMismatchSummaries.map((item) => (_jsxs("div", { style: {
3132
- display: 'flex',
3133
- flexDirection: 'column',
3134
- gap: '2px',
3135
- padding: '8px 10px',
3136
- borderRadius: '8px',
3137
- background: 'rgba(15, 23, 42, 0.18)',
3138
- border: '1px solid rgba(250, 204, 21, 0.16)'
3139
- }, children: [_jsx("span", { className: syncStatusStyles.syncStatusValue, style: { fontSize: '12px', wordBreak: 'break-all' }, children: item.pathLabel }), item.refsLabel ? (_jsx("span", { className: syncStatusStyles.syncStatusPanelMeta, children: item.refsLabel })) : null] }, item.key))) }))] })), _jsxs("div", { className: syncStatusStyles.syncStatusPanel, children: [_jsxs("div", { className: syncStatusStyles.syncStatusPanelHeader, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Local Sync Diagnostics" }), _jsx("span", { className: syncStatusStyles.syncStatusPanelMeta, style: { fontSize: '10px' }, children: "AI profiles & metadata" })] }), _jsx("div", { className: syncStatusStyles.syncStatusCompactGrid, style: { gridTemplateColumns: 'repeat(2, 1fr)' }, children: syncDiagnosticsSummary.map((item) => (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: '8px' }, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, style: { fontSize: '9px', textTransform: 'capitalize' }, children: item.label }), _jsx("span", { className: syncStatusStyles.syncStatusValue, style: { fontSize: '11px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: item.value })] }, item.label))) })] }), _jsxs("div", { className: syncStatusStyles.syncStatusPanel, children: [_jsx("div", { className: syncStatusStyles.syncStatusPanelHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Recent Events" }) }), _jsx("div", { ref: syncEventsListRef, className: syncStatusStyles.syncStatusEventsList, style: { height: '120px' }, children: syncEventRows.map((row) => (_jsx("div", { className: [
3140
- syncStatusStyles.syncStatusEventItem,
3141
- row.tone === 'error' ? syncStatusStyles.syncStatusEventItemError : '',
3142
- row.tone === 'muted' ? syncStatusStyles.syncStatusEventItemMuted : ''
3143
- ].filter(Boolean).join(' '), style: { padding: '6px 8px', borderRadius: '6px' }, children: _jsx("span", { className: syncStatusStyles.syncStatusEventLine, style: { fontSize: '11px' }, children: row.text }) }, row.key))) })] }), _jsxs("div", { className: `${modalStyles.formActions} ${syncStatusStyles.syncStatusActions}`, children: [_jsxs("div", { className: syncStatusStyles.syncStatusSecondaryActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: handleQueueOrRunRepairSync, disabled: workspaceSyncRepairBusy, title: workspaceSyncRepairBusy
3144
- ? 'Repair is running now.'
3145
- : workspaceSyncRepairQueued
3146
- ? 'Repair is queued and will start when the current sync finishes.'
3147
- : workspaceSyncBusy
3148
- ? 'Queue a repair to start automatically when the current sync finishes.'
3149
- : 'Advanced recovery: clear the saved pull cursor and re-fetch cloud state from the beginning.', children: workspaceSyncRepairBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), _jsx("span", { style: { marginLeft: '6px' }, children: "Repairing..." })] })) : workspaceSyncRepairQueued ? ('Repair Queued') : ('Repair') }), _jsxs("button", { className: styles.cancelBtn, onClick: () => { void handleCopySyncDetails(); }, title: "Copy the current sync manager report for support or AI troubleshooting.", children: [_jsx(Copy, { size: 14 }), _jsx("span", { style: { marginLeft: '6px' }, children: workspaceSyncCopied ? 'Copied' : 'Copy Report' })] })] }), _jsxs("div", { className: syncStatusStyles.syncStatusPrimaryActions, children: [_jsx("div", { className: syncStatusStyles.syncStatusPrimaryActionSlot, children: runtimeMode === 'local' && !isAuthenticated ? (_jsx("button", { className: styles.submitBtn, onClick: () => {
3150
- openSharedLoginRoute('login');
3151
- }, children: "Sign In / Register" })) : (_jsx("div", { className: syncStatusStyles.syncStatusActionPlaceholder, "aria-hidden": "true" })) }), _jsx("div", { className: syncStatusStyles.syncStatusPrimaryActionSlot, children: _jsx("button", { className: styles.submitBtn, onClick: () => void retryWorkspaceCloudSync(), disabled: workspaceSyncBusy, children: workspaceSyncBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), "Syncing..."] })) : (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { size: 14 }), "Sync"] })) }) })] })] })] }) }), unsavedModalOpen && createPortal(_jsx("div", { className: `${modalStyles.overlay} ${modalStyles.unsavedOverlay}`, style: { zIndex: 2000 }, children: _jsxs("div", { className: `tf-surface-modal tf-modal-shell ${modalStyles.modal} ${modalStyles.unsavedModal}`, "data-theme": currentTheme, children: [_jsx("div", { className: `tf-modal-header ${modalStyles.unsavedHeader}`, children: _jsxs("div", { className: `tf-modal-title ${modalStyles.unsavedTitle}`, children: [_jsx(AlertTriangle, { size: 20 }), "Unsaved Changes"] }) }), _jsx("div", { className: `${modalStyles.form} ${modalStyles.unsavedContent}`, children: _jsx("p", { className: modalStyles.unsavedText, children: "You have unsaved changes. Would you like to save them?" }) }), _jsxs("div", { className: `${modalStyles.formActions} ${modalStyles.unsavedActions}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => setUnsavedModalOpen(false), title: "Close dialog and continue editing", children: "Keep Editing" }), _jsx("button", { className: styles.destructiveBtn, onClick: handleDiscard, title: "Discard unsaved changes and leave", children: "Discard" }), _jsxs("button", { className: styles.submitBtn, onClick: handleSaveAndNavigate, disabled: loading, title: "Save changes and leave", children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Save, { size: 16 }), "Save"] })] })] }) }), document.body), showFolderBrowser && createPortal(_jsx("div", { className: folderBrowserStyles.overlay, children: _jsxs("div", { className: folderBrowserStyles.browser, children: [_jsxs("div", { className: folderBrowserStyles.header, children: [_jsxs("div", { className: folderBrowserStyles.pathInfo, children: [_jsx(Folder, { size: 14 }), _jsx("span", { children: currentBrowsePath || 'Project Root' })] }), _jsxs("div", { className: folderBrowserStyles.actions, children: [currentBrowsePath && (_jsx("button", { className: styles.helpLink, onClick: () => {
3304
+ }, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel: settingsModel, onSectionChange: setSettingsSection }) }) }), _jsx(ScheduleWarningModal, { prompt: scheduleWarningPrompt, theme: currentTheme, onClose: cancelScheduleWarning, onConfirm: confirmScheduleWarning }), _jsx(HelpModal, { isOpen: showHelpModal, theme: currentTheme, onClose: () => setShowHelpModal(false), onOpenSettings: () => {
3305
+ setShowHelpModal(false);
3306
+ setActiveTab('settings');
3307
+ setSettingsSection('general');
3308
+ } }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial, avatarInputRef: profileAvatarInputRef, avatarAccept: PROFILE_AVATAR_ACCEPT, avatarDraftId: profileAvatarDraftId, saveBusy: profileSaveBusy, avatarBusy: profileAvatarBusy, saveError: profileSaveError, saveNotice: profileSaveNotice, onDisplayNameChange: setProfileDisplayNameDraft, onAvatarInputChange: (file) => { void handleProfileAvatarSelected(file); }, onStartAvatarUpload: () => profileAvatarInputRef.current?.click(), onDiscardUpload: handleDiscardPendingProfileAvatar, onRemovePhoto: handleRemoveProfileAvatar }), _jsx(AccountHubModal, { isOpen: showAccountHub, theme: currentTheme, runtimeMode: runtimeMode, authRequiredForApi: authRequiredForApi, isAuthenticated: isAuthenticated, hasAuthIdentity: hasAuthIdentity, authIdentityLabel: authIdentityLabel, billingLoading: billingLoading, billingError: billingError, billingActionError: billingActionError, billingNotice: billingNotice, billingActionBusy: billingActionBusy, billingIntervalChoice: billingIntervalChoice, accountProfileSummary: accountProfileSummary, currentWorkspaceId: currentWorkspaceId, canOpenTeamManagement: canOpenTeamManagement, canManageWorkspaceSync: canManageWorkspaceSync, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncStatusLabel: syncStatusMeta.label, workspaceSyncError: workspaceSyncError, syncControlBusy: syncControlBusy, onClose: () => setShowAccountHub(false), onOpenWorkspaceAudit: () => {
3309
+ setShowAccountHub(false);
3310
+ void openTeamManagementModal();
3311
+ setTeamManagementTab('audit');
3312
+ }, onBillingIntervalChange: setBillingIntervalChoice, onRefreshBilling: () => { void loadAccountProfileSummary(); }, onUpdateInterval: () => { void handleSwitchSubscriptionInterval(); }, onManageBilling: () => { void handleOpenBillingPortal(); }, onStartCheckout: () => { void handleStartCheckout(); }, onToggleWorkspaceSync: (enabled) => { void handleWorkspaceSyncToggle(enabled); }, onOpenHelp: () => {
3313
+ setShowAccountHub(false);
3314
+ setShowHelpModal(true);
3315
+ } }), _jsx(TeamManagementModal, { isOpen: showTeamManagementModal, theme: currentTheme, teamPlanMode: teamPlanMode, teamMgmtError: teamMgmtError, teamManagementTab: teamManagementTab, teamUsersLoading: teamUsersLoading, teamUsers: teamUsers, teamActionBusyUserId: teamActionBusyUserId, teamInviteFeedback: teamInviteFeedback, teamInviteEmail: teamInviteEmail, teamInviteRole: teamInviteRole, teamInvitePermissionMode: teamInvitePermissionMode, teamInviteBusy: teamInviteBusy, pendingInvites: pendingInvites, teamAuditLoading: teamAuditLoading, teamAuditEvents: teamAuditEvents, teamAuditPage: teamAuditPage, teamAuditPages: teamAuditPages, teamAuditHasMore: teamAuditHasMore, onClose: () => setShowTeamManagementModal(false), onOpenMembersTab: () => {
3316
+ setTeamManagementTab('members');
3317
+ void loadTeamUsers();
3318
+ }, onOpenInvitesTab: () => setTeamManagementTab('invites'), onOpenAuditTab: () => {
3319
+ setTeamManagementTab('audit');
3320
+ void loadTeamAuditPage(teamAuditPage);
3321
+ }, onMemberRoleChange: (userId, role) => {
3322
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/role`, {
3323
+ method: 'PATCH',
3324
+ headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3325
+ credentials: 'include',
3326
+ body: JSON.stringify({
3327
+ workspaceId: teamManagementWorkspaceId,
3328
+ role
3329
+ })
3330
+ }));
3331
+ }, onMemberPermissionChange: (userId, mode) => {
3332
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/permission-mode`, {
3333
+ method: 'PATCH',
3334
+ headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3335
+ credentials: 'include',
3336
+ body: JSON.stringify({
3337
+ workspaceId: teamManagementWorkspaceId,
3338
+ mode
3339
+ })
3340
+ }));
3341
+ }, onToggleMemberDisabled: (userId, disabled) => {
3342
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/disable`, {
3343
+ method: 'PATCH',
3344
+ headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3345
+ credentials: 'include',
3346
+ body: JSON.stringify({
3347
+ workspaceId: teamManagementWorkspaceId,
3348
+ disabled
3349
+ })
3350
+ }));
3351
+ }, onRevokeInvite: (userId) => {
3352
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/invite/revoke`, {
3353
+ method: 'POST',
3354
+ headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3355
+ credentials: 'include',
3356
+ body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
3357
+ }));
3358
+ }, onRemoveMember: (userId) => {
3359
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
3360
+ method: 'DELETE',
3361
+ headers: teamAdminHeaders(),
3362
+ credentials: 'include'
3363
+ }));
3364
+ }, onInviteEmailChange: setTeamInviteEmail, onInviteRoleChange: setTeamInviteRole, onInvitePermissionModeChange: setTeamInvitePermissionMode, onSubmitInvite: () => { void submitTeamInvite(); }, onLoadAuditPrevious: () => { void loadTeamAuditPage(teamAuditPage - 1); }, onLoadAuditNext: () => { void loadTeamAuditPage(teamAuditPage + 1); } }), _jsx(CreateWorkspaceConfirmModal, { isOpen: showCreateWorkspaceConfirm, theme: currentTheme, onClose: () => setShowCreateWorkspaceConfirm(false), onConfirm: () => openWorkspaceSetup({ intent: 'create-workspace' }) }), _jsx(SyncStatusModal, { isOpen: showSyncStatusModal, theme: currentTheme, currentWorkspaceLabel: currentWorkspaceLabel, syncStatusMeta: syncStatusMeta, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncControlBusy: syncControlBusy, canManageWorkspaceSync: canManageWorkspaceSync, workspaceSyncSummary: workspaceSyncSummary, workspaceSyncRepairBusy: workspaceSyncRepairBusy, referenceMismatchCount: referenceMismatchCount, syncStageLabel: syncStageLabel, workspaceSyncPendingChanges: workspaceSyncPendingChanges, formattedLastSyncTime: formattedLastSyncTime, formattedLastPullTime: formattedLastPullTime, formattedLastPushTime: formattedLastPushTime, syncLastError: syncLastError, workspaceSyncDiagnostics: workspaceSyncDiagnostics, activeReferenceMismatchSummaries: activeReferenceMismatchSummaries, syncDiagnosticsSummary: syncDiagnosticsSummary, syncEventRows: syncEventRows, syncEventsListRef: syncEventsListRef, workspaceSyncRepairQueued: workspaceSyncRepairQueued, workspaceSyncBusy: workspaceSyncBusy, workspaceSyncCopied: workspaceSyncCopied, runtimeMode: runtimeMode, isAuthenticated: isAuthenticated, onClose: () => setShowSyncStatusModal(false), onToggleWorkspaceSync: (enabled) => {
3365
+ void handleWorkspaceSyncToggle(enabled);
3366
+ }, onRepairSync: handleQueueOrRunRepairSync, onCopyReport: () => { void handleCopySyncDetails(); }, onOpenLogin: () => {
3367
+ openSharedLoginRoute('login');
3368
+ }, onRetrySync: () => { void retryWorkspaceCloudSync(); } }), unsavedModalOpen && createPortal(_jsx("div", { className: `${modalStyles.overlay} ${modalStyles.unsavedOverlay}`, style: { zIndex: 2000 }, children: _jsxs("div", { className: `tf-surface-modal tf-modal-shell ${modalStyles.modal} ${modalStyles.unsavedModal}`, "data-theme": currentTheme, children: [_jsx("div", { className: `tf-modal-header ${modalStyles.unsavedHeader}`, children: _jsxs("div", { className: `tf-modal-title ${modalStyles.unsavedTitle}`, children: [_jsx(AlertTriangle, { size: 20 }), "Unsaved Changes"] }) }), _jsx("div", { className: `${modalStyles.form} ${modalStyles.unsavedContent}`, children: _jsx("p", { className: modalStyles.unsavedText, children: "You have unsaved changes. Would you like to save them?" }) }), _jsxs("div", { className: `${modalStyles.formActions} ${modalStyles.unsavedActions}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => setUnsavedModalOpen(false), title: "Close dialog and continue editing", children: "Keep Editing" }), _jsx("button", { className: styles.destructiveBtn, onClick: handleDiscard, title: "Discard unsaved changes and leave", children: "Discard" }), _jsxs("button", { className: styles.submitBtn, onClick: handleSaveAndNavigate, disabled: loading, title: "Save changes and leave", children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Save, { size: 16 }), "Save"] })] })] }) }), document.body), showFolderBrowser && createPortal(_jsx("div", { className: folderBrowserStyles.overlay, children: _jsxs("div", { className: folderBrowserStyles.browser, children: [_jsxs("div", { className: folderBrowserStyles.header, children: [_jsxs("div", { className: folderBrowserStyles.pathInfo, children: [_jsx(Folder, { size: 14 }), _jsx("span", { children: currentBrowsePath || 'Project Root' })] }), _jsxs("div", { className: folderBrowserStyles.actions, children: [currentBrowsePath && (_jsx("button", { className: styles.helpLink, onClick: () => {
3152
3369
  const parts = currentBrowsePath.split('/').filter(Boolean);
3153
3370
  parts.pop();
3154
3371
  fetchFolders(parts.length ? parts.join('/') + '/' : '');